diff --git a/vendor/github.com/codegangsta/cli/.flake8 b/vendor/github.com/codegangsta/cli/.flake8
new file mode 100644
index 0000000..6deafc2
--- /dev/null
+++ b/vendor/github.com/codegangsta/cli/.flake8
@@ -0,0 +1,2 @@
+[flake8]
+max-line-length = 120
diff --git a/vendor/github.com/codegangsta/cli/.gitignore b/vendor/github.com/codegangsta/cli/.gitignore
new file mode 100644
index 0000000..faf70c4
--- /dev/null
+++ b/vendor/github.com/codegangsta/cli/.gitignore
@@ -0,0 +1,2 @@
+*.coverprofile
+node_modules/
diff --git a/vendor/github.com/codegangsta/cli/.travis.yml b/vendor/github.com/codegangsta/cli/.travis.yml
new file mode 100644
index 0000000..cf8d098
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/CHANGELOG.md b/vendor/github.com/codegangsta/cli/CHANGELOG.md
new file mode 100644
index 0000000..401eae5
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/LICENSE b/vendor/github.com/codegangsta/cli/LICENSE
new file mode 100644
index 0000000..42a597e
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/README.md b/vendor/github.com/codegangsta/cli/README.md
new file mode 100644
index 0000000..2bbbd8e
--- /dev/null
+++ b/vendor/github.com/codegangsta/cli/README.md
@@ -0,0 +1,1381 @@
+cli
+===
+
+[](https://travis-ci.org/urfave/cli)
+[](https://ci.appveyor.com/project/urfave/cli)
+[](https://godoc.org/github.com/urfave/cli)
+[](https://codebeat.co/projects/github-com-urfave-cli)
+[](https://goreportcard.com/report/urfave/cli)
+[](http://gocover.io/github.com/urfave/cli) /
+[](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/codegangsta/cli/altsrc/altsrc.go b/vendor/github.com/codegangsta/cli/altsrc/altsrc.go
new file mode 100644
index 0000000..ac34bf6
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/altsrc/flag.go b/vendor/github.com/codegangsta/cli/altsrc/flag.go
new file mode 100644
index 0000000..84ef009
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/altsrc/flag_generated.go b/vendor/github.com/codegangsta/cli/altsrc/flag_generated.go
new file mode 100644
index 0000000..0aeb0b0
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/altsrc/flag_test.go b/vendor/github.com/codegangsta/cli/altsrc/flag_test.go
new file mode 100644
index 0000000..cd18294
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/altsrc/helpers_test.go b/vendor/github.com/codegangsta/cli/altsrc/helpers_test.go
new file mode 100644
index 0000000..3b7f7e9
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/altsrc/input_source_context.go b/vendor/github.com/codegangsta/cli/altsrc/input_source_context.go
new file mode 100644
index 0000000..276dcda
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/altsrc/map_input_source.go b/vendor/github.com/codegangsta/cli/altsrc/map_input_source.go
new file mode 100644
index 0000000..b3169e0
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/altsrc/toml_command_test.go b/vendor/github.com/codegangsta/cli/altsrc/toml_command_test.go
new file mode 100644
index 0000000..a5053d4
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/altsrc/toml_file_loader.go b/vendor/github.com/codegangsta/cli/altsrc/toml_file_loader.go
new file mode 100644
index 0000000..37870fc
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/altsrc/yaml_command_test.go b/vendor/github.com/codegangsta/cli/altsrc/yaml_command_test.go
new file mode 100644
index 0000000..9d3f431
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/altsrc/yaml_file_loader.go b/vendor/github.com/codegangsta/cli/altsrc/yaml_file_loader.go
new file mode 100644
index 0000000..dd808d5
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/app.go b/vendor/github.com/codegangsta/cli/app.go
new file mode 100644
index 0000000..51fc45d
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/app_test.go b/vendor/github.com/codegangsta/cli/app_test.go
new file mode 100644
index 0000000..e14ddaf
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/appveyor.yml b/vendor/github.com/codegangsta/cli/appveyor.yml
new file mode 100644
index 0000000..1e1489c
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/autocomplete/bash_autocomplete b/vendor/github.com/codegangsta/cli/autocomplete/bash_autocomplete
new file mode 100755
index 0000000..37d9c14
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/autocomplete/zsh_autocomplete b/vendor/github.com/codegangsta/cli/autocomplete/zsh_autocomplete
new file mode 100644
index 0000000..5430a18
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/category.go b/vendor/github.com/codegangsta/cli/category.go
new file mode 100644
index 0000000..1a60550
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/cli.go b/vendor/github.com/codegangsta/cli/cli.go
new file mode 100644
index 0000000..90c07eb
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/command.go b/vendor/github.com/codegangsta/cli/command.go
new file mode 100644
index 0000000..23de294
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/command_test.go b/vendor/github.com/codegangsta/cli/command_test.go
new file mode 100644
index 0000000..4ad994c
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/context.go b/vendor/github.com/codegangsta/cli/context.go
new file mode 100644
index 0000000..db94191
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/context_test.go b/vendor/github.com/codegangsta/cli/context_test.go
new file mode 100644
index 0000000..7acca10
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/errors.go b/vendor/github.com/codegangsta/cli/errors.go
new file mode 100644
index 0000000..562b295
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/errors_test.go b/vendor/github.com/codegangsta/cli/errors_test.go
new file mode 100644
index 0000000..9b609c5
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/flag-types.json b/vendor/github.com/codegangsta/cli/flag-types.json
new file mode 100644
index 0000000..1223107
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/flag.go b/vendor/github.com/codegangsta/cli/flag.go
new file mode 100644
index 0000000..877ff35
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/flag_generated.go b/vendor/github.com/codegangsta/cli/flag_generated.go
new file mode 100644
index 0000000..491b619
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/flag_test.go b/vendor/github.com/codegangsta/cli/flag_test.go
new file mode 100644
index 0000000..1ccb639
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/funcs.go b/vendor/github.com/codegangsta/cli/funcs.go
new file mode 100644
index 0000000..cba5e6c
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/generate-flag-types b/vendor/github.com/codegangsta/cli/generate-flag-types
new file mode 100755
index 0000000..7147381
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/help.go b/vendor/github.com/codegangsta/cli/help.go
new file mode 100644
index 0000000..57ec98d
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/help_test.go b/vendor/github.com/codegangsta/cli/help_test.go
new file mode 100644
index 0000000..70b6300
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/helpers_test.go b/vendor/github.com/codegangsta/cli/helpers_test.go
new file mode 100644
index 0000000..109ea7a
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/helpers_unix_test.go b/vendor/github.com/codegangsta/cli/helpers_unix_test.go
new file mode 100644
index 0000000..ae27fc5
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/helpers_windows_test.go b/vendor/github.com/codegangsta/cli/helpers_windows_test.go
new file mode 100644
index 0000000..4eb84f9
--- /dev/null
+++ b/vendor/github.com/codegangsta/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/codegangsta/cli/runtests b/vendor/github.com/codegangsta/cli/runtests
new file mode 100755
index 0000000..ee22bde
--- /dev/null
+++ b/vendor/github.com/codegangsta/cli/runtests
@@ -0,0 +1,122 @@
+#!/usr/bin/env python
+from __future__ import print_function
+
+import argparse
+import os
+import sys
+import tempfile
+
+from subprocess import check_call, check_output
+
+
+PACKAGE_NAME = os.environ.get(
+ 'CLI_PACKAGE_NAME', 'github.com/urfave/cli'
+)
+
+
+def main(sysargs=sys.argv[:]):
+ targets = {
+ 'vet': _vet,
+ 'test': _test,
+ 'gfmrun': _gfmrun,
+ 'toc': _toc,
+ 'gen': _gen,
+ }
+
+ parser = argparse.ArgumentParser()
+ parser.add_argument(
+ 'target', nargs='?', choices=tuple(targets.keys()), default='test'
+ )
+ args = parser.parse_args(sysargs[1:])
+
+ targets[args.target]()
+ return 0
+
+
+def _test():
+ if check_output('go version'.split()).split()[2] < 'go1.2':
+ _run('go test -v .')
+ return
+
+ coverprofiles = []
+ for subpackage in ['', 'altsrc']:
+ coverprofile = 'cli.coverprofile'
+ if subpackage != '':
+ coverprofile = '{}.coverprofile'.format(subpackage)
+
+ coverprofiles.append(coverprofile)
+
+ _run('go test -v'.split() + [
+ '-coverprofile={}'.format(coverprofile),
+ ('{}/{}'.format(PACKAGE_NAME, subpackage)).rstrip('/')
+ ])
+
+ combined_name = _combine_coverprofiles(coverprofiles)
+ _run('go tool cover -func={}'.format(combined_name))
+ os.remove(combined_name)
+
+
+def _gfmrun():
+ go_version = check_output('go version'.split()).split()[2]
+ if go_version < 'go1.3':
+ print('runtests: skip on {}'.format(go_version), file=sys.stderr)
+ return
+ _run(['gfmrun', '-c', str(_gfmrun_count()), '-s', 'README.md'])
+
+
+def _vet():
+ _run('go vet ./...')
+
+
+def _toc():
+ _run('node_modules/.bin/markdown-toc -i README.md')
+ _run('git diff --exit-code')
+
+
+def _gen():
+ go_version = check_output('go version'.split()).split()[2]
+ if go_version < 'go1.5':
+ print('runtests: skip on {}'.format(go_version), file=sys.stderr)
+ return
+
+ _run('go generate ./...')
+ _run('git diff --exit-code')
+
+
+def _run(command):
+ if hasattr(command, 'split'):
+ command = command.split()
+ print('runtests: {}'.format(' '.join(command)), file=sys.stderr)
+ check_call(command)
+
+
+def _gfmrun_count():
+ with open('README.md') as infile:
+ lines = infile.read().splitlines()
+ return len(filter(_is_go_runnable, lines))
+
+
+def _is_go_runnable(line):
+ return line.startswith('package main')
+
+
+def _combine_coverprofiles(coverprofiles):
+ combined = tempfile.NamedTemporaryFile(
+ suffix='.coverprofile', delete=False
+ )
+ combined.write('mode: set\n')
+
+ for coverprofile in coverprofiles:
+ with open(coverprofile, 'r') as infile:
+ for line in infile.readlines():
+ if not line.startswith('mode: '):
+ combined.write(line)
+
+ combined.flush()
+ name = combined.name
+ combined.close()
+ return name
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/vendor/github.com/garyburd/redigo/.github/CONTRIBUTING.md b/vendor/github.com/garyburd/redigo/.github/CONTRIBUTING.md
new file mode 100644
index 0000000..143443a
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/.github/CONTRIBUTING.md
@@ -0,0 +1,5 @@
+Ask questions at
+[StackOverflow](https://stackoverflow.com/questions/ask?tags=go+redis).
+
+[Open an issue](https://github.com/garyburd/redigo/issues/new) to discuss your
+plans before doing any work on Redigo.
diff --git a/vendor/github.com/garyburd/redigo/.github/ISSUE_TEMPLATE.md b/vendor/github.com/garyburd/redigo/.github/ISSUE_TEMPLATE.md
new file mode 100644
index 0000000..d8a1271
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/.github/ISSUE_TEMPLATE.md
@@ -0,0 +1 @@
+Ask questions at https://stackoverflow.com/questions/ask?tags=go+redis
diff --git a/vendor/github.com/garyburd/redigo/.travis.yml b/vendor/github.com/garyburd/redigo/.travis.yml
new file mode 100644
index 0000000..e70302e
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/.travis.yml
@@ -0,0 +1,18 @@
+language: go
+sudo: false
+services:
+ - redis-server
+
+go:
+ - 1.4
+ - 1.5
+ - 1.6
+ - 1.7
+ - 1.8
+ - tip
+
+script:
+ - go get -t -v ./...
+ - diff -u <(echo -n) <(gofmt -d .)
+ - go vet $(go list ./... | grep -v /vendor/)
+ - go test -v -race ./...
diff --git a/vendor/github.com/garyburd/redigo/LICENSE b/vendor/github.com/garyburd/redigo/LICENSE
new file mode 100644
index 0000000..67db858
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/LICENSE
@@ -0,0 +1,175 @@
+
+ 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.
diff --git a/vendor/github.com/garyburd/redigo/README.markdown b/vendor/github.com/garyburd/redigo/README.markdown
new file mode 100644
index 0000000..fb0d35c
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/README.markdown
@@ -0,0 +1,50 @@
+Redigo
+======
+
+[](https://travis-ci.org/garyburd/redigo)
+[](https://godoc.org/github.com/garyburd/redigo/redis)
+
+Redigo is a [Go](http://golang.org/) client for the [Redis](http://redis.io/) database.
+
+Features
+-------
+
+* A [Print-like](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Executing_Commands) API with support for all Redis commands.
+* [Pipelining](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Pipelining), including pipelined transactions.
+* [Publish/Subscribe](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Publish_and_Subscribe).
+* [Connection pooling](http://godoc.org/github.com/garyburd/redigo/redis#Pool).
+* [Script helper type](http://godoc.org/github.com/garyburd/redigo/redis#Script) with optimistic use of EVALSHA.
+* [Helper functions](http://godoc.org/github.com/garyburd/redigo/redis#hdr-Reply_Helpers) for working with command replies.
+
+Documentation
+-------------
+
+- [API Reference](http://godoc.org/github.com/garyburd/redigo/redis)
+- [FAQ](https://github.com/garyburd/redigo/wiki/FAQ)
+
+Installation
+------------
+
+Install Redigo using the "go get" command:
+
+ go get github.com/garyburd/redigo/redis
+
+The Go distribution is Redigo's only dependency.
+
+Related Projects
+----------------
+
+- [rafaeljusto/redigomock](https://godoc.org/github.com/rafaeljusto/redigomock) - A mock library for Redigo.
+- [chasex/redis-go-cluster](https://github.com/chasex/redis-go-cluster) - A Redis cluster client implementation.
+- [FZambia/go-sentinel](https://github.com/FZambia/go-sentinel) - Redis Sentinel support for Redigo
+- [PuerkitoBio/redisc](https://github.com/PuerkitoBio/redisc) - Redis Cluster client built on top of Redigo
+
+Contributing
+------------
+
+See [CONTRIBUTING.md](https://github.com/garyburd/redigo/blob/master/.github/CONTRIBUTING.md).
+
+License
+-------
+
+Redigo is available under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html).
diff --git a/vendor/github.com/garyburd/redigo/internal/commandinfo.go b/vendor/github.com/garyburd/redigo/internal/commandinfo.go
new file mode 100644
index 0000000..11e5842
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/internal/commandinfo.go
@@ -0,0 +1,54 @@
+// Copyright 2014 Gary Burd
+//
+// 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 internal // import "github.com/garyburd/redigo/internal"
+
+import (
+ "strings"
+)
+
+const (
+ WatchState = 1 << iota
+ MultiState
+ SubscribeState
+ MonitorState
+)
+
+type CommandInfo struct {
+ Set, Clear int
+}
+
+var commandInfos = map[string]CommandInfo{
+ "WATCH": {Set: WatchState},
+ "UNWATCH": {Clear: WatchState},
+ "MULTI": {Set: MultiState},
+ "EXEC": {Clear: WatchState | MultiState},
+ "DISCARD": {Clear: WatchState | MultiState},
+ "PSUBSCRIBE": {Set: SubscribeState},
+ "SUBSCRIBE": {Set: SubscribeState},
+ "MONITOR": {Set: MonitorState},
+}
+
+func init() {
+ for n, ci := range commandInfos {
+ commandInfos[strings.ToLower(n)] = ci
+ }
+}
+
+func LookupCommandInfo(commandName string) CommandInfo {
+ if ci, ok := commandInfos[commandName]; ok {
+ return ci
+ }
+ return commandInfos[strings.ToUpper(commandName)]
+}
diff --git a/vendor/github.com/garyburd/redigo/internal/commandinfo_test.go b/vendor/github.com/garyburd/redigo/internal/commandinfo_test.go
new file mode 100644
index 0000000..118e94b
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/internal/commandinfo_test.go
@@ -0,0 +1,27 @@
+package internal
+
+import "testing"
+
+func TestLookupCommandInfo(t *testing.T) {
+ for _, n := range []string{"watch", "WATCH", "wAtch"} {
+ if LookupCommandInfo(n) == (CommandInfo{}) {
+ t.Errorf("LookupCommandInfo(%q) = CommandInfo{}, expected non-zero value", n)
+ }
+ }
+}
+
+func benchmarkLookupCommandInfo(b *testing.B, names ...string) {
+ for i := 0; i < b.N; i++ {
+ for _, c := range names {
+ LookupCommandInfo(c)
+ }
+ }
+}
+
+func BenchmarkLookupCommandInfoCorrectCase(b *testing.B) {
+ benchmarkLookupCommandInfo(b, "watch", "WATCH", "monitor", "MONITOR")
+}
+
+func BenchmarkLookupCommandInfoMixedCase(b *testing.B) {
+ benchmarkLookupCommandInfo(b, "wAtch", "WeTCH", "monItor", "MONiTOR")
+}
diff --git a/vendor/github.com/garyburd/redigo/internal/redistest/testdb.go b/vendor/github.com/garyburd/redigo/internal/redistest/testdb.go
new file mode 100644
index 0000000..b6f205b
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/internal/redistest/testdb.go
@@ -0,0 +1,68 @@
+// Copyright 2014 Gary Burd
+//
+// 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 redistest contains utilities for writing Redigo tests.
+package redistest
+
+import (
+ "errors"
+ "time"
+
+ "github.com/garyburd/redigo/redis"
+)
+
+type testConn struct {
+ redis.Conn
+}
+
+func (t testConn) Close() error {
+ _, err := t.Conn.Do("SELECT", "9")
+ if err != nil {
+ return nil
+ }
+ _, err = t.Conn.Do("FLUSHDB")
+ if err != nil {
+ return err
+ }
+ return t.Conn.Close()
+}
+
+// Dial dials the local Redis server and selects database 9. To prevent
+// stomping on real data, DialTestDB fails if database 9 contains data. The
+// returned connection flushes database 9 on close.
+func Dial() (redis.Conn, error) {
+ c, err := redis.DialTimeout("tcp", ":6379", 0, 1*time.Second, 1*time.Second)
+ if err != nil {
+ return nil, err
+ }
+
+ _, err = c.Do("SELECT", "9")
+ if err != nil {
+ c.Close()
+ return nil, err
+ }
+
+ n, err := redis.Int(c.Do("DBSIZE"))
+ if err != nil {
+ c.Close()
+ return nil, err
+ }
+
+ if n != 0 {
+ c.Close()
+ return nil, errors.New("database #9 is not empty, test can not continue")
+ }
+
+ return testConn{c}, nil
+}
diff --git a/vendor/github.com/garyburd/redigo/redis/conn.go b/vendor/github.com/garyburd/redigo/redis/conn.go
new file mode 100644
index 0000000..6ccace0
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/redis/conn.go
@@ -0,0 +1,618 @@
+// Copyright 2012 Gary Burd
+//
+// 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 redis
+
+import (
+ "bufio"
+ "bytes"
+ "crypto/tls"
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "net/url"
+ "regexp"
+ "strconv"
+ "sync"
+ "time"
+)
+
+// conn is the low-level implementation of Conn
+type conn struct {
+
+ // Shared
+ mu sync.Mutex
+ pending int
+ err error
+ conn net.Conn
+
+ // Read
+ readTimeout time.Duration
+ br *bufio.Reader
+
+ // Write
+ writeTimeout time.Duration
+ bw *bufio.Writer
+
+ // Scratch space for formatting argument length.
+ // '*' or '$', length, "\r\n"
+ lenScratch [32]byte
+
+ // Scratch space for formatting integers and floats.
+ numScratch [40]byte
+}
+
+// DialTimeout acts like Dial but takes timeouts for establishing the
+// connection to the server, writing a command and reading a reply.
+//
+// Deprecated: Use Dial with options instead.
+func DialTimeout(network, address string, connectTimeout, readTimeout, writeTimeout time.Duration) (Conn, error) {
+ return Dial(network, address,
+ DialConnectTimeout(connectTimeout),
+ DialReadTimeout(readTimeout),
+ DialWriteTimeout(writeTimeout))
+}
+
+// DialOption specifies an option for dialing a Redis server.
+type DialOption struct {
+ f func(*dialOptions)
+}
+
+type dialOptions struct {
+ readTimeout time.Duration
+ writeTimeout time.Duration
+ dial func(network, addr string) (net.Conn, error)
+ db int
+ password string
+ dialTLS bool
+ skipVerify bool
+ tlsConfig *tls.Config
+}
+
+// DialReadTimeout specifies the timeout for reading a single command reply.
+func DialReadTimeout(d time.Duration) DialOption {
+ return DialOption{func(do *dialOptions) {
+ do.readTimeout = d
+ }}
+}
+
+// DialWriteTimeout specifies the timeout for writing a single command.
+func DialWriteTimeout(d time.Duration) DialOption {
+ return DialOption{func(do *dialOptions) {
+ do.writeTimeout = d
+ }}
+}
+
+// DialConnectTimeout specifies the timeout for connecting to the Redis server.
+func DialConnectTimeout(d time.Duration) DialOption {
+ return DialOption{func(do *dialOptions) {
+ dialer := net.Dialer{Timeout: d}
+ do.dial = dialer.Dial
+ }}
+}
+
+// DialNetDial specifies a custom dial function for creating TCP
+// connections. If this option is left out, then net.Dial is
+// used. DialNetDial overrides DialConnectTimeout.
+func DialNetDial(dial func(network, addr string) (net.Conn, error)) DialOption {
+ return DialOption{func(do *dialOptions) {
+ do.dial = dial
+ }}
+}
+
+// DialDatabase specifies the database to select when dialing a connection.
+func DialDatabase(db int) DialOption {
+ return DialOption{func(do *dialOptions) {
+ do.db = db
+ }}
+}
+
+// DialPassword specifies the password to use when connecting to
+// the Redis server.
+func DialPassword(password string) DialOption {
+ return DialOption{func(do *dialOptions) {
+ do.password = password
+ }}
+}
+
+// DialTLSConfig specifies the config to use when a TLS connection is dialed.
+// Has no effect when not dialing a TLS connection.
+func DialTLSConfig(c *tls.Config) DialOption {
+ return DialOption{func(do *dialOptions) {
+ do.tlsConfig = c
+ }}
+}
+
+// DialTLSSkipVerify to disable server name verification when connecting
+// over TLS. Has no effect when not dialing a TLS connection.
+func DialTLSSkipVerify(skip bool) DialOption {
+ return DialOption{func(do *dialOptions) {
+ do.skipVerify = skip
+ }}
+}
+
+// Dial connects to the Redis server at the given network and
+// address using the specified options.
+func Dial(network, address string, options ...DialOption) (Conn, error) {
+ do := dialOptions{
+ dial: net.Dial,
+ }
+ for _, option := range options {
+ option.f(&do)
+ }
+
+ netConn, err := do.dial(network, address)
+ if err != nil {
+ return nil, err
+ }
+
+ if do.dialTLS {
+ tlsConfig := cloneTLSClientConfig(do.tlsConfig, do.skipVerify)
+ if tlsConfig.ServerName == "" {
+ host, _, err := net.SplitHostPort(address)
+ if err != nil {
+ netConn.Close()
+ return nil, err
+ }
+ tlsConfig.ServerName = host
+ }
+
+ tlsConn := tls.Client(netConn, tlsConfig)
+ if err := tlsConn.Handshake(); err != nil {
+ netConn.Close()
+ return nil, err
+ }
+ netConn = tlsConn
+ }
+
+ c := &conn{
+ conn: netConn,
+ bw: bufio.NewWriter(netConn),
+ br: bufio.NewReader(netConn),
+ readTimeout: do.readTimeout,
+ writeTimeout: do.writeTimeout,
+ }
+
+ if do.password != "" {
+ if _, err := c.Do("AUTH", do.password); err != nil {
+ netConn.Close()
+ return nil, err
+ }
+ }
+
+ if do.db != 0 {
+ if _, err := c.Do("SELECT", do.db); err != nil {
+ netConn.Close()
+ return nil, err
+ }
+ }
+
+ return c, nil
+}
+
+func dialTLS(do *dialOptions) {
+ do.dialTLS = true
+}
+
+var pathDBRegexp = regexp.MustCompile(`/(\d*)\z`)
+
+// DialURL connects to a Redis server at the given URL using the Redis
+// URI scheme. URLs should follow the draft IANA specification for the
+// scheme (https://www.iana.org/assignments/uri-schemes/prov/redis).
+func DialURL(rawurl string, options ...DialOption) (Conn, error) {
+ u, err := url.Parse(rawurl)
+ if err != nil {
+ return nil, err
+ }
+
+ if u.Scheme != "redis" && u.Scheme != "rediss" {
+ return nil, fmt.Errorf("invalid redis URL scheme: %s", u.Scheme)
+ }
+
+ // As per the IANA draft spec, the host defaults to localhost and
+ // the port defaults to 6379.
+ host, port, err := net.SplitHostPort(u.Host)
+ if err != nil {
+ // assume port is missing
+ host = u.Host
+ port = "6379"
+ }
+ if host == "" {
+ host = "localhost"
+ }
+ address := net.JoinHostPort(host, port)
+
+ if u.User != nil {
+ password, isSet := u.User.Password()
+ if isSet {
+ options = append(options, DialPassword(password))
+ }
+ }
+
+ match := pathDBRegexp.FindStringSubmatch(u.Path)
+ if len(match) == 2 {
+ db := 0
+ if len(match[1]) > 0 {
+ db, err = strconv.Atoi(match[1])
+ if err != nil {
+ return nil, fmt.Errorf("invalid database: %s", u.Path[1:])
+ }
+ }
+ if db != 0 {
+ options = append(options, DialDatabase(db))
+ }
+ } else if u.Path != "" {
+ return nil, fmt.Errorf("invalid database: %s", u.Path[1:])
+ }
+
+ if u.Scheme == "rediss" {
+ options = append([]DialOption{{dialTLS}}, options...)
+ }
+
+ return Dial("tcp", address, options...)
+}
+
+// NewConn returns a new Redigo connection for the given net connection.
+func NewConn(netConn net.Conn, readTimeout, writeTimeout time.Duration) Conn {
+ return &conn{
+ conn: netConn,
+ bw: bufio.NewWriter(netConn),
+ br: bufio.NewReader(netConn),
+ readTimeout: readTimeout,
+ writeTimeout: writeTimeout,
+ }
+}
+
+func (c *conn) Close() error {
+ c.mu.Lock()
+ err := c.err
+ if c.err == nil {
+ c.err = errors.New("redigo: closed")
+ err = c.conn.Close()
+ }
+ c.mu.Unlock()
+ return err
+}
+
+func (c *conn) fatal(err error) error {
+ c.mu.Lock()
+ if c.err == nil {
+ c.err = err
+ // Close connection to force errors on subsequent calls and to unblock
+ // other reader or writer.
+ c.conn.Close()
+ }
+ c.mu.Unlock()
+ return err
+}
+
+func (c *conn) Err() error {
+ c.mu.Lock()
+ err := c.err
+ c.mu.Unlock()
+ return err
+}
+
+func (c *conn) writeLen(prefix byte, n int) error {
+ c.lenScratch[len(c.lenScratch)-1] = '\n'
+ c.lenScratch[len(c.lenScratch)-2] = '\r'
+ i := len(c.lenScratch) - 3
+ for {
+ c.lenScratch[i] = byte('0' + n%10)
+ i -= 1
+ n = n / 10
+ if n == 0 {
+ break
+ }
+ }
+ c.lenScratch[i] = prefix
+ _, err := c.bw.Write(c.lenScratch[i:])
+ return err
+}
+
+func (c *conn) writeString(s string) error {
+ c.writeLen('$', len(s))
+ c.bw.WriteString(s)
+ _, err := c.bw.WriteString("\r\n")
+ return err
+}
+
+func (c *conn) writeBytes(p []byte) error {
+ c.writeLen('$', len(p))
+ c.bw.Write(p)
+ _, err := c.bw.WriteString("\r\n")
+ return err
+}
+
+func (c *conn) writeInt64(n int64) error {
+ return c.writeBytes(strconv.AppendInt(c.numScratch[:0], n, 10))
+}
+
+func (c *conn) writeFloat64(n float64) error {
+ return c.writeBytes(strconv.AppendFloat(c.numScratch[:0], n, 'g', -1, 64))
+}
+
+func (c *conn) writeCommand(cmd string, args []interface{}) (err error) {
+ c.writeLen('*', 1+len(args))
+ err = c.writeString(cmd)
+ for _, arg := range args {
+ if err != nil {
+ break
+ }
+ switch arg := arg.(type) {
+ case string:
+ err = c.writeString(arg)
+ case []byte:
+ err = c.writeBytes(arg)
+ case int:
+ err = c.writeInt64(int64(arg))
+ case int64:
+ err = c.writeInt64(arg)
+ case float64:
+ err = c.writeFloat64(arg)
+ case bool:
+ if arg {
+ err = c.writeString("1")
+ } else {
+ err = c.writeString("0")
+ }
+ case nil:
+ err = c.writeString("")
+ default:
+ var buf bytes.Buffer
+ fmt.Fprint(&buf, arg)
+ err = c.writeBytes(buf.Bytes())
+ }
+ }
+ return err
+}
+
+type protocolError string
+
+func (pe protocolError) Error() string {
+ return fmt.Sprintf("redigo: %s (possible server error or unsupported concurrent read by application)", string(pe))
+}
+
+func (c *conn) readLine() ([]byte, error) {
+ p, err := c.br.ReadSlice('\n')
+ if err == bufio.ErrBufferFull {
+ return nil, protocolError("long response line")
+ }
+ if err != nil {
+ return nil, err
+ }
+ i := len(p) - 2
+ if i < 0 || p[i] != '\r' {
+ return nil, protocolError("bad response line terminator")
+ }
+ return p[:i], nil
+}
+
+// parseLen parses bulk string and array lengths.
+func parseLen(p []byte) (int, error) {
+ if len(p) == 0 {
+ return -1, protocolError("malformed length")
+ }
+
+ if p[0] == '-' && len(p) == 2 && p[1] == '1' {
+ // handle $-1 and $-1 null replies.
+ return -1, nil
+ }
+
+ var n int
+ for _, b := range p {
+ n *= 10
+ if b < '0' || b > '9' {
+ return -1, protocolError("illegal bytes in length")
+ }
+ n += int(b - '0')
+ }
+
+ return n, nil
+}
+
+// parseInt parses an integer reply.
+func parseInt(p []byte) (interface{}, error) {
+ if len(p) == 0 {
+ return 0, protocolError("malformed integer")
+ }
+
+ var negate bool
+ if p[0] == '-' {
+ negate = true
+ p = p[1:]
+ if len(p) == 0 {
+ return 0, protocolError("malformed integer")
+ }
+ }
+
+ var n int64
+ for _, b := range p {
+ n *= 10
+ if b < '0' || b > '9' {
+ return 0, protocolError("illegal bytes in length")
+ }
+ n += int64(b - '0')
+ }
+
+ if negate {
+ n = -n
+ }
+ return n, nil
+}
+
+var (
+ okReply interface{} = "OK"
+ pongReply interface{} = "PONG"
+)
+
+func (c *conn) readReply() (interface{}, error) {
+ line, err := c.readLine()
+ if err != nil {
+ return nil, err
+ }
+ if len(line) == 0 {
+ return nil, protocolError("short response line")
+ }
+ switch line[0] {
+ case '+':
+ switch {
+ case len(line) == 3 && line[1] == 'O' && line[2] == 'K':
+ // Avoid allocation for frequent "+OK" response.
+ return okReply, nil
+ case len(line) == 5 && line[1] == 'P' && line[2] == 'O' && line[3] == 'N' && line[4] == 'G':
+ // Avoid allocation in PING command benchmarks :)
+ return pongReply, nil
+ default:
+ return string(line[1:]), nil
+ }
+ case '-':
+ return Error(string(line[1:])), nil
+ case ':':
+ return parseInt(line[1:])
+ case '$':
+ n, err := parseLen(line[1:])
+ if n < 0 || err != nil {
+ return nil, err
+ }
+ p := make([]byte, n)
+ _, err = io.ReadFull(c.br, p)
+ if err != nil {
+ return nil, err
+ }
+ if line, err := c.readLine(); err != nil {
+ return nil, err
+ } else if len(line) != 0 {
+ return nil, protocolError("bad bulk string format")
+ }
+ return p, nil
+ case '*':
+ n, err := parseLen(line[1:])
+ if n < 0 || err != nil {
+ return nil, err
+ }
+ r := make([]interface{}, n)
+ for i := range r {
+ r[i], err = c.readReply()
+ if err != nil {
+ return nil, err
+ }
+ }
+ return r, nil
+ }
+ return nil, protocolError("unexpected response line")
+}
+
+func (c *conn) Send(cmd string, args ...interface{}) error {
+ c.mu.Lock()
+ c.pending += 1
+ c.mu.Unlock()
+ if c.writeTimeout != 0 {
+ c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
+ }
+ if err := c.writeCommand(cmd, args); err != nil {
+ return c.fatal(err)
+ }
+ return nil
+}
+
+func (c *conn) Flush() error {
+ if c.writeTimeout != 0 {
+ c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
+ }
+ if err := c.bw.Flush(); err != nil {
+ return c.fatal(err)
+ }
+ return nil
+}
+
+func (c *conn) Receive() (reply interface{}, err error) {
+ if c.readTimeout != 0 {
+ c.conn.SetReadDeadline(time.Now().Add(c.readTimeout))
+ }
+ if reply, err = c.readReply(); err != nil {
+ return nil, c.fatal(err)
+ }
+ // When using pub/sub, the number of receives can be greater than the
+ // number of sends. To enable normal use of the connection after
+ // unsubscribing from all channels, we do not decrement pending to a
+ // negative value.
+ //
+ // The pending field is decremented after the reply is read to handle the
+ // case where Receive is called before Send.
+ c.mu.Lock()
+ if c.pending > 0 {
+ c.pending -= 1
+ }
+ c.mu.Unlock()
+ if err, ok := reply.(Error); ok {
+ return nil, err
+ }
+ return
+}
+
+func (c *conn) Do(cmd string, args ...interface{}) (interface{}, error) {
+ c.mu.Lock()
+ pending := c.pending
+ c.pending = 0
+ c.mu.Unlock()
+
+ if cmd == "" && pending == 0 {
+ return nil, nil
+ }
+
+ if c.writeTimeout != 0 {
+ c.conn.SetWriteDeadline(time.Now().Add(c.writeTimeout))
+ }
+
+ if cmd != "" {
+ if err := c.writeCommand(cmd, args); err != nil {
+ return nil, c.fatal(err)
+ }
+ }
+
+ if err := c.bw.Flush(); err != nil {
+ return nil, c.fatal(err)
+ }
+
+ if c.readTimeout != 0 {
+ c.conn.SetReadDeadline(time.Now().Add(c.readTimeout))
+ }
+
+ if cmd == "" {
+ reply := make([]interface{}, pending)
+ for i := range reply {
+ r, e := c.readReply()
+ if e != nil {
+ return nil, c.fatal(e)
+ }
+ reply[i] = r
+ }
+ return reply, nil
+ }
+
+ var err error
+ var reply interface{}
+ for i := 0; i <= pending; i++ {
+ var e error
+ if reply, e = c.readReply(); e != nil {
+ return nil, c.fatal(e)
+ }
+ if e, ok := reply.(Error); ok && err == nil {
+ err = e
+ }
+ }
+ return reply, err
+}
diff --git a/vendor/github.com/garyburd/redigo/redis/conn_test.go b/vendor/github.com/garyburd/redigo/redis/conn_test.go
new file mode 100644
index 0000000..2ead633
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/redis/conn_test.go
@@ -0,0 +1,670 @@
+// Copyright 2012 Gary Burd
+//
+// 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 redis_test
+
+import (
+ "bytes"
+ "io"
+ "math"
+ "net"
+ "os"
+ "reflect"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/garyburd/redigo/redis"
+)
+
+type testConn struct {
+ io.Reader
+ io.Writer
+}
+
+func (*testConn) Close() error { return nil }
+func (*testConn) LocalAddr() net.Addr { return nil }
+func (*testConn) RemoteAddr() net.Addr { return nil }
+func (*testConn) SetDeadline(t time.Time) error { return nil }
+func (*testConn) SetReadDeadline(t time.Time) error { return nil }
+func (*testConn) SetWriteDeadline(t time.Time) error { return nil }
+
+func dialTestConn(r io.Reader, w io.Writer) redis.DialOption {
+ return redis.DialNetDial(func(net, addr string) (net.Conn, error) {
+ return &testConn{Reader: r, Writer: w}, nil
+ })
+}
+
+var writeTests = []struct {
+ args []interface{}
+ expected string
+}{
+ {
+ []interface{}{"SET", "key", "value"},
+ "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n",
+ },
+ {
+ []interface{}{"SET", "key", "value"},
+ "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n",
+ },
+ {
+ []interface{}{"SET", "key", byte(100)},
+ "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\n100\r\n",
+ },
+ {
+ []interface{}{"SET", "key", 100},
+ "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$3\r\n100\r\n",
+ },
+ {
+ []interface{}{"SET", "key", int64(math.MinInt64)},
+ "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$20\r\n-9223372036854775808\r\n",
+ },
+ {
+ []interface{}{"SET", "key", float64(1349673917.939762)},
+ "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$21\r\n1.349673917939762e+09\r\n",
+ },
+ {
+ []interface{}{"SET", "key", ""},
+ "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$0\r\n\r\n",
+ },
+ {
+ []interface{}{"SET", "key", nil},
+ "*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$0\r\n\r\n",
+ },
+ {
+ []interface{}{"ECHO", true, false},
+ "*3\r\n$4\r\nECHO\r\n$1\r\n1\r\n$1\r\n0\r\n",
+ },
+}
+
+func TestWrite(t *testing.T) {
+ for _, tt := range writeTests {
+ var buf bytes.Buffer
+ c, _ := redis.Dial("", "", dialTestConn(nil, &buf))
+ err := c.Send(tt.args[0].(string), tt.args[1:]...)
+ if err != nil {
+ t.Errorf("Send(%v) returned error %v", tt.args, err)
+ continue
+ }
+ c.Flush()
+ actual := buf.String()
+ if actual != tt.expected {
+ t.Errorf("Send(%v) = %q, want %q", tt.args, actual, tt.expected)
+ }
+ }
+}
+
+var errorSentinel = &struct{}{}
+
+var readTests = []struct {
+ reply string
+ expected interface{}
+}{
+ {
+ "+OK\r\n",
+ "OK",
+ },
+ {
+ "+PONG\r\n",
+ "PONG",
+ },
+ {
+ "@OK\r\n",
+ errorSentinel,
+ },
+ {
+ "$6\r\nfoobar\r\n",
+ []byte("foobar"),
+ },
+ {
+ "$-1\r\n",
+ nil,
+ },
+ {
+ ":1\r\n",
+ int64(1),
+ },
+ {
+ ":-2\r\n",
+ int64(-2),
+ },
+ {
+ "*0\r\n",
+ []interface{}{},
+ },
+ {
+ "*-1\r\n",
+ nil,
+ },
+ {
+ "*4\r\n$3\r\nfoo\r\n$3\r\nbar\r\n$5\r\nHello\r\n$5\r\nWorld\r\n",
+ []interface{}{[]byte("foo"), []byte("bar"), []byte("Hello"), []byte("World")},
+ },
+ {
+ "*3\r\n$3\r\nfoo\r\n$-1\r\n$3\r\nbar\r\n",
+ []interface{}{[]byte("foo"), nil, []byte("bar")},
+ },
+
+ {
+ // "x" is not a valid length
+ "$x\r\nfoobar\r\n",
+ errorSentinel,
+ },
+ {
+ // -2 is not a valid length
+ "$-2\r\n",
+ errorSentinel,
+ },
+ {
+ // "x" is not a valid integer
+ ":x\r\n",
+ errorSentinel,
+ },
+ {
+ // missing \r\n following value
+ "$6\r\nfoobar",
+ errorSentinel,
+ },
+ {
+ // short value
+ "$6\r\nxx",
+ errorSentinel,
+ },
+ {
+ // long value
+ "$6\r\nfoobarx\r\n",
+ errorSentinel,
+ },
+}
+
+func TestRead(t *testing.T) {
+ for _, tt := range readTests {
+ c, _ := redis.Dial("", "", dialTestConn(strings.NewReader(tt.reply), nil))
+ actual, err := c.Receive()
+ if tt.expected == errorSentinel {
+ if err == nil {
+ t.Errorf("Receive(%q) did not return expected error", tt.reply)
+ }
+ } else {
+ if err != nil {
+ t.Errorf("Receive(%q) returned error %v", tt.reply, err)
+ continue
+ }
+ if !reflect.DeepEqual(actual, tt.expected) {
+ t.Errorf("Receive(%q) = %v, want %v", tt.reply, actual, tt.expected)
+ }
+ }
+ }
+}
+
+var testCommands = []struct {
+ args []interface{}
+ expected interface{}
+}{
+ {
+ []interface{}{"PING"},
+ "PONG",
+ },
+ {
+ []interface{}{"SET", "foo", "bar"},
+ "OK",
+ },
+ {
+ []interface{}{"GET", "foo"},
+ []byte("bar"),
+ },
+ {
+ []interface{}{"GET", "nokey"},
+ nil,
+ },
+ {
+ []interface{}{"MGET", "nokey", "foo"},
+ []interface{}{nil, []byte("bar")},
+ },
+ {
+ []interface{}{"INCR", "mycounter"},
+ int64(1),
+ },
+ {
+ []interface{}{"LPUSH", "mylist", "foo"},
+ int64(1),
+ },
+ {
+ []interface{}{"LPUSH", "mylist", "bar"},
+ int64(2),
+ },
+ {
+ []interface{}{"LRANGE", "mylist", 0, -1},
+ []interface{}{[]byte("bar"), []byte("foo")},
+ },
+ {
+ []interface{}{"MULTI"},
+ "OK",
+ },
+ {
+ []interface{}{"LRANGE", "mylist", 0, -1},
+ "QUEUED",
+ },
+ {
+ []interface{}{"PING"},
+ "QUEUED",
+ },
+ {
+ []interface{}{"EXEC"},
+ []interface{}{
+ []interface{}{[]byte("bar"), []byte("foo")},
+ "PONG",
+ },
+ },
+}
+
+func TestDoCommands(t *testing.T) {
+ c, err := redis.DialDefaultServer()
+ if err != nil {
+ t.Fatalf("error connection to database, %v", err)
+ }
+ defer c.Close()
+
+ for _, cmd := range testCommands {
+ actual, err := c.Do(cmd.args[0].(string), cmd.args[1:]...)
+ if err != nil {
+ t.Errorf("Do(%v) returned error %v", cmd.args, err)
+ continue
+ }
+ if !reflect.DeepEqual(actual, cmd.expected) {
+ t.Errorf("Do(%v) = %v, want %v", cmd.args, actual, cmd.expected)
+ }
+ }
+}
+
+func TestPipelineCommands(t *testing.T) {
+ c, err := redis.DialDefaultServer()
+ if err != nil {
+ t.Fatalf("error connection to database, %v", err)
+ }
+ defer c.Close()
+
+ for _, cmd := range testCommands {
+ if err := c.Send(cmd.args[0].(string), cmd.args[1:]...); err != nil {
+ t.Fatalf("Send(%v) returned error %v", cmd.args, err)
+ }
+ }
+ if err := c.Flush(); err != nil {
+ t.Errorf("Flush() returned error %v", err)
+ }
+ for _, cmd := range testCommands {
+ actual, err := c.Receive()
+ if err != nil {
+ t.Fatalf("Receive(%v) returned error %v", cmd.args, err)
+ }
+ if !reflect.DeepEqual(actual, cmd.expected) {
+ t.Errorf("Receive(%v) = %v, want %v", cmd.args, actual, cmd.expected)
+ }
+ }
+}
+
+func TestBlankCommmand(t *testing.T) {
+ c, err := redis.DialDefaultServer()
+ if err != nil {
+ t.Fatalf("error connection to database, %v", err)
+ }
+ defer c.Close()
+
+ for _, cmd := range testCommands {
+ if err := c.Send(cmd.args[0].(string), cmd.args[1:]...); err != nil {
+ t.Fatalf("Send(%v) returned error %v", cmd.args, err)
+ }
+ }
+ reply, err := redis.Values(c.Do(""))
+ if err != nil {
+ t.Fatalf("Do() returned error %v", err)
+ }
+ if len(reply) != len(testCommands) {
+ t.Fatalf("len(reply)=%d, want %d", len(reply), len(testCommands))
+ }
+ for i, cmd := range testCommands {
+ actual := reply[i]
+ if !reflect.DeepEqual(actual, cmd.expected) {
+ t.Errorf("Receive(%v) = %v, want %v", cmd.args, actual, cmd.expected)
+ }
+ }
+}
+
+func TestRecvBeforeSend(t *testing.T) {
+ c, err := redis.DialDefaultServer()
+ if err != nil {
+ t.Fatalf("error connection to database, %v", err)
+ }
+ defer c.Close()
+ done := make(chan struct{})
+ go func() {
+ c.Receive()
+ close(done)
+ }()
+ time.Sleep(time.Millisecond)
+ c.Send("PING")
+ c.Flush()
+ <-done
+ _, err = c.Do("")
+ if err != nil {
+ t.Fatalf("error=%v", err)
+ }
+}
+
+func TestError(t *testing.T) {
+ c, err := redis.DialDefaultServer()
+ if err != nil {
+ t.Fatalf("error connection to database, %v", err)
+ }
+ defer c.Close()
+
+ c.Do("SET", "key", "val")
+ _, err = c.Do("HSET", "key", "fld", "val")
+ if err == nil {
+ t.Errorf("Expected err for HSET on string key.")
+ }
+ if c.Err() != nil {
+ t.Errorf("Conn has Err()=%v, expect nil", c.Err())
+ }
+ _, err = c.Do("SET", "key", "val")
+ if err != nil {
+ t.Errorf("Do(SET, key, val) returned error %v, expected nil.", err)
+ }
+}
+
+func TestReadTimeout(t *testing.T) {
+ l, err := net.Listen("tcp", "127.0.0.1:0")
+ if err != nil {
+ t.Fatalf("net.Listen returned %v", err)
+ }
+ defer l.Close()
+
+ go func() {
+ for {
+ c, err := l.Accept()
+ if err != nil {
+ return
+ }
+ go func() {
+ time.Sleep(time.Second)
+ c.Write([]byte("+OK\r\n"))
+ c.Close()
+ }()
+ }
+ }()
+
+ // Do
+
+ c1, err := redis.Dial(l.Addr().Network(), l.Addr().String(), redis.DialReadTimeout(time.Millisecond))
+ if err != nil {
+ t.Fatalf("redis.Dial returned %v", err)
+ }
+ defer c1.Close()
+
+ _, err = c1.Do("PING")
+ if err == nil {
+ t.Fatalf("c1.Do() returned nil, expect error")
+ }
+ if c1.Err() == nil {
+ t.Fatalf("c1.Err() = nil, expect error")
+ }
+
+ // Send/Flush/Receive
+
+ c2, err := redis.Dial(l.Addr().Network(), l.Addr().String(), redis.DialReadTimeout(time.Millisecond))
+ if err != nil {
+ t.Fatalf("redis.Dial returned %v", err)
+ }
+ defer c2.Close()
+
+ c2.Send("PING")
+ c2.Flush()
+ _, err = c2.Receive()
+ if err == nil {
+ t.Fatalf("c2.Receive() returned nil, expect error")
+ }
+ if c2.Err() == nil {
+ t.Fatalf("c2.Err() = nil, expect error")
+ }
+}
+
+var dialErrors = []struct {
+ rawurl string
+ expectedError string
+}{
+ {
+ "localhost",
+ "invalid redis URL scheme",
+ },
+ // The error message for invalid hosts is diffferent in different
+ // versions of Go, so just check that there is an error message.
+ {
+ "redis://weird url",
+ "",
+ },
+ {
+ "redis://foo:bar:baz",
+ "",
+ },
+ {
+ "http://www.google.com",
+ "invalid redis URL scheme: http",
+ },
+ {
+ "redis://localhost:6379/abc123",
+ "invalid database: abc123",
+ },
+}
+
+func TestDialURLErrors(t *testing.T) {
+ for _, d := range dialErrors {
+ _, err := redis.DialURL(d.rawurl)
+ if err == nil || !strings.Contains(err.Error(), d.expectedError) {
+ t.Errorf("DialURL did not return expected error (expected %v to contain %s)", err, d.expectedError)
+ }
+ }
+}
+
+func TestDialURLPort(t *testing.T) {
+ checkPort := func(network, address string) (net.Conn, error) {
+ if address != "localhost:6379" {
+ t.Errorf("DialURL did not set port to 6379 by default (got %v)", address)
+ }
+ return nil, nil
+ }
+ _, err := redis.DialURL("redis://localhost", redis.DialNetDial(checkPort))
+ if err != nil {
+ t.Error("dial error:", err)
+ }
+}
+
+func TestDialURLHost(t *testing.T) {
+ checkHost := func(network, address string) (net.Conn, error) {
+ if address != "localhost:6379" {
+ t.Errorf("DialURL did not set host to localhost by default (got %v)", address)
+ }
+ return nil, nil
+ }
+ _, err := redis.DialURL("redis://:6379", redis.DialNetDial(checkHost))
+ if err != nil {
+ t.Error("dial error:", err)
+ }
+}
+
+func TestDialURLPassword(t *testing.T) {
+ var buf bytes.Buffer
+ _, err := redis.DialURL("redis://x:abc123@localhost", dialTestConn(strings.NewReader("+OK\r\n"), &buf))
+ if err != nil {
+ t.Error("dial error:", err)
+ }
+ expected := "*2\r\n$4\r\nAUTH\r\n$6\r\nabc123\r\n"
+ actual := buf.String()
+ if actual != expected {
+ t.Errorf("commands = %q, want %q", actual, expected)
+ }
+}
+
+func TestDialURLDatabase(t *testing.T) {
+ var buf3 bytes.Buffer
+ _, err3 := redis.DialURL("redis://localhost/3", dialTestConn(strings.NewReader("+OK\r\n"), &buf3))
+ if err3 != nil {
+ t.Error("dial error:", err3)
+ }
+ expected3 := "*2\r\n$6\r\nSELECT\r\n$1\r\n3\r\n"
+ actual3 := buf3.String()
+ if actual3 != expected3 {
+ t.Errorf("commands = %q, want %q", actual3, expected3)
+ }
+ // empty DB means 0
+ var buf0 bytes.Buffer
+ _, err0 := redis.DialURL("redis://localhost/", dialTestConn(strings.NewReader("+OK\r\n"), &buf0))
+ if err0 != nil {
+ t.Error("dial error:", err0)
+ }
+ expected0 := ""
+ actual0 := buf0.String()
+ if actual0 != expected0 {
+ t.Errorf("commands = %q, want %q", actual0, expected0)
+ }
+}
+
+// Connect to local instance of Redis running on the default port.
+func ExampleDial() {
+ c, err := redis.Dial("tcp", ":6379")
+ if err != nil {
+ // handle error
+ }
+ defer c.Close()
+}
+
+// Connect to remote instance of Redis using a URL.
+func ExampleDialURL() {
+ c, err := redis.DialURL(os.Getenv("REDIS_URL"))
+ if err != nil {
+ // handle connection error
+ }
+ defer c.Close()
+}
+
+// TextExecError tests handling of errors in a transaction. See
+// http://redis.io/topics/transactions for information on how Redis handles
+// errors in a transaction.
+func TestExecError(t *testing.T) {
+ c, err := redis.DialDefaultServer()
+ if err != nil {
+ t.Fatalf("error connection to database, %v", err)
+ }
+ defer c.Close()
+
+ // Execute commands that fail before EXEC is called.
+
+ c.Do("DEL", "k0")
+ c.Do("ZADD", "k0", 0, 0)
+ c.Send("MULTI")
+ c.Send("NOTACOMMAND", "k0", 0, 0)
+ c.Send("ZINCRBY", "k0", 0, 0)
+ v, err := c.Do("EXEC")
+ if err == nil {
+ t.Fatalf("EXEC returned values %v, expected error", v)
+ }
+
+ // Execute commands that fail after EXEC is called. The first command
+ // returns an error.
+
+ c.Do("DEL", "k1")
+ c.Do("ZADD", "k1", 0, 0)
+ c.Send("MULTI")
+ c.Send("HSET", "k1", 0, 0)
+ c.Send("ZINCRBY", "k1", 0, 0)
+ v, err = c.Do("EXEC")
+ if err != nil {
+ t.Fatalf("EXEC returned error %v", err)
+ }
+
+ vs, err := redis.Values(v, nil)
+ if err != nil {
+ t.Fatalf("Values(v) returned error %v", err)
+ }
+
+ if len(vs) != 2 {
+ t.Fatalf("len(vs) == %d, want 2", len(vs))
+ }
+
+ if _, ok := vs[0].(error); !ok {
+ t.Fatalf("first result is type %T, expected error", vs[0])
+ }
+
+ if _, ok := vs[1].([]byte); !ok {
+ t.Fatalf("second result is type %T, expected []byte", vs[1])
+ }
+
+ // Execute commands that fail after EXEC is called. The second command
+ // returns an error.
+
+ c.Do("ZADD", "k2", 0, 0)
+ c.Send("MULTI")
+ c.Send("ZINCRBY", "k2", 0, 0)
+ c.Send("HSET", "k2", 0, 0)
+ v, err = c.Do("EXEC")
+ if err != nil {
+ t.Fatalf("EXEC returned error %v", err)
+ }
+
+ vs, err = redis.Values(v, nil)
+ if err != nil {
+ t.Fatalf("Values(v) returned error %v", err)
+ }
+
+ if len(vs) != 2 {
+ t.Fatalf("len(vs) == %d, want 2", len(vs))
+ }
+
+ if _, ok := vs[0].([]byte); !ok {
+ t.Fatalf("first result is type %T, expected []byte", vs[0])
+ }
+
+ if _, ok := vs[1].(error); !ok {
+ t.Fatalf("second result is type %T, expected error", vs[2])
+ }
+}
+
+func BenchmarkDoEmpty(b *testing.B) {
+ b.StopTimer()
+ c, err := redis.DialDefaultServer()
+ if err != nil {
+ b.Fatal(err)
+ }
+ defer c.Close()
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ if _, err := c.Do(""); err != nil {
+ b.Fatal(err)
+ }
+ }
+}
+
+func BenchmarkDoPing(b *testing.B) {
+ b.StopTimer()
+ c, err := redis.DialDefaultServer()
+ if err != nil {
+ b.Fatal(err)
+ }
+ defer c.Close()
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ if _, err := c.Do("PING"); err != nil {
+ b.Fatal(err)
+ }
+ }
+}
diff --git a/vendor/github.com/garyburd/redigo/redis/doc.go b/vendor/github.com/garyburd/redigo/redis/doc.go
new file mode 100644
index 0000000..d4f4892
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/redis/doc.go
@@ -0,0 +1,177 @@
+// Copyright 2012 Gary Burd
+//
+// 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 redis is a client for the Redis database.
+//
+// The Redigo FAQ (https://github.com/garyburd/redigo/wiki/FAQ) contains more
+// documentation about this package.
+//
+// Connections
+//
+// The Conn interface is the primary interface for working with Redis.
+// Applications create connections by calling the Dial, DialWithTimeout or
+// NewConn functions. In the future, functions will be added for creating
+// sharded and other types of connections.
+//
+// The application must call the connection Close method when the application
+// is done with the connection.
+//
+// Executing Commands
+//
+// The Conn interface has a generic method for executing Redis commands:
+//
+// Do(commandName string, args ...interface{}) (reply interface{}, err error)
+//
+// The Redis command reference (http://redis.io/commands) lists the available
+// commands. An example of using the Redis APPEND command is:
+//
+// n, err := conn.Do("APPEND", "key", "value")
+//
+// The Do method converts command arguments to binary strings for transmission
+// to the server as follows:
+//
+// Go Type Conversion
+// []byte Sent as is
+// string Sent as is
+// int, int64 strconv.FormatInt(v)
+// float64 strconv.FormatFloat(v, 'g', -1, 64)
+// bool true -> "1", false -> "0"
+// nil ""
+// all other types fmt.Print(v)
+//
+// Redis command reply types are represented using the following Go types:
+//
+// Redis type Go type
+// error redis.Error
+// integer int64
+// simple string string
+// bulk string []byte or nil if value not present.
+// array []interface{} or nil if value not present.
+//
+// Use type assertions or the reply helper functions to convert from
+// interface{} to the specific Go type for the command result.
+//
+// Pipelining
+//
+// Connections support pipelining using the Send, Flush and Receive methods.
+//
+// Send(commandName string, args ...interface{}) error
+// Flush() error
+// Receive() (reply interface{}, err error)
+//
+// Send writes the command to the connection's output buffer. Flush flushes the
+// connection's output buffer to the server. Receive reads a single reply from
+// the server. The following example shows a simple pipeline.
+//
+// c.Send("SET", "foo", "bar")
+// c.Send("GET", "foo")
+// c.Flush()
+// c.Receive() // reply from SET
+// v, err = c.Receive() // reply from GET
+//
+// The Do method combines the functionality of the Send, Flush and Receive
+// methods. The Do method starts by writing the command and flushing the output
+// buffer. Next, the Do method receives all pending replies including the reply
+// for the command just sent by Do. If any of the received replies is an error,
+// then Do returns the error. If there are no errors, then Do returns the last
+// reply. If the command argument to the Do method is "", then the Do method
+// will flush the output buffer and receive pending replies without sending a
+// command.
+//
+// Use the Send and Do methods to implement pipelined transactions.
+//
+// c.Send("MULTI")
+// c.Send("INCR", "foo")
+// c.Send("INCR", "bar")
+// r, err := c.Do("EXEC")
+// fmt.Println(r) // prints [1, 1]
+//
+// Concurrency
+//
+// Connections support one concurrent caller to the Receive method and one
+// concurrent caller to the Send and Flush methods. No other concurrency is
+// supported including concurrent calls to the Do method.
+//
+// For full concurrent access to Redis, use the thread-safe Pool to get, use
+// and release a connection from within a goroutine. Connections returned from
+// a Pool have the concurrency restrictions described in the previous
+// paragraph.
+//
+// Publish and Subscribe
+//
+// Use the Send, Flush and Receive methods to implement Pub/Sub subscribers.
+//
+// c.Send("SUBSCRIBE", "example")
+// c.Flush()
+// for {
+// reply, err := c.Receive()
+// if err != nil {
+// return err
+// }
+// // process pushed message
+// }
+//
+// The PubSubConn type wraps a Conn with convenience methods for implementing
+// subscribers. The Subscribe, PSubscribe, Unsubscribe and PUnsubscribe methods
+// send and flush a subscription management command. The receive method
+// converts a pushed message to convenient types for use in a type switch.
+//
+// psc := redis.PubSubConn{Conn: c}
+// psc.Subscribe("example")
+// for {
+// switch v := psc.Receive().(type) {
+// case redis.Message:
+// fmt.Printf("%s: message: %s\n", v.Channel, v.Data)
+// case redis.Subscription:
+// fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count)
+// case error:
+// return v
+// }
+// }
+//
+// Reply Helpers
+//
+// The Bool, Int, Bytes, String, Strings and Values functions convert a reply
+// to a value of a specific type. To allow convenient wrapping of calls to the
+// connection Do and Receive methods, the functions take a second argument of
+// type error. If the error is non-nil, then the helper function returns the
+// error. If the error is nil, the function converts the reply to the specified
+// type:
+//
+// exists, err := redis.Bool(c.Do("EXISTS", "foo"))
+// if err != nil {
+// // handle error return from c.Do or type conversion error.
+// }
+//
+// The Scan function converts elements of a array reply to Go types:
+//
+// var value1 int
+// var value2 string
+// reply, err := redis.Values(c.Do("MGET", "key1", "key2"))
+// if err != nil {
+// // handle error
+// }
+// if _, err := redis.Scan(reply, &value1, &value2); err != nil {
+// // handle error
+// }
+//
+// Errors
+//
+// Connection methods return error replies from the server as type redis.Error.
+//
+// Call the connection Err() method to determine if the connection encountered
+// non-recoverable error such as a network error or protocol parsing error. If
+// Err() returns a non-nil value, then the connection is not usable and should
+// be closed.
+package redis // import "github.com/garyburd/redigo/redis"
diff --git a/vendor/github.com/garyburd/redigo/redis/go17.go b/vendor/github.com/garyburd/redigo/redis/go17.go
new file mode 100644
index 0000000..3f951e5
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/redis/go17.go
@@ -0,0 +1,33 @@
+// +build go1.7
+
+package redis
+
+import "crypto/tls"
+
+// similar cloneTLSClientConfig in the stdlib, but also honor skipVerify for the nil case
+func cloneTLSClientConfig(cfg *tls.Config, skipVerify bool) *tls.Config {
+ if cfg == nil {
+ return &tls.Config{InsecureSkipVerify: skipVerify}
+ }
+ return &tls.Config{
+ Rand: cfg.Rand,
+ Time: cfg.Time,
+ Certificates: cfg.Certificates,
+ NameToCertificate: cfg.NameToCertificate,
+ GetCertificate: cfg.GetCertificate,
+ RootCAs: cfg.RootCAs,
+ NextProtos: cfg.NextProtos,
+ ServerName: cfg.ServerName,
+ ClientAuth: cfg.ClientAuth,
+ ClientCAs: cfg.ClientCAs,
+ InsecureSkipVerify: cfg.InsecureSkipVerify,
+ CipherSuites: cfg.CipherSuites,
+ PreferServerCipherSuites: cfg.PreferServerCipherSuites,
+ ClientSessionCache: cfg.ClientSessionCache,
+ MinVersion: cfg.MinVersion,
+ MaxVersion: cfg.MaxVersion,
+ CurvePreferences: cfg.CurvePreferences,
+ DynamicRecordSizingDisabled: cfg.DynamicRecordSizingDisabled,
+ Renegotiation: cfg.Renegotiation,
+ }
+}
diff --git a/vendor/github.com/garyburd/redigo/redis/log.go b/vendor/github.com/garyburd/redigo/redis/log.go
new file mode 100644
index 0000000..129b86d
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/redis/log.go
@@ -0,0 +1,117 @@
+// Copyright 2012 Gary Burd
+//
+// 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 redis
+
+import (
+ "bytes"
+ "fmt"
+ "log"
+)
+
+// NewLoggingConn returns a logging wrapper around a connection.
+func NewLoggingConn(conn Conn, logger *log.Logger, prefix string) Conn {
+ if prefix != "" {
+ prefix = prefix + "."
+ }
+ return &loggingConn{conn, logger, prefix}
+}
+
+type loggingConn struct {
+ Conn
+ logger *log.Logger
+ prefix string
+}
+
+func (c *loggingConn) Close() error {
+ err := c.Conn.Close()
+ var buf bytes.Buffer
+ fmt.Fprintf(&buf, "%sClose() -> (%v)", c.prefix, err)
+ c.logger.Output(2, buf.String())
+ return err
+}
+
+func (c *loggingConn) printValue(buf *bytes.Buffer, v interface{}) {
+ const chop = 32
+ switch v := v.(type) {
+ case []byte:
+ if len(v) > chop {
+ fmt.Fprintf(buf, "%q...", v[:chop])
+ } else {
+ fmt.Fprintf(buf, "%q", v)
+ }
+ case string:
+ if len(v) > chop {
+ fmt.Fprintf(buf, "%q...", v[:chop])
+ } else {
+ fmt.Fprintf(buf, "%q", v)
+ }
+ case []interface{}:
+ if len(v) == 0 {
+ buf.WriteString("[]")
+ } else {
+ sep := "["
+ fin := "]"
+ if len(v) > chop {
+ v = v[:chop]
+ fin = "...]"
+ }
+ for _, vv := range v {
+ buf.WriteString(sep)
+ c.printValue(buf, vv)
+ sep = ", "
+ }
+ buf.WriteString(fin)
+ }
+ default:
+ fmt.Fprint(buf, v)
+ }
+}
+
+func (c *loggingConn) print(method, commandName string, args []interface{}, reply interface{}, err error) {
+ var buf bytes.Buffer
+ fmt.Fprintf(&buf, "%s%s(", c.prefix, method)
+ if method != "Receive" {
+ buf.WriteString(commandName)
+ for _, arg := range args {
+ buf.WriteString(", ")
+ c.printValue(&buf, arg)
+ }
+ }
+ buf.WriteString(") -> (")
+ if method != "Send" {
+ c.printValue(&buf, reply)
+ buf.WriteString(", ")
+ }
+ fmt.Fprintf(&buf, "%v)", err)
+ c.logger.Output(3, buf.String())
+}
+
+func (c *loggingConn) Do(commandName string, args ...interface{}) (interface{}, error) {
+ reply, err := c.Conn.Do(commandName, args...)
+ c.print("Do", commandName, args, reply, err)
+ return reply, err
+}
+
+func (c *loggingConn) Send(commandName string, args ...interface{}) error {
+ err := c.Conn.Send(commandName, args...)
+ c.print("Send", commandName, args, nil, err)
+ return err
+}
+
+func (c *loggingConn) Receive() (interface{}, error) {
+ reply, err := c.Conn.Receive()
+ c.print("Receive", "", nil, reply, err)
+ return reply, err
+}
diff --git a/vendor/github.com/garyburd/redigo/redis/pool.go b/vendor/github.com/garyburd/redigo/redis/pool.go
new file mode 100644
index 0000000..283a41d
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/redis/pool.go
@@ -0,0 +1,416 @@
+// Copyright 2012 Gary Burd
+//
+// 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 redis
+
+import (
+ "bytes"
+ "container/list"
+ "crypto/rand"
+ "crypto/sha1"
+ "errors"
+ "io"
+ "strconv"
+ "sync"
+ "time"
+
+ "github.com/garyburd/redigo/internal"
+)
+
+var nowFunc = time.Now // for testing
+
+// ErrPoolExhausted is returned from a pool connection method (Do, Send,
+// Receive, Flush, Err) when the maximum number of database connections in the
+// pool has been reached.
+var ErrPoolExhausted = errors.New("redigo: connection pool exhausted")
+
+var (
+ errPoolClosed = errors.New("redigo: connection pool closed")
+ errConnClosed = errors.New("redigo: connection closed")
+)
+
+// Pool maintains a pool of connections. The application calls the Get method
+// to get a connection from the pool and the connection's Close method to
+// return the connection's resources to the pool.
+//
+// The following example shows how to use a pool in a web application. The
+// application creates a pool at application startup and makes it available to
+// request handlers using a package level variable. The pool configuration used
+// here is an example, not a recommendation.
+//
+// func newPool(addr string) *redis.Pool {
+// return &redis.Pool{
+// MaxIdle: 3,
+// IdleTimeout: 240 * time.Second,
+// Dial: func () (redis.Conn, error) { return redis.Dial("tcp", addr) },
+// }
+// }
+//
+// var (
+// pool *redis.Pool
+// redisServer = flag.String("redisServer", ":6379", "")
+// )
+//
+// func main() {
+// flag.Parse()
+// pool = newPool(*redisServer)
+// ...
+// }
+//
+// A request handler gets a connection from the pool and closes the connection
+// when the handler is done:
+//
+// func serveHome(w http.ResponseWriter, r *http.Request) {
+// conn := pool.Get()
+// defer conn.Close()
+// ...
+// }
+//
+// Use the Dial function to authenticate connections with the AUTH command or
+// select a database with the SELECT command:
+//
+// pool := &redis.Pool{
+// // Other pool configuration not shown in this example.
+// Dial: func () (redis.Conn, error) {
+// c, err := redis.Dial("tcp", server)
+// if err != nil {
+// return nil, err
+// }
+// if _, err := c.Do("AUTH", password); err != nil {
+// c.Close()
+// return nil, err
+// }
+// if _, err := c.Do("SELECT", db); err != nil {
+// c.Close()
+// return nil, err
+// }
+// return c, nil
+// }
+// }
+//
+// Use the TestOnBorrow function to check the health of an idle connection
+// before the connection is returned to the application. This example PINGs
+// connections that have been idle more than a minute:
+//
+// pool := &redis.Pool{
+// // Other pool configuration not shown in this example.
+// TestOnBorrow: func(c redis.Conn, t time.Time) error {
+// if time.Since(t) < time.Minute {
+// return nil
+// }
+// _, err := c.Do("PING")
+// return err
+// },
+// }
+//
+type Pool struct {
+
+ // Dial is an application supplied function for creating and configuring a
+ // connection.
+ //
+ // The connection returned from Dial must not be in a special state
+ // (subscribed to pubsub channel, transaction started, ...).
+ Dial func() (Conn, error)
+
+ // TestOnBorrow is an optional application supplied function for checking
+ // the health of an idle connection before the connection is used again by
+ // the application. Argument t is the time that the connection was returned
+ // to the pool. If the function returns an error, then the connection is
+ // closed.
+ TestOnBorrow func(c Conn, t time.Time) error
+
+ // Maximum number of idle connections in the pool.
+ MaxIdle int
+
+ // Maximum number of connections allocated by the pool at a given time.
+ // When zero, there is no limit on the number of connections in the pool.
+ MaxActive int
+
+ // Close connections after remaining idle for this duration. If the value
+ // is zero, then idle connections are not closed. Applications should set
+ // the timeout to a value less than the server's timeout.
+ IdleTimeout time.Duration
+
+ // If Wait is true and the pool is at the MaxActive limit, then Get() waits
+ // for a connection to be returned to the pool before returning.
+ Wait bool
+
+ // mu protects fields defined below.
+ mu sync.Mutex
+ cond *sync.Cond
+ closed bool
+ active int
+
+ // Stack of idleConn with most recently used at the front.
+ idle list.List
+}
+
+type idleConn struct {
+ c Conn
+ t time.Time
+}
+
+// NewPool creates a new pool.
+//
+// Deprecated: Initialize the Pool directory as shown in the example.
+func NewPool(newFn func() (Conn, error), maxIdle int) *Pool {
+ return &Pool{Dial: newFn, MaxIdle: maxIdle}
+}
+
+// Get gets a connection. The application must close the returned connection.
+// This method always returns a valid connection so that applications can defer
+// error handling to the first use of the connection. If there is an error
+// getting an underlying connection, then the connection Err, Do, Send, Flush
+// and Receive methods return that error.
+func (p *Pool) Get() Conn {
+ c, err := p.get()
+ if err != nil {
+ return errorConnection{err}
+ }
+ return &pooledConnection{p: p, c: c}
+}
+
+// ActiveCount returns the number of active connections in the pool.
+func (p *Pool) ActiveCount() int {
+ p.mu.Lock()
+ active := p.active
+ p.mu.Unlock()
+ return active
+}
+
+// Close releases the resources used by the pool.
+func (p *Pool) Close() error {
+ p.mu.Lock()
+ idle := p.idle
+ p.idle.Init()
+ p.closed = true
+ p.active -= idle.Len()
+ if p.cond != nil {
+ p.cond.Broadcast()
+ }
+ p.mu.Unlock()
+ for e := idle.Front(); e != nil; e = e.Next() {
+ e.Value.(idleConn).c.Close()
+ }
+ return nil
+}
+
+// release decrements the active count and signals waiters. The caller must
+// hold p.mu during the call.
+func (p *Pool) release() {
+ p.active -= 1
+ if p.cond != nil {
+ p.cond.Signal()
+ }
+}
+
+// get prunes stale connections and returns a connection from the idle list or
+// creates a new connection.
+func (p *Pool) get() (Conn, error) {
+ p.mu.Lock()
+
+ // Prune stale connections.
+
+ if timeout := p.IdleTimeout; timeout > 0 {
+ for i, n := 0, p.idle.Len(); i < n; i++ {
+ e := p.idle.Back()
+ if e == nil {
+ break
+ }
+ ic := e.Value.(idleConn)
+ if ic.t.Add(timeout).After(nowFunc()) {
+ break
+ }
+ p.idle.Remove(e)
+ p.release()
+ p.mu.Unlock()
+ ic.c.Close()
+ p.mu.Lock()
+ }
+ }
+
+ for {
+
+ // Get idle connection.
+
+ for i, n := 0, p.idle.Len(); i < n; i++ {
+ e := p.idle.Front()
+ if e == nil {
+ break
+ }
+ ic := e.Value.(idleConn)
+ p.idle.Remove(e)
+ test := p.TestOnBorrow
+ p.mu.Unlock()
+ if test == nil || test(ic.c, ic.t) == nil {
+ return ic.c, nil
+ }
+ ic.c.Close()
+ p.mu.Lock()
+ p.release()
+ }
+
+ // Check for pool closed before dialing a new connection.
+
+ if p.closed {
+ p.mu.Unlock()
+ return nil, errors.New("redigo: get on closed pool")
+ }
+
+ // Dial new connection if under limit.
+
+ if p.MaxActive == 0 || p.active < p.MaxActive {
+ dial := p.Dial
+ p.active += 1
+ p.mu.Unlock()
+ c, err := dial()
+ if err != nil {
+ p.mu.Lock()
+ p.release()
+ p.mu.Unlock()
+ c = nil
+ }
+ return c, err
+ }
+
+ if !p.Wait {
+ p.mu.Unlock()
+ return nil, ErrPoolExhausted
+ }
+
+ if p.cond == nil {
+ p.cond = sync.NewCond(&p.mu)
+ }
+ p.cond.Wait()
+ }
+}
+
+func (p *Pool) put(c Conn, forceClose bool) error {
+ err := c.Err()
+ p.mu.Lock()
+ if !p.closed && err == nil && !forceClose {
+ p.idle.PushFront(idleConn{t: nowFunc(), c: c})
+ if p.idle.Len() > p.MaxIdle {
+ c = p.idle.Remove(p.idle.Back()).(idleConn).c
+ } else {
+ c = nil
+ }
+ }
+
+ if c == nil {
+ if p.cond != nil {
+ p.cond.Signal()
+ }
+ p.mu.Unlock()
+ return nil
+ }
+
+ p.release()
+ p.mu.Unlock()
+ return c.Close()
+}
+
+type pooledConnection struct {
+ p *Pool
+ c Conn
+ state int
+}
+
+var (
+ sentinel []byte
+ sentinelOnce sync.Once
+)
+
+func initSentinel() {
+ p := make([]byte, 64)
+ if _, err := rand.Read(p); err == nil {
+ sentinel = p
+ } else {
+ h := sha1.New()
+ io.WriteString(h, "Oops, rand failed. Use time instead.")
+ io.WriteString(h, strconv.FormatInt(time.Now().UnixNano(), 10))
+ sentinel = h.Sum(nil)
+ }
+}
+
+func (pc *pooledConnection) Close() error {
+ c := pc.c
+ if _, ok := c.(errorConnection); ok {
+ return nil
+ }
+ pc.c = errorConnection{errConnClosed}
+
+ if pc.state&internal.MultiState != 0 {
+ c.Send("DISCARD")
+ pc.state &^= (internal.MultiState | internal.WatchState)
+ } else if pc.state&internal.WatchState != 0 {
+ c.Send("UNWATCH")
+ pc.state &^= internal.WatchState
+ }
+ if pc.state&internal.SubscribeState != 0 {
+ c.Send("UNSUBSCRIBE")
+ c.Send("PUNSUBSCRIBE")
+ // To detect the end of the message stream, ask the server to echo
+ // a sentinel value and read until we see that value.
+ sentinelOnce.Do(initSentinel)
+ c.Send("ECHO", sentinel)
+ c.Flush()
+ for {
+ p, err := c.Receive()
+ if err != nil {
+ break
+ }
+ if p, ok := p.([]byte); ok && bytes.Equal(p, sentinel) {
+ pc.state &^= internal.SubscribeState
+ break
+ }
+ }
+ }
+ c.Do("")
+ pc.p.put(c, pc.state != 0)
+ return nil
+}
+
+func (pc *pooledConnection) Err() error {
+ return pc.c.Err()
+}
+
+func (pc *pooledConnection) Do(commandName string, args ...interface{}) (reply interface{}, err error) {
+ ci := internal.LookupCommandInfo(commandName)
+ pc.state = (pc.state | ci.Set) &^ ci.Clear
+ return pc.c.Do(commandName, args...)
+}
+
+func (pc *pooledConnection) Send(commandName string, args ...interface{}) error {
+ ci := internal.LookupCommandInfo(commandName)
+ pc.state = (pc.state | ci.Set) &^ ci.Clear
+ return pc.c.Send(commandName, args...)
+}
+
+func (pc *pooledConnection) Flush() error {
+ return pc.c.Flush()
+}
+
+func (pc *pooledConnection) Receive() (reply interface{}, err error) {
+ return pc.c.Receive()
+}
+
+type errorConnection struct{ err error }
+
+func (ec errorConnection) Do(string, ...interface{}) (interface{}, error) { return nil, ec.err }
+func (ec errorConnection) Send(string, ...interface{}) error { return ec.err }
+func (ec errorConnection) Err() error { return ec.err }
+func (ec errorConnection) Close() error { return ec.err }
+func (ec errorConnection) Flush() error { return ec.err }
+func (ec errorConnection) Receive() (interface{}, error) { return nil, ec.err }
diff --git a/vendor/github.com/garyburd/redigo/redis/pool_test.go b/vendor/github.com/garyburd/redigo/redis/pool_test.go
new file mode 100644
index 0000000..26d2747
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/redis/pool_test.go
@@ -0,0 +1,684 @@
+// Copyright 2011 Gary Burd
+//
+// 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 redis_test
+
+import (
+ "errors"
+ "io"
+ "reflect"
+ "sync"
+ "testing"
+ "time"
+
+ "github.com/garyburd/redigo/redis"
+)
+
+type poolTestConn struct {
+ d *poolDialer
+ err error
+ redis.Conn
+}
+
+func (c *poolTestConn) Close() error {
+ c.d.mu.Lock()
+ c.d.open -= 1
+ c.d.mu.Unlock()
+ return c.Conn.Close()
+}
+
+func (c *poolTestConn) Err() error { return c.err }
+
+func (c *poolTestConn) Do(commandName string, args ...interface{}) (interface{}, error) {
+ if commandName == "ERR" {
+ c.err = args[0].(error)
+ commandName = "PING"
+ }
+ if commandName != "" {
+ c.d.commands = append(c.d.commands, commandName)
+ }
+ return c.Conn.Do(commandName, args...)
+}
+
+func (c *poolTestConn) Send(commandName string, args ...interface{}) error {
+ c.d.commands = append(c.d.commands, commandName)
+ return c.Conn.Send(commandName, args...)
+}
+
+type poolDialer struct {
+ mu sync.Mutex
+ t *testing.T
+ dialed int
+ open int
+ commands []string
+ dialErr error
+}
+
+func (d *poolDialer) dial() (redis.Conn, error) {
+ d.mu.Lock()
+ d.dialed += 1
+ dialErr := d.dialErr
+ d.mu.Unlock()
+ if dialErr != nil {
+ return nil, d.dialErr
+ }
+ c, err := redis.DialDefaultServer()
+ if err != nil {
+ return nil, err
+ }
+ d.mu.Lock()
+ d.open += 1
+ d.mu.Unlock()
+ return &poolTestConn{d: d, Conn: c}, nil
+}
+
+func (d *poolDialer) check(message string, p *redis.Pool, dialed, open int) {
+ d.mu.Lock()
+ if d.dialed != dialed {
+ d.t.Errorf("%s: dialed=%d, want %d", message, d.dialed, dialed)
+ }
+ if d.open != open {
+ d.t.Errorf("%s: open=%d, want %d", message, d.open, open)
+ }
+ if active := p.ActiveCount(); active != open {
+ d.t.Errorf("%s: active=%d, want %d", message, active, open)
+ }
+ d.mu.Unlock()
+}
+
+func TestPoolReuse(t *testing.T) {
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: 2,
+ Dial: d.dial,
+ }
+
+ for i := 0; i < 10; i++ {
+ c1 := p.Get()
+ c1.Do("PING")
+ c2 := p.Get()
+ c2.Do("PING")
+ c1.Close()
+ c2.Close()
+ }
+
+ d.check("before close", p, 2, 2)
+ p.Close()
+ d.check("after close", p, 2, 0)
+}
+
+func TestPoolMaxIdle(t *testing.T) {
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: 2,
+ Dial: d.dial,
+ }
+ defer p.Close()
+
+ for i := 0; i < 10; i++ {
+ c1 := p.Get()
+ c1.Do("PING")
+ c2 := p.Get()
+ c2.Do("PING")
+ c3 := p.Get()
+ c3.Do("PING")
+ c1.Close()
+ c2.Close()
+ c3.Close()
+ }
+ d.check("before close", p, 12, 2)
+ p.Close()
+ d.check("after close", p, 12, 0)
+}
+
+func TestPoolError(t *testing.T) {
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: 2,
+ Dial: d.dial,
+ }
+ defer p.Close()
+
+ c := p.Get()
+ c.Do("ERR", io.EOF)
+ if c.Err() == nil {
+ t.Errorf("expected c.Err() != nil")
+ }
+ c.Close()
+
+ c = p.Get()
+ c.Do("ERR", io.EOF)
+ c.Close()
+
+ d.check(".", p, 2, 0)
+}
+
+func TestPoolClose(t *testing.T) {
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: 2,
+ Dial: d.dial,
+ }
+ defer p.Close()
+
+ c1 := p.Get()
+ c1.Do("PING")
+ c2 := p.Get()
+ c2.Do("PING")
+ c3 := p.Get()
+ c3.Do("PING")
+
+ c1.Close()
+ if _, err := c1.Do("PING"); err == nil {
+ t.Errorf("expected error after connection closed")
+ }
+
+ c2.Close()
+ c2.Close()
+
+ p.Close()
+
+ d.check("after pool close", p, 3, 1)
+
+ if _, err := c1.Do("PING"); err == nil {
+ t.Errorf("expected error after connection and pool closed")
+ }
+
+ c3.Close()
+
+ d.check("after conn close", p, 3, 0)
+
+ c1 = p.Get()
+ if _, err := c1.Do("PING"); err == nil {
+ t.Errorf("expected error after pool closed")
+ }
+}
+
+func TestPoolTimeout(t *testing.T) {
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: 2,
+ IdleTimeout: 300 * time.Second,
+ Dial: d.dial,
+ }
+ defer p.Close()
+
+ now := time.Now()
+ redis.SetNowFunc(func() time.Time { return now })
+ defer redis.SetNowFunc(time.Now)
+
+ c := p.Get()
+ c.Do("PING")
+ c.Close()
+
+ d.check("1", p, 1, 1)
+
+ now = now.Add(p.IdleTimeout)
+
+ c = p.Get()
+ c.Do("PING")
+ c.Close()
+
+ d.check("2", p, 2, 1)
+}
+
+func TestPoolConcurrenSendReceive(t *testing.T) {
+ p := &redis.Pool{
+ Dial: redis.DialDefaultServer,
+ }
+ defer p.Close()
+
+ c := p.Get()
+ done := make(chan error, 1)
+ go func() {
+ _, err := c.Receive()
+ done <- err
+ }()
+ c.Send("PING")
+ c.Flush()
+ err := <-done
+ if err != nil {
+ t.Fatalf("Receive() returned error %v", err)
+ }
+ _, err = c.Do("")
+ if err != nil {
+ t.Fatalf("Do() returned error %v", err)
+ }
+ c.Close()
+}
+
+func TestPoolBorrowCheck(t *testing.T) {
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: 2,
+ Dial: d.dial,
+ TestOnBorrow: func(redis.Conn, time.Time) error { return redis.Error("BLAH") },
+ }
+ defer p.Close()
+
+ for i := 0; i < 10; i++ {
+ c := p.Get()
+ c.Do("PING")
+ c.Close()
+ }
+ d.check("1", p, 10, 1)
+}
+
+func TestPoolMaxActive(t *testing.T) {
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: 2,
+ MaxActive: 2,
+ Dial: d.dial,
+ }
+ defer p.Close()
+
+ c1 := p.Get()
+ c1.Do("PING")
+ c2 := p.Get()
+ c2.Do("PING")
+
+ d.check("1", p, 2, 2)
+
+ c3 := p.Get()
+ if _, err := c3.Do("PING"); err != redis.ErrPoolExhausted {
+ t.Errorf("expected pool exhausted")
+ }
+
+ c3.Close()
+ d.check("2", p, 2, 2)
+ c2.Close()
+ d.check("3", p, 2, 2)
+
+ c3 = p.Get()
+ if _, err := c3.Do("PING"); err != nil {
+ t.Errorf("expected good channel, err=%v", err)
+ }
+ c3.Close()
+
+ d.check("4", p, 2, 2)
+}
+
+func TestPoolMonitorCleanup(t *testing.T) {
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: 2,
+ MaxActive: 2,
+ Dial: d.dial,
+ }
+ defer p.Close()
+
+ c := p.Get()
+ c.Send("MONITOR")
+ c.Close()
+
+ d.check("", p, 1, 0)
+}
+
+func TestPoolPubSubCleanup(t *testing.T) {
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: 2,
+ MaxActive: 2,
+ Dial: d.dial,
+ }
+ defer p.Close()
+
+ c := p.Get()
+ c.Send("SUBSCRIBE", "x")
+ c.Close()
+
+ want := []string{"SUBSCRIBE", "UNSUBSCRIBE", "PUNSUBSCRIBE", "ECHO"}
+ if !reflect.DeepEqual(d.commands, want) {
+ t.Errorf("got commands %v, want %v", d.commands, want)
+ }
+ d.commands = nil
+
+ c = p.Get()
+ c.Send("PSUBSCRIBE", "x*")
+ c.Close()
+
+ want = []string{"PSUBSCRIBE", "UNSUBSCRIBE", "PUNSUBSCRIBE", "ECHO"}
+ if !reflect.DeepEqual(d.commands, want) {
+ t.Errorf("got commands %v, want %v", d.commands, want)
+ }
+ d.commands = nil
+}
+
+func TestPoolTransactionCleanup(t *testing.T) {
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: 2,
+ MaxActive: 2,
+ Dial: d.dial,
+ }
+ defer p.Close()
+
+ c := p.Get()
+ c.Do("WATCH", "key")
+ c.Do("PING")
+ c.Close()
+
+ want := []string{"WATCH", "PING", "UNWATCH"}
+ if !reflect.DeepEqual(d.commands, want) {
+ t.Errorf("got commands %v, want %v", d.commands, want)
+ }
+ d.commands = nil
+
+ c = p.Get()
+ c.Do("WATCH", "key")
+ c.Do("UNWATCH")
+ c.Do("PING")
+ c.Close()
+
+ want = []string{"WATCH", "UNWATCH", "PING"}
+ if !reflect.DeepEqual(d.commands, want) {
+ t.Errorf("got commands %v, want %v", d.commands, want)
+ }
+ d.commands = nil
+
+ c = p.Get()
+ c.Do("WATCH", "key")
+ c.Do("MULTI")
+ c.Do("PING")
+ c.Close()
+
+ want = []string{"WATCH", "MULTI", "PING", "DISCARD"}
+ if !reflect.DeepEqual(d.commands, want) {
+ t.Errorf("got commands %v, want %v", d.commands, want)
+ }
+ d.commands = nil
+
+ c = p.Get()
+ c.Do("WATCH", "key")
+ c.Do("MULTI")
+ c.Do("DISCARD")
+ c.Do("PING")
+ c.Close()
+
+ want = []string{"WATCH", "MULTI", "DISCARD", "PING"}
+ if !reflect.DeepEqual(d.commands, want) {
+ t.Errorf("got commands %v, want %v", d.commands, want)
+ }
+ d.commands = nil
+
+ c = p.Get()
+ c.Do("WATCH", "key")
+ c.Do("MULTI")
+ c.Do("EXEC")
+ c.Do("PING")
+ c.Close()
+
+ want = []string{"WATCH", "MULTI", "EXEC", "PING"}
+ if !reflect.DeepEqual(d.commands, want) {
+ t.Errorf("got commands %v, want %v", d.commands, want)
+ }
+ d.commands = nil
+}
+
+func startGoroutines(p *redis.Pool, cmd string, args ...interface{}) chan error {
+ errs := make(chan error, 10)
+ for i := 0; i < cap(errs); i++ {
+ go func() {
+ c := p.Get()
+ _, err := c.Do(cmd, args...)
+ errs <- err
+ c.Close()
+ }()
+ }
+
+ // Wait for goroutines to block.
+ time.Sleep(time.Second / 4)
+
+ return errs
+}
+
+func TestWaitPool(t *testing.T) {
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: 1,
+ MaxActive: 1,
+ Dial: d.dial,
+ Wait: true,
+ }
+ defer p.Close()
+
+ c := p.Get()
+ errs := startGoroutines(p, "PING")
+ d.check("before close", p, 1, 1)
+ c.Close()
+ timeout := time.After(2 * time.Second)
+ for i := 0; i < cap(errs); i++ {
+ select {
+ case err := <-errs:
+ if err != nil {
+ t.Fatal(err)
+ }
+ case <-timeout:
+ t.Fatalf("timeout waiting for blocked goroutine %d", i)
+ }
+ }
+ d.check("done", p, 1, 1)
+}
+
+func TestWaitPoolClose(t *testing.T) {
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: 1,
+ MaxActive: 1,
+ Dial: d.dial,
+ Wait: true,
+ }
+ defer p.Close()
+
+ c := p.Get()
+ if _, err := c.Do("PING"); err != nil {
+ t.Fatal(err)
+ }
+ errs := startGoroutines(p, "PING")
+ d.check("before close", p, 1, 1)
+ p.Close()
+ timeout := time.After(2 * time.Second)
+ for i := 0; i < cap(errs); i++ {
+ select {
+ case err := <-errs:
+ switch err {
+ case nil:
+ t.Fatal("blocked goroutine did not get error")
+ case redis.ErrPoolExhausted:
+ t.Fatal("blocked goroutine got pool exhausted error")
+ }
+ case <-timeout:
+ t.Fatal("timeout waiting for blocked goroutine")
+ }
+ }
+ c.Close()
+ d.check("done", p, 1, 0)
+}
+
+func TestWaitPoolCommandError(t *testing.T) {
+ testErr := errors.New("test")
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: 1,
+ MaxActive: 1,
+ Dial: d.dial,
+ Wait: true,
+ }
+ defer p.Close()
+
+ c := p.Get()
+ errs := startGoroutines(p, "ERR", testErr)
+ d.check("before close", p, 1, 1)
+ c.Close()
+ timeout := time.After(2 * time.Second)
+ for i := 0; i < cap(errs); i++ {
+ select {
+ case err := <-errs:
+ if err != nil {
+ t.Fatal(err)
+ }
+ case <-timeout:
+ t.Fatalf("timeout waiting for blocked goroutine %d", i)
+ }
+ }
+ d.check("done", p, cap(errs), 0)
+}
+
+func TestWaitPoolDialError(t *testing.T) {
+ testErr := errors.New("test")
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: 1,
+ MaxActive: 1,
+ Dial: d.dial,
+ Wait: true,
+ }
+ defer p.Close()
+
+ c := p.Get()
+ errs := startGoroutines(p, "ERR", testErr)
+ d.check("before close", p, 1, 1)
+
+ d.dialErr = errors.New("dial")
+ c.Close()
+
+ nilCount := 0
+ errCount := 0
+ timeout := time.After(2 * time.Second)
+ for i := 0; i < cap(errs); i++ {
+ select {
+ case err := <-errs:
+ switch err {
+ case nil:
+ nilCount++
+ case d.dialErr:
+ errCount++
+ default:
+ t.Fatalf("expected dial error or nil, got %v", err)
+ }
+ case <-timeout:
+ t.Fatalf("timeout waiting for blocked goroutine %d", i)
+ }
+ }
+ if nilCount != 1 {
+ t.Errorf("expected one nil error, got %d", nilCount)
+ }
+ if errCount != cap(errs)-1 {
+ t.Errorf("expected %d dial errors, got %d", cap(errs)-1, errCount)
+ }
+ d.check("done", p, cap(errs), 0)
+}
+
+// Borrowing requires us to iterate over the idle connections, unlock the pool,
+// and perform a blocking operation to check the connection still works. If
+// TestOnBorrow fails, we must reacquire the lock and continue iteration. This
+// test ensures that iteration will work correctly if multiple threads are
+// iterating simultaneously.
+func TestLocking_TestOnBorrowFails_PoolDoesntCrash(t *testing.T) {
+ const count = 100
+
+ // First we'll Create a pool where the pilfering of idle connections fails.
+ d := poolDialer{t: t}
+ p := &redis.Pool{
+ MaxIdle: count,
+ MaxActive: count,
+ Dial: d.dial,
+ TestOnBorrow: func(c redis.Conn, t time.Time) error {
+ return errors.New("No way back into the real world.")
+ },
+ }
+ defer p.Close()
+
+ // Fill the pool with idle connections.
+ conns := make([]redis.Conn, count)
+ for i := range conns {
+ conns[i] = p.Get()
+ }
+ for i := range conns {
+ conns[i].Close()
+ }
+
+ // Spawn a bunch of goroutines to thrash the pool.
+ var wg sync.WaitGroup
+ wg.Add(count)
+ for i := 0; i < count; i++ {
+ go func() {
+ c := p.Get()
+ if c.Err() != nil {
+ t.Errorf("pool get failed: %v", c.Err())
+ }
+ c.Close()
+ wg.Done()
+ }()
+ }
+ wg.Wait()
+ if d.dialed != count*2 {
+ t.Errorf("Expected %d dials, got %d", count*2, d.dialed)
+ }
+}
+
+func BenchmarkPoolGet(b *testing.B) {
+ b.StopTimer()
+ p := redis.Pool{Dial: redis.DialDefaultServer, MaxIdle: 2}
+ c := p.Get()
+ if err := c.Err(); err != nil {
+ b.Fatal(err)
+ }
+ c.Close()
+ defer p.Close()
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ c = p.Get()
+ c.Close()
+ }
+}
+
+func BenchmarkPoolGetErr(b *testing.B) {
+ b.StopTimer()
+ p := redis.Pool{Dial: redis.DialDefaultServer, MaxIdle: 2}
+ c := p.Get()
+ if err := c.Err(); err != nil {
+ b.Fatal(err)
+ }
+ c.Close()
+ defer p.Close()
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ c = p.Get()
+ if err := c.Err(); err != nil {
+ b.Fatal(err)
+ }
+ c.Close()
+ }
+}
+
+func BenchmarkPoolGetPing(b *testing.B) {
+ b.StopTimer()
+ p := redis.Pool{Dial: redis.DialDefaultServer, MaxIdle: 2}
+ c := p.Get()
+ if err := c.Err(); err != nil {
+ b.Fatal(err)
+ }
+ c.Close()
+ defer p.Close()
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ c = p.Get()
+ if _, err := c.Do("PING"); err != nil {
+ b.Fatal(err)
+ }
+ c.Close()
+ }
+}
diff --git a/vendor/github.com/garyburd/redigo/redis/pre_go17.go b/vendor/github.com/garyburd/redigo/redis/pre_go17.go
new file mode 100644
index 0000000..0212f60
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/redis/pre_go17.go
@@ -0,0 +1,31 @@
+// +build !go1.7
+
+package redis
+
+import "crypto/tls"
+
+// similar cloneTLSClientConfig in the stdlib, but also honor skipVerify for the nil case
+func cloneTLSClientConfig(cfg *tls.Config, skipVerify bool) *tls.Config {
+ if cfg == nil {
+ return &tls.Config{InsecureSkipVerify: skipVerify}
+ }
+ return &tls.Config{
+ Rand: cfg.Rand,
+ Time: cfg.Time,
+ Certificates: cfg.Certificates,
+ NameToCertificate: cfg.NameToCertificate,
+ GetCertificate: cfg.GetCertificate,
+ RootCAs: cfg.RootCAs,
+ NextProtos: cfg.NextProtos,
+ ServerName: cfg.ServerName,
+ ClientAuth: cfg.ClientAuth,
+ ClientCAs: cfg.ClientCAs,
+ InsecureSkipVerify: cfg.InsecureSkipVerify,
+ CipherSuites: cfg.CipherSuites,
+ PreferServerCipherSuites: cfg.PreferServerCipherSuites,
+ ClientSessionCache: cfg.ClientSessionCache,
+ MinVersion: cfg.MinVersion,
+ MaxVersion: cfg.MaxVersion,
+ CurvePreferences: cfg.CurvePreferences,
+ }
+}
diff --git a/vendor/github.com/garyburd/redigo/redis/pubsub.go b/vendor/github.com/garyburd/redigo/redis/pubsub.go
new file mode 100644
index 0000000..c0ecce8
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/redis/pubsub.go
@@ -0,0 +1,144 @@
+// Copyright 2012 Gary Burd
+//
+// 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 redis
+
+import "errors"
+
+// Subscription represents a subscribe or unsubscribe notification.
+type Subscription struct {
+
+ // Kind is "subscribe", "unsubscribe", "psubscribe" or "punsubscribe"
+ Kind string
+
+ // The channel that was changed.
+ Channel string
+
+ // The current number of subscriptions for connection.
+ Count int
+}
+
+// Message represents a message notification.
+type Message struct {
+
+ // The originating channel.
+ Channel string
+
+ // The message data.
+ Data []byte
+}
+
+// PMessage represents a pmessage notification.
+type PMessage struct {
+
+ // The matched pattern.
+ Pattern string
+
+ // The originating channel.
+ Channel string
+
+ // The message data.
+ Data []byte
+}
+
+// Pong represents a pubsub pong notification.
+type Pong struct {
+ Data string
+}
+
+// PubSubConn wraps a Conn with convenience methods for subscribers.
+type PubSubConn struct {
+ Conn Conn
+}
+
+// Close closes the connection.
+func (c PubSubConn) Close() error {
+ return c.Conn.Close()
+}
+
+// Subscribe subscribes the connection to the specified channels.
+func (c PubSubConn) Subscribe(channel ...interface{}) error {
+ c.Conn.Send("SUBSCRIBE", channel...)
+ return c.Conn.Flush()
+}
+
+// PSubscribe subscribes the connection to the given patterns.
+func (c PubSubConn) PSubscribe(channel ...interface{}) error {
+ c.Conn.Send("PSUBSCRIBE", channel...)
+ return c.Conn.Flush()
+}
+
+// Unsubscribe unsubscribes the connection from the given channels, or from all
+// of them if none is given.
+func (c PubSubConn) Unsubscribe(channel ...interface{}) error {
+ c.Conn.Send("UNSUBSCRIBE", channel...)
+ return c.Conn.Flush()
+}
+
+// PUnsubscribe unsubscribes the connection from the given patterns, or from all
+// of them if none is given.
+func (c PubSubConn) PUnsubscribe(channel ...interface{}) error {
+ c.Conn.Send("PUNSUBSCRIBE", channel...)
+ return c.Conn.Flush()
+}
+
+// Ping sends a PING to the server with the specified data.
+func (c PubSubConn) Ping(data string) error {
+ c.Conn.Send("PING", data)
+ return c.Conn.Flush()
+}
+
+// Receive returns a pushed message as a Subscription, Message, PMessage, Pong
+// or error. The return value is intended to be used directly in a type switch
+// as illustrated in the PubSubConn example.
+func (c PubSubConn) Receive() interface{} {
+ reply, err := Values(c.Conn.Receive())
+ if err != nil {
+ return err
+ }
+
+ var kind string
+ reply, err = Scan(reply, &kind)
+ if err != nil {
+ return err
+ }
+
+ switch kind {
+ case "message":
+ var m Message
+ if _, err := Scan(reply, &m.Channel, &m.Data); err != nil {
+ return err
+ }
+ return m
+ case "pmessage":
+ var pm PMessage
+ if _, err := Scan(reply, &pm.Pattern, &pm.Channel, &pm.Data); err != nil {
+ return err
+ }
+ return pm
+ case "subscribe", "psubscribe", "unsubscribe", "punsubscribe":
+ s := Subscription{Kind: kind}
+ if _, err := Scan(reply, &s.Channel, &s.Count); err != nil {
+ return err
+ }
+ return s
+ case "pong":
+ var p Pong
+ if _, err := Scan(reply, &p.Data); err != nil {
+ return err
+ }
+ return p
+ }
+ return errors.New("redigo: unknown pubsub notification")
+}
diff --git a/vendor/github.com/garyburd/redigo/redis/pubsub_test.go b/vendor/github.com/garyburd/redigo/redis/pubsub_test.go
new file mode 100644
index 0000000..b955131
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/redis/pubsub_test.go
@@ -0,0 +1,148 @@
+// Copyright 2012 Gary Burd
+//
+// 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 redis_test
+
+import (
+ "fmt"
+ "reflect"
+ "sync"
+ "testing"
+
+ "github.com/garyburd/redigo/redis"
+)
+
+func publish(channel, value interface{}) {
+ c, err := dial()
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ defer c.Close()
+ c.Do("PUBLISH", channel, value)
+}
+
+// Applications can receive pushed messages from one goroutine and manage subscriptions from another goroutine.
+func ExamplePubSubConn() {
+ c, err := dial()
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ defer c.Close()
+ var wg sync.WaitGroup
+ wg.Add(2)
+
+ psc := redis.PubSubConn{Conn: c}
+
+ // This goroutine receives and prints pushed notifications from the server.
+ // The goroutine exits when the connection is unsubscribed from all
+ // channels or there is an error.
+ go func() {
+ defer wg.Done()
+ for {
+ switch n := psc.Receive().(type) {
+ case redis.Message:
+ fmt.Printf("Message: %s %s\n", n.Channel, n.Data)
+ case redis.PMessage:
+ fmt.Printf("PMessage: %s %s %s\n", n.Pattern, n.Channel, n.Data)
+ case redis.Subscription:
+ fmt.Printf("Subscription: %s %s %d\n", n.Kind, n.Channel, n.Count)
+ if n.Count == 0 {
+ return
+ }
+ case error:
+ fmt.Printf("error: %v\n", n)
+ return
+ }
+ }
+ }()
+
+ // This goroutine manages subscriptions for the connection.
+ go func() {
+ defer wg.Done()
+
+ psc.Subscribe("example")
+ psc.PSubscribe("p*")
+
+ // The following function calls publish a message using another
+ // connection to the Redis server.
+ publish("example", "hello")
+ publish("example", "world")
+ publish("pexample", "foo")
+ publish("pexample", "bar")
+
+ // Unsubscribe from all connections. This will cause the receiving
+ // goroutine to exit.
+ psc.Unsubscribe()
+ psc.PUnsubscribe()
+ }()
+
+ wg.Wait()
+
+ // Output:
+ // Subscription: subscribe example 1
+ // Subscription: psubscribe p* 2
+ // Message: example hello
+ // Message: example world
+ // PMessage: p* pexample foo
+ // PMessage: p* pexample bar
+ // Subscription: unsubscribe example 1
+ // Subscription: punsubscribe p* 0
+}
+
+func expectPushed(t *testing.T, c redis.PubSubConn, message string, expected interface{}) {
+ actual := c.Receive()
+ if !reflect.DeepEqual(actual, expected) {
+ t.Errorf("%s = %v, want %v", message, actual, expected)
+ }
+}
+
+func TestPushed(t *testing.T) {
+ pc, err := redis.DialDefaultServer()
+ if err != nil {
+ t.Fatalf("error connection to database, %v", err)
+ }
+ defer pc.Close()
+
+ sc, err := redis.DialDefaultServer()
+ if err != nil {
+ t.Fatalf("error connection to database, %v", err)
+ }
+ defer sc.Close()
+
+ c := redis.PubSubConn{Conn: sc}
+
+ c.Subscribe("c1")
+ expectPushed(t, c, "Subscribe(c1)", redis.Subscription{Kind: "subscribe", Channel: "c1", Count: 1})
+ c.Subscribe("c2")
+ expectPushed(t, c, "Subscribe(c2)", redis.Subscription{Kind: "subscribe", Channel: "c2", Count: 2})
+ c.PSubscribe("p1")
+ expectPushed(t, c, "PSubscribe(p1)", redis.Subscription{Kind: "psubscribe", Channel: "p1", Count: 3})
+ c.PSubscribe("p2")
+ expectPushed(t, c, "PSubscribe(p2)", redis.Subscription{Kind: "psubscribe", Channel: "p2", Count: 4})
+ c.PUnsubscribe()
+ expectPushed(t, c, "Punsubscribe(p1)", redis.Subscription{Kind: "punsubscribe", Channel: "p1", Count: 3})
+ expectPushed(t, c, "Punsubscribe()", redis.Subscription{Kind: "punsubscribe", Channel: "p2", Count: 2})
+
+ pc.Do("PUBLISH", "c1", "hello")
+ expectPushed(t, c, "PUBLISH c1 hello", redis.Message{Channel: "c1", Data: []byte("hello")})
+
+ c.Ping("hello")
+ expectPushed(t, c, `Ping("hello")`, redis.Pong{Data: "hello"})
+
+ c.Conn.Send("PING")
+ c.Conn.Flush()
+ expectPushed(t, c, `Send("PING")`, redis.Pong{})
+}
diff --git a/vendor/github.com/garyburd/redigo/redis/redis.go b/vendor/github.com/garyburd/redigo/redis/redis.go
new file mode 100644
index 0000000..b729829
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/redis/redis.go
@@ -0,0 +1,41 @@
+// Copyright 2012 Gary Burd
+//
+// 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 redis
+
+// Error represents an error returned in a command reply.
+type Error string
+
+func (err Error) Error() string { return string(err) }
+
+// Conn represents a connection to a Redis server.
+type Conn interface {
+ // Close closes the connection.
+ Close() error
+
+ // Err returns a non-nil value when the connection is not usable.
+ Err() error
+
+ // Do sends a command to the server and returns the received reply.
+ Do(commandName string, args ...interface{}) (reply interface{}, err error)
+
+ // Send writes the command to the client's output buffer.
+ Send(commandName string, args ...interface{}) error
+
+ // Flush flushes the output buffer to the Redis server.
+ Flush() error
+
+ // Receive receives a single reply from the Redis server
+ Receive() (reply interface{}, err error)
+}
diff --git a/vendor/github.com/garyburd/redigo/redis/reply.go b/vendor/github.com/garyburd/redigo/redis/reply.go
new file mode 100644
index 0000000..3d25dba
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/redis/reply.go
@@ -0,0 +1,425 @@
+// Copyright 2012 Gary Burd
+//
+// 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 redis
+
+import (
+ "errors"
+ "fmt"
+ "strconv"
+)
+
+// ErrNil indicates that a reply value is nil.
+var ErrNil = errors.New("redigo: nil returned")
+
+// Int is a helper that converts a command reply to an integer. If err is not
+// equal to nil, then Int returns 0, err. Otherwise, Int converts the
+// reply to an int as follows:
+//
+// Reply type Result
+// integer int(reply), nil
+// bulk string parsed reply, nil
+// nil 0, ErrNil
+// other 0, error
+func Int(reply interface{}, err error) (int, error) {
+ if err != nil {
+ return 0, err
+ }
+ switch reply := reply.(type) {
+ case int64:
+ x := int(reply)
+ if int64(x) != reply {
+ return 0, strconv.ErrRange
+ }
+ return x, nil
+ case []byte:
+ n, err := strconv.ParseInt(string(reply), 10, 0)
+ return int(n), err
+ case nil:
+ return 0, ErrNil
+ case Error:
+ return 0, reply
+ }
+ return 0, fmt.Errorf("redigo: unexpected type for Int, got type %T", reply)
+}
+
+// Int64 is a helper that converts a command reply to 64 bit integer. If err is
+// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the
+// reply to an int64 as follows:
+//
+// Reply type Result
+// integer reply, nil
+// bulk string parsed reply, nil
+// nil 0, ErrNil
+// other 0, error
+func Int64(reply interface{}, err error) (int64, error) {
+ if err != nil {
+ return 0, err
+ }
+ switch reply := reply.(type) {
+ case int64:
+ return reply, nil
+ case []byte:
+ n, err := strconv.ParseInt(string(reply), 10, 64)
+ return n, err
+ case nil:
+ return 0, ErrNil
+ case Error:
+ return 0, reply
+ }
+ return 0, fmt.Errorf("redigo: unexpected type for Int64, got type %T", reply)
+}
+
+var errNegativeInt = errors.New("redigo: unexpected value for Uint64")
+
+// Uint64 is a helper that converts a command reply to 64 bit integer. If err is
+// not equal to nil, then Int returns 0, err. Otherwise, Int64 converts the
+// reply to an int64 as follows:
+//
+// Reply type Result
+// integer reply, nil
+// bulk string parsed reply, nil
+// nil 0, ErrNil
+// other 0, error
+func Uint64(reply interface{}, err error) (uint64, error) {
+ if err != nil {
+ return 0, err
+ }
+ switch reply := reply.(type) {
+ case int64:
+ if reply < 0 {
+ return 0, errNegativeInt
+ }
+ return uint64(reply), nil
+ case []byte:
+ n, err := strconv.ParseUint(string(reply), 10, 64)
+ return n, err
+ case nil:
+ return 0, ErrNil
+ case Error:
+ return 0, reply
+ }
+ return 0, fmt.Errorf("redigo: unexpected type for Uint64, got type %T", reply)
+}
+
+// Float64 is a helper that converts a command reply to 64 bit float. If err is
+// not equal to nil, then Float64 returns 0, err. Otherwise, Float64 converts
+// the reply to an int as follows:
+//
+// Reply type Result
+// bulk string parsed reply, nil
+// nil 0, ErrNil
+// other 0, error
+func Float64(reply interface{}, err error) (float64, error) {
+ if err != nil {
+ return 0, err
+ }
+ switch reply := reply.(type) {
+ case []byte:
+ n, err := strconv.ParseFloat(string(reply), 64)
+ return n, err
+ case nil:
+ return 0, ErrNil
+ case Error:
+ return 0, reply
+ }
+ return 0, fmt.Errorf("redigo: unexpected type for Float64, got type %T", reply)
+}
+
+// String is a helper that converts a command reply to a string. If err is not
+// equal to nil, then String returns "", err. Otherwise String converts the
+// reply to a string as follows:
+//
+// Reply type Result
+// bulk string string(reply), nil
+// simple string reply, nil
+// nil "", ErrNil
+// other "", error
+func String(reply interface{}, err error) (string, error) {
+ if err != nil {
+ return "", err
+ }
+ switch reply := reply.(type) {
+ case []byte:
+ return string(reply), nil
+ case string:
+ return reply, nil
+ case nil:
+ return "", ErrNil
+ case Error:
+ return "", reply
+ }
+ return "", fmt.Errorf("redigo: unexpected type for String, got type %T", reply)
+}
+
+// Bytes is a helper that converts a command reply to a slice of bytes. If err
+// is not equal to nil, then Bytes returns nil, err. Otherwise Bytes converts
+// the reply to a slice of bytes as follows:
+//
+// Reply type Result
+// bulk string reply, nil
+// simple string []byte(reply), nil
+// nil nil, ErrNil
+// other nil, error
+func Bytes(reply interface{}, err error) ([]byte, error) {
+ if err != nil {
+ return nil, err
+ }
+ switch reply := reply.(type) {
+ case []byte:
+ return reply, nil
+ case string:
+ return []byte(reply), nil
+ case nil:
+ return nil, ErrNil
+ case Error:
+ return nil, reply
+ }
+ return nil, fmt.Errorf("redigo: unexpected type for Bytes, got type %T", reply)
+}
+
+// Bool is a helper that converts a command reply to a boolean. If err is not
+// equal to nil, then Bool returns false, err. Otherwise Bool converts the
+// reply to boolean as follows:
+//
+// Reply type Result
+// integer value != 0, nil
+// bulk string strconv.ParseBool(reply)
+// nil false, ErrNil
+// other false, error
+func Bool(reply interface{}, err error) (bool, error) {
+ if err != nil {
+ return false, err
+ }
+ switch reply := reply.(type) {
+ case int64:
+ return reply != 0, nil
+ case []byte:
+ return strconv.ParseBool(string(reply))
+ case nil:
+ return false, ErrNil
+ case Error:
+ return false, reply
+ }
+ return false, fmt.Errorf("redigo: unexpected type for Bool, got type %T", reply)
+}
+
+// MultiBulk is a helper that converts an array command reply to a []interface{}.
+//
+// Deprecated: Use Values instead.
+func MultiBulk(reply interface{}, err error) ([]interface{}, error) { return Values(reply, err) }
+
+// Values is a helper that converts an array command reply to a []interface{}.
+// If err is not equal to nil, then Values returns nil, err. Otherwise, Values
+// converts the reply as follows:
+//
+// Reply type Result
+// array reply, nil
+// nil nil, ErrNil
+// other nil, error
+func Values(reply interface{}, err error) ([]interface{}, error) {
+ if err != nil {
+ return nil, err
+ }
+ switch reply := reply.(type) {
+ case []interface{}:
+ return reply, nil
+ case nil:
+ return nil, ErrNil
+ case Error:
+ return nil, reply
+ }
+ return nil, fmt.Errorf("redigo: unexpected type for Values, got type %T", reply)
+}
+
+// Strings is a helper that converts an array command reply to a []string. If
+// err is not equal to nil, then Strings returns nil, err. Nil array items are
+// converted to "" in the output slice. Strings returns an error if an array
+// item is not a bulk string or nil.
+func Strings(reply interface{}, err error) ([]string, error) {
+ if err != nil {
+ return nil, err
+ }
+ switch reply := reply.(type) {
+ case []interface{}:
+ result := make([]string, len(reply))
+ for i := range reply {
+ if reply[i] == nil {
+ continue
+ }
+ p, ok := reply[i].([]byte)
+ if !ok {
+ return nil, fmt.Errorf("redigo: unexpected element type for Strings, got type %T", reply[i])
+ }
+ result[i] = string(p)
+ }
+ return result, nil
+ case nil:
+ return nil, ErrNil
+ case Error:
+ return nil, reply
+ }
+ return nil, fmt.Errorf("redigo: unexpected type for Strings, got type %T", reply)
+}
+
+// ByteSlices is a helper that converts an array command reply to a [][]byte.
+// If err is not equal to nil, then ByteSlices returns nil, err. Nil array
+// items are stay nil. ByteSlices returns an error if an array item is not a
+// bulk string or nil.
+func ByteSlices(reply interface{}, err error) ([][]byte, error) {
+ if err != nil {
+ return nil, err
+ }
+ switch reply := reply.(type) {
+ case []interface{}:
+ result := make([][]byte, len(reply))
+ for i := range reply {
+ if reply[i] == nil {
+ continue
+ }
+ p, ok := reply[i].([]byte)
+ if !ok {
+ return nil, fmt.Errorf("redigo: unexpected element type for ByteSlices, got type %T", reply[i])
+ }
+ result[i] = p
+ }
+ return result, nil
+ case nil:
+ return nil, ErrNil
+ case Error:
+ return nil, reply
+ }
+ return nil, fmt.Errorf("redigo: unexpected type for ByteSlices, got type %T", reply)
+}
+
+// Ints is a helper that converts an array command reply to a []int. If
+// err is not equal to nil, then Ints returns nil, err.
+func Ints(reply interface{}, err error) ([]int, error) {
+ var ints []int
+ values, err := Values(reply, err)
+ if err != nil {
+ return ints, err
+ }
+ if err := ScanSlice(values, &ints); err != nil {
+ return ints, err
+ }
+ return ints, nil
+}
+
+// StringMap is a helper that converts an array of strings (alternating key, value)
+// into a map[string]string. The HGETALL and CONFIG GET commands return replies in this format.
+// Requires an even number of values in result.
+func StringMap(result interface{}, err error) (map[string]string, error) {
+ values, err := Values(result, err)
+ if err != nil {
+ return nil, err
+ }
+ if len(values)%2 != 0 {
+ return nil, errors.New("redigo: StringMap expects even number of values result")
+ }
+ m := make(map[string]string, len(values)/2)
+ for i := 0; i < len(values); i += 2 {
+ key, okKey := values[i].([]byte)
+ value, okValue := values[i+1].([]byte)
+ if !okKey || !okValue {
+ return nil, errors.New("redigo: ScanMap key not a bulk string value")
+ }
+ m[string(key)] = string(value)
+ }
+ return m, nil
+}
+
+// IntMap is a helper that converts an array of strings (alternating key, value)
+// into a map[string]int. The HGETALL commands return replies in this format.
+// Requires an even number of values in result.
+func IntMap(result interface{}, err error) (map[string]int, error) {
+ values, err := Values(result, err)
+ if err != nil {
+ return nil, err
+ }
+ if len(values)%2 != 0 {
+ return nil, errors.New("redigo: IntMap expects even number of values result")
+ }
+ m := make(map[string]int, len(values)/2)
+ for i := 0; i < len(values); i += 2 {
+ key, ok := values[i].([]byte)
+ if !ok {
+ return nil, errors.New("redigo: ScanMap key not a bulk string value")
+ }
+ value, err := Int(values[i+1], nil)
+ if err != nil {
+ return nil, err
+ }
+ m[string(key)] = value
+ }
+ return m, nil
+}
+
+// Int64Map is a helper that converts an array of strings (alternating key, value)
+// into a map[string]int64. The HGETALL commands return replies in this format.
+// Requires an even number of values in result.
+func Int64Map(result interface{}, err error) (map[string]int64, error) {
+ values, err := Values(result, err)
+ if err != nil {
+ return nil, err
+ }
+ if len(values)%2 != 0 {
+ return nil, errors.New("redigo: Int64Map expects even number of values result")
+ }
+ m := make(map[string]int64, len(values)/2)
+ for i := 0; i < len(values); i += 2 {
+ key, ok := values[i].([]byte)
+ if !ok {
+ return nil, errors.New("redigo: ScanMap key not a bulk string value")
+ }
+ value, err := Int64(values[i+1], nil)
+ if err != nil {
+ return nil, err
+ }
+ m[string(key)] = value
+ }
+ return m, nil
+}
+
+// Positions is a helper that converts an array of positions (lat, long)
+// into a [][2]float64. The GEOPOS command returns replies in this format.
+func Positions(result interface{}, err error) ([]*[2]float64, error) {
+ values, err := Values(result, err)
+ if err != nil {
+ return nil, err
+ }
+ positions := make([]*[2]float64, len(values))
+ for i := range values {
+ if values[i] == nil {
+ continue
+ }
+ p, ok := values[i].([]interface{})
+ if !ok {
+ return nil, fmt.Errorf("redigo: unexpected element type for interface slice, got type %T", values[i])
+ }
+ if len(p) != 2 {
+ return nil, fmt.Errorf("redigo: unexpected number of values for a member position, got %d", len(p))
+ }
+ lat, err := Float64(p[0], nil)
+ if err != nil {
+ return nil, err
+ }
+ long, err := Float64(p[1], nil)
+ if err != nil {
+ return nil, err
+ }
+ positions[i] = &[2]float64{lat, long}
+ }
+ return positions, nil
+}
diff --git a/vendor/github.com/garyburd/redigo/redis/reply_test.go b/vendor/github.com/garyburd/redigo/redis/reply_test.go
new file mode 100644
index 0000000..81a25a9
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/redis/reply_test.go
@@ -0,0 +1,184 @@
+// Copyright 2012 Gary Burd
+//
+// 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 redis_test
+
+import (
+ "fmt"
+ "reflect"
+ "testing"
+
+ "github.com/garyburd/redigo/redis"
+)
+
+type valueError struct {
+ v interface{}
+ err error
+}
+
+func ve(v interface{}, err error) valueError {
+ return valueError{v, err}
+}
+
+var replyTests = []struct {
+ name interface{}
+ actual valueError
+ expected valueError
+}{
+ {
+ "ints([v1, v2])",
+ ve(redis.Ints([]interface{}{[]byte("4"), []byte("5")}, nil)),
+ ve([]int{4, 5}, nil),
+ },
+ {
+ "ints(nil)",
+ ve(redis.Ints(nil, nil)),
+ ve([]int(nil), redis.ErrNil),
+ },
+ {
+ "strings([v1, v2])",
+ ve(redis.Strings([]interface{}{[]byte("v1"), []byte("v2")}, nil)),
+ ve([]string{"v1", "v2"}, nil),
+ },
+ {
+ "strings(nil)",
+ ve(redis.Strings(nil, nil)),
+ ve([]string(nil), redis.ErrNil),
+ },
+ {
+ "byteslices([v1, v2])",
+ ve(redis.ByteSlices([]interface{}{[]byte("v1"), []byte("v2")}, nil)),
+ ve([][]byte{[]byte("v1"), []byte("v2")}, nil),
+ },
+ {
+ "byteslices(nil)",
+ ve(redis.ByteSlices(nil, nil)),
+ ve([][]byte(nil), redis.ErrNil),
+ },
+ {
+ "values([v1, v2])",
+ ve(redis.Values([]interface{}{[]byte("v1"), []byte("v2")}, nil)),
+ ve([]interface{}{[]byte("v1"), []byte("v2")}, nil),
+ },
+ {
+ "values(nil)",
+ ve(redis.Values(nil, nil)),
+ ve([]interface{}(nil), redis.ErrNil),
+ },
+ {
+ "float64(1.0)",
+ ve(redis.Float64([]byte("1.0"), nil)),
+ ve(float64(1.0), nil),
+ },
+ {
+ "float64(nil)",
+ ve(redis.Float64(nil, nil)),
+ ve(float64(0.0), redis.ErrNil),
+ },
+ {
+ "uint64(1)",
+ ve(redis.Uint64(int64(1), nil)),
+ ve(uint64(1), nil),
+ },
+ {
+ "uint64(-1)",
+ ve(redis.Uint64(int64(-1), nil)),
+ ve(uint64(0), redis.ErrNegativeInt),
+ },
+ {
+ "positions([[1, 2], nil, [3, 4]])",
+ ve(redis.Positions([]interface{}{[]interface{}{[]byte("1"), []byte("2")}, nil, []interface{}{[]byte("3"), []byte("4")}}, nil)),
+ ve([]*[2]float64{{1.0, 2.0}, nil, {3.0, 4.0}}, nil),
+ },
+}
+
+func TestReply(t *testing.T) {
+ for _, rt := range replyTests {
+ if rt.actual.err != rt.expected.err {
+ t.Errorf("%s returned err %v, want %v", rt.name, rt.actual.err, rt.expected.err)
+ continue
+ }
+ if !reflect.DeepEqual(rt.actual.v, rt.expected.v) {
+ t.Errorf("%s=%+v, want %+v", rt.name, rt.actual.v, rt.expected.v)
+ }
+ }
+}
+
+// dial wraps DialDefaultServer() with a more suitable function name for examples.
+func dial() (redis.Conn, error) {
+ return redis.DialDefaultServer()
+}
+
+func ExampleBool() {
+ c, err := dial()
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ defer c.Close()
+
+ c.Do("SET", "foo", 1)
+ exists, _ := redis.Bool(c.Do("EXISTS", "foo"))
+ fmt.Printf("%#v\n", exists)
+ // Output:
+ // true
+}
+
+func ExampleInt() {
+ c, err := dial()
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ defer c.Close()
+
+ c.Do("SET", "k1", 1)
+ n, _ := redis.Int(c.Do("GET", "k1"))
+ fmt.Printf("%#v\n", n)
+ n, _ = redis.Int(c.Do("INCR", "k1"))
+ fmt.Printf("%#v\n", n)
+ // Output:
+ // 1
+ // 2
+}
+
+func ExampleInts() {
+ c, err := dial()
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ defer c.Close()
+
+ c.Do("SADD", "set_with_integers", 4, 5, 6)
+ ints, _ := redis.Ints(c.Do("SMEMBERS", "set_with_integers"))
+ fmt.Printf("%#v\n", ints)
+ // Output:
+ // []int{4, 5, 6}
+}
+
+func ExampleString() {
+ c, err := dial()
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ defer c.Close()
+
+ c.Do("SET", "hello", "world")
+ s, err := redis.String(c.Do("GET", "hello"))
+ fmt.Printf("%#v\n", s)
+ // Output:
+ // "world"
+}
diff --git a/vendor/github.com/garyburd/redigo/redis/scan.go b/vendor/github.com/garyburd/redigo/redis/scan.go
new file mode 100644
index 0000000..1e8f922
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/redis/scan.go
@@ -0,0 +1,559 @@
+// Copyright 2012 Gary Burd
+//
+// 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 redis
+
+import (
+ "errors"
+ "fmt"
+ "reflect"
+ "strconv"
+ "strings"
+ "sync"
+)
+
+func ensureLen(d reflect.Value, n int) {
+ if n > d.Cap() {
+ d.Set(reflect.MakeSlice(d.Type(), n, n))
+ } else {
+ d.SetLen(n)
+ }
+}
+
+func cannotConvert(d reflect.Value, s interface{}) error {
+ var sname string
+ switch s.(type) {
+ case string:
+ sname = "Redis simple string"
+ case Error:
+ sname = "Redis error"
+ case int64:
+ sname = "Redis integer"
+ case []byte:
+ sname = "Redis bulk string"
+ case []interface{}:
+ sname = "Redis array"
+ default:
+ sname = reflect.TypeOf(s).String()
+ }
+ return fmt.Errorf("cannot convert from %s to %s", sname, d.Type())
+}
+
+func convertAssignBulkString(d reflect.Value, s []byte) (err error) {
+ switch d.Type().Kind() {
+ case reflect.Float32, reflect.Float64:
+ var x float64
+ x, err = strconv.ParseFloat(string(s), d.Type().Bits())
+ d.SetFloat(x)
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ var x int64
+ x, err = strconv.ParseInt(string(s), 10, d.Type().Bits())
+ d.SetInt(x)
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ var x uint64
+ x, err = strconv.ParseUint(string(s), 10, d.Type().Bits())
+ d.SetUint(x)
+ case reflect.Bool:
+ var x bool
+ x, err = strconv.ParseBool(string(s))
+ d.SetBool(x)
+ case reflect.String:
+ d.SetString(string(s))
+ case reflect.Slice:
+ if d.Type().Elem().Kind() != reflect.Uint8 {
+ err = cannotConvert(d, s)
+ } else {
+ d.SetBytes(s)
+ }
+ default:
+ err = cannotConvert(d, s)
+ }
+ return
+}
+
+func convertAssignInt(d reflect.Value, s int64) (err error) {
+ switch d.Type().Kind() {
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ d.SetInt(s)
+ if d.Int() != s {
+ err = strconv.ErrRange
+ d.SetInt(0)
+ }
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ if s < 0 {
+ err = strconv.ErrRange
+ } else {
+ x := uint64(s)
+ d.SetUint(x)
+ if d.Uint() != x {
+ err = strconv.ErrRange
+ d.SetUint(0)
+ }
+ }
+ case reflect.Bool:
+ d.SetBool(s != 0)
+ default:
+ err = cannotConvert(d, s)
+ }
+ return
+}
+
+func convertAssignValue(d reflect.Value, s interface{}) (err error) {
+ switch s := s.(type) {
+ case []byte:
+ err = convertAssignBulkString(d, s)
+ case int64:
+ err = convertAssignInt(d, s)
+ default:
+ err = cannotConvert(d, s)
+ }
+ return err
+}
+
+func convertAssignArray(d reflect.Value, s []interface{}) error {
+ if d.Type().Kind() != reflect.Slice {
+ return cannotConvert(d, s)
+ }
+ ensureLen(d, len(s))
+ for i := 0; i < len(s); i++ {
+ if err := convertAssignValue(d.Index(i), s[i]); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func convertAssign(d interface{}, s interface{}) (err error) {
+ // Handle the most common destination types using type switches and
+ // fall back to reflection for all other types.
+ switch s := s.(type) {
+ case nil:
+ // ingore
+ case []byte:
+ switch d := d.(type) {
+ case *string:
+ *d = string(s)
+ case *int:
+ *d, err = strconv.Atoi(string(s))
+ case *bool:
+ *d, err = strconv.ParseBool(string(s))
+ case *[]byte:
+ *d = s
+ case *interface{}:
+ *d = s
+ case nil:
+ // skip value
+ default:
+ if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr {
+ err = cannotConvert(d, s)
+ } else {
+ err = convertAssignBulkString(d.Elem(), s)
+ }
+ }
+ case int64:
+ switch d := d.(type) {
+ case *int:
+ x := int(s)
+ if int64(x) != s {
+ err = strconv.ErrRange
+ x = 0
+ }
+ *d = x
+ case *bool:
+ *d = s != 0
+ case *interface{}:
+ *d = s
+ case nil:
+ // skip value
+ default:
+ if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr {
+ err = cannotConvert(d, s)
+ } else {
+ err = convertAssignInt(d.Elem(), s)
+ }
+ }
+ case string:
+ switch d := d.(type) {
+ case *string:
+ *d = s
+ case *interface{}:
+ *d = s
+ case nil:
+ // skip value
+ default:
+ err = cannotConvert(reflect.ValueOf(d), s)
+ }
+ case []interface{}:
+ switch d := d.(type) {
+ case *[]interface{}:
+ *d = s
+ case *interface{}:
+ *d = s
+ case nil:
+ // skip value
+ default:
+ if d := reflect.ValueOf(d); d.Type().Kind() != reflect.Ptr {
+ err = cannotConvert(d, s)
+ } else {
+ err = convertAssignArray(d.Elem(), s)
+ }
+ }
+ case Error:
+ err = s
+ default:
+ err = cannotConvert(reflect.ValueOf(d), s)
+ }
+ return
+}
+
+// Scan copies from src to the values pointed at by dest.
+//
+// The values pointed at by dest must be an integer, float, boolean, string,
+// []byte, interface{} or slices of these types. Scan uses the standard strconv
+// package to convert bulk strings to numeric and boolean types.
+//
+// If a dest value is nil, then the corresponding src value is skipped.
+//
+// If a src element is nil, then the corresponding dest value is not modified.
+//
+// To enable easy use of Scan in a loop, Scan returns the slice of src
+// following the copied values.
+func Scan(src []interface{}, dest ...interface{}) ([]interface{}, error) {
+ if len(src) < len(dest) {
+ return nil, errors.New("redigo.Scan: array short")
+ }
+ var err error
+ for i, d := range dest {
+ err = convertAssign(d, src[i])
+ if err != nil {
+ err = fmt.Errorf("redigo.Scan: cannot assign to dest %d: %v", i, err)
+ break
+ }
+ }
+ return src[len(dest):], err
+}
+
+type fieldSpec struct {
+ name string
+ index []int
+ omitEmpty bool
+}
+
+type structSpec struct {
+ m map[string]*fieldSpec
+ l []*fieldSpec
+}
+
+func (ss *structSpec) fieldSpec(name []byte) *fieldSpec {
+ return ss.m[string(name)]
+}
+
+func compileStructSpec(t reflect.Type, depth map[string]int, index []int, ss *structSpec) {
+ for i := 0; i < t.NumField(); i++ {
+ f := t.Field(i)
+ switch {
+ case f.PkgPath != "" && !f.Anonymous:
+ // Ignore unexported fields.
+ case f.Anonymous:
+ // TODO: Handle pointers. Requires change to decoder and
+ // protection against infinite recursion.
+ if f.Type.Kind() == reflect.Struct {
+ compileStructSpec(f.Type, depth, append(index, i), ss)
+ }
+ default:
+ fs := &fieldSpec{name: f.Name}
+ tag := f.Tag.Get("redis")
+ p := strings.Split(tag, ",")
+ if len(p) > 0 {
+ if p[0] == "-" {
+ continue
+ }
+ if len(p[0]) > 0 {
+ fs.name = p[0]
+ }
+ for _, s := range p[1:] {
+ switch s {
+ case "omitempty":
+ fs.omitEmpty = true
+ default:
+ panic(fmt.Errorf("redigo: unknown field tag %s for type %s", s, t.Name()))
+ }
+ }
+ }
+ d, found := depth[fs.name]
+ if !found {
+ d = 1 << 30
+ }
+ switch {
+ case len(index) == d:
+ // At same depth, remove from result.
+ delete(ss.m, fs.name)
+ j := 0
+ for i := 0; i < len(ss.l); i++ {
+ if fs.name != ss.l[i].name {
+ ss.l[j] = ss.l[i]
+ j += 1
+ }
+ }
+ ss.l = ss.l[:j]
+ case len(index) < d:
+ fs.index = make([]int, len(index)+1)
+ copy(fs.index, index)
+ fs.index[len(index)] = i
+ depth[fs.name] = len(index)
+ ss.m[fs.name] = fs
+ ss.l = append(ss.l, fs)
+ }
+ }
+ }
+}
+
+var (
+ structSpecMutex sync.RWMutex
+ structSpecCache = make(map[reflect.Type]*structSpec)
+ defaultFieldSpec = &fieldSpec{}
+)
+
+func structSpecForType(t reflect.Type) *structSpec {
+
+ structSpecMutex.RLock()
+ ss, found := structSpecCache[t]
+ structSpecMutex.RUnlock()
+ if found {
+ return ss
+ }
+
+ structSpecMutex.Lock()
+ defer structSpecMutex.Unlock()
+ ss, found = structSpecCache[t]
+ if found {
+ return ss
+ }
+
+ ss = &structSpec{m: make(map[string]*fieldSpec)}
+ compileStructSpec(t, make(map[string]int), nil, ss)
+ structSpecCache[t] = ss
+ return ss
+}
+
+var errScanStructValue = errors.New("redigo.ScanStruct: value must be non-nil pointer to a struct")
+
+// ScanStruct scans alternating names and values from src to a struct. The
+// HGETALL and CONFIG GET commands return replies in this format.
+//
+// ScanStruct uses exported field names to match values in the response. Use
+// 'redis' field tag to override the name:
+//
+// Field int `redis:"myName"`
+//
+// Fields with the tag redis:"-" are ignored.
+//
+// Integer, float, boolean, string and []byte fields are supported. Scan uses the
+// standard strconv package to convert bulk string values to numeric and
+// boolean types.
+//
+// If a src element is nil, then the corresponding field is not modified.
+func ScanStruct(src []interface{}, dest interface{}) error {
+ d := reflect.ValueOf(dest)
+ if d.Kind() != reflect.Ptr || d.IsNil() {
+ return errScanStructValue
+ }
+ d = d.Elem()
+ if d.Kind() != reflect.Struct {
+ return errScanStructValue
+ }
+ ss := structSpecForType(d.Type())
+
+ if len(src)%2 != 0 {
+ return errors.New("redigo.ScanStruct: number of values not a multiple of 2")
+ }
+
+ for i := 0; i < len(src); i += 2 {
+ s := src[i+1]
+ if s == nil {
+ continue
+ }
+ name, ok := src[i].([]byte)
+ if !ok {
+ return fmt.Errorf("redigo.ScanStruct: key %d not a bulk string value", i)
+ }
+ fs := ss.fieldSpec(name)
+ if fs == nil {
+ continue
+ }
+ if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil {
+ return fmt.Errorf("redigo.ScanStruct: cannot assign field %s: %v", fs.name, err)
+ }
+ }
+ return nil
+}
+
+var (
+ errScanSliceValue = errors.New("redigo.ScanSlice: dest must be non-nil pointer to a struct")
+)
+
+// ScanSlice scans src to the slice pointed to by dest. The elements the dest
+// slice must be integer, float, boolean, string, struct or pointer to struct
+// values.
+//
+// Struct fields must be integer, float, boolean or string values. All struct
+// fields are used unless a subset is specified using fieldNames.
+func ScanSlice(src []interface{}, dest interface{}, fieldNames ...string) error {
+ d := reflect.ValueOf(dest)
+ if d.Kind() != reflect.Ptr || d.IsNil() {
+ return errScanSliceValue
+ }
+ d = d.Elem()
+ if d.Kind() != reflect.Slice {
+ return errScanSliceValue
+ }
+
+ isPtr := false
+ t := d.Type().Elem()
+ if t.Kind() == reflect.Ptr && t.Elem().Kind() == reflect.Struct {
+ isPtr = true
+ t = t.Elem()
+ }
+
+ if t.Kind() != reflect.Struct {
+ ensureLen(d, len(src))
+ for i, s := range src {
+ if s == nil {
+ continue
+ }
+ if err := convertAssignValue(d.Index(i), s); err != nil {
+ return fmt.Errorf("redigo.ScanSlice: cannot assign element %d: %v", i, err)
+ }
+ }
+ return nil
+ }
+
+ ss := structSpecForType(t)
+ fss := ss.l
+ if len(fieldNames) > 0 {
+ fss = make([]*fieldSpec, len(fieldNames))
+ for i, name := range fieldNames {
+ fss[i] = ss.m[name]
+ if fss[i] == nil {
+ return fmt.Errorf("redigo.ScanSlice: ScanSlice bad field name %s", name)
+ }
+ }
+ }
+
+ if len(fss) == 0 {
+ return errors.New("redigo.ScanSlice: no struct fields")
+ }
+
+ n := len(src) / len(fss)
+ if n*len(fss) != len(src) {
+ return errors.New("redigo.ScanSlice: length not a multiple of struct field count")
+ }
+
+ ensureLen(d, n)
+ for i := 0; i < n; i++ {
+ d := d.Index(i)
+ if isPtr {
+ if d.IsNil() {
+ d.Set(reflect.New(t))
+ }
+ d = d.Elem()
+ }
+ for j, fs := range fss {
+ s := src[i*len(fss)+j]
+ if s == nil {
+ continue
+ }
+ if err := convertAssignValue(d.FieldByIndex(fs.index), s); err != nil {
+ return fmt.Errorf("redigo.ScanSlice: cannot assign element %d to field %s: %v", i*len(fss)+j, fs.name, err)
+ }
+ }
+ }
+ return nil
+}
+
+// Args is a helper for constructing command arguments from structured values.
+type Args []interface{}
+
+// Add returns the result of appending value to args.
+func (args Args) Add(value ...interface{}) Args {
+ return append(args, value...)
+}
+
+// AddFlat returns the result of appending the flattened value of v to args.
+//
+// Maps are flattened by appending the alternating keys and map values to args.
+//
+// Slices are flattened by appending the slice elements to args.
+//
+// Structs are flattened by appending the alternating names and values of
+// exported fields to args. If v is a nil struct pointer, then nothing is
+// appended. The 'redis' field tag overrides struct field names. See ScanStruct
+// for more information on the use of the 'redis' field tag.
+//
+// Other types are appended to args as is.
+func (args Args) AddFlat(v interface{}) Args {
+ rv := reflect.ValueOf(v)
+ switch rv.Kind() {
+ case reflect.Struct:
+ args = flattenStruct(args, rv)
+ case reflect.Slice:
+ for i := 0; i < rv.Len(); i++ {
+ args = append(args, rv.Index(i).Interface())
+ }
+ case reflect.Map:
+ for _, k := range rv.MapKeys() {
+ args = append(args, k.Interface(), rv.MapIndex(k).Interface())
+ }
+ case reflect.Ptr:
+ if rv.Type().Elem().Kind() == reflect.Struct {
+ if !rv.IsNil() {
+ args = flattenStruct(args, rv.Elem())
+ }
+ } else {
+ args = append(args, v)
+ }
+ default:
+ args = append(args, v)
+ }
+ return args
+}
+
+func flattenStruct(args Args, v reflect.Value) Args {
+ ss := structSpecForType(v.Type())
+ for _, fs := range ss.l {
+ fv := v.FieldByIndex(fs.index)
+ if fs.omitEmpty {
+ var empty = false
+ switch fv.Kind() {
+ case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
+ empty = fv.Len() == 0
+ case reflect.Bool:
+ empty = !fv.Bool()
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ empty = fv.Int() == 0
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ empty = fv.Uint() == 0
+ case reflect.Float32, reflect.Float64:
+ empty = fv.Float() == 0
+ case reflect.Interface, reflect.Ptr:
+ empty = fv.IsNil()
+ }
+ if empty {
+ continue
+ }
+ }
+ args = append(args, fs.name, fv.Interface())
+ }
+ return args
+}
diff --git a/vendor/github.com/garyburd/redigo/redis/scan_test.go b/vendor/github.com/garyburd/redigo/redis/scan_test.go
new file mode 100644
index 0000000..d364dff
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/redis/scan_test.go
@@ -0,0 +1,440 @@
+// Copyright 2012 Gary Burd
+//
+// 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 redis_test
+
+import (
+ "fmt"
+ "math"
+ "reflect"
+ "testing"
+
+ "github.com/garyburd/redigo/redis"
+)
+
+var scanConversionTests = []struct {
+ src interface{}
+ dest interface{}
+}{
+ {[]byte("-inf"), math.Inf(-1)},
+ {[]byte("+inf"), math.Inf(1)},
+ {[]byte("0"), float64(0)},
+ {[]byte("3.14159"), float64(3.14159)},
+ {[]byte("3.14"), float32(3.14)},
+ {[]byte("-100"), int(-100)},
+ {[]byte("101"), int(101)},
+ {int64(102), int(102)},
+ {[]byte("103"), uint(103)},
+ {int64(104), uint(104)},
+ {[]byte("105"), int8(105)},
+ {int64(106), int8(106)},
+ {[]byte("107"), uint8(107)},
+ {int64(108), uint8(108)},
+ {[]byte("0"), false},
+ {int64(0), false},
+ {[]byte("f"), false},
+ {[]byte("1"), true},
+ {int64(1), true},
+ {[]byte("t"), true},
+ {"hello", "hello"},
+ {[]byte("hello"), "hello"},
+ {[]byte("world"), []byte("world")},
+ {[]interface{}{[]byte("foo")}, []interface{}{[]byte("foo")}},
+ {[]interface{}{[]byte("foo")}, []string{"foo"}},
+ {[]interface{}{[]byte("hello"), []byte("world")}, []string{"hello", "world"}},
+ {[]interface{}{[]byte("bar")}, [][]byte{[]byte("bar")}},
+ {[]interface{}{[]byte("1")}, []int{1}},
+ {[]interface{}{[]byte("1"), []byte("2")}, []int{1, 2}},
+ {[]interface{}{[]byte("1"), []byte("2")}, []float64{1, 2}},
+ {[]interface{}{[]byte("1")}, []byte{1}},
+ {[]interface{}{[]byte("1")}, []bool{true}},
+}
+
+func TestScanConversion(t *testing.T) {
+ for _, tt := range scanConversionTests {
+ values := []interface{}{tt.src}
+ dest := reflect.New(reflect.TypeOf(tt.dest))
+ values, err := redis.Scan(values, dest.Interface())
+ if err != nil {
+ t.Errorf("Scan(%v) returned error %v", tt, err)
+ continue
+ }
+ if !reflect.DeepEqual(tt.dest, dest.Elem().Interface()) {
+ t.Errorf("Scan(%v) returned %v, want %v", tt, dest.Elem().Interface(), tt.dest)
+ }
+ }
+}
+
+var scanConversionErrorTests = []struct {
+ src interface{}
+ dest interface{}
+}{
+ {[]byte("1234"), byte(0)},
+ {int64(1234), byte(0)},
+ {[]byte("-1"), byte(0)},
+ {int64(-1), byte(0)},
+ {[]byte("junk"), false},
+ {redis.Error("blah"), false},
+}
+
+func TestScanConversionError(t *testing.T) {
+ for _, tt := range scanConversionErrorTests {
+ values := []interface{}{tt.src}
+ dest := reflect.New(reflect.TypeOf(tt.dest))
+ values, err := redis.Scan(values, dest.Interface())
+ if err == nil {
+ t.Errorf("Scan(%v) did not return error", tt)
+ }
+ }
+}
+
+func ExampleScan() {
+ c, err := dial()
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ defer c.Close()
+
+ c.Send("HMSET", "album:1", "title", "Red", "rating", 5)
+ c.Send("HMSET", "album:2", "title", "Earthbound", "rating", 1)
+ c.Send("HMSET", "album:3", "title", "Beat")
+ c.Send("LPUSH", "albums", "1")
+ c.Send("LPUSH", "albums", "2")
+ c.Send("LPUSH", "albums", "3")
+ values, err := redis.Values(c.Do("SORT", "albums",
+ "BY", "album:*->rating",
+ "GET", "album:*->title",
+ "GET", "album:*->rating"))
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ for len(values) > 0 {
+ var title string
+ rating := -1 // initialize to illegal value to detect nil.
+ values, err = redis.Scan(values, &title, &rating)
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ if rating == -1 {
+ fmt.Println(title, "not-rated")
+ } else {
+ fmt.Println(title, rating)
+ }
+ }
+ // Output:
+ // Beat not-rated
+ // Earthbound 1
+ // Red 5
+}
+
+type s0 struct {
+ X int
+ Y int `redis:"y"`
+ Bt bool
+}
+
+type s1 struct {
+ X int `redis:"-"`
+ I int `redis:"i"`
+ U uint `redis:"u"`
+ S string `redis:"s"`
+ P []byte `redis:"p"`
+ B bool `redis:"b"`
+ Bt bool
+ Bf bool
+ s0
+}
+
+var scanStructTests = []struct {
+ title string
+ reply []string
+ value interface{}
+}{
+ {"basic",
+ []string{"i", "-1234", "u", "5678", "s", "hello", "p", "world", "b", "t", "Bt", "1", "Bf", "0", "X", "123", "y", "456"},
+ &s1{I: -1234, U: 5678, S: "hello", P: []byte("world"), B: true, Bt: true, Bf: false, s0: s0{X: 123, Y: 456}},
+ },
+}
+
+func TestScanStruct(t *testing.T) {
+ for _, tt := range scanStructTests {
+
+ var reply []interface{}
+ for _, v := range tt.reply {
+ reply = append(reply, []byte(v))
+ }
+
+ value := reflect.New(reflect.ValueOf(tt.value).Type().Elem())
+
+ if err := redis.ScanStruct(reply, value.Interface()); err != nil {
+ t.Fatalf("ScanStruct(%s) returned error %v", tt.title, err)
+ }
+
+ if !reflect.DeepEqual(value.Interface(), tt.value) {
+ t.Fatalf("ScanStruct(%s) returned %v, want %v", tt.title, value.Interface(), tt.value)
+ }
+ }
+}
+
+func TestBadScanStructArgs(t *testing.T) {
+ x := []interface{}{"A", "b"}
+ test := func(v interface{}) {
+ if err := redis.ScanStruct(x, v); err == nil {
+ t.Errorf("Expect error for ScanStruct(%T, %T)", x, v)
+ }
+ }
+
+ test(nil)
+
+ var v0 *struct{}
+ test(v0)
+
+ var v1 int
+ test(&v1)
+
+ x = x[:1]
+ v2 := struct{ A string }{}
+ test(&v2)
+}
+
+var scanSliceTests = []struct {
+ src []interface{}
+ fieldNames []string
+ ok bool
+ dest interface{}
+}{
+ {
+ []interface{}{[]byte("1"), nil, []byte("-1")},
+ nil,
+ true,
+ []int{1, 0, -1},
+ },
+ {
+ []interface{}{[]byte("1"), nil, []byte("2")},
+ nil,
+ true,
+ []uint{1, 0, 2},
+ },
+ {
+ []interface{}{[]byte("-1")},
+ nil,
+ false,
+ []uint{1},
+ },
+ {
+ []interface{}{[]byte("hello"), nil, []byte("world")},
+ nil,
+ true,
+ [][]byte{[]byte("hello"), nil, []byte("world")},
+ },
+ {
+ []interface{}{[]byte("hello"), nil, []byte("world")},
+ nil,
+ true,
+ []string{"hello", "", "world"},
+ },
+ {
+ []interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
+ nil,
+ true,
+ []struct{ A, B string }{{"a1", "b1"}, {"a2", "b2"}},
+ },
+ {
+ []interface{}{[]byte("a1"), []byte("b1")},
+ nil,
+ false,
+ []struct{ A, B, C string }{{"a1", "b1", ""}},
+ },
+ {
+ []interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
+ nil,
+ true,
+ []*struct{ A, B string }{{"a1", "b1"}, {"a2", "b2"}},
+ },
+ {
+ []interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
+ []string{"A", "B"},
+ true,
+ []struct{ A, C, B string }{{"a1", "", "b1"}, {"a2", "", "b2"}},
+ },
+ {
+ []interface{}{[]byte("a1"), []byte("b1"), []byte("a2"), []byte("b2")},
+ nil,
+ false,
+ []struct{}{},
+ },
+}
+
+func TestScanSlice(t *testing.T) {
+ for _, tt := range scanSliceTests {
+
+ typ := reflect.ValueOf(tt.dest).Type()
+ dest := reflect.New(typ)
+
+ err := redis.ScanSlice(tt.src, dest.Interface(), tt.fieldNames...)
+ if tt.ok != (err == nil) {
+ t.Errorf("ScanSlice(%v, []%s, %v) returned error %v", tt.src, typ, tt.fieldNames, err)
+ continue
+ }
+ if tt.ok && !reflect.DeepEqual(dest.Elem().Interface(), tt.dest) {
+ t.Errorf("ScanSlice(src, []%s) returned %#v, want %#v", typ, dest.Elem().Interface(), tt.dest)
+ }
+ }
+}
+
+func ExampleScanSlice() {
+ c, err := dial()
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ defer c.Close()
+
+ c.Send("HMSET", "album:1", "title", "Red", "rating", 5)
+ c.Send("HMSET", "album:2", "title", "Earthbound", "rating", 1)
+ c.Send("HMSET", "album:3", "title", "Beat", "rating", 4)
+ c.Send("LPUSH", "albums", "1")
+ c.Send("LPUSH", "albums", "2")
+ c.Send("LPUSH", "albums", "3")
+ values, err := redis.Values(c.Do("SORT", "albums",
+ "BY", "album:*->rating",
+ "GET", "album:*->title",
+ "GET", "album:*->rating"))
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ var albums []struct {
+ Title string
+ Rating int
+ }
+ if err := redis.ScanSlice(values, &albums); err != nil {
+ fmt.Println(err)
+ return
+ }
+ fmt.Printf("%v\n", albums)
+ // Output:
+ // [{Earthbound 1} {Beat 4} {Red 5}]
+}
+
+var argsTests = []struct {
+ title string
+ actual redis.Args
+ expected redis.Args
+}{
+ {"struct ptr",
+ redis.Args{}.AddFlat(&struct {
+ I int `redis:"i"`
+ U uint `redis:"u"`
+ S string `redis:"s"`
+ P []byte `redis:"p"`
+ M map[string]string `redis:"m"`
+ Bt bool
+ Bf bool
+ }{
+ -1234, 5678, "hello", []byte("world"), map[string]string{"hello": "world"}, true, false,
+ }),
+ redis.Args{"i", int(-1234), "u", uint(5678), "s", "hello", "p", []byte("world"), "m", map[string]string{"hello": "world"}, "Bt", true, "Bf", false},
+ },
+ {"struct",
+ redis.Args{}.AddFlat(struct{ I int }{123}),
+ redis.Args{"I", 123},
+ },
+ {"slice",
+ redis.Args{}.Add(1).AddFlat([]string{"a", "b", "c"}).Add(2),
+ redis.Args{1, "a", "b", "c", 2},
+ },
+ {"struct omitempty",
+ redis.Args{}.AddFlat(&struct {
+ I int `redis:"i,omitempty"`
+ U uint `redis:"u,omitempty"`
+ S string `redis:"s,omitempty"`
+ P []byte `redis:"p,omitempty"`
+ M map[string]string `redis:"m,omitempty"`
+ Bt bool `redis:"Bt,omitempty"`
+ Bf bool `redis:"Bf,omitempty"`
+ }{
+ 0, 0, "", []byte{}, map[string]string{}, true, false,
+ }),
+ redis.Args{"Bt", true},
+ },
+}
+
+func TestArgs(t *testing.T) {
+ for _, tt := range argsTests {
+ if !reflect.DeepEqual(tt.actual, tt.expected) {
+ t.Fatalf("%s is %v, want %v", tt.title, tt.actual, tt.expected)
+ }
+ }
+}
+
+func ExampleArgs() {
+ c, err := dial()
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ defer c.Close()
+
+ var p1, p2 struct {
+ Title string `redis:"title"`
+ Author string `redis:"author"`
+ Body string `redis:"body"`
+ }
+
+ p1.Title = "Example"
+ p1.Author = "Gary"
+ p1.Body = "Hello"
+
+ if _, err := c.Do("HMSET", redis.Args{}.Add("id1").AddFlat(&p1)...); err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ m := map[string]string{
+ "title": "Example2",
+ "author": "Steve",
+ "body": "Map",
+ }
+
+ if _, err := c.Do("HMSET", redis.Args{}.Add("id2").AddFlat(m)...); err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ for _, id := range []string{"id1", "id2"} {
+
+ v, err := redis.Values(c.Do("HGETALL", id))
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ if err := redis.ScanStruct(v, &p2); err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ fmt.Printf("%+v\n", p2)
+ }
+
+ // Output:
+ // {Title:Example Author:Gary Body:Hello}
+ // {Title:Example2 Author:Steve Body:Map}
+}
diff --git a/vendor/github.com/garyburd/redigo/redis/script.go b/vendor/github.com/garyburd/redigo/redis/script.go
new file mode 100644
index 0000000..78605a9
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/redis/script.go
@@ -0,0 +1,86 @@
+// Copyright 2012 Gary Burd
+//
+// 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 redis
+
+import (
+ "crypto/sha1"
+ "encoding/hex"
+ "io"
+ "strings"
+)
+
+// Script encapsulates the source, hash and key count for a Lua script. See
+// http://redis.io/commands/eval for information on scripts in Redis.
+type Script struct {
+ keyCount int
+ src string
+ hash string
+}
+
+// NewScript returns a new script object. If keyCount is greater than or equal
+// to zero, then the count is automatically inserted in the EVAL command
+// argument list. If keyCount is less than zero, then the application supplies
+// the count as the first value in the keysAndArgs argument to the Do, Send and
+// SendHash methods.
+func NewScript(keyCount int, src string) *Script {
+ h := sha1.New()
+ io.WriteString(h, src)
+ return &Script{keyCount, src, hex.EncodeToString(h.Sum(nil))}
+}
+
+func (s *Script) args(spec string, keysAndArgs []interface{}) []interface{} {
+ var args []interface{}
+ if s.keyCount < 0 {
+ args = make([]interface{}, 1+len(keysAndArgs))
+ args[0] = spec
+ copy(args[1:], keysAndArgs)
+ } else {
+ args = make([]interface{}, 2+len(keysAndArgs))
+ args[0] = spec
+ args[1] = s.keyCount
+ copy(args[2:], keysAndArgs)
+ }
+ return args
+}
+
+// Do evaluates the script. Under the covers, Do optimistically evaluates the
+// script using the EVALSHA command. If the command fails because the script is
+// not loaded, then Do evaluates the script using the EVAL command (thus
+// causing the script to load).
+func (s *Script) Do(c Conn, keysAndArgs ...interface{}) (interface{}, error) {
+ v, err := c.Do("EVALSHA", s.args(s.hash, keysAndArgs)...)
+ if e, ok := err.(Error); ok && strings.HasPrefix(string(e), "NOSCRIPT ") {
+ v, err = c.Do("EVAL", s.args(s.src, keysAndArgs)...)
+ }
+ return v, err
+}
+
+// SendHash evaluates the script without waiting for the reply. The script is
+// evaluated with the EVALSHA command. The application must ensure that the
+// script is loaded by a previous call to Send, Do or Load methods.
+func (s *Script) SendHash(c Conn, keysAndArgs ...interface{}) error {
+ return c.Send("EVALSHA", s.args(s.hash, keysAndArgs)...)
+}
+
+// Send evaluates the script without waiting for the reply.
+func (s *Script) Send(c Conn, keysAndArgs ...interface{}) error {
+ return c.Send("EVAL", s.args(s.src, keysAndArgs)...)
+}
+
+// Load loads the script without evaluating it.
+func (s *Script) Load(c Conn) error {
+ _, err := c.Do("SCRIPT", "LOAD", s.src)
+ return err
+}
diff --git a/vendor/github.com/garyburd/redigo/redis/script_test.go b/vendor/github.com/garyburd/redigo/redis/script_test.go
new file mode 100644
index 0000000..af28241
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/redis/script_test.go
@@ -0,0 +1,100 @@
+// Copyright 2012 Gary Burd
+//
+// 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 redis_test
+
+import (
+ "fmt"
+ "reflect"
+ "testing"
+ "time"
+
+ "github.com/garyburd/redigo/redis"
+)
+
+var (
+ // These variables are declared at package level to remove distracting
+ // details from the examples.
+ c redis.Conn
+ reply interface{}
+ err error
+)
+
+func ExampleScript() {
+ // Initialize a package-level variable with a script.
+ var getScript = redis.NewScript(1, `return redis.call('get', KEYS[1])`)
+
+ // In a function, use the script Do method to evaluate the script. The Do
+ // method optimistically uses the EVALSHA command. If the script is not
+ // loaded, then the Do method falls back to the EVAL command.
+ reply, err = getScript.Do(c, "foo")
+}
+
+func TestScript(t *testing.T) {
+ c, err := redis.DialDefaultServer()
+ if err != nil {
+ t.Fatalf("error connection to database, %v", err)
+ }
+ defer c.Close()
+
+ // To test fall back in Do, we make script unique by adding comment with current time.
+ script := fmt.Sprintf("--%d\nreturn {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}", time.Now().UnixNano())
+ s := redis.NewScript(2, script)
+ reply := []interface{}{[]byte("key1"), []byte("key2"), []byte("arg1"), []byte("arg2")}
+
+ v, err := s.Do(c, "key1", "key2", "arg1", "arg2")
+ if err != nil {
+ t.Errorf("s.Do(c, ...) returned %v", err)
+ }
+
+ if !reflect.DeepEqual(v, reply) {
+ t.Errorf("s.Do(c, ..); = %v, want %v", v, reply)
+ }
+
+ err = s.Load(c)
+ if err != nil {
+ t.Errorf("s.Load(c) returned %v", err)
+ }
+
+ err = s.SendHash(c, "key1", "key2", "arg1", "arg2")
+ if err != nil {
+ t.Errorf("s.SendHash(c, ...) returned %v", err)
+ }
+
+ err = c.Flush()
+ if err != nil {
+ t.Errorf("c.Flush() returned %v", err)
+ }
+
+ v, err = c.Receive()
+ if !reflect.DeepEqual(v, reply) {
+ t.Errorf("s.SendHash(c, ..); c.Receive() = %v, want %v", v, reply)
+ }
+
+ err = s.Send(c, "key1", "key2", "arg1", "arg2")
+ if err != nil {
+ t.Errorf("s.Send(c, ...) returned %v", err)
+ }
+
+ err = c.Flush()
+ if err != nil {
+ t.Errorf("c.Flush() returned %v", err)
+ }
+
+ v, err = c.Receive()
+ if !reflect.DeepEqual(v, reply) {
+ t.Errorf("s.Send(c, ..); c.Receive() = %v, want %v", v, reply)
+ }
+
+}
diff --git a/vendor/github.com/garyburd/redigo/redis/test_test.go b/vendor/github.com/garyburd/redigo/redis/test_test.go
new file mode 100644
index 0000000..7240fa1
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/redis/test_test.go
@@ -0,0 +1,177 @@
+// Copyright 2012 Gary Burd
+//
+// 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 redis
+
+import (
+ "bufio"
+ "errors"
+ "flag"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "os/exec"
+ "strconv"
+ "strings"
+ "sync"
+ "testing"
+ "time"
+)
+
+func SetNowFunc(f func() time.Time) {
+ nowFunc = f
+}
+
+var (
+ ErrNegativeInt = errNegativeInt
+
+ serverPath = flag.String("redis-server", "redis-server", "Path to redis server binary")
+ serverBasePort = flag.Int("redis-port", 16379, "Beginning of port range for test servers")
+ serverLogName = flag.String("redis-log", "", "Write Redis server logs to `filename`")
+ serverLog = ioutil.Discard
+
+ defaultServerMu sync.Mutex
+ defaultServer *Server
+ defaultServerErr error
+)
+
+type Server struct {
+ name string
+ cmd *exec.Cmd
+ done chan struct{}
+}
+
+func NewServer(name string, args ...string) (*Server, error) {
+ s := &Server{
+ name: name,
+ cmd: exec.Command(*serverPath, args...),
+ done: make(chan struct{}),
+ }
+
+ r, err := s.cmd.StdoutPipe()
+ if err != nil {
+ return nil, err
+ }
+
+ err = s.cmd.Start()
+ if err != nil {
+ return nil, err
+ }
+
+ ready := make(chan error, 1)
+ go s.watch(r, ready)
+
+ select {
+ case err = <-ready:
+ case <-time.After(time.Second * 10):
+ err = errors.New("timeout waiting for server to start")
+ }
+
+ if err != nil {
+ s.Stop()
+ return nil, err
+ }
+
+ return s, nil
+}
+
+func (s *Server) watch(r io.Reader, ready chan error) {
+ fmt.Fprintf(serverLog, "%d START %s \n", s.cmd.Process.Pid, s.name)
+ var listening bool
+ var text string
+ scn := bufio.NewScanner(r)
+ for scn.Scan() {
+ text = scn.Text()
+ fmt.Fprintf(serverLog, "%s\n", text)
+ if !listening {
+ if strings.Contains(text, "The server is now ready to accept connections on port") {
+ listening = true
+ ready <- nil
+ }
+ }
+ }
+ if !listening {
+ ready <- fmt.Errorf("server exited: %s", text)
+ }
+ s.cmd.Wait()
+ fmt.Fprintf(serverLog, "%d STOP %s \n", s.cmd.Process.Pid, s.name)
+ close(s.done)
+}
+
+func (s *Server) Stop() {
+ s.cmd.Process.Signal(os.Interrupt)
+ <-s.done
+}
+
+// stopDefaultServer stops the server created by DialDefaultServer.
+func stopDefaultServer() {
+ defaultServerMu.Lock()
+ defer defaultServerMu.Unlock()
+ if defaultServer != nil {
+ defaultServer.Stop()
+ defaultServer = nil
+ }
+}
+
+// startDefaultServer starts the default server if not already running.
+func startDefaultServer() error {
+ defaultServerMu.Lock()
+ defer defaultServerMu.Unlock()
+ if defaultServer != nil || defaultServerErr != nil {
+ return defaultServerErr
+ }
+ defaultServer, defaultServerErr = NewServer(
+ "default",
+ "--port", strconv.Itoa(*serverBasePort),
+ "--save", "",
+ "--appendonly", "no")
+ return defaultServerErr
+}
+
+// DialDefaultServer starts the test server if not already started and dials a
+// connection to the server.
+func DialDefaultServer() (Conn, error) {
+ if err := startDefaultServer(); err != nil {
+ return nil, err
+ }
+ c, err := Dial("tcp", fmt.Sprintf(":%d", *serverBasePort), DialReadTimeout(1*time.Second), DialWriteTimeout(1*time.Second))
+ if err != nil {
+ return nil, err
+ }
+ c.Do("FLUSHDB")
+ return c, nil
+}
+
+func TestMain(m *testing.M) {
+ os.Exit(func() int {
+ flag.Parse()
+
+ var f *os.File
+ if *serverLogName != "" {
+ var err error
+ f, err = os.OpenFile(*serverLogName, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error opening redis-log: %v\n", err)
+ return 1
+ }
+ defer f.Close()
+ serverLog = f
+ }
+
+ defer stopDefaultServer()
+
+ return m.Run()
+ }())
+}
diff --git a/vendor/github.com/garyburd/redigo/redis/zpop_example_test.go b/vendor/github.com/garyburd/redigo/redis/zpop_example_test.go
new file mode 100644
index 0000000..1d86ee6
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/redis/zpop_example_test.go
@@ -0,0 +1,113 @@
+// Copyright 2013 Gary Burd
+//
+// 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 redis_test
+
+import (
+ "fmt"
+ "github.com/garyburd/redigo/redis"
+)
+
+// zpop pops a value from the ZSET key using WATCH/MULTI/EXEC commands.
+func zpop(c redis.Conn, key string) (result string, err error) {
+
+ defer func() {
+ // Return connection to normal state on error.
+ if err != nil {
+ c.Do("DISCARD")
+ }
+ }()
+
+ // Loop until transaction is successful.
+ for {
+ if _, err := c.Do("WATCH", key); err != nil {
+ return "", err
+ }
+
+ members, err := redis.Strings(c.Do("ZRANGE", key, 0, 0))
+ if err != nil {
+ return "", err
+ }
+ if len(members) != 1 {
+ return "", redis.ErrNil
+ }
+
+ c.Send("MULTI")
+ c.Send("ZREM", key, members[0])
+ queued, err := c.Do("EXEC")
+ if err != nil {
+ return "", err
+ }
+
+ if queued != nil {
+ result = members[0]
+ break
+ }
+ }
+
+ return result, nil
+}
+
+// zpopScript pops a value from a ZSET.
+var zpopScript = redis.NewScript(1, `
+ local r = redis.call('ZRANGE', KEYS[1], 0, 0)
+ if r ~= nil then
+ r = r[1]
+ redis.call('ZREM', KEYS[1], r)
+ end
+ return r
+`)
+
+// This example implements ZPOP as described at
+// http://redis.io/topics/transactions using WATCH/MULTI/EXEC and scripting.
+func Example_zpop() {
+ c, err := dial()
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ defer c.Close()
+
+ // Add test data using a pipeline.
+
+ for i, member := range []string{"red", "blue", "green"} {
+ c.Send("ZADD", "zset", i, member)
+ }
+ if _, err := c.Do(""); err != nil {
+ fmt.Println(err)
+ return
+ }
+
+ // Pop using WATCH/MULTI/EXEC
+
+ v, err := zpop(c, "zset")
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ fmt.Println(v)
+
+ // Pop using a script.
+
+ v, err = redis.String(zpopScript.Do(c, "zset"))
+ if err != nil {
+ fmt.Println(err)
+ return
+ }
+ fmt.Println(v)
+
+ // Output:
+ // red
+ // blue
+}
diff --git a/vendor/github.com/garyburd/redigo/redisx/connmux.go b/vendor/github.com/garyburd/redigo/redisx/connmux.go
new file mode 100644
index 0000000..af2cced
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/redisx/connmux.go
@@ -0,0 +1,152 @@
+// Copyright 2014 Gary Burd
+//
+// 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 redisx
+
+import (
+ "errors"
+ "sync"
+
+ "github.com/garyburd/redigo/internal"
+ "github.com/garyburd/redigo/redis"
+)
+
+// ConnMux multiplexes one or more connections to a single underlying
+// connection. The ConnMux connections do not support concurrency, commands
+// that associate server side state with the connection or commands that put
+// the connection in a special mode.
+type ConnMux struct {
+ c redis.Conn
+
+ sendMu sync.Mutex
+ sendID uint
+
+ recvMu sync.Mutex
+ recvID uint
+ recvWait map[uint]chan struct{}
+}
+
+func NewConnMux(c redis.Conn) *ConnMux {
+ return &ConnMux{c: c, recvWait: make(map[uint]chan struct{})}
+}
+
+// Get gets a connection. The application must close the returned connection.
+func (p *ConnMux) Get() redis.Conn {
+ c := &muxConn{p: p}
+ c.ids = c.buf[:0]
+ return c
+}
+
+// Close closes the underlying connection.
+func (p *ConnMux) Close() error {
+ return p.c.Close()
+}
+
+type muxConn struct {
+ p *ConnMux
+ ids []uint
+ buf [8]uint
+}
+
+func (c *muxConn) send(flush bool, cmd string, args ...interface{}) error {
+ if internal.LookupCommandInfo(cmd).Set != 0 {
+ return errors.New("command not supported by mux pool")
+ }
+ p := c.p
+ p.sendMu.Lock()
+ id := p.sendID
+ c.ids = append(c.ids, id)
+ p.sendID++
+ err := p.c.Send(cmd, args...)
+ if flush {
+ err = p.c.Flush()
+ }
+ p.sendMu.Unlock()
+ return err
+}
+
+func (c *muxConn) Send(cmd string, args ...interface{}) error {
+ return c.send(false, cmd, args...)
+}
+
+func (c *muxConn) Flush() error {
+ p := c.p
+ p.sendMu.Lock()
+ err := p.c.Flush()
+ p.sendMu.Unlock()
+ return err
+}
+
+func (c *muxConn) Receive() (interface{}, error) {
+ if len(c.ids) == 0 {
+ return nil, errors.New("mux pool underflow")
+ }
+
+ id := c.ids[0]
+ c.ids = c.ids[1:]
+ if len(c.ids) == 0 {
+ c.ids = c.buf[:0]
+ }
+
+ p := c.p
+ p.recvMu.Lock()
+ if p.recvID != id {
+ ch := make(chan struct{})
+ p.recvWait[id] = ch
+ p.recvMu.Unlock()
+ <-ch
+ p.recvMu.Lock()
+ if p.recvID != id {
+ panic("out of sync")
+ }
+ }
+
+ v, err := p.c.Receive()
+
+ id++
+ p.recvID = id
+ ch, ok := p.recvWait[id]
+ if ok {
+ delete(p.recvWait, id)
+ }
+ p.recvMu.Unlock()
+ if ok {
+ ch <- struct{}{}
+ }
+
+ return v, err
+}
+
+func (c *muxConn) Close() error {
+ var err error
+ if len(c.ids) == 0 {
+ return nil
+ }
+ c.Flush()
+ for _ = range c.ids {
+ _, err = c.Receive()
+ }
+ return err
+}
+
+func (c *muxConn) Do(cmd string, args ...interface{}) (interface{}, error) {
+ if err := c.send(true, cmd, args...); err != nil {
+ return nil, err
+ }
+ return c.Receive()
+}
+
+func (c *muxConn) Err() error {
+ return c.p.c.Err()
+}
diff --git a/vendor/github.com/garyburd/redigo/redisx/connmux_test.go b/vendor/github.com/garyburd/redigo/redisx/connmux_test.go
new file mode 100644
index 0000000..9c3c8b1
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/redisx/connmux_test.go
@@ -0,0 +1,259 @@
+// Copyright 2014 Gary Burd
+//
+// 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 redisx_test
+
+import (
+ "net/textproto"
+ "sync"
+ "testing"
+
+ "github.com/garyburd/redigo/internal/redistest"
+ "github.com/garyburd/redigo/redis"
+ "github.com/garyburd/redigo/redisx"
+)
+
+func TestConnMux(t *testing.T) {
+ c, err := redistest.Dial()
+ if err != nil {
+ t.Fatalf("error connection to database, %v", err)
+ }
+ m := redisx.NewConnMux(c)
+ defer m.Close()
+
+ c1 := m.Get()
+ c2 := m.Get()
+ c1.Send("ECHO", "hello")
+ c2.Send("ECHO", "world")
+ c1.Flush()
+ c2.Flush()
+ s, err := redis.String(c1.Receive())
+ if err != nil {
+ t.Fatal(err)
+ }
+ if s != "hello" {
+ t.Fatalf("echo returned %q, want %q", s, "hello")
+ }
+ s, err = redis.String(c2.Receive())
+ if err != nil {
+ t.Fatal(err)
+ }
+ if s != "world" {
+ t.Fatalf("echo returned %q, want %q", s, "world")
+ }
+ c1.Close()
+ c2.Close()
+}
+
+func TestConnMuxClose(t *testing.T) {
+ c, err := redistest.Dial()
+ if err != nil {
+ t.Fatalf("error connection to database, %v", err)
+ }
+ m := redisx.NewConnMux(c)
+ defer m.Close()
+
+ c1 := m.Get()
+ c2 := m.Get()
+
+ if err := c1.Send("ECHO", "hello"); err != nil {
+ t.Fatal(err)
+ }
+ if err := c1.Close(); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := c2.Send("ECHO", "world"); err != nil {
+ t.Fatal(err)
+ }
+ if err := c2.Flush(); err != nil {
+ t.Fatal(err)
+ }
+
+ s, err := redis.String(c2.Receive())
+ if err != nil {
+ t.Fatal(err)
+ }
+ if s != "world" {
+ t.Fatalf("echo returned %q, want %q", s, "world")
+ }
+ c2.Close()
+}
+
+func BenchmarkConn(b *testing.B) {
+ b.StopTimer()
+ c, err := redistest.Dial()
+ if err != nil {
+ b.Fatalf("error connection to database, %v", err)
+ }
+ defer c.Close()
+ b.StartTimer()
+
+ for i := 0; i < b.N; i++ {
+ if _, err := c.Do("PING"); err != nil {
+ b.Fatal(err)
+ }
+ }
+}
+
+func BenchmarkConnMux(b *testing.B) {
+ b.StopTimer()
+ c, err := redistest.Dial()
+ if err != nil {
+ b.Fatalf("error connection to database, %v", err)
+ }
+ m := redisx.NewConnMux(c)
+ defer m.Close()
+
+ b.StartTimer()
+
+ for i := 0; i < b.N; i++ {
+ c := m.Get()
+ if _, err := c.Do("PING"); err != nil {
+ b.Fatal(err)
+ }
+ c.Close()
+ }
+}
+
+func BenchmarkPool(b *testing.B) {
+ b.StopTimer()
+
+ p := redis.Pool{Dial: redistest.Dial, MaxIdle: 1}
+ defer p.Close()
+
+ // Fill the pool.
+ c := p.Get()
+ if err := c.Err(); err != nil {
+ b.Fatal(err)
+ }
+ c.Close()
+
+ b.StartTimer()
+
+ for i := 0; i < b.N; i++ {
+ c := p.Get()
+ if _, err := c.Do("PING"); err != nil {
+ b.Fatal(err)
+ }
+ c.Close()
+ }
+}
+
+const numConcurrent = 10
+
+func BenchmarkConnMuxConcurrent(b *testing.B) {
+ b.StopTimer()
+ c, err := redistest.Dial()
+ if err != nil {
+ b.Fatalf("error connection to database, %v", err)
+ }
+ defer c.Close()
+
+ m := redisx.NewConnMux(c)
+
+ var wg sync.WaitGroup
+ wg.Add(numConcurrent)
+
+ b.StartTimer()
+
+ for i := 0; i < numConcurrent; i++ {
+ go func() {
+ defer wg.Done()
+ for i := 0; i < b.N; i++ {
+ c := m.Get()
+ if _, err := c.Do("PING"); err != nil {
+ b.Fatal(err)
+ }
+ c.Close()
+ }
+ }()
+ }
+ wg.Wait()
+}
+
+func BenchmarkPoolConcurrent(b *testing.B) {
+ b.StopTimer()
+
+ p := redis.Pool{Dial: redistest.Dial, MaxIdle: numConcurrent}
+ defer p.Close()
+
+ // Fill the pool.
+ conns := make([]redis.Conn, numConcurrent)
+ for i := range conns {
+ c := p.Get()
+ if err := c.Err(); err != nil {
+ b.Fatal(err)
+ }
+ conns[i] = c
+ }
+ for _, c := range conns {
+ c.Close()
+ }
+
+ var wg sync.WaitGroup
+ wg.Add(numConcurrent)
+
+ b.StartTimer()
+
+ for i := 0; i < numConcurrent; i++ {
+ go func() {
+ defer wg.Done()
+ for i := 0; i < b.N; i++ {
+ c := p.Get()
+ if _, err := c.Do("PING"); err != nil {
+ b.Fatal(err)
+ }
+ c.Close()
+ }
+ }()
+ }
+ wg.Wait()
+}
+
+func BenchmarkPipelineConcurrency(b *testing.B) {
+ b.StopTimer()
+ c, err := redistest.Dial()
+ if err != nil {
+ b.Fatalf("error connection to database, %v", err)
+ }
+ defer c.Close()
+
+ var wg sync.WaitGroup
+ wg.Add(numConcurrent)
+
+ var pipeline textproto.Pipeline
+
+ b.StartTimer()
+
+ for i := 0; i < numConcurrent; i++ {
+ go func() {
+ defer wg.Done()
+ for i := 0; i < b.N; i++ {
+ id := pipeline.Next()
+ pipeline.StartRequest(id)
+ c.Send("PING")
+ c.Flush()
+ pipeline.EndRequest(id)
+ pipeline.StartResponse(id)
+ _, err := c.Receive()
+ if err != nil {
+ b.Fatal(err)
+ }
+ pipeline.EndResponse(id)
+ }
+ }()
+ }
+ wg.Wait()
+}
diff --git a/vendor/github.com/garyburd/redigo/redisx/doc.go b/vendor/github.com/garyburd/redigo/redisx/doc.go
new file mode 100644
index 0000000..91653db
--- /dev/null
+++ b/vendor/github.com/garyburd/redigo/redisx/doc.go
@@ -0,0 +1,17 @@
+// Copyright 2012 Gary Burd
+//
+// 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 redisx contains experimental features for Redigo. Features in this
+// package may be modified or deleted at any time.
+package redisx // import "github.com/garyburd/redigo/redisx"
diff --git a/vendor/github.com/gorilla/handlers/.travis.yml b/vendor/github.com/gorilla/handlers/.travis.yml
new file mode 100644
index 0000000..4ea1e7a
--- /dev/null
+++ b/vendor/github.com/gorilla/handlers/.travis.yml
@@ -0,0 +1,18 @@
+language: go
+sudo: false
+
+matrix:
+ include:
+ - go: 1.4
+ - go: 1.5
+ - go: 1.6
+ - go: 1.7
+ - go: tip
+ allow_failures:
+ - go: tip
+
+script:
+ - go get -t -v ./...
+ - diff -u <(echo -n) <(gofmt -d .)
+ - go vet $(go list ./... | grep -v /vendor/)
+ - go test -v -race ./...
diff --git a/vendor/github.com/gorilla/handlers/LICENSE b/vendor/github.com/gorilla/handlers/LICENSE
new file mode 100644
index 0000000..66ea3c8
--- /dev/null
+++ b/vendor/github.com/gorilla/handlers/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2013 The Gorilla Handlers Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+ Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/github.com/gorilla/handlers/README.md b/vendor/github.com/gorilla/handlers/README.md
new file mode 100644
index 0000000..4a6895d
--- /dev/null
+++ b/vendor/github.com/gorilla/handlers/README.md
@@ -0,0 +1,55 @@
+gorilla/handlers
+================
+[](https://godoc.org/github.com/gorilla/handlers) [](https://travis-ci.org/gorilla/handlers)
+[](https://sourcegraph.com/github.com/gorilla/handlers?badge)
+
+
+Package handlers is a collection of handlers (aka "HTTP middleware") for use
+with Go's `net/http` package (or any framework supporting `http.Handler`), including:
+
+* [**LoggingHandler**](https://godoc.org/github.com/gorilla/handlers#LoggingHandler) for logging HTTP requests in the Apache [Common Log
+ Format](http://httpd.apache.org/docs/2.2/logs.html#common).
+* [**CombinedLoggingHandler**](https://godoc.org/github.com/gorilla/handlers#CombinedLoggingHandler) for logging HTTP requests in the Apache [Combined Log
+ Format](http://httpd.apache.org/docs/2.2/logs.html#combined) commonly used by
+ both Apache and nginx.
+* [**CompressHandler**](https://godoc.org/github.com/gorilla/handlers#CompressHandler) for gzipping responses.
+* [**ContentTypeHandler**](https://godoc.org/github.com/gorilla/handlers#ContentTypeHandler) for validating requests against a list of accepted
+ content types.
+* [**MethodHandler**](https://godoc.org/github.com/gorilla/handlers#MethodHandler) for matching HTTP methods against handlers in a
+ `map[string]http.Handler`
+* [**ProxyHeaders**](https://godoc.org/github.com/gorilla/handlers#ProxyHeaders) for populating `r.RemoteAddr` and `r.URL.Scheme` based on the
+ `X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Proto` and RFC7239 `Forwarded`
+ headers when running a Go server behind a HTTP reverse proxy.
+* [**CanonicalHost**](https://godoc.org/github.com/gorilla/handlers#CanonicalHost) for re-directing to the preferred host when handling multiple
+ domains (i.e. multiple CNAME aliases).
+* [**RecoveryHandler**](https://godoc.org/github.com/gorilla/handlers#RecoveryHandler) for recovering from unexpected panics.
+
+Other handlers are documented [on the Gorilla
+website](http://www.gorillatoolkit.org/pkg/handlers).
+
+## Example
+
+A simple example using `handlers.LoggingHandler` and `handlers.CompressHandler`:
+
+```go
+import (
+ "net/http"
+ "github.com/gorilla/handlers"
+)
+
+func main() {
+ r := http.NewServeMux()
+
+ // Only log requests to our admin dashboard to stdout
+ r.Handle("/admin", handlers.LoggingHandler(os.Stdout, http.HandlerFunc(ShowAdminDashboard)))
+ r.HandleFunc("/", ShowIndex)
+
+ // Wrap our server with our gzip handler to gzip compress all responses.
+ http.ListenAndServe(":8000", handlers.CompressHandler(r))
+}
+```
+
+## License
+
+BSD licensed. See the included LICENSE file for details.
+
diff --git a/vendor/github.com/gorilla/handlers/canonical.go b/vendor/github.com/gorilla/handlers/canonical.go
new file mode 100644
index 0000000..8437fef
--- /dev/null
+++ b/vendor/github.com/gorilla/handlers/canonical.go
@@ -0,0 +1,74 @@
+package handlers
+
+import (
+ "net/http"
+ "net/url"
+ "strings"
+)
+
+type canonical struct {
+ h http.Handler
+ domain string
+ code int
+}
+
+// CanonicalHost is HTTP middleware that re-directs requests to the canonical
+// domain. It accepts a domain and a status code (e.g. 301 or 302) and
+// re-directs clients to this domain. The existing request path is maintained.
+//
+// Note: If the provided domain is considered invalid by url.Parse or otherwise
+// returns an empty scheme or host, clients are not re-directed.
+//
+// Example:
+//
+// r := mux.NewRouter()
+// canonical := handlers.CanonicalHost("http://www.gorillatoolkit.org", 302)
+// r.HandleFunc("/route", YourHandler)
+//
+// log.Fatal(http.ListenAndServe(":7000", canonical(r)))
+//
+func CanonicalHost(domain string, code int) func(h http.Handler) http.Handler {
+ fn := func(h http.Handler) http.Handler {
+ return canonical{h, domain, code}
+ }
+
+ return fn
+}
+
+func (c canonical) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ dest, err := url.Parse(c.domain)
+ if err != nil {
+ // Call the next handler if the provided domain fails to parse.
+ c.h.ServeHTTP(w, r)
+ return
+ }
+
+ if dest.Scheme == "" || dest.Host == "" {
+ // Call the next handler if the scheme or host are empty.
+ // Note that url.Parse won't fail on in this case.
+ c.h.ServeHTTP(w, r)
+ return
+ }
+
+ if !strings.EqualFold(cleanHost(r.Host), dest.Host) {
+ // Re-build the destination URL
+ dest := dest.Scheme + "://" + dest.Host + r.URL.Path
+ if r.URL.RawQuery != "" {
+ dest += "?" + r.URL.RawQuery
+ }
+ http.Redirect(w, r, dest, c.code)
+ return
+ }
+
+ c.h.ServeHTTP(w, r)
+}
+
+// cleanHost cleans invalid Host headers by stripping anything after '/' or ' '.
+// This is backported from Go 1.5 (in response to issue #11206) and attempts to
+// mitigate malformed Host headers that do not match the format in RFC7230.
+func cleanHost(in string) string {
+ if i := strings.IndexAny(in, " /"); i != -1 {
+ return in[:i]
+ }
+ return in
+}
diff --git a/vendor/github.com/gorilla/handlers/canonical_test.go b/vendor/github.com/gorilla/handlers/canonical_test.go
new file mode 100644
index 0000000..615e4b0
--- /dev/null
+++ b/vendor/github.com/gorilla/handlers/canonical_test.go
@@ -0,0 +1,127 @@
+package handlers
+
+import (
+ "bufio"
+ "bytes"
+ "log"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "strings"
+ "testing"
+)
+
+func TestCleanHost(t *testing.T) {
+ tests := []struct {
+ in, want string
+ }{
+ {"www.google.com", "www.google.com"},
+ {"www.google.com foo", "www.google.com"},
+ {"www.google.com/foo", "www.google.com"},
+ {" first character is a space", ""},
+ }
+ for _, tt := range tests {
+ got := cleanHost(tt.in)
+ if tt.want != got {
+ t.Errorf("cleanHost(%q) = %q, want %q", tt.in, got, tt.want)
+ }
+ }
+}
+
+func TestCanonicalHost(t *testing.T) {
+ gorilla := "http://www.gorillatoolkit.org"
+
+ rr := httptest.NewRecorder()
+ r := newRequest("GET", "http://www.example.com/")
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ // Test a re-direct: should return a 302 Found.
+ CanonicalHost(gorilla, http.StatusFound)(testHandler).ServeHTTP(rr, r)
+
+ if rr.Code != http.StatusFound {
+ t.Fatalf("bad status: got %v want %v", rr.Code, http.StatusFound)
+ }
+
+ if rr.Header().Get("Location") != gorilla+r.URL.Path {
+ t.Fatalf("bad re-direct: got %q want %q", rr.Header().Get("Location"), gorilla+r.URL.Path)
+ }
+
+}
+
+func TestKeepsQueryString(t *testing.T) {
+ google := "https://www.google.com"
+
+ rr := httptest.NewRecorder()
+ querystring := url.Values{"q": {"golang"}, "format": {"json"}}.Encode()
+ r := newRequest("GET", "http://www.example.com/search?"+querystring)
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+ CanonicalHost(google, http.StatusFound)(testHandler).ServeHTTP(rr, r)
+
+ want := google + r.URL.Path + "?" + querystring
+ if rr.Header().Get("Location") != want {
+ t.Fatalf("bad re-direct: got %q want %q", rr.Header().Get("Location"), want)
+ }
+}
+
+func TestBadDomain(t *testing.T) {
+ rr := httptest.NewRecorder()
+ r := newRequest("GET", "http://www.example.com/")
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ // Test a bad domain - should return 200 OK.
+ CanonicalHost("%", http.StatusFound)(testHandler).ServeHTTP(rr, r)
+
+ if rr.Code != http.StatusOK {
+ t.Fatalf("bad status: got %v want %v", rr.Code, http.StatusOK)
+ }
+}
+
+func TestEmptyHost(t *testing.T) {
+ rr := httptest.NewRecorder()
+ r := newRequest("GET", "http://www.example.com/")
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ // Test a domain that returns an empty url.Host from url.Parse.
+ CanonicalHost("hello.com", http.StatusFound)(testHandler).ServeHTTP(rr, r)
+
+ if rr.Code != http.StatusOK {
+ t.Fatalf("bad status: got %v want %v", rr.Code, http.StatusOK)
+ }
+}
+
+func TestHeaderWrites(t *testing.T) {
+ gorilla := "http://www.gorillatoolkit.org"
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(200)
+ })
+
+ // Catch the log output to ensure we don't write multiple headers.
+ var b bytes.Buffer
+ buf := bufio.NewWriter(&b)
+ tl := log.New(buf, "test: ", log.Lshortfile)
+
+ srv := httptest.NewServer(
+ CanonicalHost(gorilla, http.StatusFound)(testHandler))
+ defer srv.Close()
+ srv.Config.ErrorLog = tl
+
+ _, err := http.Get(srv.URL)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ err = buf.Flush()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // We rely on the error not changing: net/http does not export it.
+ if strings.Contains(b.String(), "multiple response.WriteHeader calls") {
+ t.Fatalf("re-direct did not return early: multiple header writes")
+ }
+}
diff --git a/vendor/github.com/gorilla/handlers/compress.go b/vendor/github.com/gorilla/handlers/compress.go
new file mode 100644
index 0000000..e8345d7
--- /dev/null
+++ b/vendor/github.com/gorilla/handlers/compress.go
@@ -0,0 +1,148 @@
+// Copyright 2013 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package handlers
+
+import (
+ "compress/flate"
+ "compress/gzip"
+ "io"
+ "net/http"
+ "strings"
+)
+
+type compressResponseWriter struct {
+ io.Writer
+ http.ResponseWriter
+ http.Hijacker
+ http.Flusher
+ http.CloseNotifier
+}
+
+func (w *compressResponseWriter) WriteHeader(c int) {
+ w.ResponseWriter.Header().Del("Content-Length")
+ w.ResponseWriter.WriteHeader(c)
+}
+
+func (w *compressResponseWriter) Header() http.Header {
+ return w.ResponseWriter.Header()
+}
+
+func (w *compressResponseWriter) Write(b []byte) (int, error) {
+ h := w.ResponseWriter.Header()
+ if h.Get("Content-Type") == "" {
+ h.Set("Content-Type", http.DetectContentType(b))
+ }
+ h.Del("Content-Length")
+
+ return w.Writer.Write(b)
+}
+
+type flusher interface {
+ Flush() error
+}
+
+func (w *compressResponseWriter) Flush() {
+ // Flush compressed data if compressor supports it.
+ if f, ok := w.Writer.(flusher); ok {
+ f.Flush()
+ }
+ // Flush HTTP response.
+ if w.Flusher != nil {
+ w.Flusher.Flush()
+ }
+}
+
+// CompressHandler gzip compresses HTTP responses for clients that support it
+// via the 'Accept-Encoding' header.
+//
+// Compressing TLS traffic may leak the page contents to an attacker if the
+// page contains user input: http://security.stackexchange.com/a/102015/12208
+func CompressHandler(h http.Handler) http.Handler {
+ return CompressHandlerLevel(h, gzip.DefaultCompression)
+}
+
+// CompressHandlerLevel gzip compresses HTTP responses with specified compression level
+// for clients that support it via the 'Accept-Encoding' header.
+//
+// The compression level should be gzip.DefaultCompression, gzip.NoCompression,
+// or any integer value between gzip.BestSpeed and gzip.BestCompression inclusive.
+// gzip.DefaultCompression is used in case of invalid compression level.
+func CompressHandlerLevel(h http.Handler, level int) http.Handler {
+ if level < gzip.DefaultCompression || level > gzip.BestCompression {
+ level = gzip.DefaultCompression
+ }
+
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ L:
+ for _, enc := range strings.Split(r.Header.Get("Accept-Encoding"), ",") {
+ switch strings.TrimSpace(enc) {
+ case "gzip":
+ w.Header().Set("Content-Encoding", "gzip")
+ w.Header().Add("Vary", "Accept-Encoding")
+
+ gw, _ := gzip.NewWriterLevel(w, level)
+ defer gw.Close()
+
+ h, hok := w.(http.Hijacker)
+ if !hok { /* w is not Hijacker... oh well... */
+ h = nil
+ }
+
+ f, fok := w.(http.Flusher)
+ if !fok {
+ f = nil
+ }
+
+ cn, cnok := w.(http.CloseNotifier)
+ if !cnok {
+ cn = nil
+ }
+
+ w = &compressResponseWriter{
+ Writer: gw,
+ ResponseWriter: w,
+ Hijacker: h,
+ Flusher: f,
+ CloseNotifier: cn,
+ }
+
+ break L
+ case "deflate":
+ w.Header().Set("Content-Encoding", "deflate")
+ w.Header().Add("Vary", "Accept-Encoding")
+
+ fw, _ := flate.NewWriter(w, level)
+ defer fw.Close()
+
+ h, hok := w.(http.Hijacker)
+ if !hok { /* w is not Hijacker... oh well... */
+ h = nil
+ }
+
+ f, fok := w.(http.Flusher)
+ if !fok {
+ f = nil
+ }
+
+ cn, cnok := w.(http.CloseNotifier)
+ if !cnok {
+ cn = nil
+ }
+
+ w = &compressResponseWriter{
+ Writer: fw,
+ ResponseWriter: w,
+ Hijacker: h,
+ Flusher: f,
+ CloseNotifier: cn,
+ }
+
+ break L
+ }
+ }
+
+ h.ServeHTTP(w, r)
+ })
+}
diff --git a/vendor/github.com/gorilla/handlers/compress_test.go b/vendor/github.com/gorilla/handlers/compress_test.go
new file mode 100644
index 0000000..6f07f44
--- /dev/null
+++ b/vendor/github.com/gorilla/handlers/compress_test.go
@@ -0,0 +1,154 @@
+// Copyright 2013 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package handlers
+
+import (
+ "bufio"
+ "io"
+ "net"
+ "net/http"
+ "net/http/httptest"
+ "strconv"
+ "testing"
+)
+
+var contentType = "text/plain; charset=utf-8"
+
+func compressedRequest(w *httptest.ResponseRecorder, compression string) {
+ CompressHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Length", strconv.Itoa(9*1024))
+ w.Header().Set("Content-Type", contentType)
+ for i := 0; i < 1024; i++ {
+ io.WriteString(w, "Gorilla!\n")
+ }
+ })).ServeHTTP(w, &http.Request{
+ Method: "GET",
+ Header: http.Header{
+ "Accept-Encoding": []string{compression},
+ },
+ })
+
+}
+
+func TestCompressHandlerNoCompression(t *testing.T) {
+ w := httptest.NewRecorder()
+ compressedRequest(w, "")
+ if enc := w.HeaderMap.Get("Content-Encoding"); enc != "" {
+ t.Errorf("wrong content encoding, got %q want %q", enc, "")
+ }
+ if ct := w.HeaderMap.Get("Content-Type"); ct != contentType {
+ t.Errorf("wrong content type, got %q want %q", ct, contentType)
+ }
+ if w.Body.Len() != 1024*9 {
+ t.Errorf("wrong len, got %d want %d", w.Body.Len(), 1024*9)
+ }
+ if l := w.HeaderMap.Get("Content-Length"); l != "9216" {
+ t.Errorf("wrong content-length. got %q expected %d", l, 1024*9)
+ }
+}
+
+func TestCompressHandlerGzip(t *testing.T) {
+ w := httptest.NewRecorder()
+ compressedRequest(w, "gzip")
+ if w.HeaderMap.Get("Content-Encoding") != "gzip" {
+ t.Errorf("wrong content encoding, got %q want %q", w.HeaderMap.Get("Content-Encoding"), "gzip")
+ }
+ if w.HeaderMap.Get("Content-Type") != "text/plain; charset=utf-8" {
+ t.Errorf("wrong content type, got %s want %s", w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8")
+ }
+ if w.Body.Len() != 72 {
+ t.Errorf("wrong len, got %d want %d", w.Body.Len(), 72)
+ }
+ if l := w.HeaderMap.Get("Content-Length"); l != "" {
+ t.Errorf("wrong content-length. got %q expected %q", l, "")
+ }
+}
+
+func TestCompressHandlerDeflate(t *testing.T) {
+ w := httptest.NewRecorder()
+ compressedRequest(w, "deflate")
+ if w.HeaderMap.Get("Content-Encoding") != "deflate" {
+ t.Fatalf("wrong content encoding, got %q want %q", w.HeaderMap.Get("Content-Encoding"), "deflate")
+ }
+ if w.HeaderMap.Get("Content-Type") != "text/plain; charset=utf-8" {
+ t.Fatalf("wrong content type, got %s want %s", w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8")
+ }
+ if w.Body.Len() != 54 {
+ t.Fatalf("wrong len, got %d want %d", w.Body.Len(), 54)
+ }
+}
+
+func TestCompressHandlerGzipDeflate(t *testing.T) {
+ w := httptest.NewRecorder()
+ compressedRequest(w, "gzip, deflate ")
+ if w.HeaderMap.Get("Content-Encoding") != "gzip" {
+ t.Fatalf("wrong content encoding, got %q want %q", w.HeaderMap.Get("Content-Encoding"), "gzip")
+ }
+ if w.HeaderMap.Get("Content-Type") != "text/plain; charset=utf-8" {
+ t.Fatalf("wrong content type, got %s want %s", w.HeaderMap.Get("Content-Type"), "text/plain; charset=utf-8")
+ }
+}
+
+type fullyFeaturedResponseWriter struct{}
+
+// Header/Write/WriteHeader implement the http.ResponseWriter interface.
+func (fullyFeaturedResponseWriter) Header() http.Header {
+ return http.Header{}
+}
+func (fullyFeaturedResponseWriter) Write([]byte) (int, error) {
+ return 0, nil
+}
+func (fullyFeaturedResponseWriter) WriteHeader(int) {}
+
+// Flush implements the http.Flusher interface.
+func (fullyFeaturedResponseWriter) Flush() {}
+
+// Hijack implements the http.Hijacker interface.
+func (fullyFeaturedResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
+ return nil, nil, nil
+}
+
+// CloseNotify implements the http.CloseNotifier interface.
+func (fullyFeaturedResponseWriter) CloseNotify() <-chan bool {
+ return nil
+}
+
+func TestCompressHandlerPreserveInterfaces(t *testing.T) {
+ // Compile time validation fullyFeaturedResponseWriter implements all the
+ // interfaces we're asserting in the test case below.
+ var (
+ _ http.Flusher = fullyFeaturedResponseWriter{}
+ _ http.CloseNotifier = fullyFeaturedResponseWriter{}
+ _ http.Hijacker = fullyFeaturedResponseWriter{}
+ )
+ var h http.Handler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
+ comp := r.Header.Get("Accept-Encoding")
+ if _, ok := rw.(*compressResponseWriter); !ok {
+ t.Fatalf("ResponseWriter wasn't wrapped by compressResponseWriter, got %T type", rw)
+ }
+ if _, ok := rw.(http.Flusher); !ok {
+ t.Errorf("ResponseWriter lost http.Flusher interface for %q", comp)
+ }
+ if _, ok := rw.(http.CloseNotifier); !ok {
+ t.Errorf("ResponseWriter lost http.CloseNotifier interface for %q", comp)
+ }
+ if _, ok := rw.(http.Hijacker); !ok {
+ t.Errorf("ResponseWriter lost http.Hijacker interface for %q", comp)
+ }
+ })
+ h = CompressHandler(h)
+ var (
+ rw fullyFeaturedResponseWriter
+ )
+ r, err := http.NewRequest("GET", "/", nil)
+ if err != nil {
+ t.Fatalf("Failed to create test request: %v", err)
+ }
+ r.Header.Set("Accept-Encoding", "gzip")
+ h.ServeHTTP(rw, r)
+
+ r.Header.Set("Accept-Encoding", "deflate")
+ h.ServeHTTP(rw, r)
+}
diff --git a/vendor/github.com/gorilla/handlers/cors.go b/vendor/github.com/gorilla/handlers/cors.go
new file mode 100644
index 0000000..1f92d1a
--- /dev/null
+++ b/vendor/github.com/gorilla/handlers/cors.go
@@ -0,0 +1,317 @@
+package handlers
+
+import (
+ "net/http"
+ "strconv"
+ "strings"
+)
+
+// CORSOption represents a functional option for configuring the CORS middleware.
+type CORSOption func(*cors) error
+
+type cors struct {
+ h http.Handler
+ allowedHeaders []string
+ allowedMethods []string
+ allowedOrigins []string
+ allowedOriginValidator OriginValidator
+ exposedHeaders []string
+ maxAge int
+ ignoreOptions bool
+ allowCredentials bool
+}
+
+// OriginValidator takes an origin string and returns whether or not that origin is allowed.
+type OriginValidator func(string) bool
+
+var (
+ defaultCorsMethods = []string{"GET", "HEAD", "POST"}
+ defaultCorsHeaders = []string{"Accept", "Accept-Language", "Content-Language", "Origin"}
+ // (WebKit/Safari v9 sends the Origin header by default in AJAX requests)
+)
+
+const (
+ corsOptionMethod string = "OPTIONS"
+ corsAllowOriginHeader string = "Access-Control-Allow-Origin"
+ corsExposeHeadersHeader string = "Access-Control-Expose-Headers"
+ corsMaxAgeHeader string = "Access-Control-Max-Age"
+ corsAllowMethodsHeader string = "Access-Control-Allow-Methods"
+ corsAllowHeadersHeader string = "Access-Control-Allow-Headers"
+ corsAllowCredentialsHeader string = "Access-Control-Allow-Credentials"
+ corsRequestMethodHeader string = "Access-Control-Request-Method"
+ corsRequestHeadersHeader string = "Access-Control-Request-Headers"
+ corsOriginHeader string = "Origin"
+ corsVaryHeader string = "Vary"
+ corsOriginMatchAll string = "*"
+)
+
+func (ch *cors) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ origin := r.Header.Get(corsOriginHeader)
+ if !ch.isOriginAllowed(origin) {
+ ch.h.ServeHTTP(w, r)
+ return
+ }
+
+ if r.Method == corsOptionMethod {
+ if ch.ignoreOptions {
+ ch.h.ServeHTTP(w, r)
+ return
+ }
+
+ if _, ok := r.Header[corsRequestMethodHeader]; !ok {
+ w.WriteHeader(http.StatusBadRequest)
+ return
+ }
+
+ method := r.Header.Get(corsRequestMethodHeader)
+ if !ch.isMatch(method, ch.allowedMethods) {
+ w.WriteHeader(http.StatusMethodNotAllowed)
+ return
+ }
+
+ requestHeaders := strings.Split(r.Header.Get(corsRequestHeadersHeader), ",")
+ allowedHeaders := []string{}
+ for _, v := range requestHeaders {
+ canonicalHeader := http.CanonicalHeaderKey(strings.TrimSpace(v))
+ if canonicalHeader == "" || ch.isMatch(canonicalHeader, defaultCorsHeaders) {
+ continue
+ }
+
+ if !ch.isMatch(canonicalHeader, ch.allowedHeaders) {
+ w.WriteHeader(http.StatusForbidden)
+ return
+ }
+
+ allowedHeaders = append(allowedHeaders, canonicalHeader)
+ }
+
+ if len(allowedHeaders) > 0 {
+ w.Header().Set(corsAllowHeadersHeader, strings.Join(allowedHeaders, ","))
+ }
+
+ if ch.maxAge > 0 {
+ w.Header().Set(corsMaxAgeHeader, strconv.Itoa(ch.maxAge))
+ }
+
+ if !ch.isMatch(method, defaultCorsMethods) {
+ w.Header().Set(corsAllowMethodsHeader, method)
+ }
+ } else {
+ if len(ch.exposedHeaders) > 0 {
+ w.Header().Set(corsExposeHeadersHeader, strings.Join(ch.exposedHeaders, ","))
+ }
+ }
+
+ if ch.allowCredentials {
+ w.Header().Set(corsAllowCredentialsHeader, "true")
+ }
+
+ if len(ch.allowedOrigins) > 1 {
+ w.Header().Set(corsVaryHeader, corsOriginHeader)
+ }
+
+ w.Header().Set(corsAllowOriginHeader, origin)
+
+ if r.Method == corsOptionMethod {
+ return
+ }
+ ch.h.ServeHTTP(w, r)
+}
+
+// CORS provides Cross-Origin Resource Sharing middleware.
+// Example:
+//
+// import (
+// "net/http"
+//
+// "github.com/gorilla/handlers"
+// "github.com/gorilla/mux"
+// )
+//
+// func main() {
+// r := mux.NewRouter()
+// r.HandleFunc("/users", UserEndpoint)
+// r.HandleFunc("/projects", ProjectEndpoint)
+//
+// // Apply the CORS middleware to our top-level router, with the defaults.
+// http.ListenAndServe(":8000", handlers.CORS()(r))
+// }
+//
+func CORS(opts ...CORSOption) func(http.Handler) http.Handler {
+ return func(h http.Handler) http.Handler {
+ ch := parseCORSOptions(opts...)
+ ch.h = h
+ return ch
+ }
+}
+
+func parseCORSOptions(opts ...CORSOption) *cors {
+ ch := &cors{
+ allowedMethods: defaultCorsMethods,
+ allowedHeaders: defaultCorsHeaders,
+ allowedOrigins: []string{corsOriginMatchAll},
+ }
+
+ for _, option := range opts {
+ option(ch)
+ }
+
+ return ch
+}
+
+//
+// Functional options for configuring CORS.
+//
+
+// AllowedHeaders adds the provided headers to the list of allowed headers in a
+// CORS request.
+// This is an append operation so the headers Accept, Accept-Language,
+// and Content-Language are always allowed.
+// Content-Type must be explicitly declared if accepting Content-Types other than
+// application/x-www-form-urlencoded, multipart/form-data, or text/plain.
+func AllowedHeaders(headers []string) CORSOption {
+ return func(ch *cors) error {
+ for _, v := range headers {
+ normalizedHeader := http.CanonicalHeaderKey(strings.TrimSpace(v))
+ if normalizedHeader == "" {
+ continue
+ }
+
+ if !ch.isMatch(normalizedHeader, ch.allowedHeaders) {
+ ch.allowedHeaders = append(ch.allowedHeaders, normalizedHeader)
+ }
+ }
+
+ return nil
+ }
+}
+
+// AllowedMethods can be used to explicitly allow methods in the
+// Access-Control-Allow-Methods header.
+// This is a replacement operation so you must also
+// pass GET, HEAD, and POST if you wish to support those methods.
+func AllowedMethods(methods []string) CORSOption {
+ return func(ch *cors) error {
+ ch.allowedMethods = []string{}
+ for _, v := range methods {
+ normalizedMethod := strings.ToUpper(strings.TrimSpace(v))
+ if normalizedMethod == "" {
+ continue
+ }
+
+ if !ch.isMatch(normalizedMethod, ch.allowedMethods) {
+ ch.allowedMethods = append(ch.allowedMethods, normalizedMethod)
+ }
+ }
+
+ return nil
+ }
+}
+
+// AllowedOrigins sets the allowed origins for CORS requests, as used in the
+// 'Allow-Access-Control-Origin' HTTP header.
+// Note: Passing in a []string{"*"} will allow any domain.
+func AllowedOrigins(origins []string) CORSOption {
+ return func(ch *cors) error {
+ for _, v := range origins {
+ if v == corsOriginMatchAll {
+ ch.allowedOrigins = []string{corsOriginMatchAll}
+ return nil
+ }
+ }
+
+ ch.allowedOrigins = origins
+ return nil
+ }
+}
+
+// AllowedOriginValidator sets a function for evaluating allowed origins in CORS requests, represented by the
+// 'Allow-Access-Control-Origin' HTTP header.
+func AllowedOriginValidator(fn OriginValidator) CORSOption {
+ return func(ch *cors) error {
+ ch.allowedOriginValidator = fn
+ return nil
+ }
+}
+
+// ExposeHeaders can be used to specify headers that are available
+// and will not be stripped out by the user-agent.
+func ExposedHeaders(headers []string) CORSOption {
+ return func(ch *cors) error {
+ ch.exposedHeaders = []string{}
+ for _, v := range headers {
+ normalizedHeader := http.CanonicalHeaderKey(strings.TrimSpace(v))
+ if normalizedHeader == "" {
+ continue
+ }
+
+ if !ch.isMatch(normalizedHeader, ch.exposedHeaders) {
+ ch.exposedHeaders = append(ch.exposedHeaders, normalizedHeader)
+ }
+ }
+
+ return nil
+ }
+}
+
+// MaxAge determines the maximum age (in seconds) between preflight requests. A
+// maximum of 10 minutes is allowed. An age above this value will default to 10
+// minutes.
+func MaxAge(age int) CORSOption {
+ return func(ch *cors) error {
+ // Maximum of 10 minutes.
+ if age > 600 {
+ age = 600
+ }
+
+ ch.maxAge = age
+ return nil
+ }
+}
+
+// IgnoreOptions causes the CORS middleware to ignore OPTIONS requests, instead
+// passing them through to the next handler. This is useful when your application
+// or framework has a pre-existing mechanism for responding to OPTIONS requests.
+func IgnoreOptions() CORSOption {
+ return func(ch *cors) error {
+ ch.ignoreOptions = true
+ return nil
+ }
+}
+
+// AllowCredentials can be used to specify that the user agent may pass
+// authentication details along with the request.
+func AllowCredentials() CORSOption {
+ return func(ch *cors) error {
+ ch.allowCredentials = true
+ return nil
+ }
+}
+
+func (ch *cors) isOriginAllowed(origin string) bool {
+ if origin == "" {
+ return false
+ }
+
+ if ch.allowedOriginValidator != nil {
+ return ch.allowedOriginValidator(origin)
+ }
+
+ for _, allowedOrigin := range ch.allowedOrigins {
+ if allowedOrigin == origin || allowedOrigin == corsOriginMatchAll {
+ return true
+ }
+ }
+
+ return false
+}
+
+func (ch *cors) isMatch(needle string, haystack []string) bool {
+ for _, v := range haystack {
+ if v == needle {
+ return true
+ }
+ }
+
+ return false
+}
diff --git a/vendor/github.com/gorilla/handlers/cors_test.go b/vendor/github.com/gorilla/handlers/cors_test.go
new file mode 100644
index 0000000..c63913e
--- /dev/null
+++ b/vendor/github.com/gorilla/handlers/cors_test.go
@@ -0,0 +1,336 @@
+package handlers
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+)
+
+func TestDefaultCORSHandlerReturnsOk(t *testing.T) {
+ r := newRequest("GET", "http://www.example.com/")
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ CORS()(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusFound)
+ }
+}
+
+func TestDefaultCORSHandlerReturnsOkWithOrigin(t *testing.T) {
+ r := newRequest("GET", "http://www.example.com/")
+ r.Header.Set("Origin", r.URL.String())
+
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ CORS()(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusFound)
+ }
+}
+
+func TestCORSHandlerIgnoreOptionsFallsThrough(t *testing.T) {
+ r := newRequest("OPTIONS", "http://www.example.com/")
+ r.Header.Set("Origin", r.URL.String())
+
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(http.StatusTeapot)
+ })
+
+ CORS(IgnoreOptions())(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusTeapot {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusTeapot)
+ }
+}
+
+func TestCORSHandlerSetsExposedHeaders(t *testing.T) {
+ // Test default configuration.
+ r := newRequest("GET", "http://www.example.com/")
+ r.Header.Set("Origin", r.URL.String())
+
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ CORS(ExposedHeaders([]string{"X-CORS-TEST"}))(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusOK)
+ }
+
+ header := rr.HeaderMap.Get(corsExposeHeadersHeader)
+ if header != "X-Cors-Test" {
+ t.Fatal("bad header: expected X-Cors-Test header, got empty header for method.")
+ }
+}
+
+func TestCORSHandlerUnsetRequestMethodForPreflightBadRequest(t *testing.T) {
+ r := newRequest("OPTIONS", "http://www.example.com/")
+ r.Header.Set("Origin", r.URL.String())
+
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ CORS(AllowedMethods([]string{"DELETE"}))(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusBadRequest {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusBadRequest)
+ }
+}
+
+func TestCORSHandlerInvalidRequestMethodForPreflightMethodNotAllowed(t *testing.T) {
+ r := newRequest("OPTIONS", "http://www.example.com/")
+ r.Header.Set("Origin", r.URL.String())
+ r.Header.Set(corsRequestMethodHeader, "DELETE")
+
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ CORS()(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusMethodNotAllowed {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusMethodNotAllowed)
+ }
+}
+
+func TestCORSHandlerOptionsRequestMustNotBePassedToNextHandler(t *testing.T) {
+ r := newRequest("OPTIONS", "http://www.example.com/")
+ r.Header.Set("Origin", r.URL.String())
+ r.Header.Set(corsRequestMethodHeader, "GET")
+
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ t.Fatal("Options request must not be passed to next handler")
+ })
+
+ CORS()(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusOK)
+ }
+}
+
+func TestCORSHandlerAllowedMethodForPreflight(t *testing.T) {
+ r := newRequest("OPTIONS", "http://www.example.com/")
+ r.Header.Set("Origin", r.URL.String())
+ r.Header.Set(corsRequestMethodHeader, "DELETE")
+
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ CORS(AllowedMethods([]string{"DELETE"}))(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusOK)
+ }
+
+ header := rr.HeaderMap.Get(corsAllowMethodsHeader)
+ if header != "DELETE" {
+ t.Fatalf("bad header: expected DELETE method header, got empty header.")
+ }
+}
+
+func TestCORSHandlerAllowMethodsNotSetForSimpleRequestPreflight(t *testing.T) {
+ for _, method := range defaultCorsMethods {
+ r := newRequest("OPTIONS", "http://www.example.com/")
+ r.Header.Set("Origin", r.URL.String())
+ r.Header.Set(corsRequestMethodHeader, method)
+
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ CORS()(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusOK)
+ }
+
+ header := rr.HeaderMap.Get(corsAllowMethodsHeader)
+ if header != "" {
+ t.Fatalf("bad header: expected empty method header, got %s.", header)
+ }
+ }
+}
+
+func TestCORSHandlerAllowedHeaderNotSetForSimpleRequestPreflight(t *testing.T) {
+ for _, simpleHeader := range defaultCorsHeaders {
+ r := newRequest("OPTIONS", "http://www.example.com/")
+ r.Header.Set("Origin", r.URL.String())
+ r.Header.Set(corsRequestMethodHeader, "GET")
+ r.Header.Set(corsRequestHeadersHeader, simpleHeader)
+
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ CORS()(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusOK)
+ }
+
+ header := rr.HeaderMap.Get(corsAllowHeadersHeader)
+ if header != "" {
+ t.Fatalf("bad header: expected empty header, got %s.", header)
+ }
+ }
+}
+
+func TestCORSHandlerAllowedHeaderForPreflight(t *testing.T) {
+ r := newRequest("OPTIONS", "http://www.example.com/")
+ r.Header.Set("Origin", r.URL.String())
+ r.Header.Set(corsRequestMethodHeader, "POST")
+ r.Header.Set(corsRequestHeadersHeader, "Content-Type")
+
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ CORS(AllowedHeaders([]string{"Content-Type"}))(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusOK)
+ }
+
+ header := rr.HeaderMap.Get(corsAllowHeadersHeader)
+ if header != "Content-Type" {
+ t.Fatalf("bad header: expected Content-Type header, got empty header.")
+ }
+}
+
+func TestCORSHandlerInvalidHeaderForPreflightForbidden(t *testing.T) {
+ r := newRequest("OPTIONS", "http://www.example.com/")
+ r.Header.Set("Origin", r.URL.String())
+ r.Header.Set(corsRequestMethodHeader, "POST")
+ r.Header.Set(corsRequestHeadersHeader, "Content-Type")
+
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ CORS()(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusForbidden {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusForbidden)
+ }
+}
+
+func TestCORSHandlerMaxAgeForPreflight(t *testing.T) {
+ r := newRequest("OPTIONS", "http://www.example.com/")
+ r.Header.Set("Origin", r.URL.String())
+ r.Header.Set(corsRequestMethodHeader, "POST")
+
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ CORS(MaxAge(3500))(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusOK)
+ }
+
+ header := rr.HeaderMap.Get(corsMaxAgeHeader)
+ if header != "600" {
+ t.Fatalf("bad header: expected %s to be %s, got %s.", corsMaxAgeHeader, "600", header)
+ }
+}
+
+func TestCORSHandlerAllowedCredentials(t *testing.T) {
+ r := newRequest("GET", "http://www.example.com/")
+ r.Header.Set("Origin", r.URL.String())
+
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ CORS(AllowCredentials())(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusOK)
+ }
+
+ header := rr.HeaderMap.Get(corsAllowCredentialsHeader)
+ if header != "true" {
+ t.Fatalf("bad header: expected %s to be %s, got %s.", corsAllowCredentialsHeader, "true", header)
+ }
+}
+
+func TestCORSHandlerMultipleAllowOriginsSetsVaryHeader(t *testing.T) {
+ r := newRequest("GET", "http://www.example.com/")
+ r.Header.Set("Origin", r.URL.String())
+
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ CORS(AllowedOrigins([]string{r.URL.String(), "http://google.com"}))(testHandler).ServeHTTP(rr, r)
+
+ if status := rr.Code; status != http.StatusOK {
+ t.Fatalf("bad status: got %v want %v", status, http.StatusOK)
+ }
+
+ header := rr.HeaderMap.Get(corsVaryHeader)
+ if header != corsOriginHeader {
+ t.Fatalf("bad header: expected %s to be %s, got %s.", corsVaryHeader, corsOriginHeader, header)
+ }
+}
+
+func TestCORSWithMultipleHandlers(t *testing.T) {
+ var lastHandledBy string
+ corsMiddleware := CORS()
+
+ testHandler1 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ lastHandledBy = "testHandler1"
+ })
+ testHandler2 := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ lastHandledBy = "testHandler2"
+ })
+
+ r1 := newRequest("GET", "http://www.example.com/")
+ rr1 := httptest.NewRecorder()
+ handler1 := corsMiddleware(testHandler1)
+
+ corsMiddleware(testHandler2)
+
+ handler1.ServeHTTP(rr1, r1)
+ if lastHandledBy != "testHandler1" {
+ t.Fatalf("bad CORS() registration: Handler served should be Handler registered")
+ }
+}
+
+func TestCORSHandlerWithCustomValidator(t *testing.T) {
+ r := newRequest("GET", "http://a.example.com")
+ r.Header.Set("Origin", r.URL.String())
+ rr := httptest.NewRecorder()
+
+ testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
+
+ originValidator := func(origin string) bool {
+ if strings.HasSuffix(origin, ".example.com") {
+ return true
+ }
+ return false
+ }
+
+ CORS(AllowedOriginValidator(originValidator))(testHandler).ServeHTTP(rr, r)
+ header := rr.HeaderMap.Get(corsAllowOriginHeader)
+ if header != r.URL.String() {
+ t.Fatalf("bad header: expected %s to be %s, got %s.", corsAllowOriginHeader, r.URL.String(), header)
+ }
+
+}
diff --git a/vendor/github.com/gorilla/handlers/doc.go b/vendor/github.com/gorilla/handlers/doc.go
new file mode 100644
index 0000000..944e5a8
--- /dev/null
+++ b/vendor/github.com/gorilla/handlers/doc.go
@@ -0,0 +1,9 @@
+/*
+Package handlers is a collection of handlers (aka "HTTP middleware") for use
+with Go's net/http package (or any framework supporting http.Handler).
+
+The package includes handlers for logging in standardised formats, compressing
+HTTP responses, validating content types and other useful tools for manipulating
+requests and responses.
+*/
+package handlers
diff --git a/vendor/github.com/gorilla/handlers/handlers.go b/vendor/github.com/gorilla/handlers/handlers.go
new file mode 100644
index 0000000..75db7f8
--- /dev/null
+++ b/vendor/github.com/gorilla/handlers/handlers.go
@@ -0,0 +1,399 @@
+// Copyright 2013 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package handlers
+
+import (
+ "bufio"
+ "fmt"
+ "io"
+ "net"
+ "net/http"
+ "net/url"
+ "sort"
+ "strconv"
+ "strings"
+ "time"
+ "unicode/utf8"
+)
+
+// MethodHandler is an http.Handler that dispatches to a handler whose key in the
+// MethodHandler's map matches the name of the HTTP request's method, eg: GET
+//
+// If the request's method is OPTIONS and OPTIONS is not a key in the map then
+// the handler responds with a status of 200 and sets the Allow header to a
+// comma-separated list of available methods.
+//
+// If the request's method doesn't match any of its keys the handler responds
+// with a status of HTTP 405 "Method Not Allowed" and sets the Allow header to a
+// comma-separated list of available methods.
+type MethodHandler map[string]http.Handler
+
+func (h MethodHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+ if handler, ok := h[req.Method]; ok {
+ handler.ServeHTTP(w, req)
+ } else {
+ allow := []string{}
+ for k := range h {
+ allow = append(allow, k)
+ }
+ sort.Strings(allow)
+ w.Header().Set("Allow", strings.Join(allow, ", "))
+ if req.Method == "OPTIONS" {
+ w.WriteHeader(http.StatusOK)
+ } else {
+ http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+ }
+ }
+}
+
+// loggingHandler is the http.Handler implementation for LoggingHandlerTo and its
+// friends
+type loggingHandler struct {
+ writer io.Writer
+ handler http.Handler
+}
+
+// combinedLoggingHandler is the http.Handler implementation for LoggingHandlerTo
+// and its friends
+type combinedLoggingHandler struct {
+ writer io.Writer
+ handler http.Handler
+}
+
+func (h loggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+ t := time.Now()
+ logger := makeLogger(w)
+ url := *req.URL
+ h.handler.ServeHTTP(logger, req)
+ writeLog(h.writer, req, url, t, logger.Status(), logger.Size())
+}
+
+func (h combinedLoggingHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+ t := time.Now()
+ logger := makeLogger(w)
+ url := *req.URL
+ h.handler.ServeHTTP(logger, req)
+ writeCombinedLog(h.writer, req, url, t, logger.Status(), logger.Size())
+}
+
+func makeLogger(w http.ResponseWriter) loggingResponseWriter {
+ var logger loggingResponseWriter = &responseLogger{w: w, status: http.StatusOK}
+ if _, ok := w.(http.Hijacker); ok {
+ logger = &hijackLogger{responseLogger{w: w, status: http.StatusOK}}
+ }
+ h, ok1 := logger.(http.Hijacker)
+ c, ok2 := w.(http.CloseNotifier)
+ if ok1 && ok2 {
+ return hijackCloseNotifier{logger, h, c}
+ }
+ if ok2 {
+ return &closeNotifyWriter{logger, c}
+ }
+ return logger
+}
+
+type commonLoggingResponseWriter interface {
+ http.ResponseWriter
+ http.Flusher
+ Status() int
+ Size() int
+}
+
+// responseLogger is wrapper of http.ResponseWriter that keeps track of its HTTP
+// status code and body size
+type responseLogger struct {
+ w http.ResponseWriter
+ status int
+ size int
+}
+
+func (l *responseLogger) Header() http.Header {
+ return l.w.Header()
+}
+
+func (l *responseLogger) Write(b []byte) (int, error) {
+ size, err := l.w.Write(b)
+ l.size += size
+ return size, err
+}
+
+func (l *responseLogger) WriteHeader(s int) {
+ l.w.WriteHeader(s)
+ l.status = s
+}
+
+func (l *responseLogger) Status() int {
+ return l.status
+}
+
+func (l *responseLogger) Size() int {
+ return l.size
+}
+
+func (l *responseLogger) Flush() {
+ f, ok := l.w.(http.Flusher)
+ if ok {
+ f.Flush()
+ }
+}
+
+type hijackLogger struct {
+ responseLogger
+}
+
+func (l *hijackLogger) Hijack() (net.Conn, *bufio.ReadWriter, error) {
+ h := l.responseLogger.w.(http.Hijacker)
+ conn, rw, err := h.Hijack()
+ if err == nil && l.responseLogger.status == 0 {
+ // The status will be StatusSwitchingProtocols if there was no error and
+ // WriteHeader has not been called yet
+ l.responseLogger.status = http.StatusSwitchingProtocols
+ }
+ return conn, rw, err
+}
+
+type closeNotifyWriter struct {
+ loggingResponseWriter
+ http.CloseNotifier
+}
+
+type hijackCloseNotifier struct {
+ loggingResponseWriter
+ http.Hijacker
+ http.CloseNotifier
+}
+
+const lowerhex = "0123456789abcdef"
+
+func appendQuoted(buf []byte, s string) []byte {
+ var runeTmp [utf8.UTFMax]byte
+ for width := 0; len(s) > 0; s = s[width:] {
+ r := rune(s[0])
+ width = 1
+ if r >= utf8.RuneSelf {
+ r, width = utf8.DecodeRuneInString(s)
+ }
+ if width == 1 && r == utf8.RuneError {
+ buf = append(buf, `\x`...)
+ buf = append(buf, lowerhex[s[0]>>4])
+ buf = append(buf, lowerhex[s[0]&0xF])
+ continue
+ }
+ if r == rune('"') || r == '\\' { // always backslashed
+ buf = append(buf, '\\')
+ buf = append(buf, byte(r))
+ continue
+ }
+ if strconv.IsPrint(r) {
+ n := utf8.EncodeRune(runeTmp[:], r)
+ buf = append(buf, runeTmp[:n]...)
+ continue
+ }
+ switch r {
+ case '\a':
+ buf = append(buf, `\a`...)
+ case '\b':
+ buf = append(buf, `\b`...)
+ case '\f':
+ buf = append(buf, `\f`...)
+ case '\n':
+ buf = append(buf, `\n`...)
+ case '\r':
+ buf = append(buf, `\r`...)
+ case '\t':
+ buf = append(buf, `\t`...)
+ case '\v':
+ buf = append(buf, `\v`...)
+ default:
+ switch {
+ case r < ' ':
+ buf = append(buf, `\x`...)
+ buf = append(buf, lowerhex[s[0]>>4])
+ buf = append(buf, lowerhex[s[0]&0xF])
+ case r > utf8.MaxRune:
+ r = 0xFFFD
+ fallthrough
+ case r < 0x10000:
+ buf = append(buf, `\u`...)
+ for s := 12; s >= 0; s -= 4 {
+ buf = append(buf, lowerhex[r>>uint(s)&0xF])
+ }
+ default:
+ buf = append(buf, `\U`...)
+ for s := 28; s >= 0; s -= 4 {
+ buf = append(buf, lowerhex[r>>uint(s)&0xF])
+ }
+ }
+ }
+ }
+ return buf
+
+}
+
+// buildCommonLogLine builds a log entry for req in Apache Common Log Format.
+// ts is the timestamp with which the entry should be logged.
+// status and size are used to provide the response HTTP status and size.
+func buildCommonLogLine(req *http.Request, url url.URL, ts time.Time, status int, size int) []byte {
+ username := "-"
+ if url.User != nil {
+ if name := url.User.Username(); name != "" {
+ username = name
+ }
+ }
+
+ host, _, err := net.SplitHostPort(req.RemoteAddr)
+
+ if err != nil {
+ host = req.RemoteAddr
+ }
+
+ uri := req.RequestURI
+
+ // Requests using the CONNECT method over HTTP/2.0 must use
+ // the authority field (aka r.Host) to identify the target.
+ // Refer: https://httpwg.github.io/specs/rfc7540.html#CONNECT
+ if req.ProtoMajor == 2 && req.Method == "CONNECT" {
+ uri = req.Host
+ }
+ if uri == "" {
+ uri = url.RequestURI()
+ }
+
+ buf := make([]byte, 0, 3*(len(host)+len(username)+len(req.Method)+len(uri)+len(req.Proto)+50)/2)
+ buf = append(buf, host...)
+ buf = append(buf, " - "...)
+ buf = append(buf, username...)
+ buf = append(buf, " ["...)
+ buf = append(buf, ts.Format("02/Jan/2006:15:04:05 -0700")...)
+ buf = append(buf, `] "`...)
+ buf = append(buf, req.Method...)
+ buf = append(buf, " "...)
+ buf = appendQuoted(buf, uri)
+ buf = append(buf, " "...)
+ buf = append(buf, req.Proto...)
+ buf = append(buf, `" `...)
+ buf = append(buf, strconv.Itoa(status)...)
+ buf = append(buf, " "...)
+ buf = append(buf, strconv.Itoa(size)...)
+ return buf
+}
+
+// writeLog writes a log entry for req to w in Apache Common Log Format.
+// ts is the timestamp with which the entry should be logged.
+// status and size are used to provide the response HTTP status and size.
+func writeLog(w io.Writer, req *http.Request, url url.URL, ts time.Time, status, size int) {
+ buf := buildCommonLogLine(req, url, ts, status, size)
+ buf = append(buf, '\n')
+ w.Write(buf)
+}
+
+// writeCombinedLog writes a log entry for req to w in Apache Combined Log Format.
+// ts is the timestamp with which the entry should be logged.
+// status and size are used to provide the response HTTP status and size.
+func writeCombinedLog(w io.Writer, req *http.Request, url url.URL, ts time.Time, status, size int) {
+ buf := buildCommonLogLine(req, url, ts, status, size)
+ buf = append(buf, ` "`...)
+ buf = appendQuoted(buf, req.Referer())
+ buf = append(buf, `" "`...)
+ buf = appendQuoted(buf, req.UserAgent())
+ buf = append(buf, '"', '\n')
+ w.Write(buf)
+}
+
+// CombinedLoggingHandler return a http.Handler that wraps h and logs requests to out in
+// Apache Combined Log Format.
+//
+// See http://httpd.apache.org/docs/2.2/logs.html#combined for a description of this format.
+//
+// LoggingHandler always sets the ident field of the log to -
+func CombinedLoggingHandler(out io.Writer, h http.Handler) http.Handler {
+ return combinedLoggingHandler{out, h}
+}
+
+// LoggingHandler return a http.Handler that wraps h and logs requests to out in
+// Apache Common Log Format (CLF).
+//
+// See http://httpd.apache.org/docs/2.2/logs.html#common for a description of this format.
+//
+// LoggingHandler always sets the ident field of the log to -
+//
+// Example:
+//
+// r := mux.NewRouter()
+// r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+// w.Write([]byte("This is a catch-all route"))
+// })
+// loggedRouter := handlers.LoggingHandler(os.Stdout, r)
+// http.ListenAndServe(":1123", loggedRouter)
+//
+func LoggingHandler(out io.Writer, h http.Handler) http.Handler {
+ return loggingHandler{out, h}
+}
+
+// isContentType validates the Content-Type header matches the supplied
+// contentType. That is, its type and subtype match.
+func isContentType(h http.Header, contentType string) bool {
+ ct := h.Get("Content-Type")
+ if i := strings.IndexRune(ct, ';'); i != -1 {
+ ct = ct[0:i]
+ }
+ return ct == contentType
+}
+
+// ContentTypeHandler wraps and returns a http.Handler, validating the request
+// content type is compatible with the contentTypes list. It writes a HTTP 415
+// error if that fails.
+//
+// Only PUT, POST, and PATCH requests are considered.
+func ContentTypeHandler(h http.Handler, contentTypes ...string) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if !(r.Method == "PUT" || r.Method == "POST" || r.Method == "PATCH") {
+ h.ServeHTTP(w, r)
+ return
+ }
+
+ for _, ct := range contentTypes {
+ if isContentType(r.Header, ct) {
+ h.ServeHTTP(w, r)
+ return
+ }
+ }
+ http.Error(w, fmt.Sprintf("Unsupported content type %q; expected one of %q", r.Header.Get("Content-Type"), contentTypes), http.StatusUnsupportedMediaType)
+ })
+}
+
+const (
+ // HTTPMethodOverrideHeader is a commonly used
+ // http header to override a request method.
+ HTTPMethodOverrideHeader = "X-HTTP-Method-Override"
+ // HTTPMethodOverrideFormKey is a commonly used
+ // HTML form key to override a request method.
+ HTTPMethodOverrideFormKey = "_method"
+)
+
+// HTTPMethodOverrideHandler wraps and returns a http.Handler which checks for
+// the X-HTTP-Method-Override header or the _method form key, and overrides (if
+// valid) request.Method with its value.
+//
+// This is especially useful for HTTP clients that don't support many http verbs.
+// It isn't secure to override e.g a GET to a POST, so only POST requests are
+// considered. Likewise, the override method can only be a "write" method: PUT,
+// PATCH or DELETE.
+//
+// Form method takes precedence over header method.
+func HTTPMethodOverrideHandler(h http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if r.Method == "POST" {
+ om := r.FormValue(HTTPMethodOverrideFormKey)
+ if om == "" {
+ om = r.Header.Get(HTTPMethodOverrideHeader)
+ }
+ if om == "PUT" || om == "PATCH" || om == "DELETE" {
+ r.Method = om
+ }
+ }
+ h.ServeHTTP(w, r)
+ })
+}
diff --git a/vendor/github.com/gorilla/handlers/handlers_go18.go b/vendor/github.com/gorilla/handlers/handlers_go18.go
new file mode 100644
index 0000000..35eb8d4
--- /dev/null
+++ b/vendor/github.com/gorilla/handlers/handlers_go18.go
@@ -0,0 +1,21 @@
+// +build go1.8
+
+package handlers
+
+import (
+ "fmt"
+ "net/http"
+)
+
+type loggingResponseWriter interface {
+ commonLoggingResponseWriter
+ http.Pusher
+}
+
+func (l *responseLogger) Push(target string, opts *http.PushOptions) error {
+ p, ok := l.w.(http.Pusher)
+ if !ok {
+ return fmt.Errorf("responseLogger does not implement http.Pusher")
+ }
+ return p.Push(target, opts)
+}
diff --git a/vendor/github.com/gorilla/handlers/handlers_go18_test.go b/vendor/github.com/gorilla/handlers/handlers_go18_test.go
new file mode 100644
index 0000000..c8cfa72
--- /dev/null
+++ b/vendor/github.com/gorilla/handlers/handlers_go18_test.go
@@ -0,0 +1,34 @@
+// +build go1.8
+
+package handlers
+
+import (
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+)
+
+func TestLoggingHandlerWithPush(t *testing.T) {
+ handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ if _, ok := w.(http.Pusher); !ok {
+ t.Fatalf("%T from LoggingHandler does not satisfy http.Pusher interface when built with Go >=1.8", w)
+ }
+ w.WriteHeader(200)
+ })
+
+ logger := LoggingHandler(ioutil.Discard, handler)
+ logger.ServeHTTP(httptest.NewRecorder(), newRequest("GET", "/"))
+}
+
+func TestCombinedLoggingHandlerWithPush(t *testing.T) {
+ handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ if _, ok := w.(http.Pusher); !ok {
+ t.Fatalf("%T from CombinedLoggingHandler does not satisfy http.Pusher interface when built with Go >=1.8", w)
+ }
+ w.WriteHeader(200)
+ })
+
+ logger := CombinedLoggingHandler(ioutil.Discard, handler)
+ logger.ServeHTTP(httptest.NewRecorder(), newRequest("GET", "/"))
+}
diff --git a/vendor/github.com/gorilla/handlers/handlers_pre18.go b/vendor/github.com/gorilla/handlers/handlers_pre18.go
new file mode 100644
index 0000000..197836a
--- /dev/null
+++ b/vendor/github.com/gorilla/handlers/handlers_pre18.go
@@ -0,0 +1,7 @@
+// +build !go1.8
+
+package handlers
+
+type loggingResponseWriter interface {
+ commonLoggingResponseWriter
+}
diff --git a/vendor/github.com/gorilla/handlers/handlers_test.go b/vendor/github.com/gorilla/handlers/handlers_test.go
new file mode 100644
index 0000000..04ee244
--- /dev/null
+++ b/vendor/github.com/gorilla/handlers/handlers_test.go
@@ -0,0 +1,378 @@
+// Copyright 2013 The Gorilla Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package handlers
+
+import (
+ "bytes"
+ "net"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "strings"
+ "testing"
+ "time"
+)
+
+const (
+ ok = "ok\n"
+ notAllowed = "Method not allowed\n"
+)
+
+var okHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ w.Write([]byte(ok))
+})
+
+func newRequest(method, url string) *http.Request {
+ req, err := http.NewRequest(method, url, nil)
+ if err != nil {
+ panic(err)
+ }
+ return req
+}
+
+func TestMethodHandler(t *testing.T) {
+ tests := []struct {
+ req *http.Request
+ handler http.Handler
+ code int
+ allow string // Contents of the Allow header
+ body string
+ }{
+ // No handlers
+ {newRequest("GET", "/foo"), MethodHandler{}, http.StatusMethodNotAllowed, "", notAllowed},
+ {newRequest("OPTIONS", "/foo"), MethodHandler{}, http.StatusOK, "", ""},
+
+ // A single handler
+ {newRequest("GET", "/foo"), MethodHandler{"GET": okHandler}, http.StatusOK, "", ok},
+ {newRequest("POST", "/foo"), MethodHandler{"GET": okHandler}, http.StatusMethodNotAllowed, "GET", notAllowed},
+
+ // Multiple handlers
+ {newRequest("GET", "/foo"), MethodHandler{"GET": okHandler, "POST": okHandler}, http.StatusOK, "", ok},
+ {newRequest("POST", "/foo"), MethodHandler{"GET": okHandler, "POST": okHandler}, http.StatusOK, "", ok},
+ {newRequest("DELETE", "/foo"), MethodHandler{"GET": okHandler, "POST": okHandler}, http.StatusMethodNotAllowed, "GET, POST", notAllowed},
+ {newRequest("OPTIONS", "/foo"), MethodHandler{"GET": okHandler, "POST": okHandler}, http.StatusOK, "GET, POST", ""},
+
+ // Override OPTIONS
+ {newRequest("OPTIONS", "/foo"), MethodHandler{"OPTIONS": okHandler}, http.StatusOK, "", ok},
+ }
+
+ for i, test := range tests {
+ rec := httptest.NewRecorder()
+ test.handler.ServeHTTP(rec, test.req)
+ if rec.Code != test.code {
+ t.Fatalf("%d: wrong code, got %d want %d", i, rec.Code, test.code)
+ }
+ if allow := rec.HeaderMap.Get("Allow"); allow != test.allow {
+ t.Fatalf("%d: wrong Allow, got %s want %s", i, allow, test.allow)
+ }
+ if body := rec.Body.String(); body != test.body {
+ t.Fatalf("%d: wrong body, got %q want %q", i, body, test.body)
+ }
+ }
+}
+
+func TestMakeLogger(t *testing.T) {
+ rec := httptest.NewRecorder()
+ logger := makeLogger(rec)
+ // initial status
+ if logger.Status() != http.StatusOK {
+ t.Fatalf("wrong status, got %d want %d", logger.Status(), http.StatusOK)
+ }
+ // WriteHeader
+ logger.WriteHeader(http.StatusInternalServerError)
+ if logger.Status() != http.StatusInternalServerError {
+ t.Fatalf("wrong status, got %d want %d", logger.Status(), http.StatusInternalServerError)
+ }
+ // Write
+ logger.Write([]byte(ok))
+ if logger.Size() != len(ok) {
+ t.Fatalf("wrong size, got %d want %d", logger.Size(), len(ok))
+ }
+ // Header
+ logger.Header().Set("key", "value")
+ if val := logger.Header().Get("key"); val != "value" {
+ t.Fatalf("wrong header, got %s want %s", val, "value")
+ }
+}
+
+func TestWriteLog(t *testing.T) {
+ loc, err := time.LoadLocation("Europe/Warsaw")
+ if err != nil {
+ panic(err)
+ }
+ ts := time.Date(1983, 05, 26, 3, 30, 45, 0, loc)
+
+ // A typical request with an OK response
+ req := newRequest("GET", "http://example.com")
+ req.RemoteAddr = "192.168.100.5"
+
+ buf := new(bytes.Buffer)
+ writeLog(buf, req, *req.URL, ts, http.StatusOK, 100)
+ log := buf.String()
+
+ expected := "192.168.100.5 - - [26/May/1983:03:30:45 +0200] \"GET / HTTP/1.1\" 200 100\n"
+ if log != expected {
+ t.Fatalf("wrong log, got %q want %q", log, expected)
+ }
+
+ // CONNECT request over http/2.0
+ req = &http.Request{
+ Method: "CONNECT",
+ Proto: "HTTP/2.0",
+ ProtoMajor: 2,
+ ProtoMinor: 0,
+ URL: &url.URL{Host: "www.example.com:443"},
+ Host: "www.example.com:443",
+ RemoteAddr: "192.168.100.5",
+ }
+
+ buf = new(bytes.Buffer)
+ writeLog(buf, req, *req.URL, ts, http.StatusOK, 100)
+ log = buf.String()
+
+ expected = "192.168.100.5 - - [26/May/1983:03:30:45 +0200] \"CONNECT www.example.com:443 HTTP/2.0\" 200 100\n"
+ if log != expected {
+ t.Fatalf("wrong log, got %q want %q", log, expected)
+ }
+
+ // Request with an unauthorized user
+ req = newRequest("GET", "http://example.com")
+ req.RemoteAddr = "192.168.100.5"
+ req.URL.User = url.User("kamil")
+
+ buf.Reset()
+ writeLog(buf, req, *req.URL, ts, http.StatusUnauthorized, 500)
+ log = buf.String()
+
+ expected = "192.168.100.5 - kamil [26/May/1983:03:30:45 +0200] \"GET / HTTP/1.1\" 401 500\n"
+ if log != expected {
+ t.Fatalf("wrong log, got %q want %q", log, expected)
+ }
+
+ // Request with url encoded parameters
+ req = newRequest("GET", "http://example.com/test?abc=hello%20world&a=b%3F")
+ req.RemoteAddr = "192.168.100.5"
+
+ buf.Reset()
+ writeLog(buf, req, *req.URL, ts, http.StatusOK, 100)
+ log = buf.String()
+
+ expected = "192.168.100.5 - - [26/May/1983:03:30:45 +0200] \"GET /test?abc=hello%20world&a=b%3F HTTP/1.1\" 200 100\n"
+ if log != expected {
+ t.Fatalf("wrong log, got %q want %q", log, expected)
+ }
+}
+
+func TestWriteCombinedLog(t *testing.T) {
+ loc, err := time.LoadLocation("Europe/Warsaw")
+ if err != nil {
+ panic(err)
+ }
+ ts := time.Date(1983, 05, 26, 3, 30, 45, 0, loc)
+
+ // A typical request with an OK response
+ req := newRequest("GET", "http://example.com")
+ req.RemoteAddr = "192.168.100.5"
+ req.Header.Set("Referer", "http://example.com")
+ req.Header.Set(
+ "User-Agent",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.33 "+
+ "(KHTML, like Gecko) Chrome/27.0.1430.0 Safari/537.33",
+ )
+
+ buf := new(bytes.Buffer)
+ writeCombinedLog(buf, req, *req.URL, ts, http.StatusOK, 100)
+ log := buf.String()
+
+ expected := "192.168.100.5 - - [26/May/1983:03:30:45 +0200] \"GET / HTTP/1.1\" 200 100 \"http://example.com\" " +
+ "\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) " +
+ "AppleWebKit/537.33 (KHTML, like Gecko) Chrome/27.0.1430.0 Safari/537.33\"\n"
+ if log != expected {
+ t.Fatalf("wrong log, got %q want %q", log, expected)
+ }
+
+ // CONNECT request over http/2.0
+ req1 := &http.Request{
+ Method: "CONNECT",
+ Host: "www.example.com:443",
+ Proto: "HTTP/2.0",
+ ProtoMajor: 2,
+ ProtoMinor: 0,
+ RemoteAddr: "192.168.100.5",
+ Header: http.Header{},
+ URL: &url.URL{Host: "www.example.com:443"},
+ }
+ req1.Header.Set("Referer", "http://example.com")
+ req1.Header.Set(
+ "User-Agent",
+ "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) AppleWebKit/537.33 "+
+ "(KHTML, like Gecko) Chrome/27.0.1430.0 Safari/537.33",
+ )
+
+ buf = new(bytes.Buffer)
+ writeCombinedLog(buf, req1, *req1.URL, ts, http.StatusOK, 100)
+ log = buf.String()
+
+ expected = "192.168.100.5 - - [26/May/1983:03:30:45 +0200] \"CONNECT www.example.com:443 HTTP/2.0\" 200 100 \"http://example.com\" " +
+ "\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) " +
+ "AppleWebKit/537.33 (KHTML, like Gecko) Chrome/27.0.1430.0 Safari/537.33\"\n"
+ if log != expected {
+ t.Fatalf("wrong log, got %q want %q", log, expected)
+ }
+
+ // Request with an unauthorized user
+ req.URL.User = url.User("kamil")
+
+ buf.Reset()
+ writeCombinedLog(buf, req, *req.URL, ts, http.StatusUnauthorized, 500)
+ log = buf.String()
+
+ expected = "192.168.100.5 - kamil [26/May/1983:03:30:45 +0200] \"GET / HTTP/1.1\" 401 500 \"http://example.com\" " +
+ "\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) " +
+ "AppleWebKit/537.33 (KHTML, like Gecko) Chrome/27.0.1430.0 Safari/537.33\"\n"
+ if log != expected {
+ t.Fatalf("wrong log, got %q want %q", log, expected)
+ }
+
+ // Test with remote ipv6 address
+ req.RemoteAddr = "::1"
+
+ buf.Reset()
+ writeCombinedLog(buf, req, *req.URL, ts, http.StatusOK, 100)
+ log = buf.String()
+
+ expected = "::1 - kamil [26/May/1983:03:30:45 +0200] \"GET / HTTP/1.1\" 200 100 \"http://example.com\" " +
+ "\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) " +
+ "AppleWebKit/537.33 (KHTML, like Gecko) Chrome/27.0.1430.0 Safari/537.33\"\n"
+ if log != expected {
+ t.Fatalf("wrong log, got %q want %q", log, expected)
+ }
+
+ // Test remote ipv6 addr, with port
+ req.RemoteAddr = net.JoinHostPort("::1", "65000")
+
+ buf.Reset()
+ writeCombinedLog(buf, req, *req.URL, ts, http.StatusOK, 100)
+ log = buf.String()
+
+ expected = "::1 - kamil [26/May/1983:03:30:45 +0200] \"GET / HTTP/1.1\" 200 100 \"http://example.com\" " +
+ "\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_2) " +
+ "AppleWebKit/537.33 (KHTML, like Gecko) Chrome/27.0.1430.0 Safari/537.33\"\n"
+ if log != expected {
+ t.Fatalf("wrong log, got %q want %q", log, expected)
+ }
+}
+
+func TestLogPathRewrites(t *testing.T) {
+ var buf bytes.Buffer
+
+ handler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ req.URL.Path = "/" // simulate http.StripPrefix and friends
+ w.WriteHeader(200)
+ })
+ logger := LoggingHandler(&buf, handler)
+
+ logger.ServeHTTP(httptest.NewRecorder(), newRequest("GET", "/subdir/asdf"))
+
+ if !strings.Contains(buf.String(), "GET /subdir/asdf HTTP") {
+ t.Fatalf("Got log %#v, wanted substring %#v", buf.String(), "GET /subdir/asdf HTTP")
+ }
+}
+
+func BenchmarkWriteLog(b *testing.B) {
+ loc, err := time.LoadLocation("Europe/Warsaw")
+ if err != nil {
+ b.Fatalf(err.Error())
+ }
+ ts := time.Date(1983, 05, 26, 3, 30, 45, 0, loc)
+
+ req := newRequest("GET", "http://example.com")
+ req.RemoteAddr = "192.168.100.5"
+
+ b.ResetTimer()
+
+ buf := &bytes.Buffer{}
+ for i := 0; i < b.N; i++ {
+ buf.Reset()
+ writeLog(buf, req, *req.URL, ts, http.StatusUnauthorized, 500)
+ }
+}
+
+func TestContentTypeHandler(t *testing.T) {
+ tests := []struct {
+ Method string
+ AllowContentTypes []string
+ ContentType string
+ Code int
+ }{
+ {"POST", []string{"application/json"}, "application/json", http.StatusOK},
+ {"POST", []string{"application/json", "application/xml"}, "application/json", http.StatusOK},
+ {"POST", []string{"application/json"}, "application/json; charset=utf-8", http.StatusOK},
+ {"POST", []string{"application/json"}, "application/json+xxx", http.StatusUnsupportedMediaType},
+ {"POST", []string{"application/json"}, "text/plain", http.StatusUnsupportedMediaType},
+ {"GET", []string{"application/json"}, "", http.StatusOK},
+ {"GET", []string{}, "", http.StatusOK},
+ }
+ for _, test := range tests {
+ r, err := http.NewRequest(test.Method, "/", nil)
+ if err != nil {
+ t.Error(err)
+ continue
+ }
+
+ h := ContentTypeHandler(okHandler, test.AllowContentTypes...)
+ r.Header.Set("Content-Type", test.ContentType)
+ w := httptest.NewRecorder()
+ h.ServeHTTP(w, r)
+ if w.Code != test.Code {
+ t.Errorf("expected %d, got %d", test.Code, w.Code)
+ }
+ }
+}
+
+func TestHTTPMethodOverride(t *testing.T) {
+ var tests = []struct {
+ Method string
+ OverrideMethod string
+ ExpectedMethod string
+ }{
+ {"POST", "PUT", "PUT"},
+ {"POST", "PATCH", "PATCH"},
+ {"POST", "DELETE", "DELETE"},
+ {"PUT", "DELETE", "PUT"},
+ {"GET", "GET", "GET"},
+ {"HEAD", "HEAD", "HEAD"},
+ {"GET", "PUT", "GET"},
+ {"HEAD", "DELETE", "HEAD"},
+ }
+
+ for _, test := range tests {
+ h := HTTPMethodOverrideHandler(okHandler)
+ reqs := make([]*http.Request, 0, 2)
+
+ rHeader, err := http.NewRequest(test.Method, "/", nil)
+ if err != nil {
+ t.Error(err)
+ }
+ rHeader.Header.Set(HTTPMethodOverrideHeader, test.OverrideMethod)
+ reqs = append(reqs, rHeader)
+
+ f := url.Values{HTTPMethodOverrideFormKey: []string{test.OverrideMethod}}
+ rForm, err := http.NewRequest(test.Method, "/", strings.NewReader(f.Encode()))
+ if err != nil {
+ t.Error(err)
+ }
+ rForm.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+ reqs = append(reqs, rForm)
+
+ for _, r := range reqs {
+ w := httptest.NewRecorder()
+ h.ServeHTTP(w, r)
+ if r.Method != test.ExpectedMethod {
+ t.Errorf("Expected %s, got %s", test.ExpectedMethod, r.Method)
+ }
+ }
+ }
+}
diff --git a/vendor/github.com/gorilla/handlers/proxy_headers.go b/vendor/github.com/gorilla/handlers/proxy_headers.go
new file mode 100644
index 0000000..0be750f
--- /dev/null
+++ b/vendor/github.com/gorilla/handlers/proxy_headers.go
@@ -0,0 +1,120 @@
+package handlers
+
+import (
+ "net/http"
+ "regexp"
+ "strings"
+)
+
+var (
+ // De-facto standard header keys.
+ xForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For")
+ xForwardedHost = http.CanonicalHeaderKey("X-Forwarded-Host")
+ xForwardedProto = http.CanonicalHeaderKey("X-Forwarded-Proto")
+ xForwardedScheme = http.CanonicalHeaderKey("X-Forwarded-Scheme")
+ xRealIP = http.CanonicalHeaderKey("X-Real-IP")
+)
+
+var (
+ // RFC7239 defines a new "Forwarded: " header designed to replace the
+ // existing use of X-Forwarded-* headers.
+ // e.g. Forwarded: for=192.0.2.60;proto=https;by=203.0.113.43
+ forwarded = http.CanonicalHeaderKey("Forwarded")
+ // Allows for a sub-match of the first value after 'for=' to the next
+ // comma, semi-colon or space. The match is case-insensitive.
+ forRegex = regexp.MustCompile(`(?i)(?:for=)([^(;|,| )]+)`)
+ // Allows for a sub-match for the first instance of scheme (http|https)
+ // prefixed by 'proto='. The match is case-insensitive.
+ protoRegex = regexp.MustCompile(`(?i)(?:proto=)(https|http)`)
+)
+
+// ProxyHeaders inspects common reverse proxy headers and sets the corresponding
+// fields in the HTTP request struct. These are X-Forwarded-For and X-Real-IP
+// for the remote (client) IP address, X-Forwarded-Proto or X-Forwarded-Scheme
+// for the scheme (http|https) and the RFC7239 Forwarded header, which may
+// include both client IPs and schemes.
+//
+// NOTE: This middleware should only be used when behind a reverse
+// proxy like nginx, HAProxy or Apache. Reverse proxies that don't (or are
+// configured not to) strip these headers from client requests, or where these
+// headers are accepted "as is" from a remote client (e.g. when Go is not behind
+// a proxy), can manifest as a vulnerability if your application uses these
+// headers for validating the 'trustworthiness' of a request.
+func ProxyHeaders(h http.Handler) http.Handler {
+ fn := func(w http.ResponseWriter, r *http.Request) {
+ // Set the remote IP with the value passed from the proxy.
+ if fwd := getIP(r); fwd != "" {
+ r.RemoteAddr = fwd
+ }
+
+ // Set the scheme (proto) with the value passed from the proxy.
+ if scheme := getScheme(r); scheme != "" {
+ r.URL.Scheme = scheme
+ }
+ // Set the host with the value passed by the proxy
+ if r.Header.Get(xForwardedHost) != "" {
+ r.Host = r.Header.Get(xForwardedHost)
+ }
+ // Call the next handler in the chain.
+ h.ServeHTTP(w, r)
+ }
+
+ return http.HandlerFunc(fn)
+}
+
+// getIP retrieves the IP from the X-Forwarded-For, X-Real-IP and RFC7239
+// Forwarded headers (in that order).
+func getIP(r *http.Request) string {
+ var addr string
+
+ if fwd := r.Header.Get(xForwardedFor); fwd != "" {
+ // Only grab the first (client) address. Note that '192.168.0.1,
+ // 10.1.1.1' is a valid key for X-Forwarded-For where addresses after
+ // the first may represent forwarding proxies earlier in the chain.
+ s := strings.Index(fwd, ", ")
+ if s == -1 {
+ s = len(fwd)
+ }
+ addr = fwd[:s]
+ } else if fwd := r.Header.Get(xRealIP); fwd != "" {
+ // X-Real-IP should only contain one IP address (the client making the
+ // request).
+ addr = fwd
+ } else if fwd := r.Header.Get(forwarded); fwd != "" {
+ // match should contain at least two elements if the protocol was
+ // specified in the Forwarded header. The first element will always be
+ // the 'for=' capture, which we ignore. In the case of multiple IP
+ // addresses (for=8.8.8.8, 8.8.4.4,172.16.1.20 is valid) we only
+ // extract the first, which should be the client IP.
+ if match := forRegex.FindStringSubmatch(fwd); len(match) > 1 {
+ // IPv6 addresses in Forwarded headers are quoted-strings. We strip
+ // these quotes.
+ addr = strings.Trim(match[1], `"`)
+ }
+ }
+
+ return addr
+}
+
+// getScheme retrieves the scheme from the X-Forwarded-Proto and RFC7239
+// Forwarded headers (in that order).
+func getScheme(r *http.Request) string {
+ var scheme string
+
+ // Retrieve the scheme from X-Forwarded-Proto.
+ if proto := r.Header.Get(xForwardedProto); proto != "" {
+ scheme = strings.ToLower(proto)
+ } else if proto = r.Header.Get(xForwardedScheme); proto != "" {
+ scheme = strings.ToLower(proto)
+ } else if proto = r.Header.Get(forwarded); proto != "" {
+ // match should contain at least two elements if the protocol was
+ // specified in the Forwarded header. The first element will always be
+ // the 'proto=' capture, which we ignore. In the case of multiple proto
+ // parameters (invalid) we only extract the first.
+ if match := protoRegex.FindStringSubmatch(proto); len(match) > 1 {
+ scheme = strings.ToLower(match[1])
+ }
+ }
+
+ return scheme
+}
diff --git a/vendor/github.com/gorilla/handlers/proxy_headers_test.go b/vendor/github.com/gorilla/handlers/proxy_headers_test.go
new file mode 100644
index 0000000..1bd7805
--- /dev/null
+++ b/vendor/github.com/gorilla/handlers/proxy_headers_test.go
@@ -0,0 +1,111 @@
+package handlers
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "testing"
+)
+
+type headerTable struct {
+ key string // header key
+ val string // header val
+ expected string // expected result
+}
+
+func TestGetIP(t *testing.T) {
+ headers := []headerTable{
+ {xForwardedFor, "8.8.8.8", "8.8.8.8"}, // Single address
+ {xForwardedFor, "8.8.8.8, 8.8.4.4", "8.8.8.8"}, // Multiple
+ {xForwardedFor, "[2001:db8:cafe::17]:4711", "[2001:db8:cafe::17]:4711"}, // IPv6 address
+ {xForwardedFor, "", ""}, // None
+ {xRealIP, "8.8.8.8", "8.8.8.8"}, // Single address
+ {xRealIP, "8.8.8.8, 8.8.4.4", "8.8.8.8, 8.8.4.4"}, // Multiple
+ {xRealIP, "[2001:db8:cafe::17]:4711", "[2001:db8:cafe::17]:4711"}, // IPv6 address
+ {xRealIP, "", ""}, // None
+ {forwarded, `for="_gazonk"`, "_gazonk"}, // Hostname
+ {forwarded, `For="[2001:db8:cafe::17]:4711`, `[2001:db8:cafe::17]:4711`}, // IPv6 address
+ {forwarded, `for=192.0.2.60;proto=http;by=203.0.113.43`, `192.0.2.60`}, // Multiple params
+ {forwarded, `for=192.0.2.43, for=198.51.100.17`, "192.0.2.43"}, // Multiple params
+ {forwarded, `for="workstation.local",for=198.51.100.17`, "workstation.local"}, // Hostname
+ }
+
+ for _, v := range headers {
+ req := &http.Request{
+ Header: http.Header{
+ v.key: []string{v.val},
+ }}
+ res := getIP(req)
+ if res != v.expected {
+ t.Fatalf("wrong header for %s: got %s want %s", v.key, res,
+ v.expected)
+ }
+ }
+}
+
+func TestGetScheme(t *testing.T) {
+ headers := []headerTable{
+ {xForwardedProto, "https", "https"},
+ {xForwardedProto, "http", "http"},
+ {xForwardedProto, "HTTP", "http"},
+ {xForwardedScheme, "https", "https"},
+ {xForwardedScheme, "http", "http"},
+ {xForwardedScheme, "HTTP", "http"},
+ {forwarded, `For="[2001:db8:cafe::17]:4711`, ""}, // No proto
+ {forwarded, `for=192.0.2.43, for=198.51.100.17;proto=https`, "https"}, // Multiple params before proto
+ {forwarded, `for=172.32.10.15; proto=https;by=127.0.0.1`, "https"}, // Space before proto
+ {forwarded, `for=192.0.2.60;proto=http;by=203.0.113.43`, "http"}, // Multiple params
+ }
+
+ for _, v := range headers {
+ req := &http.Request{
+ Header: http.Header{
+ v.key: []string{v.val},
+ },
+ }
+ res := getScheme(req)
+ if res != v.expected {
+ t.Fatalf("wrong header for %s: got %s want %s", v.key, res,
+ v.expected)
+ }
+ }
+}
+
+// Test the middleware end-to-end
+func TestProxyHeaders(t *testing.T) {
+ rr := httptest.NewRecorder()
+ r := newRequest("GET", "/")
+
+ r.Header.Set(xForwardedFor, "8.8.8.8")
+ r.Header.Set(xForwardedProto, "https")
+ r.Header.Set(xForwardedHost, "google.com")
+ var (
+ addr string
+ proto string
+ host string
+ )
+ ProxyHeaders(http.HandlerFunc(
+ func(w http.ResponseWriter, r *http.Request) {
+ addr = r.RemoteAddr
+ proto = r.URL.Scheme
+ host = r.Host
+ })).ServeHTTP(rr, r)
+
+ if rr.Code != http.StatusOK {
+ t.Fatalf("bad status: got %d want %d", rr.Code, http.StatusOK)
+ }
+
+ if addr != r.Header.Get(xForwardedFor) {
+ t.Fatalf("wrong address: got %s want %s", addr,
+ r.Header.Get(xForwardedFor))
+ }
+
+ if proto != r.Header.Get(xForwardedProto) {
+ t.Fatalf("wrong address: got %s want %s", proto,
+ r.Header.Get(xForwardedProto))
+ }
+ if host != r.Header.Get(xForwardedHost) {
+ t.Fatalf("wrong address: got %s want %s", host,
+ r.Header.Get(xForwardedHost))
+ }
+
+}
diff --git a/vendor/github.com/gorilla/handlers/recovery.go b/vendor/github.com/gorilla/handlers/recovery.go
new file mode 100644
index 0000000..b1be9dc
--- /dev/null
+++ b/vendor/github.com/gorilla/handlers/recovery.go
@@ -0,0 +1,91 @@
+package handlers
+
+import (
+ "log"
+ "net/http"
+ "runtime/debug"
+)
+
+// RecoveryHandlerLogger is an interface used by the recovering handler to print logs.
+type RecoveryHandlerLogger interface {
+ Println(...interface{})
+}
+
+type recoveryHandler struct {
+ handler http.Handler
+ logger RecoveryHandlerLogger
+ printStack bool
+}
+
+// RecoveryOption provides a functional approach to define
+// configuration for a handler; such as setting the logging
+// whether or not to print strack traces on panic.
+type RecoveryOption func(http.Handler)
+
+func parseRecoveryOptions(h http.Handler, opts ...RecoveryOption) http.Handler {
+ for _, option := range opts {
+ option(h)
+ }
+
+ return h
+}
+
+// RecoveryHandler is HTTP middleware that recovers from a panic,
+// logs the panic, writes http.StatusInternalServerError, and
+// continues to the next handler.
+//
+// Example:
+//
+// r := mux.NewRouter()
+// r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+// panic("Unexpected error!")
+// })
+//
+// http.ListenAndServe(":1123", handlers.RecoveryHandler()(r))
+func RecoveryHandler(opts ...RecoveryOption) func(h http.Handler) http.Handler {
+ return func(h http.Handler) http.Handler {
+ r := &recoveryHandler{handler: h}
+ return parseRecoveryOptions(r, opts...)
+ }
+}
+
+// RecoveryLogger is a functional option to override
+// the default logger
+func RecoveryLogger(logger RecoveryHandlerLogger) RecoveryOption {
+ return func(h http.Handler) {
+ r := h.(*recoveryHandler)
+ r.logger = logger
+ }
+}
+
+// PrintRecoveryStack is a functional option to enable
+// or disable printing stack traces on panic.
+func PrintRecoveryStack(print bool) RecoveryOption {
+ return func(h http.Handler) {
+ r := h.(*recoveryHandler)
+ r.printStack = print
+ }
+}
+
+func (h recoveryHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
+ defer func() {
+ if err := recover(); err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ h.log(err)
+ }
+ }()
+
+ h.handler.ServeHTTP(w, req)
+}
+
+func (h recoveryHandler) log(v ...interface{}) {
+ if h.logger != nil {
+ h.logger.Println(v...)
+ } else {
+ log.Println(v...)
+ }
+
+ if h.printStack {
+ debug.PrintStack()
+ }
+}
diff --git a/vendor/github.com/gorilla/handlers/recovery_test.go b/vendor/github.com/gorilla/handlers/recovery_test.go
new file mode 100644
index 0000000..1ae0e58
--- /dev/null
+++ b/vendor/github.com/gorilla/handlers/recovery_test.go
@@ -0,0 +1,44 @@
+package handlers
+
+import (
+ "bytes"
+ "log"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+)
+
+func TestRecoveryLoggerWithDefaultOptions(t *testing.T) {
+ var buf bytes.Buffer
+ log.SetOutput(&buf)
+
+ handler := RecoveryHandler()
+ handlerFunc := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ panic("Unexpected error!")
+ })
+
+ recovery := handler(handlerFunc)
+ recovery.ServeHTTP(httptest.NewRecorder(), newRequest("GET", "/subdir/asdf"))
+
+ if !strings.Contains(buf.String(), "Unexpected error!") {
+ t.Fatalf("Got log %#v, wanted substring %#v", buf.String(), "Unexpected error!")
+ }
+}
+
+func TestRecoveryLoggerWithCustomLogger(t *testing.T) {
+ var buf bytes.Buffer
+ var logger = log.New(&buf, "", log.LstdFlags)
+
+ handler := RecoveryHandler(RecoveryLogger(logger), PrintRecoveryStack(false))
+ handlerFunc := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
+ panic("Unexpected error!")
+ })
+
+ recovery := handler(handlerFunc)
+ recovery.ServeHTTP(httptest.NewRecorder(), newRequest("GET", "/subdir/asdf"))
+
+ if !strings.Contains(buf.String(), "Unexpected error!") {
+ t.Fatalf("Got log %#v, wanted substring %#v", buf.String(), "Unexpected error!")
+ }
+}
diff --git a/vendor/github.com/hashicorp/golang-lru/.gitignore b/vendor/github.com/hashicorp/golang-lru/.gitignore
new file mode 100644
index 0000000..8365624
--- /dev/null
+++ b/vendor/github.com/hashicorp/golang-lru/.gitignore
@@ -0,0 +1,23 @@
+# 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
diff --git a/vendor/github.com/hashicorp/golang-lru/2q.go b/vendor/github.com/hashicorp/golang-lru/2q.go
new file mode 100644
index 0000000..337d963
--- /dev/null
+++ b/vendor/github.com/hashicorp/golang-lru/2q.go
@@ -0,0 +1,212 @@
+package lru
+
+import (
+ "fmt"
+ "sync"
+
+ "github.com/hashicorp/golang-lru/simplelru"
+)
+
+const (
+ // Default2QRecentRatio is the ratio of the 2Q cache dedicated
+ // to recently added entries that have only been accessed once.
+ Default2QRecentRatio = 0.25
+
+ // Default2QGhostEntries is the default ratio of ghost
+ // entries kept to track entries recently evicted
+ Default2QGhostEntries = 0.50
+)
+
+// TwoQueueCache is a thread-safe fixed size 2Q cache.
+// 2Q is an enhancement over the standard LRU cache
+// in that it tracks both frequently and recently used
+// entries separately. This avoids a burst in access to new
+// entries from evicting frequently used entries. It adds some
+// additional tracking overhead to the standard LRU cache, and is
+// computationally about 2x the cost, and adds some metadata over
+// head. The ARCCache is similar, but does not require setting any
+// parameters.
+type TwoQueueCache struct {
+ size int
+ recentSize int
+
+ recent *simplelru.LRU
+ frequent *simplelru.LRU
+ recentEvict *simplelru.LRU
+ lock sync.RWMutex
+}
+
+// New2Q creates a new TwoQueueCache using the default
+// values for the parameters.
+func New2Q(size int) (*TwoQueueCache, error) {
+ return New2QParams(size, Default2QRecentRatio, Default2QGhostEntries)
+}
+
+// New2QParams creates a new TwoQueueCache using the provided
+// parameter values.
+func New2QParams(size int, recentRatio float64, ghostRatio float64) (*TwoQueueCache, error) {
+ if size <= 0 {
+ return nil, fmt.Errorf("invalid size")
+ }
+ if recentRatio < 0.0 || recentRatio > 1.0 {
+ return nil, fmt.Errorf("invalid recent ratio")
+ }
+ if ghostRatio < 0.0 || ghostRatio > 1.0 {
+ return nil, fmt.Errorf("invalid ghost ratio")
+ }
+
+ // Determine the sub-sizes
+ recentSize := int(float64(size) * recentRatio)
+ evictSize := int(float64(size) * ghostRatio)
+
+ // Allocate the LRUs
+ recent, err := simplelru.NewLRU(size, nil)
+ if err != nil {
+ return nil, err
+ }
+ frequent, err := simplelru.NewLRU(size, nil)
+ if err != nil {
+ return nil, err
+ }
+ recentEvict, err := simplelru.NewLRU(evictSize, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ // Initialize the cache
+ c := &TwoQueueCache{
+ size: size,
+ recentSize: recentSize,
+ recent: recent,
+ frequent: frequent,
+ recentEvict: recentEvict,
+ }
+ return c, nil
+}
+
+func (c *TwoQueueCache) Get(key interface{}) (interface{}, bool) {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+
+ // Check if this is a frequent value
+ if val, ok := c.frequent.Get(key); ok {
+ return val, ok
+ }
+
+ // If the value is contained in recent, then we
+ // promote it to frequent
+ if val, ok := c.recent.Peek(key); ok {
+ c.recent.Remove(key)
+ c.frequent.Add(key, val)
+ return val, ok
+ }
+
+ // No hit
+ return nil, false
+}
+
+func (c *TwoQueueCache) Add(key, value interface{}) {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+
+ // Check if the value is frequently used already,
+ // and just update the value
+ if c.frequent.Contains(key) {
+ c.frequent.Add(key, value)
+ return
+ }
+
+ // Check if the value is recently used, and promote
+ // the value into the frequent list
+ if c.recent.Contains(key) {
+ c.recent.Remove(key)
+ c.frequent.Add(key, value)
+ return
+ }
+
+ // If the value was recently evicted, add it to the
+ // frequently used list
+ if c.recentEvict.Contains(key) {
+ c.ensureSpace(true)
+ c.recentEvict.Remove(key)
+ c.frequent.Add(key, value)
+ return
+ }
+
+ // Add to the recently seen list
+ c.ensureSpace(false)
+ c.recent.Add(key, value)
+ return
+}
+
+// ensureSpace is used to ensure we have space in the cache
+func (c *TwoQueueCache) ensureSpace(recentEvict bool) {
+ // If we have space, nothing to do
+ recentLen := c.recent.Len()
+ freqLen := c.frequent.Len()
+ if recentLen+freqLen < c.size {
+ return
+ }
+
+ // If the recent buffer is larger than
+ // the target, evict from there
+ if recentLen > 0 && (recentLen > c.recentSize || (recentLen == c.recentSize && !recentEvict)) {
+ k, _, _ := c.recent.RemoveOldest()
+ c.recentEvict.Add(k, nil)
+ return
+ }
+
+ // Remove from the frequent list otherwise
+ c.frequent.RemoveOldest()
+}
+
+func (c *TwoQueueCache) Len() int {
+ c.lock.RLock()
+ defer c.lock.RUnlock()
+ return c.recent.Len() + c.frequent.Len()
+}
+
+func (c *TwoQueueCache) Keys() []interface{} {
+ c.lock.RLock()
+ defer c.lock.RUnlock()
+ k1 := c.frequent.Keys()
+ k2 := c.recent.Keys()
+ return append(k1, k2...)
+}
+
+func (c *TwoQueueCache) Remove(key interface{}) {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+ if c.frequent.Remove(key) {
+ return
+ }
+ if c.recent.Remove(key) {
+ return
+ }
+ if c.recentEvict.Remove(key) {
+ return
+ }
+}
+
+func (c *TwoQueueCache) Purge() {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+ c.recent.Purge()
+ c.frequent.Purge()
+ c.recentEvict.Purge()
+}
+
+func (c *TwoQueueCache) Contains(key interface{}) bool {
+ c.lock.RLock()
+ defer c.lock.RUnlock()
+ return c.frequent.Contains(key) || c.recent.Contains(key)
+}
+
+func (c *TwoQueueCache) Peek(key interface{}) (interface{}, bool) {
+ c.lock.RLock()
+ defer c.lock.RUnlock()
+ if val, ok := c.frequent.Peek(key); ok {
+ return val, ok
+ }
+ return c.recent.Peek(key)
+}
diff --git a/vendor/github.com/hashicorp/golang-lru/2q_test.go b/vendor/github.com/hashicorp/golang-lru/2q_test.go
new file mode 100644
index 0000000..1b0f351
--- /dev/null
+++ b/vendor/github.com/hashicorp/golang-lru/2q_test.go
@@ -0,0 +1,306 @@
+package lru
+
+import (
+ "math/rand"
+ "testing"
+)
+
+func Benchmark2Q_Rand(b *testing.B) {
+ l, err := New2Q(8192)
+ if err != nil {
+ b.Fatalf("err: %v", err)
+ }
+
+ trace := make([]int64, b.N*2)
+ for i := 0; i < b.N*2; i++ {
+ trace[i] = rand.Int63() % 32768
+ }
+
+ b.ResetTimer()
+
+ var hit, miss int
+ for i := 0; i < 2*b.N; i++ {
+ if i%2 == 0 {
+ l.Add(trace[i], trace[i])
+ } else {
+ _, ok := l.Get(trace[i])
+ if ok {
+ hit++
+ } else {
+ miss++
+ }
+ }
+ }
+ b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss))
+}
+
+func Benchmark2Q_Freq(b *testing.B) {
+ l, err := New2Q(8192)
+ if err != nil {
+ b.Fatalf("err: %v", err)
+ }
+
+ trace := make([]int64, b.N*2)
+ for i := 0; i < b.N*2; i++ {
+ if i%2 == 0 {
+ trace[i] = rand.Int63() % 16384
+ } else {
+ trace[i] = rand.Int63() % 32768
+ }
+ }
+
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ l.Add(trace[i], trace[i])
+ }
+ var hit, miss int
+ for i := 0; i < b.N; i++ {
+ _, ok := l.Get(trace[i])
+ if ok {
+ hit++
+ } else {
+ miss++
+ }
+ }
+ b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss))
+}
+
+func Test2Q_RandomOps(t *testing.T) {
+ size := 128
+ l, err := New2Q(128)
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+
+ n := 200000
+ for i := 0; i < n; i++ {
+ key := rand.Int63() % 512
+ r := rand.Int63()
+ switch r % 3 {
+ case 0:
+ l.Add(key, key)
+ case 1:
+ l.Get(key)
+ case 2:
+ l.Remove(key)
+ }
+
+ if l.recent.Len()+l.frequent.Len() > size {
+ t.Fatalf("bad: recent: %d freq: %d",
+ l.recent.Len(), l.frequent.Len())
+ }
+ }
+}
+
+func Test2Q_Get_RecentToFrequent(t *testing.T) {
+ l, err := New2Q(128)
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+
+ // Touch all the entries, should be in t1
+ for i := 0; i < 128; i++ {
+ l.Add(i, i)
+ }
+ if n := l.recent.Len(); n != 128 {
+ t.Fatalf("bad: %d", n)
+ }
+ if n := l.frequent.Len(); n != 0 {
+ t.Fatalf("bad: %d", n)
+ }
+
+ // Get should upgrade to t2
+ for i := 0; i < 128; i++ {
+ _, ok := l.Get(i)
+ if !ok {
+ t.Fatalf("missing: %d", i)
+ }
+ }
+ if n := l.recent.Len(); n != 0 {
+ t.Fatalf("bad: %d", n)
+ }
+ if n := l.frequent.Len(); n != 128 {
+ t.Fatalf("bad: %d", n)
+ }
+
+ // Get be from t2
+ for i := 0; i < 128; i++ {
+ _, ok := l.Get(i)
+ if !ok {
+ t.Fatalf("missing: %d", i)
+ }
+ }
+ if n := l.recent.Len(); n != 0 {
+ t.Fatalf("bad: %d", n)
+ }
+ if n := l.frequent.Len(); n != 128 {
+ t.Fatalf("bad: %d", n)
+ }
+}
+
+func Test2Q_Add_RecentToFrequent(t *testing.T) {
+ l, err := New2Q(128)
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+
+ // Add initially to recent
+ l.Add(1, 1)
+ if n := l.recent.Len(); n != 1 {
+ t.Fatalf("bad: %d", n)
+ }
+ if n := l.frequent.Len(); n != 0 {
+ t.Fatalf("bad: %d", n)
+ }
+
+ // Add should upgrade to frequent
+ l.Add(1, 1)
+ if n := l.recent.Len(); n != 0 {
+ t.Fatalf("bad: %d", n)
+ }
+ if n := l.frequent.Len(); n != 1 {
+ t.Fatalf("bad: %d", n)
+ }
+
+ // Add should remain in frequent
+ l.Add(1, 1)
+ if n := l.recent.Len(); n != 0 {
+ t.Fatalf("bad: %d", n)
+ }
+ if n := l.frequent.Len(); n != 1 {
+ t.Fatalf("bad: %d", n)
+ }
+}
+
+func Test2Q_Add_RecentEvict(t *testing.T) {
+ l, err := New2Q(4)
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+
+ // Add 1,2,3,4,5 -> Evict 1
+ l.Add(1, 1)
+ l.Add(2, 2)
+ l.Add(3, 3)
+ l.Add(4, 4)
+ l.Add(5, 5)
+ if n := l.recent.Len(); n != 4 {
+ t.Fatalf("bad: %d", n)
+ }
+ if n := l.recentEvict.Len(); n != 1 {
+ t.Fatalf("bad: %d", n)
+ }
+ if n := l.frequent.Len(); n != 0 {
+ t.Fatalf("bad: %d", n)
+ }
+
+ // Pull in the recently evicted
+ l.Add(1, 1)
+ if n := l.recent.Len(); n != 3 {
+ t.Fatalf("bad: %d", n)
+ }
+ if n := l.recentEvict.Len(); n != 1 {
+ t.Fatalf("bad: %d", n)
+ }
+ if n := l.frequent.Len(); n != 1 {
+ t.Fatalf("bad: %d", n)
+ }
+
+ // Add 6, should cause another recent evict
+ l.Add(6, 6)
+ if n := l.recent.Len(); n != 3 {
+ t.Fatalf("bad: %d", n)
+ }
+ if n := l.recentEvict.Len(); n != 2 {
+ t.Fatalf("bad: %d", n)
+ }
+ if n := l.frequent.Len(); n != 1 {
+ t.Fatalf("bad: %d", n)
+ }
+}
+
+func Test2Q(t *testing.T) {
+ l, err := New2Q(128)
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+
+ for i := 0; i < 256; i++ {
+ l.Add(i, i)
+ }
+ if l.Len() != 128 {
+ t.Fatalf("bad len: %v", l.Len())
+ }
+
+ for i, k := range l.Keys() {
+ if v, ok := l.Get(k); !ok || v != k || v != i+128 {
+ t.Fatalf("bad key: %v", k)
+ }
+ }
+ for i := 0; i < 128; i++ {
+ _, ok := l.Get(i)
+ if ok {
+ t.Fatalf("should be evicted")
+ }
+ }
+ for i := 128; i < 256; i++ {
+ _, ok := l.Get(i)
+ if !ok {
+ t.Fatalf("should not be evicted")
+ }
+ }
+ for i := 128; i < 192; i++ {
+ l.Remove(i)
+ _, ok := l.Get(i)
+ if ok {
+ t.Fatalf("should be deleted")
+ }
+ }
+
+ l.Purge()
+ if l.Len() != 0 {
+ t.Fatalf("bad len: %v", l.Len())
+ }
+ if _, ok := l.Get(200); ok {
+ t.Fatalf("should contain nothing")
+ }
+}
+
+// Test that Contains doesn't update recent-ness
+func Test2Q_Contains(t *testing.T) {
+ l, err := New2Q(2)
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+
+ l.Add(1, 1)
+ l.Add(2, 2)
+ if !l.Contains(1) {
+ t.Errorf("1 should be contained")
+ }
+
+ l.Add(3, 3)
+ if l.Contains(1) {
+ t.Errorf("Contains should not have updated recent-ness of 1")
+ }
+}
+
+// Test that Peek doesn't update recent-ness
+func Test2Q_Peek(t *testing.T) {
+ l, err := New2Q(2)
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+
+ l.Add(1, 1)
+ l.Add(2, 2)
+ if v, ok := l.Peek(1); !ok || v != 1 {
+ t.Errorf("1 should be set to 1: %v, %v", v, ok)
+ }
+
+ l.Add(3, 3)
+ if l.Contains(1) {
+ t.Errorf("should not have updated recent-ness of 1")
+ }
+}
diff --git a/vendor/github.com/hashicorp/golang-lru/LICENSE b/vendor/github.com/hashicorp/golang-lru/LICENSE
new file mode 100644
index 0000000..be2cc4d
--- /dev/null
+++ b/vendor/github.com/hashicorp/golang-lru/LICENSE
@@ -0,0 +1,362 @@
+Mozilla Public License, version 2.0
+
+1. Definitions
+
+1.1. "Contributor"
+
+ means each individual or legal entity that creates, contributes to the
+ creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+
+ means the combination of the Contributions of others (if any) used by a
+ Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+
+ means Source Code Form to which the initial Contributor has attached the
+ notice in Exhibit A, the Executable Form of such Source Code Form, and
+ Modifications of such Source Code Form, in each case including portions
+ thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ a. that the initial Contributor has attached the notice described in
+ Exhibit B to the Covered Software; or
+
+ b. that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the terms of
+ a Secondary License.
+
+1.6. "Executable Form"
+
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+
+ means a work that combines Covered Software with other material, in a
+ separate file or files, that is not Covered Software.
+
+1.8. "License"
+
+ means this document.
+
+1.9. "Licensable"
+
+ means having the right to grant, to the maximum extent possible, whether
+ at the time of the initial grant or subsequently, any and all of the
+ rights conveyed by this License.
+
+1.10. "Modifications"
+
+ means any of the following:
+
+ a. any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered Software; or
+
+ b. any new file in Source Code Form that contains any Covered Software.
+
+1.11. "Patent Claims" of a Contributor
+
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the License,
+ by the making, using, selling, offering for sale, having made, import,
+ or transfer of either its Contributions or its Contributor Version.
+
+1.12. "Secondary License"
+
+ means either the GNU General Public License, Version 2.0, the GNU Lesser
+ General Public License, Version 2.1, the GNU Affero General Public
+ License, Version 3.0, or any later versions of those licenses.
+
+1.13. "Source Code Form"
+
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that controls, is
+ controlled by, or is under common control with You. For purposes of this
+ definition, "control" means (a) the power, direct or indirect, to cause
+ the direction or management of such entity, whether by contract or
+ otherwise, or (b) ownership of more than fifty percent (50%) of the
+ outstanding shares or beneficial ownership of such entity.
+
+
+2. License Grants and Conditions
+
+2.1. Grants
+
+ Each Contributor hereby grants You a world-wide, royalty-free,
+ non-exclusive license:
+
+ a. under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+ b. under Patent Claims of such Contributor to make, use, sell, offer for
+ sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+ The licenses granted in Section 2.1 with respect to any Contribution
+ become effective for each Contribution on the date the Contributor first
+ distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+ The licenses granted in this Section 2 are the only rights granted under
+ this License. No additional rights or licenses will be implied from the
+ distribution or licensing of Covered Software under this License.
+ Notwithstanding Section 2.1(b) above, no patent license is granted by a
+ Contributor:
+
+ a. for any code that a Contributor has removed from Covered Software; or
+
+ b. for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+ c. under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+ This License does not grant any rights in the trademarks, service marks,
+ or logos of any Contributor (except as may be necessary to comply with
+ the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+ No Contributor makes additional grants as a result of Your choice to
+ distribute the Covered Software under a subsequent version of this
+ License (see Section 10.2) or under the terms of a Secondary License (if
+ permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+ Each Contributor represents that the Contributor believes its
+ Contributions are its original creation(s) or it has sufficient rights to
+ grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+ This License is not intended to limit any rights You have under
+ applicable copyright doctrines of fair use, fair dealing, or other
+ equivalents.
+
+2.7. Conditions
+
+ Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
+ Section 2.1.
+
+
+3. Responsibilities
+
+3.1. Distribution of Source Form
+
+ All distribution of Covered Software in Source Code Form, including any
+ Modifications that You create or to which You contribute, must be under
+ the terms of this License. You must inform recipients that the Source
+ Code Form of the Covered Software is governed by the terms of this
+ License, and how they can obtain a copy of this License. You may not
+ attempt to alter or restrict the recipients' rights in the Source Code
+ Form.
+
+3.2. Distribution of Executable Form
+
+ If You distribute Covered Software in Executable Form then:
+
+ a. such Covered Software must also be made available in Source Code Form,
+ as described in Section 3.1, and You must inform recipients of the
+ Executable Form how they can obtain a copy of such Source Code Form by
+ reasonable means in a timely manner, at a charge no more than the cost
+ of distribution to the recipient; and
+
+ b. You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter the
+ recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+ You may create and distribute a Larger Work under terms of Your choice,
+ provided that You also comply with the requirements of this License for
+ the Covered Software. If the Larger Work is a combination of Covered
+ Software with a work governed by one or more Secondary Licenses, and the
+ Covered Software is not Incompatible With Secondary Licenses, this
+ License permits You to additionally distribute such Covered Software
+ under the terms of such Secondary License(s), so that the recipient of
+ the Larger Work may, at their option, further distribute the Covered
+ Software under the terms of either this License or such Secondary
+ License(s).
+
+3.4. Notices
+
+ You may not remove or alter the substance of any license notices
+ (including copyright notices, patent notices, disclaimers of warranty, or
+ limitations of liability) contained within the Source Code Form of the
+ Covered Software, except that You may alter any license notices to the
+ extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+ You may choose to offer, and to charge a fee for, warranty, support,
+ indemnity or liability obligations to one or more recipients of Covered
+ Software. However, You may do so only on Your own behalf, and not on
+ behalf of any Contributor. You must make it absolutely clear that any
+ such warranty, support, indemnity, or liability obligation is offered by
+ You alone, and You hereby agree to indemnify every Contributor for any
+ liability incurred by such Contributor as a result of warranty, support,
+ indemnity or liability terms You offer. You may include additional
+ disclaimers of warranty and limitations of liability specific to any
+ jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+
+ If it is impossible for You to comply with any of the terms of this License
+ with respect to some or all of the Covered Software due to statute,
+ judicial order, or regulation then You must: (a) comply with the terms of
+ this License to the maximum extent possible; and (b) describe the
+ limitations and the code they affect. Such description must be placed in a
+ text file included with all distributions of the Covered Software under
+ this License. Except to the extent prohibited by statute or regulation,
+ such description must be sufficiently detailed for a recipient of ordinary
+ skill to be able to understand it.
+
+5. Termination
+
+5.1. The rights granted under this License will terminate automatically if You
+ fail to comply with any of its terms. However, if You become compliant,
+ then the rights granted under this License from a particular Contributor
+ are reinstated (a) provisionally, unless and until such Contributor
+ explicitly and finally terminates Your grants, and (b) on an ongoing
+ basis, if such Contributor fails to notify You of the non-compliance by
+ some reasonable means prior to 60 days after You have come back into
+ compliance. Moreover, Your grants from a particular Contributor are
+ reinstated on an ongoing basis if such Contributor notifies You of the
+ non-compliance by some reasonable means, this is the first time You have
+ received notice of non-compliance with this License from such
+ Contributor, and You become compliant prior to 30 days after Your receipt
+ of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+ infringement claim (excluding declaratory judgment actions,
+ counter-claims, and cross-claims) alleging that a Contributor Version
+ directly or indirectly infringes any patent, then the rights granted to
+ You by any and all Contributors for the Covered Software under Section
+ 2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
+ license agreements (excluding distributors and resellers) which have been
+ validly granted by You or Your distributors under this License prior to
+ termination shall survive termination.
+
+6. Disclaimer of Warranty
+
+ Covered Software is provided under this License on an "as is" basis,
+ without warranty of any kind, either expressed, implied, or statutory,
+ including, without limitation, warranties that the Covered Software is free
+ of defects, merchantable, fit for a particular purpose or non-infringing.
+ The entire risk as to the quality and performance of the Covered Software
+ is with You. Should any Covered Software prove defective in any respect,
+ You (not any Contributor) assume the cost of any necessary servicing,
+ repair, or correction. This disclaimer of warranty constitutes an essential
+ part of this License. No use of any Covered Software is authorized under
+ this License except under this disclaimer.
+
+7. Limitation of Liability
+
+ Under no circumstances and under no legal theory, whether tort (including
+ negligence), contract, or otherwise, shall any Contributor, or anyone who
+ distributes Covered Software as permitted above, be liable to You for any
+ direct, indirect, special, incidental, or consequential damages of any
+ character including, without limitation, damages for lost profits, loss of
+ goodwill, work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses, even if such party shall have been
+ informed of the possibility of such damages. This limitation of liability
+ shall not apply to liability for death or personal injury resulting from
+ such party's negligence to the extent applicable law prohibits such
+ limitation. Some jurisdictions do not allow the exclusion or limitation of
+ incidental or consequential damages, so this exclusion and limitation may
+ not apply to You.
+
+8. Litigation
+
+ Any litigation relating to this License may be brought only in the courts
+ of a jurisdiction where the defendant maintains its principal place of
+ business and such litigation shall be governed by laws of that
+ jurisdiction, without reference to its conflict-of-law provisions. Nothing
+ in this Section shall prevent a party's ability to bring cross-claims or
+ counter-claims.
+
+9. Miscellaneous
+
+ This License represents the complete agreement concerning the subject
+ matter hereof. If any provision of this License is held to be
+ unenforceable, such provision shall be reformed only to the extent
+ necessary to make it enforceable. Any law or regulation which provides that
+ the language of a contract shall be construed against the drafter shall not
+ be used to construe this License against a Contributor.
+
+
+10. Versions of the License
+
+10.1. New Versions
+
+ Mozilla Foundation is the license steward. Except as provided in Section
+ 10.3, no one other than the license steward has the right to modify or
+ publish new versions of this License. Each version will be given a
+ distinguishing version number.
+
+10.2. Effect of New Versions
+
+ You may distribute the Covered Software under the terms of the version
+ of the License under which You originally received the Covered Software,
+ or under the terms of any subsequent version published by the license
+ steward.
+
+10.3. Modified Versions
+
+ If you create software not governed by this License, and you want to
+ create a new license for such software, you may create and use a
+ modified version of this License if you rename the license and remove
+ any references to the name of the license steward (except to note that
+ such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+ Licenses If You choose to distribute Source Code Form that is
+ Incompatible With Secondary Licenses under the terms of this version of
+ the License, the notice described in Exhibit B of this License must be
+ attached.
+
+Exhibit A - Source Code Form License Notice
+
+ This Source Code Form is subject to the
+ terms of the Mozilla Public License, v.
+ 2.0. If a copy of the MPL was not
+ distributed with this file, You can
+ obtain one at
+ http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular file,
+then You may include the notice in a location (such as a LICENSE file in a
+relevant directory) where a recipient would be likely to look for such a
+notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+
+ This Source Code Form is "Incompatible
+ With Secondary Licenses", as defined by
+ the Mozilla Public License, v. 2.0.
diff --git a/vendor/github.com/hashicorp/golang-lru/README.md b/vendor/github.com/hashicorp/golang-lru/README.md
new file mode 100644
index 0000000..33e58cf
--- /dev/null
+++ b/vendor/github.com/hashicorp/golang-lru/README.md
@@ -0,0 +1,25 @@
+golang-lru
+==========
+
+This provides the `lru` package which implements a fixed-size
+thread safe LRU cache. It is based on the cache in Groupcache.
+
+Documentation
+=============
+
+Full docs are available on [Godoc](http://godoc.org/github.com/hashicorp/golang-lru)
+
+Example
+=======
+
+Using the LRU is very simple:
+
+```go
+l, _ := New(128)
+for i := 0; i < 256; i++ {
+ l.Add(i, nil)
+}
+if l.Len() != 128 {
+ panic(fmt.Sprintf("bad len: %v", l.Len()))
+}
+```
diff --git a/vendor/github.com/hashicorp/golang-lru/arc.go b/vendor/github.com/hashicorp/golang-lru/arc.go
new file mode 100644
index 0000000..a2a2528
--- /dev/null
+++ b/vendor/github.com/hashicorp/golang-lru/arc.go
@@ -0,0 +1,257 @@
+package lru
+
+import (
+ "sync"
+
+ "github.com/hashicorp/golang-lru/simplelru"
+)
+
+// ARCCache is a thread-safe fixed size Adaptive Replacement Cache (ARC).
+// ARC is an enhancement over the standard LRU cache in that tracks both
+// frequency and recency of use. This avoids a burst in access to new
+// entries from evicting the frequently used older entries. It adds some
+// additional tracking overhead to a standard LRU cache, computationally
+// it is roughly 2x the cost, and the extra memory overhead is linear
+// with the size of the cache. ARC has been patented by IBM, but is
+// similar to the TwoQueueCache (2Q) which requires setting parameters.
+type ARCCache struct {
+ size int // Size is the total capacity of the cache
+ p int // P is the dynamic preference towards T1 or T2
+
+ t1 *simplelru.LRU // T1 is the LRU for recently accessed items
+ b1 *simplelru.LRU // B1 is the LRU for evictions from t1
+
+ t2 *simplelru.LRU // T2 is the LRU for frequently accessed items
+ b2 *simplelru.LRU // B2 is the LRU for evictions from t2
+
+ lock sync.RWMutex
+}
+
+// NewARC creates an ARC of the given size
+func NewARC(size int) (*ARCCache, error) {
+ // Create the sub LRUs
+ b1, err := simplelru.NewLRU(size, nil)
+ if err != nil {
+ return nil, err
+ }
+ b2, err := simplelru.NewLRU(size, nil)
+ if err != nil {
+ return nil, err
+ }
+ t1, err := simplelru.NewLRU(size, nil)
+ if err != nil {
+ return nil, err
+ }
+ t2, err := simplelru.NewLRU(size, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ // Initialize the ARC
+ c := &ARCCache{
+ size: size,
+ p: 0,
+ t1: t1,
+ b1: b1,
+ t2: t2,
+ b2: b2,
+ }
+ return c, nil
+}
+
+// Get looks up a key's value from the cache.
+func (c *ARCCache) Get(key interface{}) (interface{}, bool) {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+
+ // Ff the value is contained in T1 (recent), then
+ // promote it to T2 (frequent)
+ if val, ok := c.t1.Peek(key); ok {
+ c.t1.Remove(key)
+ c.t2.Add(key, val)
+ return val, ok
+ }
+
+ // Check if the value is contained in T2 (frequent)
+ if val, ok := c.t2.Get(key); ok {
+ return val, ok
+ }
+
+ // No hit
+ return nil, false
+}
+
+// Add adds a value to the cache.
+func (c *ARCCache) Add(key, value interface{}) {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+
+ // Check if the value is contained in T1 (recent), and potentially
+ // promote it to frequent T2
+ if c.t1.Contains(key) {
+ c.t1.Remove(key)
+ c.t2.Add(key, value)
+ return
+ }
+
+ // Check if the value is already in T2 (frequent) and update it
+ if c.t2.Contains(key) {
+ c.t2.Add(key, value)
+ return
+ }
+
+ // Check if this value was recently evicted as part of the
+ // recently used list
+ if c.b1.Contains(key) {
+ // T1 set is too small, increase P appropriately
+ delta := 1
+ b1Len := c.b1.Len()
+ b2Len := c.b2.Len()
+ if b2Len > b1Len {
+ delta = b2Len / b1Len
+ }
+ if c.p+delta >= c.size {
+ c.p = c.size
+ } else {
+ c.p += delta
+ }
+
+ // Potentially need to make room in the cache
+ if c.t1.Len()+c.t2.Len() >= c.size {
+ c.replace(false)
+ }
+
+ // Remove from B1
+ c.b1.Remove(key)
+
+ // Add the key to the frequently used list
+ c.t2.Add(key, value)
+ return
+ }
+
+ // Check if this value was recently evicted as part of the
+ // frequently used list
+ if c.b2.Contains(key) {
+ // T2 set is too small, decrease P appropriately
+ delta := 1
+ b1Len := c.b1.Len()
+ b2Len := c.b2.Len()
+ if b1Len > b2Len {
+ delta = b1Len / b2Len
+ }
+ if delta >= c.p {
+ c.p = 0
+ } else {
+ c.p -= delta
+ }
+
+ // Potentially need to make room in the cache
+ if c.t1.Len()+c.t2.Len() >= c.size {
+ c.replace(true)
+ }
+
+ // Remove from B2
+ c.b2.Remove(key)
+
+ // Add the key to the frequntly used list
+ c.t2.Add(key, value)
+ return
+ }
+
+ // Potentially need to make room in the cache
+ if c.t1.Len()+c.t2.Len() >= c.size {
+ c.replace(false)
+ }
+
+ // Keep the size of the ghost buffers trim
+ if c.b1.Len() > c.size-c.p {
+ c.b1.RemoveOldest()
+ }
+ if c.b2.Len() > c.p {
+ c.b2.RemoveOldest()
+ }
+
+ // Add to the recently seen list
+ c.t1.Add(key, value)
+ return
+}
+
+// replace is used to adaptively evict from either T1 or T2
+// based on the current learned value of P
+func (c *ARCCache) replace(b2ContainsKey bool) {
+ t1Len := c.t1.Len()
+ if t1Len > 0 && (t1Len > c.p || (t1Len == c.p && b2ContainsKey)) {
+ k, _, ok := c.t1.RemoveOldest()
+ if ok {
+ c.b1.Add(k, nil)
+ }
+ } else {
+ k, _, ok := c.t2.RemoveOldest()
+ if ok {
+ c.b2.Add(k, nil)
+ }
+ }
+}
+
+// Len returns the number of cached entries
+func (c *ARCCache) Len() int {
+ c.lock.RLock()
+ defer c.lock.RUnlock()
+ return c.t1.Len() + c.t2.Len()
+}
+
+// Keys returns all the cached keys
+func (c *ARCCache) Keys() []interface{} {
+ c.lock.RLock()
+ defer c.lock.RUnlock()
+ k1 := c.t1.Keys()
+ k2 := c.t2.Keys()
+ return append(k1, k2...)
+}
+
+// Remove is used to purge a key from the cache
+func (c *ARCCache) Remove(key interface{}) {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+ if c.t1.Remove(key) {
+ return
+ }
+ if c.t2.Remove(key) {
+ return
+ }
+ if c.b1.Remove(key) {
+ return
+ }
+ if c.b2.Remove(key) {
+ return
+ }
+}
+
+// Purge is used to clear the cache
+func (c *ARCCache) Purge() {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+ c.t1.Purge()
+ c.t2.Purge()
+ c.b1.Purge()
+ c.b2.Purge()
+}
+
+// Contains is used to check if the cache contains a key
+// without updating recency or frequency.
+func (c *ARCCache) Contains(key interface{}) bool {
+ c.lock.RLock()
+ defer c.lock.RUnlock()
+ return c.t1.Contains(key) || c.t2.Contains(key)
+}
+
+// Peek is used to inspect the cache value of a key
+// without updating recency or frequency.
+func (c *ARCCache) Peek(key interface{}) (interface{}, bool) {
+ c.lock.RLock()
+ defer c.lock.RUnlock()
+ if val, ok := c.t1.Peek(key); ok {
+ return val, ok
+ }
+ return c.t2.Peek(key)
+}
diff --git a/vendor/github.com/hashicorp/golang-lru/arc_test.go b/vendor/github.com/hashicorp/golang-lru/arc_test.go
new file mode 100644
index 0000000..e2d9b68
--- /dev/null
+++ b/vendor/github.com/hashicorp/golang-lru/arc_test.go
@@ -0,0 +1,377 @@
+package lru
+
+import (
+ "math/rand"
+ "testing"
+ "time"
+)
+
+func init() {
+ rand.Seed(time.Now().Unix())
+}
+
+func BenchmarkARC_Rand(b *testing.B) {
+ l, err := NewARC(8192)
+ if err != nil {
+ b.Fatalf("err: %v", err)
+ }
+
+ trace := make([]int64, b.N*2)
+ for i := 0; i < b.N*2; i++ {
+ trace[i] = rand.Int63() % 32768
+ }
+
+ b.ResetTimer()
+
+ var hit, miss int
+ for i := 0; i < 2*b.N; i++ {
+ if i%2 == 0 {
+ l.Add(trace[i], trace[i])
+ } else {
+ _, ok := l.Get(trace[i])
+ if ok {
+ hit++
+ } else {
+ miss++
+ }
+ }
+ }
+ b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss))
+}
+
+func BenchmarkARC_Freq(b *testing.B) {
+ l, err := NewARC(8192)
+ if err != nil {
+ b.Fatalf("err: %v", err)
+ }
+
+ trace := make([]int64, b.N*2)
+ for i := 0; i < b.N*2; i++ {
+ if i%2 == 0 {
+ trace[i] = rand.Int63() % 16384
+ } else {
+ trace[i] = rand.Int63() % 32768
+ }
+ }
+
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ l.Add(trace[i], trace[i])
+ }
+ var hit, miss int
+ for i := 0; i < b.N; i++ {
+ _, ok := l.Get(trace[i])
+ if ok {
+ hit++
+ } else {
+ miss++
+ }
+ }
+ b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss))
+}
+
+func TestARC_RandomOps(t *testing.T) {
+ size := 128
+ l, err := NewARC(128)
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+
+ n := 200000
+ for i := 0; i < n; i++ {
+ key := rand.Int63() % 512
+ r := rand.Int63()
+ switch r % 3 {
+ case 0:
+ l.Add(key, key)
+ case 1:
+ l.Get(key)
+ case 2:
+ l.Remove(key)
+ }
+
+ if l.t1.Len()+l.t2.Len() > size {
+ t.Fatalf("bad: t1: %d t2: %d b1: %d b2: %d p: %d",
+ l.t1.Len(), l.t2.Len(), l.b1.Len(), l.b2.Len(), l.p)
+ }
+ if l.b1.Len()+l.b2.Len() > size {
+ t.Fatalf("bad: t1: %d t2: %d b1: %d b2: %d p: %d",
+ l.t1.Len(), l.t2.Len(), l.b1.Len(), l.b2.Len(), l.p)
+ }
+ }
+}
+
+func TestARC_Get_RecentToFrequent(t *testing.T) {
+ l, err := NewARC(128)
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+
+ // Touch all the entries, should be in t1
+ for i := 0; i < 128; i++ {
+ l.Add(i, i)
+ }
+ if n := l.t1.Len(); n != 128 {
+ t.Fatalf("bad: %d", n)
+ }
+ if n := l.t2.Len(); n != 0 {
+ t.Fatalf("bad: %d", n)
+ }
+
+ // Get should upgrade to t2
+ for i := 0; i < 128; i++ {
+ _, ok := l.Get(i)
+ if !ok {
+ t.Fatalf("missing: %d", i)
+ }
+ }
+ if n := l.t1.Len(); n != 0 {
+ t.Fatalf("bad: %d", n)
+ }
+ if n := l.t2.Len(); n != 128 {
+ t.Fatalf("bad: %d", n)
+ }
+
+ // Get be from t2
+ for i := 0; i < 128; i++ {
+ _, ok := l.Get(i)
+ if !ok {
+ t.Fatalf("missing: %d", i)
+ }
+ }
+ if n := l.t1.Len(); n != 0 {
+ t.Fatalf("bad: %d", n)
+ }
+ if n := l.t2.Len(); n != 128 {
+ t.Fatalf("bad: %d", n)
+ }
+}
+
+func TestARC_Add_RecentToFrequent(t *testing.T) {
+ l, err := NewARC(128)
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+
+ // Add initially to t1
+ l.Add(1, 1)
+ if n := l.t1.Len(); n != 1 {
+ t.Fatalf("bad: %d", n)
+ }
+ if n := l.t2.Len(); n != 0 {
+ t.Fatalf("bad: %d", n)
+ }
+
+ // Add should upgrade to t2
+ l.Add(1, 1)
+ if n := l.t1.Len(); n != 0 {
+ t.Fatalf("bad: %d", n)
+ }
+ if n := l.t2.Len(); n != 1 {
+ t.Fatalf("bad: %d", n)
+ }
+
+ // Add should remain in t2
+ l.Add(1, 1)
+ if n := l.t1.Len(); n != 0 {
+ t.Fatalf("bad: %d", n)
+ }
+ if n := l.t2.Len(); n != 1 {
+ t.Fatalf("bad: %d", n)
+ }
+}
+
+func TestARC_Adaptive(t *testing.T) {
+ l, err := NewARC(4)
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+
+ // Fill t1
+ for i := 0; i < 4; i++ {
+ l.Add(i, i)
+ }
+ if n := l.t1.Len(); n != 4 {
+ t.Fatalf("bad: %d", n)
+ }
+
+ // Move to t2
+ l.Get(0)
+ l.Get(1)
+ if n := l.t2.Len(); n != 2 {
+ t.Fatalf("bad: %d", n)
+ }
+
+ // Evict from t1
+ l.Add(4, 4)
+ if n := l.b1.Len(); n != 1 {
+ t.Fatalf("bad: %d", n)
+ }
+
+ // Current state
+ // t1 : (MRU) [4, 3] (LRU)
+ // t2 : (MRU) [1, 0] (LRU)
+ // b1 : (MRU) [2] (LRU)
+ // b2 : (MRU) [] (LRU)
+
+ // Add 2, should cause hit on b1
+ l.Add(2, 2)
+ if n := l.b1.Len(); n != 1 {
+ t.Fatalf("bad: %d", n)
+ }
+ if l.p != 1 {
+ t.Fatalf("bad: %d", l.p)
+ }
+ if n := l.t2.Len(); n != 3 {
+ t.Fatalf("bad: %d", n)
+ }
+
+ // Current state
+ // t1 : (MRU) [4] (LRU)
+ // t2 : (MRU) [2, 1, 0] (LRU)
+ // b1 : (MRU) [3] (LRU)
+ // b2 : (MRU) [] (LRU)
+
+ // Add 4, should migrate to t2
+ l.Add(4, 4)
+ if n := l.t1.Len(); n != 0 {
+ t.Fatalf("bad: %d", n)
+ }
+ if n := l.t2.Len(); n != 4 {
+ t.Fatalf("bad: %d", n)
+ }
+
+ // Current state
+ // t1 : (MRU) [] (LRU)
+ // t2 : (MRU) [4, 2, 1, 0] (LRU)
+ // b1 : (MRU) [3] (LRU)
+ // b2 : (MRU) [] (LRU)
+
+ // Add 4, should evict to b2
+ l.Add(5, 5)
+ if n := l.t1.Len(); n != 1 {
+ t.Fatalf("bad: %d", n)
+ }
+ if n := l.t2.Len(); n != 3 {
+ t.Fatalf("bad: %d", n)
+ }
+ if n := l.b2.Len(); n != 1 {
+ t.Fatalf("bad: %d", n)
+ }
+
+ // Current state
+ // t1 : (MRU) [5] (LRU)
+ // t2 : (MRU) [4, 2, 1] (LRU)
+ // b1 : (MRU) [3] (LRU)
+ // b2 : (MRU) [0] (LRU)
+
+ // Add 0, should decrease p
+ l.Add(0, 0)
+ if n := l.t1.Len(); n != 0 {
+ t.Fatalf("bad: %d", n)
+ }
+ if n := l.t2.Len(); n != 4 {
+ t.Fatalf("bad: %d", n)
+ }
+ if n := l.b1.Len(); n != 2 {
+ t.Fatalf("bad: %d", n)
+ }
+ if n := l.b2.Len(); n != 0 {
+ t.Fatalf("bad: %d", n)
+ }
+ if l.p != 0 {
+ t.Fatalf("bad: %d", l.p)
+ }
+
+ // Current state
+ // t1 : (MRU) [] (LRU)
+ // t2 : (MRU) [0, 4, 2, 1] (LRU)
+ // b1 : (MRU) [5, 3] (LRU)
+ // b2 : (MRU) [0] (LRU)
+}
+
+func TestARC(t *testing.T) {
+ l, err := NewARC(128)
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+
+ for i := 0; i < 256; i++ {
+ l.Add(i, i)
+ }
+ if l.Len() != 128 {
+ t.Fatalf("bad len: %v", l.Len())
+ }
+
+ for i, k := range l.Keys() {
+ if v, ok := l.Get(k); !ok || v != k || v != i+128 {
+ t.Fatalf("bad key: %v", k)
+ }
+ }
+ for i := 0; i < 128; i++ {
+ _, ok := l.Get(i)
+ if ok {
+ t.Fatalf("should be evicted")
+ }
+ }
+ for i := 128; i < 256; i++ {
+ _, ok := l.Get(i)
+ if !ok {
+ t.Fatalf("should not be evicted")
+ }
+ }
+ for i := 128; i < 192; i++ {
+ l.Remove(i)
+ _, ok := l.Get(i)
+ if ok {
+ t.Fatalf("should be deleted")
+ }
+ }
+
+ l.Purge()
+ if l.Len() != 0 {
+ t.Fatalf("bad len: %v", l.Len())
+ }
+ if _, ok := l.Get(200); ok {
+ t.Fatalf("should contain nothing")
+ }
+}
+
+// Test that Contains doesn't update recent-ness
+func TestARC_Contains(t *testing.T) {
+ l, err := NewARC(2)
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+
+ l.Add(1, 1)
+ l.Add(2, 2)
+ if !l.Contains(1) {
+ t.Errorf("1 should be contained")
+ }
+
+ l.Add(3, 3)
+ if l.Contains(1) {
+ t.Errorf("Contains should not have updated recent-ness of 1")
+ }
+}
+
+// Test that Peek doesn't update recent-ness
+func TestARC_Peek(t *testing.T) {
+ l, err := NewARC(2)
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+
+ l.Add(1, 1)
+ l.Add(2, 2)
+ if v, ok := l.Peek(1); !ok || v != 1 {
+ t.Errorf("1 should be set to 1: %v, %v", v, ok)
+ }
+
+ l.Add(3, 3)
+ if l.Contains(1) {
+ t.Errorf("should not have updated recent-ness of 1")
+ }
+}
diff --git a/vendor/github.com/hashicorp/golang-lru/lru.go b/vendor/github.com/hashicorp/golang-lru/lru.go
new file mode 100644
index 0000000..a6285f9
--- /dev/null
+++ b/vendor/github.com/hashicorp/golang-lru/lru.go
@@ -0,0 +1,114 @@
+// This package provides a simple LRU cache. It is based on the
+// LRU implementation in groupcache:
+// https://github.com/golang/groupcache/tree/master/lru
+package lru
+
+import (
+ "sync"
+
+ "github.com/hashicorp/golang-lru/simplelru"
+)
+
+// Cache is a thread-safe fixed size LRU cache.
+type Cache struct {
+ lru *simplelru.LRU
+ lock sync.RWMutex
+}
+
+// New creates an LRU of the given size
+func New(size int) (*Cache, error) {
+ return NewWithEvict(size, nil)
+}
+
+// NewWithEvict constructs a fixed size cache with the given eviction
+// callback.
+func NewWithEvict(size int, onEvicted func(key interface{}, value interface{})) (*Cache, error) {
+ lru, err := simplelru.NewLRU(size, simplelru.EvictCallback(onEvicted))
+ if err != nil {
+ return nil, err
+ }
+ c := &Cache{
+ lru: lru,
+ }
+ return c, nil
+}
+
+// Purge is used to completely clear the cache
+func (c *Cache) Purge() {
+ c.lock.Lock()
+ c.lru.Purge()
+ c.lock.Unlock()
+}
+
+// Add adds a value to the cache. Returns true if an eviction occurred.
+func (c *Cache) Add(key, value interface{}) bool {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+ return c.lru.Add(key, value)
+}
+
+// Get looks up a key's value from the cache.
+func (c *Cache) Get(key interface{}) (interface{}, bool) {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+ return c.lru.Get(key)
+}
+
+// Check if a key is in the cache, without updating the recent-ness
+// or deleting it for being stale.
+func (c *Cache) Contains(key interface{}) bool {
+ c.lock.RLock()
+ defer c.lock.RUnlock()
+ return c.lru.Contains(key)
+}
+
+// Returns the key value (or undefined if not found) without updating
+// the "recently used"-ness of the key.
+func (c *Cache) Peek(key interface{}) (interface{}, bool) {
+ c.lock.RLock()
+ defer c.lock.RUnlock()
+ return c.lru.Peek(key)
+}
+
+// ContainsOrAdd checks if a key is in the cache without updating the
+// recent-ness or deleting it for being stale, and if not, adds the value.
+// Returns whether found and whether an eviction occurred.
+func (c *Cache) ContainsOrAdd(key, value interface{}) (ok, evict bool) {
+ c.lock.Lock()
+ defer c.lock.Unlock()
+
+ if c.lru.Contains(key) {
+ return true, false
+ } else {
+ evict := c.lru.Add(key, value)
+ return false, evict
+ }
+}
+
+// Remove removes the provided key from the cache.
+func (c *Cache) Remove(key interface{}) {
+ c.lock.Lock()
+ c.lru.Remove(key)
+ c.lock.Unlock()
+}
+
+// RemoveOldest removes the oldest item from the cache.
+func (c *Cache) RemoveOldest() {
+ c.lock.Lock()
+ c.lru.RemoveOldest()
+ c.lock.Unlock()
+}
+
+// Keys returns a slice of the keys in the cache, from oldest to newest.
+func (c *Cache) Keys() []interface{} {
+ c.lock.RLock()
+ defer c.lock.RUnlock()
+ return c.lru.Keys()
+}
+
+// Len returns the number of items in the cache.
+func (c *Cache) Len() int {
+ c.lock.RLock()
+ defer c.lock.RUnlock()
+ return c.lru.Len()
+}
diff --git a/vendor/github.com/hashicorp/golang-lru/lru_test.go b/vendor/github.com/hashicorp/golang-lru/lru_test.go
new file mode 100644
index 0000000..2b31218
--- /dev/null
+++ b/vendor/github.com/hashicorp/golang-lru/lru_test.go
@@ -0,0 +1,221 @@
+package lru
+
+import (
+ "math/rand"
+ "testing"
+)
+
+func BenchmarkLRU_Rand(b *testing.B) {
+ l, err := New(8192)
+ if err != nil {
+ b.Fatalf("err: %v", err)
+ }
+
+ trace := make([]int64, b.N*2)
+ for i := 0; i < b.N*2; i++ {
+ trace[i] = rand.Int63() % 32768
+ }
+
+ b.ResetTimer()
+
+ var hit, miss int
+ for i := 0; i < 2*b.N; i++ {
+ if i%2 == 0 {
+ l.Add(trace[i], trace[i])
+ } else {
+ _, ok := l.Get(trace[i])
+ if ok {
+ hit++
+ } else {
+ miss++
+ }
+ }
+ }
+ b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss))
+}
+
+func BenchmarkLRU_Freq(b *testing.B) {
+ l, err := New(8192)
+ if err != nil {
+ b.Fatalf("err: %v", err)
+ }
+
+ trace := make([]int64, b.N*2)
+ for i := 0; i < b.N*2; i++ {
+ if i%2 == 0 {
+ trace[i] = rand.Int63() % 16384
+ } else {
+ trace[i] = rand.Int63() % 32768
+ }
+ }
+
+ b.ResetTimer()
+
+ for i := 0; i < b.N; i++ {
+ l.Add(trace[i], trace[i])
+ }
+ var hit, miss int
+ for i := 0; i < b.N; i++ {
+ _, ok := l.Get(trace[i])
+ if ok {
+ hit++
+ } else {
+ miss++
+ }
+ }
+ b.Logf("hit: %d miss: %d ratio: %f", hit, miss, float64(hit)/float64(miss))
+}
+
+func TestLRU(t *testing.T) {
+ evictCounter := 0
+ onEvicted := func(k interface{}, v interface{}) {
+ if k != v {
+ t.Fatalf("Evict values not equal (%v!=%v)", k, v)
+ }
+ evictCounter += 1
+ }
+ l, err := NewWithEvict(128, onEvicted)
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+
+ for i := 0; i < 256; i++ {
+ l.Add(i, i)
+ }
+ if l.Len() != 128 {
+ t.Fatalf("bad len: %v", l.Len())
+ }
+
+ if evictCounter != 128 {
+ t.Fatalf("bad evict count: %v", evictCounter)
+ }
+
+ for i, k := range l.Keys() {
+ if v, ok := l.Get(k); !ok || v != k || v != i+128 {
+ t.Fatalf("bad key: %v", k)
+ }
+ }
+ for i := 0; i < 128; i++ {
+ _, ok := l.Get(i)
+ if ok {
+ t.Fatalf("should be evicted")
+ }
+ }
+ for i := 128; i < 256; i++ {
+ _, ok := l.Get(i)
+ if !ok {
+ t.Fatalf("should not be evicted")
+ }
+ }
+ for i := 128; i < 192; i++ {
+ l.Remove(i)
+ _, ok := l.Get(i)
+ if ok {
+ t.Fatalf("should be deleted")
+ }
+ }
+
+ l.Get(192) // expect 192 to be last key in l.Keys()
+
+ for i, k := range l.Keys() {
+ if (i < 63 && k != i+193) || (i == 63 && k != 192) {
+ t.Fatalf("out of order key: %v", k)
+ }
+ }
+
+ l.Purge()
+ if l.Len() != 0 {
+ t.Fatalf("bad len: %v", l.Len())
+ }
+ if _, ok := l.Get(200); ok {
+ t.Fatalf("should contain nothing")
+ }
+}
+
+// test that Add returns true/false if an eviction occurred
+func TestLRUAdd(t *testing.T) {
+ evictCounter := 0
+ onEvicted := func(k interface{}, v interface{}) {
+ evictCounter += 1
+ }
+
+ l, err := NewWithEvict(1, onEvicted)
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+
+ if l.Add(1, 1) == true || evictCounter != 0 {
+ t.Errorf("should not have an eviction")
+ }
+ if l.Add(2, 2) == false || evictCounter != 1 {
+ t.Errorf("should have an eviction")
+ }
+}
+
+// test that Contains doesn't update recent-ness
+func TestLRUContains(t *testing.T) {
+ l, err := New(2)
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+
+ l.Add(1, 1)
+ l.Add(2, 2)
+ if !l.Contains(1) {
+ t.Errorf("1 should be contained")
+ }
+
+ l.Add(3, 3)
+ if l.Contains(1) {
+ t.Errorf("Contains should not have updated recent-ness of 1")
+ }
+}
+
+// test that Contains doesn't update recent-ness
+func TestLRUContainsOrAdd(t *testing.T) {
+ l, err := New(2)
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+
+ l.Add(1, 1)
+ l.Add(2, 2)
+ contains, evict := l.ContainsOrAdd(1, 1)
+ if !contains {
+ t.Errorf("1 should be contained")
+ }
+ if evict {
+ t.Errorf("nothing should be evicted here")
+ }
+
+ l.Add(3, 3)
+ contains, evict = l.ContainsOrAdd(1, 1)
+ if contains {
+ t.Errorf("1 should not have been contained")
+ }
+ if !evict {
+ t.Errorf("an eviction should have occurred")
+ }
+ if !l.Contains(1) {
+ t.Errorf("now 1 should be contained")
+ }
+}
+
+// test that Peek doesn't update recent-ness
+func TestLRUPeek(t *testing.T) {
+ l, err := New(2)
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+
+ l.Add(1, 1)
+ l.Add(2, 2)
+ if v, ok := l.Peek(1); !ok || v != 1 {
+ t.Errorf("1 should be set to 1: %v, %v", v, ok)
+ }
+
+ l.Add(3, 3)
+ if l.Contains(1) {
+ t.Errorf("should not have updated recent-ness of 1")
+ }
+}
diff --git a/vendor/github.com/hashicorp/golang-lru/simplelru/lru.go b/vendor/github.com/hashicorp/golang-lru/simplelru/lru.go
new file mode 100644
index 0000000..cb416b3
--- /dev/null
+++ b/vendor/github.com/hashicorp/golang-lru/simplelru/lru.go
@@ -0,0 +1,160 @@
+package simplelru
+
+import (
+ "container/list"
+ "errors"
+)
+
+// EvictCallback is used to get a callback when a cache entry is evicted
+type EvictCallback func(key interface{}, value interface{})
+
+// LRU implements a non-thread safe fixed size LRU cache
+type LRU struct {
+ size int
+ evictList *list.List
+ items map[interface{}]*list.Element
+ onEvict EvictCallback
+}
+
+// entry is used to hold a value in the evictList
+type entry struct {
+ key interface{}
+ value interface{}
+}
+
+// NewLRU constructs an LRU of the given size
+func NewLRU(size int, onEvict EvictCallback) (*LRU, error) {
+ if size <= 0 {
+ return nil, errors.New("Must provide a positive size")
+ }
+ c := &LRU{
+ size: size,
+ evictList: list.New(),
+ items: make(map[interface{}]*list.Element),
+ onEvict: onEvict,
+ }
+ return c, nil
+}
+
+// Purge is used to completely clear the cache
+func (c *LRU) Purge() {
+ for k, v := range c.items {
+ if c.onEvict != nil {
+ c.onEvict(k, v.Value.(*entry).value)
+ }
+ delete(c.items, k)
+ }
+ c.evictList.Init()
+}
+
+// Add adds a value to the cache. Returns true if an eviction occurred.
+func (c *LRU) Add(key, value interface{}) bool {
+ // Check for existing item
+ if ent, ok := c.items[key]; ok {
+ c.evictList.MoveToFront(ent)
+ ent.Value.(*entry).value = value
+ return false
+ }
+
+ // Add new item
+ ent := &entry{key, value}
+ entry := c.evictList.PushFront(ent)
+ c.items[key] = entry
+
+ evict := c.evictList.Len() > c.size
+ // Verify size not exceeded
+ if evict {
+ c.removeOldest()
+ }
+ return evict
+}
+
+// Get looks up a key's value from the cache.
+func (c *LRU) Get(key interface{}) (value interface{}, ok bool) {
+ if ent, ok := c.items[key]; ok {
+ c.evictList.MoveToFront(ent)
+ return ent.Value.(*entry).value, true
+ }
+ return
+}
+
+// Check if a key is in the cache, without updating the recent-ness
+// or deleting it for being stale.
+func (c *LRU) Contains(key interface{}) (ok bool) {
+ _, ok = c.items[key]
+ return ok
+}
+
+// Returns the key value (or undefined if not found) without updating
+// the "recently used"-ness of the key.
+func (c *LRU) Peek(key interface{}) (value interface{}, ok bool) {
+ if ent, ok := c.items[key]; ok {
+ return ent.Value.(*entry).value, true
+ }
+ return nil, ok
+}
+
+// Remove removes the provided key from the cache, returning if the
+// key was contained.
+func (c *LRU) Remove(key interface{}) bool {
+ if ent, ok := c.items[key]; ok {
+ c.removeElement(ent)
+ return true
+ }
+ return false
+}
+
+// RemoveOldest removes the oldest item from the cache.
+func (c *LRU) RemoveOldest() (interface{}, interface{}, bool) {
+ ent := c.evictList.Back()
+ if ent != nil {
+ c.removeElement(ent)
+ kv := ent.Value.(*entry)
+ return kv.key, kv.value, true
+ }
+ return nil, nil, false
+}
+
+// GetOldest returns the oldest entry
+func (c *LRU) GetOldest() (interface{}, interface{}, bool) {
+ ent := c.evictList.Back()
+ if ent != nil {
+ kv := ent.Value.(*entry)
+ return kv.key, kv.value, true
+ }
+ return nil, nil, false
+}
+
+// Keys returns a slice of the keys in the cache, from oldest to newest.
+func (c *LRU) Keys() []interface{} {
+ keys := make([]interface{}, len(c.items))
+ i := 0
+ for ent := c.evictList.Back(); ent != nil; ent = ent.Prev() {
+ keys[i] = ent.Value.(*entry).key
+ i++
+ }
+ return keys
+}
+
+// Len returns the number of items in the cache.
+func (c *LRU) Len() int {
+ return c.evictList.Len()
+}
+
+// removeOldest removes the oldest item from the cache.
+func (c *LRU) removeOldest() {
+ ent := c.evictList.Back()
+ if ent != nil {
+ c.removeElement(ent)
+ }
+}
+
+// removeElement is used to remove a given list element from the cache
+func (c *LRU) removeElement(e *list.Element) {
+ c.evictList.Remove(e)
+ kv := e.Value.(*entry)
+ delete(c.items, kv.key)
+ if c.onEvict != nil {
+ c.onEvict(kv.key, kv.value)
+ }
+}
diff --git a/vendor/github.com/hashicorp/golang-lru/simplelru/lru_test.go b/vendor/github.com/hashicorp/golang-lru/simplelru/lru_test.go
new file mode 100644
index 0000000..a958934
--- /dev/null
+++ b/vendor/github.com/hashicorp/golang-lru/simplelru/lru_test.go
@@ -0,0 +1,167 @@
+package simplelru
+
+import "testing"
+
+func TestLRU(t *testing.T) {
+ evictCounter := 0
+ onEvicted := func(k interface{}, v interface{}) {
+ if k != v {
+ t.Fatalf("Evict values not equal (%v!=%v)", k, v)
+ }
+ evictCounter += 1
+ }
+ l, err := NewLRU(128, onEvicted)
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+
+ for i := 0; i < 256; i++ {
+ l.Add(i, i)
+ }
+ if l.Len() != 128 {
+ t.Fatalf("bad len: %v", l.Len())
+ }
+
+ if evictCounter != 128 {
+ t.Fatalf("bad evict count: %v", evictCounter)
+ }
+
+ for i, k := range l.Keys() {
+ if v, ok := l.Get(k); !ok || v != k || v != i+128 {
+ t.Fatalf("bad key: %v", k)
+ }
+ }
+ for i := 0; i < 128; i++ {
+ _, ok := l.Get(i)
+ if ok {
+ t.Fatalf("should be evicted")
+ }
+ }
+ for i := 128; i < 256; i++ {
+ _, ok := l.Get(i)
+ if !ok {
+ t.Fatalf("should not be evicted")
+ }
+ }
+ for i := 128; i < 192; i++ {
+ ok := l.Remove(i)
+ if !ok {
+ t.Fatalf("should be contained")
+ }
+ ok = l.Remove(i)
+ if ok {
+ t.Fatalf("should not be contained")
+ }
+ _, ok = l.Get(i)
+ if ok {
+ t.Fatalf("should be deleted")
+ }
+ }
+
+ l.Get(192) // expect 192 to be last key in l.Keys()
+
+ for i, k := range l.Keys() {
+ if (i < 63 && k != i+193) || (i == 63 && k != 192) {
+ t.Fatalf("out of order key: %v", k)
+ }
+ }
+
+ l.Purge()
+ if l.Len() != 0 {
+ t.Fatalf("bad len: %v", l.Len())
+ }
+ if _, ok := l.Get(200); ok {
+ t.Fatalf("should contain nothing")
+ }
+}
+
+func TestLRU_GetOldest_RemoveOldest(t *testing.T) {
+ l, err := NewLRU(128, nil)
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+ for i := 0; i < 256; i++ {
+ l.Add(i, i)
+ }
+ k, _, ok := l.GetOldest()
+ if !ok {
+ t.Fatalf("missing")
+ }
+ if k.(int) != 128 {
+ t.Fatalf("bad: %v", k)
+ }
+
+ k, _, ok = l.RemoveOldest()
+ if !ok {
+ t.Fatalf("missing")
+ }
+ if k.(int) != 128 {
+ t.Fatalf("bad: %v", k)
+ }
+
+ k, _, ok = l.RemoveOldest()
+ if !ok {
+ t.Fatalf("missing")
+ }
+ if k.(int) != 129 {
+ t.Fatalf("bad: %v", k)
+ }
+}
+
+// Test that Add returns true/false if an eviction occurred
+func TestLRU_Add(t *testing.T) {
+ evictCounter := 0
+ onEvicted := func(k interface{}, v interface{}) {
+ evictCounter += 1
+ }
+
+ l, err := NewLRU(1, onEvicted)
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+
+ if l.Add(1, 1) == true || evictCounter != 0 {
+ t.Errorf("should not have an eviction")
+ }
+ if l.Add(2, 2) == false || evictCounter != 1 {
+ t.Errorf("should have an eviction")
+ }
+}
+
+// Test that Contains doesn't update recent-ness
+func TestLRU_Contains(t *testing.T) {
+ l, err := NewLRU(2, nil)
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+
+ l.Add(1, 1)
+ l.Add(2, 2)
+ if !l.Contains(1) {
+ t.Errorf("1 should be contained")
+ }
+
+ l.Add(3, 3)
+ if l.Contains(1) {
+ t.Errorf("Contains should not have updated recent-ness of 1")
+ }
+}
+
+// Test that Peek doesn't update recent-ness
+func TestLRU_Peek(t *testing.T) {
+ l, err := NewLRU(2, nil)
+ if err != nil {
+ t.Fatalf("err: %v", err)
+ }
+
+ l.Add(1, 1)
+ l.Add(2, 2)
+ if v, ok := l.Peek(1); !ok || v != 1 {
+ t.Errorf("1 should be set to 1: %v, %v", v, ok)
+ }
+
+ l.Add(3, 3)
+ if l.Contains(1) {
+ t.Errorf("should not have updated recent-ness of 1")
+ }
+}
diff --git a/vendor/github.com/justinas/alice/.travis.yml b/vendor/github.com/justinas/alice/.travis.yml
new file mode 100644
index 0000000..b2c7a4b
--- /dev/null
+++ b/vendor/github.com/justinas/alice/.travis.yml
@@ -0,0 +1,15 @@
+language: go
+
+matrix:
+ include:
+ - go: 1.0
+ - go: 1.1
+ - go: 1.2
+ - go: 1.3
+ - go: 1.4
+ - go: 1.5
+ - go: 1.6
+ - go: 1.7
+ - go: tip
+ allow_failures:
+ - go: tip
diff --git a/vendor/github.com/justinas/alice/LICENSE b/vendor/github.com/justinas/alice/LICENSE
new file mode 100644
index 0000000..0d0d352
--- /dev/null
+++ b/vendor/github.com/justinas/alice/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Justinas Stankevicius
+
+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/justinas/alice/README.md b/vendor/github.com/justinas/alice/README.md
new file mode 100644
index 0000000..e4f9157
--- /dev/null
+++ b/vendor/github.com/justinas/alice/README.md
@@ -0,0 +1,98 @@
+# Alice
+
+[](http://godoc.org/github.com/justinas/alice)
+[](https://travis-ci.org/justinas/alice)
+[](http://gocover.io/github.com/justinas/alice)
+
+Alice provides a convenient way to chain
+your HTTP middleware functions and the app handler.
+
+In short, it transforms
+
+```go
+Middleware1(Middleware2(Middleware3(App)))
+```
+
+to
+
+```go
+alice.New(Middleware1, Middleware2, Middleware3).Then(App)
+```
+
+### Why?
+
+None of the other middleware chaining solutions
+behaves exactly like Alice.
+Alice is as minimal as it gets:
+in essence, it's just a for loop that does the wrapping for you.
+
+Check out [this blog post](http://justinas.org/alice-painless-middleware-chaining-for-go/)
+for explanation how Alice is different from other chaining solutions.
+
+### Usage
+
+Your middleware constructors should have the form of
+
+```go
+func (http.Handler) http.Handler
+```
+
+Some middleware provide this out of the box.
+For ones that don't, it's trivial to write one yourself.
+
+```go
+func myStripPrefix(h http.Handler) http.Handler {
+ return http.StripPrefix("/old", h)
+}
+```
+
+This complete example shows the full power of Alice.
+
+```go
+package main
+
+import (
+ "net/http"
+ "time"
+
+ "github.com/throttled/throttled"
+ "github.com/justinas/alice"
+ "github.com/justinas/nosurf"
+)
+
+func timeoutHandler(h http.Handler) http.Handler {
+ return http.TimeoutHandler(h, 1*time.Second, "timed out")
+}
+
+func myApp(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte("Hello world!"))
+}
+
+func main() {
+ th := throttled.Interval(throttled.PerSec(10), 1, &throttled.VaryBy{Path: true}, 50)
+ myHandler := http.HandlerFunc(myApp)
+
+ chain := alice.New(th.Throttle, timeoutHandler, nosurf.NewPure).Then(myHandler)
+ http.ListenAndServe(":8000", chain)
+}
+```
+
+Here, the request will pass [throttled](https://github.com/PuerkitoBio/throttled) first,
+then an http.TimeoutHandler we've set up,
+then [nosurf](https://github.com/justinas/nosurf)
+and will finally reach our handler.
+
+Note that Alice makes **no guarantees** for
+how one or another piece of middleware will behave.
+Once it passes the execution to the outer layer of middleware,
+it has no saying in whether middleware will execute the inner handlers.
+This is intentional behavior.
+
+Alice works with Go 1.0 and higher.
+
+### Contributing
+
+0. Find an issue that bugs you / open a new one.
+1. Discuss.
+2. Branch off, commit, test.
+3. Make a pull request / attach the commits to the issue.
diff --git a/vendor/github.com/justinas/alice/chain.go b/vendor/github.com/justinas/alice/chain.go
new file mode 100644
index 0000000..da0e2b5
--- /dev/null
+++ b/vendor/github.com/justinas/alice/chain.go
@@ -0,0 +1,112 @@
+// Package alice provides a convenient way to chain http handlers.
+package alice
+
+import "net/http"
+
+// A constructor for a piece of middleware.
+// Some middleware use this constructor out of the box,
+// so in most cases you can just pass somepackage.New
+type Constructor func(http.Handler) http.Handler
+
+// Chain acts as a list of http.Handler constructors.
+// Chain is effectively immutable:
+// once created, it will always hold
+// the same set of constructors in the same order.
+type Chain struct {
+ constructors []Constructor
+}
+
+// New creates a new chain,
+// memorizing the given list of middleware constructors.
+// New serves no other function,
+// constructors are only called upon a call to Then().
+func New(constructors ...Constructor) Chain {
+ return Chain{append(([]Constructor)(nil), constructors...)}
+}
+
+// Then chains the middleware and returns the final http.Handler.
+// New(m1, m2, m3).Then(h)
+// is equivalent to:
+// m1(m2(m3(h)))
+// When the request comes in, it will be passed to m1, then m2, then m3
+// and finally, the given handler
+// (assuming every middleware calls the following one).
+//
+// A chain can be safely reused by calling Then() several times.
+// stdStack := alice.New(ratelimitHandler, csrfHandler)
+// indexPipe = stdStack.Then(indexHandler)
+// authPipe = stdStack.Then(authHandler)
+// Note that constructors are called on every call to Then()
+// and thus several instances of the same middleware will be created
+// when a chain is reused in this way.
+// For proper middleware, this should cause no problems.
+//
+// Then() treats nil as http.DefaultServeMux.
+func (c Chain) Then(h http.Handler) http.Handler {
+ if h == nil {
+ h = http.DefaultServeMux
+ }
+
+ for i := range c.constructors {
+ h = c.constructors[len(c.constructors)-1-i](h)
+ }
+
+ return h
+}
+
+// ThenFunc works identically to Then, but takes
+// a HandlerFunc instead of a Handler.
+//
+// The following two statements are equivalent:
+// c.Then(http.HandlerFunc(fn))
+// c.ThenFunc(fn)
+//
+// ThenFunc provides all the guarantees of Then.
+func (c Chain) ThenFunc(fn http.HandlerFunc) http.Handler {
+ if fn == nil {
+ return c.Then(nil)
+ }
+ return c.Then(fn)
+}
+
+// Append extends a chain, adding the specified constructors
+// as the last ones in the request flow.
+//
+// Append returns a new chain, leaving the original one untouched.
+//
+// stdChain := alice.New(m1, m2)
+// extChain := stdChain.Append(m3, m4)
+// // requests in stdChain go m1 -> m2
+// // requests in extChain go m1 -> m2 -> m3 -> m4
+func (c Chain) Append(constructors ...Constructor) Chain {
+ newCons := make([]Constructor, 0, len(c.constructors)+len(constructors))
+ newCons = append(newCons, c.constructors...)
+ newCons = append(newCons, constructors...)
+
+ return Chain{newCons}
+}
+
+// Extend extends a chain by adding the specified chain
+// as the last one in the request flow.
+//
+// Extend returns a new chain, leaving the original one untouched.
+//
+// stdChain := alice.New(m1, m2)
+// ext1Chain := alice.New(m3, m4)
+// ext2Chain := stdChain.Extend(ext1Chain)
+// // requests in stdChain go m1 -> m2
+// // requests in ext1Chain go m3 -> m4
+// // requests in ext2Chain go m1 -> m2 -> m3 -> m4
+//
+// Another example:
+// aHtmlAfterNosurf := alice.New(m2)
+// aHtml := alice.New(m1, func(h http.Handler) http.Handler {
+// csrf := nosurf.New(h)
+// csrf.SetFailureHandler(aHtmlAfterNosurf.ThenFunc(csrfFail))
+// return csrf
+// }).Extend(aHtmlAfterNosurf)
+// // requests to aHtml hitting nosurfs success handler go m1 -> nosurf -> m2 -> target-handler
+// // requests to aHtml hitting nosurfs failure handler go m1 -> nosurf -> m2 -> csrfFail
+func (c Chain) Extend(chain Chain) Chain {
+ return c.Append(chain.constructors...)
+}
diff --git a/vendor/github.com/justinas/alice/chain_test.go b/vendor/github.com/justinas/alice/chain_test.go
new file mode 100644
index 0000000..6f4316b
--- /dev/null
+++ b/vendor/github.com/justinas/alice/chain_test.go
@@ -0,0 +1,178 @@
+// Package alice implements a middleware chaining solution.
+package alice
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "reflect"
+ "testing"
+)
+
+// A constructor for middleware
+// that writes its own "tag" into the RW and does nothing else.
+// Useful in checking if a chain is behaving in the right order.
+func tagMiddleware(tag string) Constructor {
+ return func(h http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte(tag))
+ h.ServeHTTP(w, r)
+ })
+ }
+}
+
+// Not recommended (https://golang.org/pkg/reflect/#Value.Pointer),
+// but the best we can do.
+func funcsEqual(f1, f2 interface{}) bool {
+ val1 := reflect.ValueOf(f1)
+ val2 := reflect.ValueOf(f2)
+ return val1.Pointer() == val2.Pointer()
+}
+
+var testApp = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte("app\n"))
+})
+
+func TestNew(t *testing.T) {
+ c1 := func(h http.Handler) http.Handler {
+ return nil
+ }
+
+ c2 := func(h http.Handler) http.Handler {
+ return http.StripPrefix("potato", nil)
+ }
+
+ slice := []Constructor{c1, c2}
+
+ chain := New(slice...)
+ for k := range slice {
+ if !funcsEqual(chain.constructors[k], slice[k]) {
+ t.Error("New does not add constructors correctly")
+ }
+ }
+}
+
+func TestThenWorksWithNoMiddleware(t *testing.T) {
+ if !funcsEqual(New().Then(testApp), testApp) {
+ t.Error("Then does not work with no middleware")
+ }
+}
+
+func TestThenTreatsNilAsDefaultServeMux(t *testing.T) {
+ if New().Then(nil) != http.DefaultServeMux {
+ t.Error("Then does not treat nil as DefaultServeMux")
+ }
+}
+
+func TestThenFuncTreatsNilAsDefaultServeMux(t *testing.T) {
+ if New().ThenFunc(nil) != http.DefaultServeMux {
+ t.Error("ThenFunc does not treat nil as DefaultServeMux")
+ }
+}
+
+func TestThenFuncConstructsHandlerFunc(t *testing.T) {
+ fn := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(200)
+ })
+ chained := New().ThenFunc(fn)
+ rec := httptest.NewRecorder()
+
+ chained.ServeHTTP(rec, (*http.Request)(nil))
+
+ if reflect.TypeOf(chained) != reflect.TypeOf((http.HandlerFunc)(nil)) {
+ t.Error("ThenFunc does not construct HandlerFunc")
+ }
+}
+
+func TestThenOrdersHandlersCorrectly(t *testing.T) {
+ t1 := tagMiddleware("t1\n")
+ t2 := tagMiddleware("t2\n")
+ t3 := tagMiddleware("t3\n")
+
+ chained := New(t1, t2, t3).Then(testApp)
+
+ w := httptest.NewRecorder()
+ r, err := http.NewRequest("GET", "/", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ chained.ServeHTTP(w, r)
+
+ if w.Body.String() != "t1\nt2\nt3\napp\n" {
+ t.Error("Then does not order handlers correctly")
+ }
+}
+
+func TestAppendAddsHandlersCorrectly(t *testing.T) {
+ chain := New(tagMiddleware("t1\n"), tagMiddleware("t2\n"))
+ newChain := chain.Append(tagMiddleware("t3\n"), tagMiddleware("t4\n"))
+
+ if len(chain.constructors) != 2 {
+ t.Error("chain should have 2 constructors")
+ }
+ if len(newChain.constructors) != 4 {
+ t.Error("newChain should have 4 constructors")
+ }
+
+ chained := newChain.Then(testApp)
+
+ w := httptest.NewRecorder()
+ r, err := http.NewRequest("GET", "/", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ chained.ServeHTTP(w, r)
+
+ if w.Body.String() != "t1\nt2\nt3\nt4\napp\n" {
+ t.Error("Append does not add handlers correctly")
+ }
+}
+
+func TestAppendRespectsImmutability(t *testing.T) {
+ chain := New(tagMiddleware(""))
+ newChain := chain.Append(tagMiddleware(""))
+
+ if &chain.constructors[0] == &newChain.constructors[0] {
+ t.Error("Apppend does not respect immutability")
+ }
+}
+
+func TestExtendAddsHandlersCorrectly(t *testing.T) {
+ chain1 := New(tagMiddleware("t1\n"), tagMiddleware("t2\n"))
+ chain2 := New(tagMiddleware("t3\n"), tagMiddleware("t4\n"))
+ newChain := chain1.Extend(chain2)
+
+ if len(chain1.constructors) != 2 {
+ t.Error("chain1 should contain 2 constructors")
+ }
+ if len(chain2.constructors) != 2 {
+ t.Error("chain2 should contain 2 constructors")
+ }
+ if len(newChain.constructors) != 4 {
+ t.Error("newChain should contain 4 constructors")
+ }
+
+ chained := newChain.Then(testApp)
+
+ w := httptest.NewRecorder()
+ r, err := http.NewRequest("GET", "/", nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ chained.ServeHTTP(w, r)
+
+ if w.Body.String() != "t1\nt2\nt3\nt4\napp\n" {
+ t.Error("Extend does not add handlers in correctly")
+ }
+}
+
+func TestExtendRespectsImmutability(t *testing.T) {
+ chain := New(tagMiddleware(""))
+ newChain := chain.Extend(New(tagMiddleware("")))
+
+ if &chain.constructors[0] == &newChain.constructors[0] {
+ t.Error("Extend does not respect immutability")
+ }
+}
diff --git a/vendor/gopkg.in/throttled/throttled.v2/.gitignore b/vendor/gopkg.in/throttled/throttled.v2/.gitignore
new file mode 100644
index 0000000..96c4e10
--- /dev/null
+++ b/vendor/gopkg.in/throttled/throttled.v2/.gitignore
@@ -0,0 +1,5 @@
+.DS_Store
+*.swp
+*.swo
+*.test
+*.out
diff --git a/vendor/gopkg.in/throttled/throttled.v2/.travis.yml b/vendor/gopkg.in/throttled/throttled.v2/.travis.yml
new file mode 100644
index 0000000..29225d7
--- /dev/null
+++ b/vendor/gopkg.in/throttled/throttled.v2/.travis.yml
@@ -0,0 +1,24 @@
+sudo: false
+
+language: go
+
+go:
+ - 1.3
+ - tip
+
+notifications:
+ email: false
+
+services:
+ - redis-server
+
+install:
+ - make get-deps
+ # Move to the gopkg location that rather than the default clone location
+ # otherwise Go 1.4+ complains about incorrect import paths.
+ - export GOPKG_DIR=$GOPATH/src/gopkg.in/throttled
+ - mkdir -p $GOPKG_DIR
+ - mv $TRAVIS_BUILD_DIR $GOPKG_DIR/throttled.v2
+ - cd $GOPKG_DIR/throttled.v2
+
+script: make
diff --git a/vendor/gopkg.in/throttled/throttled.v2/LICENSE b/vendor/gopkg.in/throttled/throttled.v2/LICENSE
new file mode 100644
index 0000000..f961648
--- /dev/null
+++ b/vendor/gopkg.in/throttled/throttled.v2/LICENSE
@@ -0,0 +1,12 @@
+Copyright (c) 2014, Martin Angers and Contributors.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
+
+* Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/vendor/gopkg.in/throttled/throttled.v2/Makefile b/vendor/gopkg.in/throttled/throttled.v2/Makefile
new file mode 100644
index 0000000..cfc235c
--- /dev/null
+++ b/vendor/gopkg.in/throttled/throttled.v2/Makefile
@@ -0,0 +1,31 @@
+
+
+.PHONY: test test-cover bench lint get-deps .go-test .go-test-cover
+
+test: .go-test bench lint
+
+test-cover: .go-test-cover bench lint
+
+bench:
+ go test -race -bench=. -cpu=1,2,4
+
+lint:
+ gofmt -l .
+ifneq ($(TRAVIS_GO_VERSION),1.3) # go vet doesn't play nicely on 1.3
+ go vet ./...
+endif
+ which golint # Fail if golint doesn't exist
+ -golint . # Don't fail on golint warnings themselves
+ -golint store # Don't fail on golint warnings themselves
+
+get-deps:
+ go get github.com/garyburd/redigo/redis
+ go get github.com/hashicorp/golang-lru
+ go get github.com/golang/lint/golint
+
+.go-test:
+ go test ./...
+
+.go-test-cover:
+ go test -coverprofile=throttled.coverage.out .
+ go test -coverprofile=store.coverage.out ./store
diff --git a/vendor/gopkg.in/throttled/throttled.v2/README.md b/vendor/gopkg.in/throttled/throttled.v2/README.md
new file mode 100644
index 0000000..b18dcbc
--- /dev/null
+++ b/vendor/gopkg.in/throttled/throttled.v2/README.md
@@ -0,0 +1,85 @@
+# Throttled [](https://travis-ci.org/throttled/throttled) [](https://godoc.org/gopkg.in/throttled/throttled.v2)
+
+Package throttled implements rate limiting access to resources such as
+HTTP endpoints.
+
+The 2.0.0 release made some major changes to the throttled API. If
+this change broke your code in problematic ways or you wish a feature
+of the old API had been retained, please open an issue. We don't
+guarantee any particular changes but would like to hear more about
+what our users need. Thanks!
+
+## Installation
+
+throttled uses gopkg.in for semantic versioning:
+`go get gopkg.in/throttled/throttled.v2`
+
+As of July 27, 2015, the package is located under its own Github
+organization. Please adjust your imports to
+`gopkg.in/throttled/throttled.v2`.
+
+The 1.x release series is compatible with the original, unversioned
+library written by [Martin Angers][puerkitobio]. There is a
+[blog post explaining that version's usage on 0value.com][blog].
+
+## Documentation
+
+API documentation is available on [godoc.org][doc]. The following
+example demonstrates the usage of HTTPLimiter for rate-limiting access
+to an http.Handler to 20 requests per path per minute with bursts of
+up to 5 additional requests:
+
+ store, err := memstore.New(65536)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ quota := throttled.RateQuota{throttled.PerMin(20), 5}
+ rateLimiter, err := throttled.NewGCRARateLimiter(store, quota)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ httpRateLimiter := throttled.HTTPRateLimiter{
+ RateLimiter: rateLimiter,
+ VaryBy: &throttled.VaryBy{Path: true},
+ }
+
+ http.ListenAndServe(":8080", httpRateLimiter.RateLimit(myHandler))
+
+## Contributing
+
+Since throttled uses gopkg.in for versioning, running `go get` against
+a fork or cloning from Github to the default path will break
+imports. Instead, use the following process for setting up your
+environment and contributing:
+
+```sh
+# Retrieve the source and dependencies.
+go get gopkg.in/throttled/throttled.v2/...
+
+# Fork the project on Github. For all following directions replace
+# with your Github username. Add your fork as a remote.
+cd $GOPATH/src/gopkg.in/throttled/throttled.v2
+git remote add fork git@github.com:/throttled.git
+
+# Create a branch, make your changes, test them and commit.
+git checkout -b my-new-feature
+#
+make test
+git commit -a
+git push -u fork my-new-feature
+```
+
+When your changes are ready, [open a pull request][pr] using "compare
+across forks".
+
+## License
+
+The [BSD 3-clause license][bsd]. Copyright (c) 2014 Martin Angers and Contributors.
+
+[blog]: http://0value.com/throttled--guardian-of-the-web-server
+[bsd]: https://opensource.org/licenses/BSD-3-Clause
+[doc]: https://godoc.org/gopkg.in/throttled/throttled.v2
+[puerkitobio]: https://github.com/puerkitobio/
+[pr]: https://github.com/throttled/throttled/compare
diff --git a/vendor/gopkg.in/throttled/throttled.v2/deprecated.go b/vendor/gopkg.in/throttled/throttled.v2/deprecated.go
new file mode 100644
index 0000000..f2c648a
--- /dev/null
+++ b/vendor/gopkg.in/throttled/throttled.v2/deprecated.go
@@ -0,0 +1,73 @@
+package throttled
+
+import (
+ "net/http"
+ "time"
+)
+
+// DEPRECATED. Quota returns the number of requests allowed and the custom time window.
+func (q Rate) Quota() (int, time.Duration) {
+ return q.count, q.period * time.Duration(q.count)
+}
+
+// DEPRECATED. Q represents a custom quota.
+type Q struct {
+ Requests int
+ Window time.Duration
+}
+
+// DEPRECATED. Quota returns the number of requests allowed and the custom time window.
+func (q Q) Quota() (int, time.Duration) {
+ return q.Requests, q.Window
+}
+
+// DEPRECATED. The Quota interface defines the method to implement to describe
+// a time-window quota, as required by the RateLimit throttler.
+type Quota interface {
+ // Quota returns a number of requests allowed, and a duration.
+ Quota() (int, time.Duration)
+}
+
+// DEPRECATED. Throttler is a backwards-compatible alias for HTTPLimiter.
+type Throttler struct {
+ HTTPRateLimiter
+}
+
+// DEPRECATED. Throttle is an alias for HTTPLimiter#Limit
+func (t *Throttler) Throttle(h http.Handler) http.Handler {
+ return t.RateLimit(h)
+}
+
+// DEPRECATED. RateLimit creates a Throttler that conforms to the given
+// rate limits
+func RateLimit(q Quota, vary *VaryBy, store GCRAStore) *Throttler {
+ count, period := q.Quota()
+
+ if count < 1 {
+ count = 1
+ }
+ if period <= 0 {
+ period = time.Second
+ }
+
+ rate := Rate{period: period / time.Duration(count)}
+ limiter, err := NewGCRARateLimiter(store, RateQuota{rate, count - 1})
+
+ // This panic in unavoidable because the original interface does
+ // not support returning an error.
+ if err != nil {
+ panic(err)
+ }
+
+ return &Throttler{
+ HTTPRateLimiter{
+ RateLimiter: limiter,
+ VaryBy: vary,
+ },
+ }
+}
+
+// DEPRECATED. Store is an alias for GCRAStore
+type Store interface {
+ GCRAStore
+}
diff --git a/vendor/gopkg.in/throttled/throttled.v2/deprecated_test.go b/vendor/gopkg.in/throttled/throttled.v2/deprecated_test.go
new file mode 100644
index 0000000..9340664
--- /dev/null
+++ b/vendor/gopkg.in/throttled/throttled.v2/deprecated_test.go
@@ -0,0 +1,59 @@
+package throttled_test
+
+import (
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "gopkg.in/throttled/throttled.v2"
+ "gopkg.in/throttled/throttled.v2/store"
+)
+
+// Ensure that the current implementation remains compatible with the
+// supported but deprecated usage until the next major version.
+func TestDeprecatedUsage(t *testing.T) {
+ // Declare interfaces to statically check that names haven't changed
+ var st throttled.Store
+ var thr *throttled.Throttler
+ var q throttled.Quota
+
+ st = store.NewMemStore(100)
+ vary := &throttled.VaryBy{Path: true}
+ q = throttled.PerMin(2)
+ thr = throttled.RateLimit(q, vary, st)
+ handler := thr.Throttle(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(200)
+ }))
+
+ cases := []struct {
+ path string
+ code int
+ headers map[string]string
+ }{
+ {"/foo", 200, map[string]string{"X-Ratelimit-Limit": "2", "X-Ratelimit-Remaining": "1", "X-Ratelimit-Reset": "30"}},
+ {"/foo", 200, map[string]string{"X-Ratelimit-Limit": "2", "X-Ratelimit-Remaining": "0", "X-Ratelimit-Reset": "60"}},
+ {"/foo", 429, map[string]string{"X-Ratelimit-Limit": "2", "X-Ratelimit-Remaining": "0", "X-Ratelimit-Reset": "60", "Retry-After": "30"}},
+ {"/bar", 200, map[string]string{"X-Ratelimit-Limit": "2", "X-Ratelimit-Remaining": "1", "X-Ratelimit-Reset": "30"}},
+ }
+
+ for i, c := range cases {
+ req, err := http.NewRequest("GET", c.path, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ rr := httptest.NewRecorder()
+ handler.ServeHTTP(rr, req)
+ if have, want := rr.Code, c.code; have != want {
+ t.Errorf("Expected request %d at %s to return %d but got %d",
+ i, c.path, want, have)
+ }
+
+ for name, want := range c.headers {
+ if have := rr.HeaderMap.Get(name); have != want {
+ t.Errorf("Expected request %d at %s to have header '%s: %s' but got '%s'",
+ i, c.path, name, want, have)
+ }
+ }
+ }
+}
diff --git a/vendor/gopkg.in/throttled/throttled.v2/doc.go b/vendor/gopkg.in/throttled/throttled.v2/doc.go
new file mode 100644
index 0000000..302c2be
--- /dev/null
+++ b/vendor/gopkg.in/throttled/throttled.v2/doc.go
@@ -0,0 +1,3 @@
+// Package throttled implements rate limiting access to resources such
+// as HTTP endpoints.
+package throttled // import "gopkg.in/throttled/throttled.v2"
diff --git a/vendor/gopkg.in/throttled/throttled.v2/example_test.go b/vendor/gopkg.in/throttled/throttled.v2/example_test.go
new file mode 100644
index 0000000..66e6374
--- /dev/null
+++ b/vendor/gopkg.in/throttled/throttled.v2/example_test.go
@@ -0,0 +1,103 @@
+package throttled_test
+
+import (
+ "fmt"
+ "log"
+ "net/http"
+
+ "gopkg.in/throttled/throttled.v2"
+ "gopkg.in/throttled/throttled.v2/store/memstore"
+)
+
+var myHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Write([]byte("hi there!"))
+})
+
+// ExampleHTTPRateLimiter demonstrates the usage of HTTPRateLimiter
+// for rate-limiting access to an http.Handler to 20 requests per path
+// per minute with a maximum burst of 5 requests.
+func ExampleHTTPRateLimiter() {
+ store, err := memstore.New(65536)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Maximum burst of 5 which refills at 20 tokens per minute.
+ quota := throttled.RateQuota{throttled.PerMin(20), 5}
+
+ rateLimiter, err := throttled.NewGCRARateLimiter(store, quota)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ httpRateLimiter := throttled.HTTPRateLimiter{
+ RateLimiter: rateLimiter,
+ VaryBy: &throttled.VaryBy{Path: true},
+ }
+
+ http.ListenAndServe(":8080", httpRateLimiter.RateLimit(myHandler))
+}
+
+// Demonstrates direct use of GCRARateLimiter's RateLimit function (and the
+// more general RateLimiter interface). This should be used anywhere where
+// granular control over rate limiting is required.
+func ExampleGCRARateLimiter() {
+ store, err := memstore.New(65536)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Maximum burst of 5 which refills at 1 token per hour.
+ quota := throttled.RateQuota{throttled.PerHour(1), 5}
+
+ rateLimiter, err := throttled.NewGCRARateLimiter(store, quota)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Bucket according to the number i / 10 (so 1 falls into the bucket 0
+ // while 11 falls into the bucket 1). This has the effect of allowing a
+ // burst of 5 plus 1 (a single emission interval) on every ten iterations
+ // of the loop. See the output for better clarity here.
+ //
+ // We also refill the bucket at 1 token per hour, but that has no effect
+ // for the purposes of this example.
+ for i := 0; i < 20; i++ {
+ bucket := fmt.Sprintf("by-order:%v", i/10)
+
+ limited, result, err := rateLimiter.RateLimit(bucket, 1)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ if limited {
+ fmt.Printf("Iteration %2v; bucket %v: FAILED. Rate limit exceeded.\n",
+ i, bucket)
+ } else {
+ fmt.Printf("Iteration %2v; bucket %v: Operation successful (remaining=%v).\n",
+ i, bucket, result.Remaining)
+ }
+ }
+
+ // Output:
+ // Iteration 0; bucket by-order:0: Operation successful (remaining=5).
+ // Iteration 1; bucket by-order:0: Operation successful (remaining=4).
+ // Iteration 2; bucket by-order:0: Operation successful (remaining=3).
+ // Iteration 3; bucket by-order:0: Operation successful (remaining=2).
+ // Iteration 4; bucket by-order:0: Operation successful (remaining=1).
+ // Iteration 5; bucket by-order:0: Operation successful (remaining=0).
+ // Iteration 6; bucket by-order:0: FAILED. Rate limit exceeded.
+ // Iteration 7; bucket by-order:0: FAILED. Rate limit exceeded.
+ // Iteration 8; bucket by-order:0: FAILED. Rate limit exceeded.
+ // Iteration 9; bucket by-order:0: FAILED. Rate limit exceeded.
+ // Iteration 10; bucket by-order:1: Operation successful (remaining=5).
+ // Iteration 11; bucket by-order:1: Operation successful (remaining=4).
+ // Iteration 12; bucket by-order:1: Operation successful (remaining=3).
+ // Iteration 13; bucket by-order:1: Operation successful (remaining=2).
+ // Iteration 14; bucket by-order:1: Operation successful (remaining=1).
+ // Iteration 15; bucket by-order:1: Operation successful (remaining=0).
+ // Iteration 16; bucket by-order:1: FAILED. Rate limit exceeded.
+ // Iteration 17; bucket by-order:1: FAILED. Rate limit exceeded.
+ // Iteration 18; bucket by-order:1: FAILED. Rate limit exceeded.
+ // Iteration 19; bucket by-order:1: FAILED. Rate limit exceeded.
+}
diff --git a/vendor/gopkg.in/throttled/throttled.v2/http.go b/vendor/gopkg.in/throttled/throttled.v2/http.go
new file mode 100644
index 0000000..4c513a8
--- /dev/null
+++ b/vendor/gopkg.in/throttled/throttled.v2/http.go
@@ -0,0 +1,110 @@
+package throttled
+
+import (
+ "errors"
+ "math"
+ "net/http"
+ "strconv"
+)
+
+var (
+ // DefaultDeniedHandler is the default DeniedHandler for an
+ // HTTPRateLimiter. It returns a 429 status code with a generic
+ // message.
+ DefaultDeniedHandler = http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ http.Error(w, "limit exceeded", 429)
+ }))
+
+ // DefaultError is the default Error function for an HTTPRateLimiter.
+ // It returns a 500 status code with a generic message.
+ DefaultError = func(w http.ResponseWriter, r *http.Request, err error) {
+ http.Error(w, "internal error", http.StatusInternalServerError)
+ }
+)
+
+// HTTPRateLimiter faciliates using a Limiter to limit HTTP requests.
+type HTTPRateLimiter struct {
+ // DeniedHandler is called if the request is disallowed. If it is
+ // nil, the DefaultDeniedHandler variable is used.
+ DeniedHandler http.Handler
+
+ // Error is called if the RateLimiter returns an error. If it is
+ // nil, the DefaultErrorFunc is used.
+ Error func(w http.ResponseWriter, r *http.Request, err error)
+
+ // Limiter is call for each request to determine whether the
+ // request is permitted and update internal state. It must be set.
+ RateLimiter RateLimiter
+
+ // VaryBy is called for each request to generate a key for the
+ // limiter. If it is nil, all requests use an empty string key.
+ VaryBy interface {
+ Key(*http.Request) string
+ }
+}
+
+// RateLimit wraps an http.Handler to limit incoming requests.
+// Requests that are not limited will be passed to the handler
+// unchanged. Limited requests will be passed to the DeniedHandler.
+// X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset and
+// Retry-After headers will be written to the response based on the
+// values in the RateLimitResult.
+func (t *HTTPRateLimiter) RateLimit(h http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if t.RateLimiter == nil {
+ t.error(w, r, errors.New("You must set a RateLimiter on HTTPRateLimiter"))
+ }
+
+ var k string
+ if t.VaryBy != nil {
+ k = t.VaryBy.Key(r)
+ }
+
+ limited, context, err := t.RateLimiter.RateLimit(k, 1)
+
+ if err != nil {
+ t.error(w, r, err)
+ return
+ }
+
+ setRateLimitHeaders(w, context)
+
+ if !limited {
+ h.ServeHTTP(w, r)
+ } else {
+ dh := t.DeniedHandler
+ if dh == nil {
+ dh = DefaultDeniedHandler
+ }
+ dh.ServeHTTP(w, r)
+ }
+ })
+}
+
+func (t *HTTPRateLimiter) error(w http.ResponseWriter, r *http.Request, err error) {
+ e := t.Error
+ if e == nil {
+ e = DefaultError
+ }
+ e(w, r, err)
+}
+
+func setRateLimitHeaders(w http.ResponseWriter, context RateLimitResult) {
+ if v := context.Limit; v >= 0 {
+ w.Header().Add("X-RateLimit-Limit", strconv.Itoa(v))
+ }
+
+ if v := context.Remaining; v >= 0 {
+ w.Header().Add("X-RateLimit-Remaining", strconv.Itoa(v))
+ }
+
+ if v := context.ResetAfter; v >= 0 {
+ vi := int(math.Ceil(v.Seconds()))
+ w.Header().Add("X-RateLimit-Reset", strconv.Itoa(vi))
+ }
+
+ if v := context.RetryAfter; v >= 0 {
+ vi := int(math.Ceil(v.Seconds()))
+ w.Header().Add("Retry-After", strconv.Itoa(vi))
+ }
+}
diff --git a/vendor/gopkg.in/throttled/throttled.v2/http_test.go b/vendor/gopkg.in/throttled/throttled.v2/http_test.go
new file mode 100644
index 0000000..42761da
--- /dev/null
+++ b/vendor/gopkg.in/throttled/throttled.v2/http_test.go
@@ -0,0 +1,99 @@
+package throttled_test
+
+import (
+ "errors"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+ "time"
+
+ "gopkg.in/throttled/throttled.v2"
+)
+
+type stubLimiter struct {
+}
+
+func (sl *stubLimiter) RateLimit(key string, quantity int) (bool, throttled.RateLimitResult, error) {
+ switch key {
+ case "limit":
+ return true, throttled.RateLimitResult{-1, -1, -1, time.Minute}, nil
+ case "error":
+ return false, throttled.RateLimitResult{}, errors.New("stubLimiter error")
+ default:
+ return false, throttled.RateLimitResult{1, 2, time.Minute, -1}, nil
+ }
+}
+
+type pathGetter struct{}
+
+func (*pathGetter) Key(r *http.Request) string {
+ return r.URL.Path
+}
+
+type httpTestCase struct {
+ path string
+ code int
+ headers map[string]string
+}
+
+func TestHTTPRateLimiter(t *testing.T) {
+ limiter := throttled.HTTPRateLimiter{
+ RateLimiter: &stubLimiter{},
+ VaryBy: &pathGetter{},
+ }
+
+ handler := limiter.RateLimit(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(200)
+ }))
+
+ runHTTPTestCases(t, handler, []httpTestCase{
+ {"ok", 200, map[string]string{"X-Ratelimit-Limit": "1", "X-Ratelimit-Remaining": "2", "X-Ratelimit-Reset": "60"}},
+ {"error", 500, map[string]string{}},
+ {"limit", 429, map[string]string{"Retry-After": "60"}},
+ })
+}
+
+func TestCustomHTTPRateLimiterHandlers(t *testing.T) {
+ limiter := throttled.HTTPRateLimiter{
+ RateLimiter: &stubLimiter{},
+ VaryBy: &pathGetter{},
+ DeniedHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ http.Error(w, "custom limit exceeded", 400)
+ }),
+ Error: func(w http.ResponseWriter, r *http.Request, err error) {
+ http.Error(w, "custom internal error", 501)
+ },
+ }
+
+ handler := limiter.RateLimit(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.WriteHeader(200)
+ }))
+
+ runHTTPTestCases(t, handler, []httpTestCase{
+ {"limit", 400, map[string]string{}},
+ {"error", 501, map[string]string{}},
+ })
+}
+
+func runHTTPTestCases(t *testing.T, h http.Handler, cs []httpTestCase) {
+ for i, c := range cs {
+ req, err := http.NewRequest("GET", c.path, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ rr := httptest.NewRecorder()
+ h.ServeHTTP(rr, req)
+ if have, want := rr.Code, c.code; have != want {
+ t.Errorf("Expected request %d at %s to return %d but got %d",
+ i, c.path, want, have)
+ }
+
+ for name, want := range c.headers {
+ if have := rr.HeaderMap.Get(name); have != want {
+ t.Errorf("Expected request %d at %s to have header '%s: %s' but got '%s'",
+ i, c.path, name, want, have)
+ }
+ }
+ }
+}
diff --git a/vendor/gopkg.in/throttled/throttled.v2/rate.go b/vendor/gopkg.in/throttled/throttled.v2/rate.go
new file mode 100644
index 0000000..8c11cdb
--- /dev/null
+++ b/vendor/gopkg.in/throttled/throttled.v2/rate.go
@@ -0,0 +1,239 @@
+package throttled
+
+import (
+ "fmt"
+ "time"
+)
+
+const (
+ // Maximum number of times to retry SetIfNotExists/CompareAndSwap operations
+ // before returning an error.
+ maxCASAttempts = 10
+)
+
+// A RateLimiter manages limiting the rate of actions by key.
+type RateLimiter interface {
+ // RateLimit checks whether a particular key has exceeded a rate
+ // limit. It also returns a RateLimitResult to provide additional
+ // information about the state of the RateLimiter.
+ //
+ // If the rate limit has not been exceeded, the underlying storage
+ // is updated by the supplied quantity. For example, a quantity of
+ // 1 might be used to rate limit a single request while a greater
+ // quantity could rate limit based on the size of a file upload in
+ // megabytes. If quantity is 0, no update is performed allowing
+ // you to "peek" at the state of the RateLimiter for a given key.
+ RateLimit(key string, quantity int) (bool, RateLimitResult, error)
+}
+
+// RateLimitResult represents the state of the RateLimiter for a
+// given key at the time of the query. This state can be used, for
+// example, to communicate information to the client via HTTP
+// headers. Negative values indicate that the attribute is not
+// relevant to the implementation or state.
+type RateLimitResult struct {
+ // Limit is the maximum number of requests that could be permitted
+ // instantaneously for this key starting from an empty state. For
+ // example, if a rate limiter allows 10 requests per second per
+ // key, Limit would always be 10.
+ Limit int
+
+ // Remaining is the maximum number of requests that could be
+ // permitted instantaneously for this key given the current
+ // state. For example, if a rate limiter allows 10 requests per
+ // second and has already received 6 requests for this key this
+ // second, Remaining would be 4.
+ Remaining int
+
+ // ResetAfter is the time until the RateLimiter returns to its
+ // initial state for a given key. For example, if a rate limiter
+ // manages requests per second and received one request 200ms ago,
+ // Reset would return 800ms. You can also think of this as the time
+ // until Limit and Remaining will be equal.
+ ResetAfter time.Duration
+
+ // RetryAfter is the time until the next request will be permitted.
+ // It should be -1 unless the rate limit has been exceeded.
+ RetryAfter time.Duration
+}
+
+type limitResult struct {
+ limited bool
+}
+
+func (r *limitResult) Limited() bool { return r.limited }
+
+type rateLimitResult struct {
+ limitResult
+
+ limit, remaining int
+ reset, retryAfter time.Duration
+}
+
+func (r *rateLimitResult) Limit() int { return r.limit }
+func (r *rateLimitResult) Remaining() int { return r.remaining }
+func (r *rateLimitResult) Reset() time.Duration { return r.reset }
+func (r *rateLimitResult) RetryAfter() time.Duration { return r.retryAfter }
+
+// Rate describes a frequency of an activity such as the number of requests
+// allowed per minute.
+type Rate struct {
+ period time.Duration // Time between equally spaced requests at the rate
+ count int // Used internally for deprecated `RateLimit` interface only
+}
+
+// RateQuota describes the number of requests allowed per time period.
+// MaxRate specified the maximum sustained rate of requests and must
+// be greater than zero. MaxBurst defines the number of requests that
+// will be allowed to exceed the rate in a single burst and must be
+// greater than or equal to zero.
+//
+// Rate{PerSec(1), 0} would mean that after each request, no more
+// requests will be permitted for that client for one second. In
+// practice, you probably want to set MaxBurst >0 to provide some
+// flexibility to clients that only need to make a handful of
+// requests. In fact a MaxBurst of zero will *never* permit a request
+// with a quantity greater than one because it will immediately exceed
+// the limit.
+type RateQuota struct {
+ MaxRate Rate
+ MaxBurst int
+}
+
+// PerSec represents a number of requests per second.
+func PerSec(n int) Rate { return Rate{time.Second / time.Duration(n), n} }
+
+// PerMin represents a number of requests per minute.
+func PerMin(n int) Rate { return Rate{time.Minute / time.Duration(n), n} }
+
+// PerHour represents a number of requests per hour.
+func PerHour(n int) Rate { return Rate{time.Hour / time.Duration(n), n} }
+
+// PerDay represents a number of requests per day.
+func PerDay(n int) Rate { return Rate{24 * time.Hour / time.Duration(n), n} }
+
+// GCRARateLimiter is a RateLimiter that users the generic cell-rate
+// algorithm. The algorithm has been slightly modified from its usual
+// form to support limiting with an additional quantity parameter, such
+// as for limiting the number of bytes uploaded.
+type GCRARateLimiter struct {
+ limit int
+ // Think of the DVT as our flexibility:
+ // How far can you deviate from the nominal equally spaced schedule?
+ // If you like leaky buckets, think about it as the size of your bucket.
+ delayVariationTolerance time.Duration
+ // Think of the emission interval as the time between events
+ // in the nominal equally spaced schedule. If you like leaky buckets,
+ // think of it as how frequently the bucket leaks one unit.
+ emissionInterval time.Duration
+
+ store GCRAStore
+}
+
+// NewGCRARateLimiter creates a GCRARateLimiter. quota.Count defines
+// the maximum number of requests permitted in an instantaneous burst
+// and quota.Count / quota.Period defines the maximum sustained
+// rate. For example, PerMin(60) permits 60 requests instantly per key
+// followed by one request per second indefinitely whereas PerSec(1)
+// only permits one request per second with no tolerance for bursts.
+func NewGCRARateLimiter(st GCRAStore, quota RateQuota) (*GCRARateLimiter, error) {
+ if quota.MaxBurst < 0 {
+ return nil, fmt.Errorf("Invalid RateQuota %#v. MaxBurst must be greater than zero.", quota)
+ }
+ if quota.MaxRate.period <= 0 {
+ return nil, fmt.Errorf("Invalid RateQuota %#v. MaxRate must be greater than zero.", quota)
+ }
+
+ return &GCRARateLimiter{
+ delayVariationTolerance: quota.MaxRate.period * (time.Duration(quota.MaxBurst) + 1),
+ emissionInterval: quota.MaxRate.period,
+ limit: quota.MaxBurst + 1,
+ store: st,
+ }, nil
+}
+
+// RateLimit checks whether a particular key has exceeded a rate
+// limit. It also returns a RateLimitResult to provide additional
+// information about the state of the RateLimiter.
+//
+// If the rate limit has not been exceeded, the underlying storage is
+// updated by the supplied quantity. For example, a quantity of 1
+// might be used to rate limit a single request while a greater
+// quantity could rate limit based on the size of a file upload in
+// megabytes. If quantity is 0, no update is performed allowing you
+// to "peek" at the state of the RateLimiter for a given key.
+func (g *GCRARateLimiter) RateLimit(key string, quantity int) (bool, RateLimitResult, error) {
+ var tat, newTat, now time.Time
+ var ttl time.Duration
+ rlc := RateLimitResult{Limit: g.limit, RetryAfter: -1}
+ limited := false
+
+ i := 0
+ for {
+ var err error
+ var tatVal int64
+ var updated bool
+
+ // tat refers to the theoretical arrival time that would be expected
+ // from equally spaced requests at exactly the rate limit.
+ tatVal, now, err = g.store.GetWithTime(key)
+ if err != nil {
+ return false, rlc, err
+ }
+
+ if tatVal == -1 {
+ tat = now
+ } else {
+ tat = time.Unix(0, tatVal)
+ }
+
+ increment := time.Duration(quantity) * g.emissionInterval
+ if now.After(tat) {
+ newTat = now.Add(increment)
+ } else {
+ newTat = tat.Add(increment)
+ }
+
+ // Block the request if the next permitted time is in the future
+ allowAt := newTat.Add(-(g.delayVariationTolerance))
+ if diff := now.Sub(allowAt); diff < 0 {
+ if increment <= g.delayVariationTolerance {
+ rlc.RetryAfter = -diff
+ }
+ ttl = tat.Sub(now)
+ limited = true
+ break
+ }
+
+ ttl = newTat.Sub(now)
+
+ if tatVal == -1 {
+ updated, err = g.store.SetIfNotExistsWithTTL(key, newTat.UnixNano(), ttl)
+ } else {
+ updated, err = g.store.CompareAndSwapWithTTL(key, tatVal, newTat.UnixNano(), ttl)
+ }
+
+ if err != nil {
+ return false, rlc, err
+ }
+ if updated {
+ break
+ }
+
+ i++
+ if i > maxCASAttempts {
+ return false, rlc, fmt.Errorf(
+ "Failed to store updated rate limit data for key %s after %d attempts",
+ key, i,
+ )
+ }
+ }
+
+ next := g.delayVariationTolerance - ttl
+ if next > -g.emissionInterval {
+ rlc.Remaining = int(next / g.emissionInterval)
+ }
+ rlc.ResetAfter = ttl
+
+ return limited, rlc, nil
+}
diff --git a/vendor/gopkg.in/throttled/throttled.v2/rate_test.go b/vendor/gopkg.in/throttled/throttled.v2/rate_test.go
new file mode 100644
index 0000000..292a050
--- /dev/null
+++ b/vendor/gopkg.in/throttled/throttled.v2/rate_test.go
@@ -0,0 +1,128 @@
+package throttled_test
+
+import (
+ "testing"
+ "time"
+
+ "gopkg.in/throttled/throttled.v2"
+ "gopkg.in/throttled/throttled.v2/store/memstore"
+)
+
+const deniedStatus = 429
+
+type testStore struct {
+ store throttled.GCRAStore
+
+ clock time.Time
+ failUpdates bool
+}
+
+func (ts *testStore) GetWithTime(key string) (int64, time.Time, error) {
+ v, _, e := ts.store.GetWithTime(key)
+ return v, ts.clock, e
+}
+
+func (ts *testStore) SetIfNotExistsWithTTL(key string, value int64, ttl time.Duration) (bool, error) {
+ if ts.failUpdates {
+ return false, nil
+ }
+ return ts.store.SetIfNotExistsWithTTL(key, value, ttl)
+}
+
+func (ts *testStore) CompareAndSwapWithTTL(key string, old, new int64, ttl time.Duration) (bool, error) {
+ if ts.failUpdates {
+ return false, nil
+ }
+ return ts.store.CompareAndSwapWithTTL(key, old, new, ttl)
+}
+
+func TestRateLimit(t *testing.T) {
+ limit := 5
+ rq := throttled.RateQuota{throttled.PerSec(1), limit - 1}
+ start := time.Unix(0, 0)
+ cases := []struct {
+ now time.Time
+ volume, remaining int
+ reset, retry time.Duration
+ limited bool
+ }{
+ // You can never make a request larger than the maximum
+ 0: {start, 6, 5, 0, -1, true},
+ // Rate limit normal requests appropriately
+ 1: {start, 1, 4, time.Second, -1, false},
+ 2: {start, 1, 3, 2 * time.Second, -1, false},
+ 3: {start, 1, 2, 3 * time.Second, -1, false},
+ 4: {start, 1, 1, 4 * time.Second, -1, false},
+ 5: {start, 1, 0, 5 * time.Second, -1, false},
+ 6: {start, 1, 0, 5 * time.Second, time.Second, true},
+ 7: {start.Add(3000 * time.Millisecond), 1, 2, 3000 * time.Millisecond, -1, false},
+ 8: {start.Add(3100 * time.Millisecond), 1, 1, 3900 * time.Millisecond, -1, false},
+ 9: {start.Add(4000 * time.Millisecond), 1, 1, 4000 * time.Millisecond, -1, false},
+ 10: {start.Add(8000 * time.Millisecond), 1, 4, 1000 * time.Millisecond, -1, false},
+ 11: {start.Add(9500 * time.Millisecond), 1, 4, 1000 * time.Millisecond, -1, false},
+ // Zero-volume request just peeks at the state
+ 12: {start.Add(9500 * time.Millisecond), 0, 4, time.Second, -1, false},
+ // High-volume request uses up more of the limit
+ 13: {start.Add(9500 * time.Millisecond), 2, 2, 3 * time.Second, -1, false},
+ // Large requests cannot exceed limits
+ 14: {start.Add(9500 * time.Millisecond), 5, 2, 3 * time.Second, 3 * time.Second, true},
+ }
+
+ mst, err := memstore.New(0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ st := testStore{store: mst}
+
+ rl, err := throttled.NewGCRARateLimiter(&st, rq)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // Start the server
+ for i, c := range cases {
+ st.clock = c.now
+
+ limited, context, err := rl.RateLimit("foo", c.volume)
+ if err != nil {
+ t.Fatalf("%d: %#v", i, err)
+ }
+
+ if limited != c.limited {
+ t.Errorf("%d: expected Limited to be %t but got %t", i, c.limited, limited)
+ }
+
+ if have, want := context.Limit, limit; have != want {
+ t.Errorf("%d: expected Limit to be %d but got %d", i, want, have)
+ }
+
+ if have, want := context.Remaining, c.remaining; have != want {
+ t.Errorf("%d: expected Remaining to be %d but got %d", i, want, have)
+ }
+
+ if have, want := context.ResetAfter, c.reset; have != want {
+ t.Errorf("%d: expected ResetAfter to be %s but got %s", i, want, have)
+ }
+
+ if have, want := context.RetryAfter, c.retry; have != want {
+ t.Errorf("%d: expected RetryAfter to be %d but got %d", i, want, have)
+ }
+ }
+}
+
+func TestRateLimitUpdateFailures(t *testing.T) {
+ rq := throttled.RateQuota{throttled.PerSec(1), 1}
+ mst, err := memstore.New(0)
+ if err != nil {
+ t.Fatal(err)
+ }
+ st := testStore{store: mst, failUpdates: true}
+ rl, err := throttled.NewGCRARateLimiter(&st, rq)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if _, _, err := rl.RateLimit("foo", 1); err == nil {
+ t.Error("Expected limiting to fail when store updates fail")
+ }
+}
diff --git a/vendor/gopkg.in/throttled/throttled.v2/store.go b/vendor/gopkg.in/throttled/throttled.v2/store.go
new file mode 100644
index 0000000..a26bbc2
--- /dev/null
+++ b/vendor/gopkg.in/throttled/throttled.v2/store.go
@@ -0,0 +1,34 @@
+package throttled
+
+import (
+ "time"
+)
+
+// GCRAStore is the interface to implement to store state for a GCRA
+// rate limiter
+type GCRAStore interface {
+ // GetWithTime returns the value of the key if it is in the store
+ // or -1 if it does not exist. It also returns the current time at
+ // the Store. The time must be representable as a positive int64
+ // of nanoseconds since the epoch.
+ //
+ // GCRA assumes that all instances sharing the same Store also
+ // share the same clock. Using separate clocks will work if the
+ // skew is small but not recommended in practice unless you're
+ // lucky enough to be hooked up to GPS or atomic clocks.
+ GetWithTime(key string) (int64, time.Time, error)
+
+ // SetIfNotExistsWithTTL sets the value of key only if it is not
+ // already set in the store it returns whether a new value was
+ // set. If the store supports expiring keys and a new value was
+ // set, the key will expire after the provided ttl.
+ SetIfNotExistsWithTTL(key string, value int64, ttl time.Duration) (bool, error)
+
+ // CompareAndSwapWithTTL atomically compares the value at key to
+ // the old value. If it matches, it sets it to the new value and
+ // returns true. Otherwise, it returns false. If the key does not
+ // exist in the store, it returns false with no error. If the
+ // store supports expiring keys and the swap succeeded, the key
+ // will expire after the provided ttl.
+ CompareAndSwapWithTTL(key string, old, new int64, ttl time.Duration) (bool, error)
+}
diff --git a/vendor/gopkg.in/throttled/throttled.v2/store/deprecated.go b/vendor/gopkg.in/throttled/throttled.v2/store/deprecated.go
new file mode 100644
index 0000000..5476e87
--- /dev/null
+++ b/vendor/gopkg.in/throttled/throttled.v2/store/deprecated.go
@@ -0,0 +1,32 @@
+// Package store contains deprecated aliases for subpackages
+package store // import "gopkg.in/throttled/throttled.v2/store"
+
+import (
+ "github.com/garyburd/redigo/redis"
+
+ "gopkg.in/throttled/throttled.v2/store/memstore"
+ "gopkg.in/throttled/throttled.v2/store/redigostore"
+)
+
+// DEPRECATED. NewMemStore is a compatible alias for mem.New
+func NewMemStore(maxKeys int) *memstore.MemStore {
+ st, err := memstore.New(maxKeys)
+ if err != nil {
+ // As of this writing, `lru.New` can only return an error if you pass
+ // maxKeys <= 0 so this should never occur.
+ panic(err)
+ }
+ return st
+}
+
+// DEPRECATED. NewMemStore is a compatible alias for redis.New
+func NewRedisStore(pool *redis.Pool, keyPrefix string, db int) *redigostore.RedigoStore {
+ st, err := redigostore.New(pool, keyPrefix, db)
+ if err != nil {
+ // As of this writing, creating a Redis store never returns an error
+ // so this should be safe while providing some ability to return errors
+ // in the future.
+ panic(err)
+ }
+ return st
+}
diff --git a/vendor/gopkg.in/throttled/throttled.v2/store/memstore/memstore.go b/vendor/gopkg.in/throttled/throttled.v2/store/memstore/memstore.go
new file mode 100644
index 0000000..5d8fee8
--- /dev/null
+++ b/vendor/gopkg.in/throttled/throttled.v2/store/memstore/memstore.go
@@ -0,0 +1,127 @@
+// Package memstore offers an in-memory store implementation for throttled.
+package memstore // import "gopkg.in/throttled/throttled.v2/store/memstore"
+
+import (
+ "sync"
+ "sync/atomic"
+ "time"
+
+ "github.com/hashicorp/golang-lru"
+)
+
+// MemStore is an in-memory store implementation for throttled. It
+// supports evicting the least recently used keys to control memory
+// usage. It is stored in memory in the current process and thus
+// doesn't share state with other rate limiters.
+type MemStore struct {
+ sync.RWMutex
+ keys *lru.Cache
+ m map[string]*int64
+}
+
+// New initializes a Store. If maxKeys > 0, the number of different
+// keys is restricted to the specified amount. In this case, it uses
+// an LRU algorithm to evict older keys to make room for newer
+// ones. If maxKeys <= 0, there is no limit on the number of keys,
+// which may use an unbounded amount of memory.
+func New(maxKeys int) (*MemStore, error) {
+ var m *MemStore
+
+ if maxKeys > 0 {
+ keys, err := lru.New(maxKeys)
+ if err != nil {
+ return nil, err
+ }
+
+ m = &MemStore{
+ keys: keys,
+ }
+ } else {
+ m = &MemStore{
+ m: make(map[string]*int64),
+ }
+ }
+ return m, nil
+}
+
+// GetWithTime returns the value of the key if it is in the store or
+// -1 if it does not exist. It also returns the current local time on
+// the machine.
+func (ms *MemStore) GetWithTime(key string) (int64, time.Time, error) {
+ now := time.Now()
+ valP, ok := ms.get(key, false)
+
+ if !ok {
+ return -1, now, nil
+ }
+
+ return atomic.LoadInt64(valP), now, nil
+}
+
+// SetIfNotExistsWithTTL sets the value of key only if it is not
+// already set in the store it returns whether a new value was set. It
+// ignores the ttl.
+func (ms *MemStore) SetIfNotExistsWithTTL(key string, value int64, _ time.Duration) (bool, error) {
+ _, ok := ms.get(key, false)
+
+ if ok {
+ return false, nil
+ }
+
+ ms.Lock()
+ defer ms.Unlock()
+
+ _, ok = ms.get(key, true)
+
+ if ok {
+ return false, nil
+ }
+
+ // Store a pointer to a new instance so that the caller
+ // can't mutate the value after setting
+ v := value
+
+ if ms.keys != nil {
+ ms.keys.Add(key, &v)
+ } else {
+ ms.m[key] = &v
+ }
+
+ return true, nil
+}
+
+// CompareAndSwapWithTTL atomically compares the value at key to the
+// old value. If it matches, it sets it to the new value and returns
+// true. Otherwise, it returns false. If the key does not exist in the
+// store, it returns false with no error. It ignores the ttl.
+func (ms *MemStore) CompareAndSwapWithTTL(key string, old, new int64, _ time.Duration) (bool, error) {
+ valP, ok := ms.get(key, false)
+
+ if !ok {
+ return false, nil
+ }
+
+ return atomic.CompareAndSwapInt64(valP, old, new), nil
+}
+
+func (ms *MemStore) get(key string, locked bool) (*int64, bool) {
+ var valP *int64
+ var ok bool
+
+ if ms.keys != nil {
+ var valI interface{}
+
+ valI, ok = ms.keys.Get(key)
+ if ok {
+ valP = valI.(*int64)
+ }
+ } else {
+ if !locked {
+ ms.RLock()
+ defer ms.RUnlock()
+ }
+ valP, ok = ms.m[key]
+ }
+
+ return valP, ok
+}
diff --git a/vendor/gopkg.in/throttled/throttled.v2/store/memstore/memstore_test.go b/vendor/gopkg.in/throttled/throttled.v2/store/memstore/memstore_test.go
new file mode 100644
index 0000000..ef003d3
--- /dev/null
+++ b/vendor/gopkg.in/throttled/throttled.v2/store/memstore/memstore_test.go
@@ -0,0 +1,40 @@
+package memstore_test
+
+import (
+ "testing"
+
+ "gopkg.in/throttled/throttled.v2/store/memstore"
+ "gopkg.in/throttled/throttled.v2/store/storetest"
+)
+
+func TestMemStoreLRU(t *testing.T) {
+ st, err := memstore.New(10)
+ if err != nil {
+ t.Fatal(err)
+ }
+ storetest.TestGCRAStore(t, st)
+}
+
+func TestMemStoreUnlimited(t *testing.T) {
+ st, err := memstore.New(10)
+ if err != nil {
+ t.Fatal(err)
+ }
+ storetest.TestGCRAStore(t, st)
+}
+
+func BenchmarkMemStoreLRU(b *testing.B) {
+ st, err := memstore.New(10)
+ if err != nil {
+ b.Fatal(err)
+ }
+ storetest.BenchmarkGCRAStore(b, st)
+}
+
+func BenchmarkMemStoreUnlimited(b *testing.B) {
+ st, err := memstore.New(0)
+ if err != nil {
+ b.Fatal(err)
+ }
+ storetest.BenchmarkGCRAStore(b, st)
+}
diff --git a/vendor/gopkg.in/throttled/throttled.v2/store/redigostore/redigostore.go b/vendor/gopkg.in/throttled/throttled.v2/store/redigostore/redigostore.go
new file mode 100644
index 0000000..54208fa
--- /dev/null
+++ b/vendor/gopkg.in/throttled/throttled.v2/store/redigostore/redigostore.go
@@ -0,0 +1,156 @@
+// Package redigostore offers Redis-based store implementation for throttled using redigo.
+package redigostore // import "gopkg.in/throttled/throttled.v2/store/redigostore"
+
+import (
+ "strings"
+ "time"
+
+ "github.com/garyburd/redigo/redis"
+)
+
+const (
+ redisCASMissingKey = "key does not exist"
+ redisCASScript = `
+local v = redis.call('get', KEYS[1])
+if v == false then
+ return redis.error_reply("key does not exist")
+end
+if v ~= ARGV[1] then
+ return 0
+end
+if ARGV[3] ~= "0" then
+ redis.call('setex', KEYS[1], ARGV[3], ARGV[2])
+else
+ redis.call('set', KEYS[1], ARGV[2])
+end
+return 1
+`
+)
+
+// RedigoStore implements a Redis-based store using redigo.
+type RedigoStore struct {
+ pool *redis.Pool
+ prefix string
+ db int
+}
+
+// New creates a new Redis-based store, using the provided pool to get
+// its connections. The keys will have the specified keyPrefix, which
+// may be an empty string, and the database index specified by db will
+// be selected to store the keys. Any updating operations will reset
+// the key TTL to the provided value rounded down to the nearest
+// second. Depends on Redis 2.6+ for EVAL support.
+func New(pool *redis.Pool, keyPrefix string, db int) (*RedigoStore, error) {
+ return &RedigoStore{
+ pool: pool,
+ prefix: keyPrefix,
+ db: db,
+ }, nil
+}
+
+// GetWithTime returns the value of the key if it is in the store
+// or -1 if it does not exist. It also returns the current time at
+// the redis server to microsecond precision.
+func (r *RedigoStore) GetWithTime(key string) (int64, time.Time, error) {
+ var now time.Time
+
+ key = r.prefix + key
+
+ conn, err := r.getConn()
+ if err != nil {
+ return 0, now, err
+ }
+ defer conn.Close()
+
+ conn.Send("TIME")
+ conn.Send("GET", key)
+ conn.Flush()
+ timeReply, err := redis.Values(conn.Receive())
+ if err != nil {
+ return 0, now, err
+ }
+
+ var s, us int64
+ if _, err := redis.Scan(timeReply, &s, &us); err != nil {
+ return 0, now, err
+ }
+ now = time.Unix(s, us*int64(time.Microsecond))
+
+ v, err := redis.Int64(conn.Receive())
+ if err == redis.ErrNil {
+ return -1, now, nil
+ } else if err != nil {
+ return 0, now, err
+ }
+
+ return v, now, nil
+}
+
+// SetIfNotExistsWithTTL sets the value of key only if it is not
+// already set in the store it returns whether a new value was set.
+// If a new value was set, the ttl in the key is also set, though this
+// operation is not performed atomically.
+func (r *RedigoStore) SetIfNotExistsWithTTL(key string, value int64, ttl time.Duration) (bool, error) {
+ key = r.prefix + key
+
+ conn, err := r.getConn()
+ if err != nil {
+ return false, err
+ }
+ defer conn.Close()
+
+ v, err := redis.Int64(conn.Do("SETNX", key, value))
+ if err != nil {
+ return false, err
+ }
+
+ updated := v == 1
+
+ if ttl >= time.Second {
+ if _, err := conn.Do("EXPIRE", key, int(ttl.Seconds())); err != nil {
+ return updated, err
+ }
+ }
+
+ return updated, nil
+}
+
+// CompareAndSwapWithTTL atomically compares the value at key to the
+// old value. If it matches, it sets it to the new value and returns
+// true. Otherwise, it returns false. If the key does not exist in the
+// store, it returns false with no error. If the swap succeeds, the
+// ttl for the key is updated atomically.
+func (r *RedigoStore) CompareAndSwapWithTTL(key string, old, new int64, ttl time.Duration) (bool, error) {
+ key = r.prefix + key
+ conn, err := r.getConn()
+ if err != nil {
+ return false, err
+ }
+ defer conn.Close()
+
+ swapped, err := redis.Bool(conn.Do("EVAL", redisCASScript, 1, key, old, new, int(ttl.Seconds())))
+ if err != nil {
+ if strings.Contains(err.Error(), redisCASMissingKey) {
+ return false, nil
+ }
+
+ return false, err
+ }
+
+ return swapped, nil
+}
+
+// Select the specified database index.
+func (r *RedigoStore) getConn() (redis.Conn, error) {
+ conn := r.pool.Get()
+
+ // Select the specified database
+ if r.db > 0 {
+ if _, err := redis.String(conn.Do("SELECT", r.db)); err != nil {
+ conn.Close()
+ return nil, err
+ }
+ }
+
+ return conn, nil
+}
diff --git a/vendor/gopkg.in/throttled/throttled.v2/store/redigostore/redisstore_test.go b/vendor/gopkg.in/throttled/throttled.v2/store/redigostore/redisstore_test.go
new file mode 100644
index 0000000..d47b635
--- /dev/null
+++ b/vendor/gopkg.in/throttled/throttled.v2/store/redigostore/redisstore_test.go
@@ -0,0 +1,85 @@
+package redigostore_test
+
+import (
+ "testing"
+ "time"
+
+ "github.com/garyburd/redigo/redis"
+
+ "gopkg.in/throttled/throttled.v2/store/redigostore"
+ "gopkg.in/throttled/throttled.v2/store/storetest"
+)
+
+const (
+ redisTestDB = 1
+ redisTestPrefix = "throttled:"
+)
+
+func getPool() *redis.Pool {
+ pool := &redis.Pool{
+ MaxIdle: 3,
+ IdleTimeout: 30 * time.Second,
+ Dial: func() (redis.Conn, error) {
+ return redis.Dial("tcp", ":6379")
+ },
+ TestOnBorrow: func(c redis.Conn, t time.Time) error {
+ _, err := c.Do("PING")
+ return err
+ },
+ }
+ return pool
+}
+
+func TestRedisStore(t *testing.T) {
+ c, st := setupRedis(t, 0)
+ defer c.Close()
+ defer clearRedis(c)
+
+ clearRedis(c)
+ storetest.TestGCRAStore(t, st)
+ storetest.TestGCRAStoreTTL(t, st)
+}
+
+func BenchmarkRedisStore(b *testing.B) {
+ c, st := setupRedis(b, 0)
+ defer c.Close()
+ defer clearRedis(c)
+
+ storetest.BenchmarkGCRAStore(b, st)
+}
+
+func clearRedis(c redis.Conn) error {
+ keys, err := redis.Values(c.Do("KEYS", redisTestPrefix+"*"))
+ if err != nil {
+ return err
+ }
+
+ if _, err := redis.Int(c.Do("DEL", keys...)); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func setupRedis(tb testing.TB, ttl time.Duration) (redis.Conn, *redigostore.RedigoStore) {
+ pool := getPool()
+ c := pool.Get()
+
+ if _, err := redis.String(c.Do("PING")); err != nil {
+ c.Close()
+ tb.Skip("redis server not available on localhost port 6379")
+ }
+
+ if _, err := redis.String(c.Do("SELECT", redisTestDB)); err != nil {
+ c.Close()
+ tb.Fatal(err)
+ }
+
+ st, err := redigostore.New(pool, redisTestPrefix, redisTestDB)
+ if err != nil {
+ c.Close()
+ tb.Fatal(err)
+ }
+
+ return c, st
+}
diff --git a/vendor/gopkg.in/throttled/throttled.v2/store/storetest/doc.go b/vendor/gopkg.in/throttled/throttled.v2/store/storetest/doc.go
new file mode 100644
index 0000000..ecfee26
--- /dev/null
+++ b/vendor/gopkg.in/throttled/throttled.v2/store/storetest/doc.go
@@ -0,0 +1,2 @@
+// Package storetest provides a helper for testing throttled stores.
+package storetest // import "gopkg.in/throttled/throttled.v2/store/storetest"
diff --git a/vendor/gopkg.in/throttled/throttled.v2/store/storetest/storetest.go b/vendor/gopkg.in/throttled/throttled.v2/store/storetest/storetest.go
new file mode 100644
index 0000000..191b40a
--- /dev/null
+++ b/vendor/gopkg.in/throttled/throttled.v2/store/storetest/storetest.go
@@ -0,0 +1,176 @@
+// Package storetest provides a helper for testing throttled stores.
+package storetest // import "gopkg.in/throttled/throttled.v2/store/storetest"
+
+import (
+ "math/rand"
+ "strconv"
+ "sync/atomic"
+ "testing"
+ "time"
+
+ "gopkg.in/throttled/throttled.v2"
+)
+
+// TestGCRAStore tests the behavior of a GCRAStore implementation for
+// compliance with the throttled API. It does not require support
+// for TTLs.
+func TestGCRAStore(t *testing.T, st throttled.GCRAStore) {
+ // GetWithTime a missing key
+ if have, _, err := st.GetWithTime("foo"); err != nil {
+ t.Fatal(err)
+ } else if have != -1 {
+ t.Errorf("expected GetWithTime to return -1 for a missing key but got %d", have)
+ }
+
+ // SetIfNotExists on a new key
+ want := int64(1)
+
+ if set, err := st.SetIfNotExistsWithTTL("foo", want, 0); err != nil {
+ t.Fatal(err)
+ } else if !set {
+ t.Errorf("expected SetIfNotExists on an empty key to succeed")
+ }
+
+ before := time.Now()
+
+ if have, now, err := st.GetWithTime("foo"); err != nil {
+ t.Fatal(err)
+ } else if have != want {
+ t.Errorf("expected GetWithTime to return %d but got %d", want, have)
+ } else if now.UnixNano() <= 0 {
+ t.Errorf("expected GetWithTime to return a time representable representable as a positive int64 of nanoseconds since the epoch")
+ } else if now.Before(before) || now.After(time.Now()) {
+ // Note that we make the assumption here that the store is running on
+ // the same machine as this test and thus shares a clock. This can be a
+ // little tricky in the case of Redis, which could be running
+ // elsewhere. The test assumes that it's running either locally on on
+ // Travis (where currently the Redis is available on localhost). If new
+ // test environments are procured, this may need to be revisited.
+ t.Errorf("expected GetWithTime to return a time between the time before the call and the time after the call")
+ }
+
+ // SetIfNotExists on an existing key
+ if set, err := st.SetIfNotExistsWithTTL("foo", 123, 0); err != nil {
+ t.Fatal(err)
+ } else if set {
+ t.Errorf("expected SetIfNotExists on an existing key to fail")
+ }
+
+ if have, _, err := st.GetWithTime("foo"); err != nil {
+ t.Fatal(err)
+ } else if have != want {
+ t.Errorf("expected GetWithTime to return %d but got %d", want, have)
+ }
+
+ // SetIfNotExists on a different key
+ if set, err := st.SetIfNotExistsWithTTL("bar", 456, 0); err != nil {
+ t.Fatal(err)
+ } else if !set {
+ t.Errorf("expected SetIfNotExists on an empty key to succeed")
+ }
+
+ // Returns the false on a missing key
+ if swapped, err := st.CompareAndSwapWithTTL("baz", 1, 2, 0); err != nil {
+ t.Fatal(err)
+ } else if swapped {
+ t.Errorf("expected CompareAndSwap to fail on a missing key")
+ }
+
+ // Test a successful CAS
+ want = int64(2)
+
+ if swapped, err := st.CompareAndSwapWithTTL("foo", 1, want, 0); err != nil {
+ t.Fatal(err)
+ } else if !swapped {
+ t.Errorf("expected CompareAndSwap to succeed")
+ }
+
+ if have, _, err := st.GetWithTime("foo"); err != nil {
+ t.Fatal(err)
+ } else if have != want {
+ t.Errorf("expected GetWithTime to return %d but got %d", want, have)
+ }
+
+ // Test an unsuccessful CAS
+ if swapped, err := st.CompareAndSwapWithTTL("foo", 1, 2, 0); err != nil {
+ t.Fatal(err)
+ } else if swapped {
+ t.Errorf("expected CompareAndSwap to fail")
+ }
+
+ if have, _, err := st.GetWithTime("foo"); err != nil {
+ t.Fatal(err)
+ } else if have != want {
+ t.Errorf("expected GetWithTime to return %d but got %d", want, have)
+ }
+}
+
+// TestGCRAStoreTTL tests the behavior of TTLs in a GCRAStore implementation.
+func TestGCRAStoreTTL(t *testing.T, st throttled.GCRAStore) {
+ ttl := time.Second
+ want := int64(1)
+ key := "ttl"
+
+ if _, err := st.SetIfNotExistsWithTTL(key, want, ttl); err != nil {
+ t.Fatal(err)
+ }
+
+ if have, _, err := st.GetWithTime(key); err != nil {
+ t.Fatal(err)
+ } else if have != want {
+ t.Errorf("expected GetWithTime to return %d, got %d", want, have)
+ }
+
+ // I can't think of a generic way to test expiration without a sleep
+ time.Sleep(ttl + time.Millisecond)
+
+ if have, _, err := st.GetWithTime(key); err != nil {
+ t.Fatal(err)
+ } else if have != -1 {
+ t.Errorf("expected GetWithTime to fail on an expired key but got %d", have)
+ }
+}
+
+// BenchmarkGCRAStore runs parallel benchmarks against a GCRAStore implementation.
+// Aside from being useful for performance testing, this is useful for finding
+// race conditions with the Go race detector.
+func BenchmarkGCRAStore(b *testing.B, st throttled.GCRAStore) {
+ seed := int64(42)
+ var attempts, updates int64
+
+ b.RunParallel(func(pb *testing.PB) {
+ // We need atomic behavior around the RNG or go detects a race in the test
+ delta := int64(1)
+ seedValue := atomic.AddInt64(&seed, delta) - delta
+ gen := rand.New(rand.NewSource(seedValue))
+
+ for pb.Next() {
+ key := strconv.FormatInt(gen.Int63n(50), 10)
+
+ var v int64
+ var updated bool
+
+ v, _, err := st.GetWithTime(key)
+ if v == -1 {
+ updated, err = st.SetIfNotExistsWithTTL(key, gen.Int63(), 0)
+ if err != nil {
+ b.Error(err)
+ }
+ } else if err != nil {
+ b.Error(err)
+ } else {
+ updated, err = st.CompareAndSwapWithTTL(key, v, gen.Int63(), 0)
+ if err != nil {
+ b.Error(err)
+ }
+ }
+
+ atomic.AddInt64(&attempts, 1)
+ if updated {
+ atomic.AddInt64(&updates, 1)
+ }
+ }
+ })
+
+ b.Logf("%d/%d update operations succeeed", updates, attempts)
+}
diff --git a/vendor/gopkg.in/throttled/throttled.v2/varyby.go b/vendor/gopkg.in/throttled/throttled.v2/varyby.go
new file mode 100644
index 0000000..dc6a017
--- /dev/null
+++ b/vendor/gopkg.in/throttled/throttled.v2/varyby.go
@@ -0,0 +1,78 @@
+package throttled
+
+import (
+ "bytes"
+ "net/http"
+ "strings"
+)
+
+// VaryBy defines the criteria to use to group requests.
+type VaryBy struct {
+ // Vary by the RemoteAddr as specified by the net/http.Request field.
+ RemoteAddr bool
+
+ // Vary by the HTTP Method as specified by the net/http.Request field.
+ Method bool
+
+ // Vary by the URL's Path as specified by the Path field of the net/http.Request
+ // URL field.
+ Path bool
+
+ // Vary by this list of header names, read from the net/http.Request Header field.
+ Headers []string
+
+ // Vary by this list of parameters, read from the net/http.Request FormValue method.
+ Params []string
+
+ // Vary by this list of cookie names, read from the net/http.Request Cookie method.
+ Cookies []string
+
+ // Use this separator string to concatenate the various criteria of the VaryBy struct.
+ // Defaults to a newline character if empty (\n).
+ Separator string
+
+ // DEPRECATED. Custom specifies the custom-generated key to use for this request.
+ // If not nil, the value returned by this function is used instead of any
+ // VaryBy criteria.
+ Custom func(r *http.Request) string
+}
+
+// Key returns the key for this request based on the criteria defined by the VaryBy struct.
+func (vb *VaryBy) Key(r *http.Request) string {
+ var buf bytes.Buffer
+
+ if vb == nil {
+ return "" // Special case for no vary-by option
+ }
+ if vb.Custom != nil {
+ // A custom key generator is specified
+ return vb.Custom(r)
+ }
+ sep := vb.Separator
+ if sep == "" {
+ sep = "\n" // Separator defaults to newline
+ }
+ if vb.RemoteAddr {
+ buf.WriteString(strings.ToLower(r.RemoteAddr) + sep)
+ }
+ if vb.Method {
+ buf.WriteString(strings.ToLower(r.Method) + sep)
+ }
+ for _, h := range vb.Headers {
+ buf.WriteString(strings.ToLower(r.Header.Get(h)) + sep)
+ }
+ if vb.Path {
+ buf.WriteString(r.URL.Path + sep)
+ }
+ for _, p := range vb.Params {
+ buf.WriteString(r.FormValue(p) + sep)
+ }
+ for _, c := range vb.Cookies {
+ ck, err := r.Cookie(c)
+ if err == nil {
+ buf.WriteString(ck.Value)
+ }
+ buf.WriteString(sep) // Write the separator anyway, whether or not the cookie exists
+ }
+ return buf.String()
+}
diff --git a/vendor/gopkg.in/throttled/throttled.v2/varyby_test.go b/vendor/gopkg.in/throttled/throttled.v2/varyby_test.go
new file mode 100644
index 0000000..66a5f4e
--- /dev/null
+++ b/vendor/gopkg.in/throttled/throttled.v2/varyby_test.go
@@ -0,0 +1,58 @@
+package throttled_test
+
+import (
+ "net/http"
+ "net/url"
+ "testing"
+
+ "gopkg.in/throttled/throttled.v2"
+)
+
+func TestVaryBy(t *testing.T) {
+ u, err := url.Parse("http://localhost/test/path?q=s")
+ if err != nil {
+ panic(err)
+ }
+ ck := &http.Cookie{Name: "ssn", Value: "test"}
+ cases := []struct {
+ vb *throttled.VaryBy
+ r *http.Request
+ k string
+ }{
+ 0: {nil, &http.Request{}, ""},
+ 1: {&throttled.VaryBy{RemoteAddr: true}, &http.Request{RemoteAddr: "::"}, "::\n"},
+ 2: {
+ &throttled.VaryBy{Method: true, Path: true},
+ &http.Request{Method: "POST", URL: u},
+ "post\n/test/path\n",
+ },
+ 3: {
+ &throttled.VaryBy{Headers: []string{"Content-length"}},
+ &http.Request{Header: http.Header{"Content-Type": []string{"text/plain"}, "Content-Length": []string{"123"}}},
+ "123\n",
+ },
+ 4: {
+ &throttled.VaryBy{Separator: ",", Method: true, Headers: []string{"Content-length"}, Params: []string{"q", "user"}},
+ &http.Request{Method: "GET", Header: http.Header{"Content-Type": []string{"text/plain"}, "Content-Length": []string{"123"}}, Form: url.Values{"q": []string{"s"}, "pwd": []string{"secret"}, "user": []string{"test"}}},
+ "get,123,s,test,",
+ },
+ 5: {
+ &throttled.VaryBy{Cookies: []string{"ssn"}},
+ &http.Request{Header: http.Header{"Cookie": []string{ck.String()}}},
+ "test\n",
+ },
+ 6: {
+ &throttled.VaryBy{Cookies: []string{"ssn"}, RemoteAddr: true, Custom: func(r *http.Request) string {
+ return "blah"
+ }},
+ &http.Request{Header: http.Header{"Cookie": []string{ck.String()}}},
+ "blah",
+ },
+ }
+ for i, c := range cases {
+ got := c.vb.Key(c.r)
+ if got != c.k {
+ t.Errorf("%d: expected '%s' (%d), got '%s' (%d)", i, c.k, len(c.k), got, len(got))
+ }
+ }
+}