1
0
Fork 0
mirror of https://github.com/Luzifer/go-holidays.git synced 2024-10-18 14:14:20 +00:00

Modify repo structure

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2018-09-09 23:27:55 +02:00
parent 5d2a405c24
commit a516b9b687
Signed by: luzifer
GPG key ID: DC2729FDD34BE99E
107 changed files with 5473 additions and 1266 deletions

60
Gopkg.lock generated Normal file
View file

@ -0,0 +1,60 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
digest = "1:d2fae669d679d02294b95e1d9f22e794837a3272cfabc6e09545aee3c82f714e"
name = "github.com/Luzifer/rconfig"
packages = ["."]
pruneopts = "UT"
revision = "913d4f1e882a955db383d72af88f50f216b373c5"
version = "v2.1.0"
[[projects]]
digest = "1:c79fb010be38a59d657c48c6ba1d003a8aa651fa56b579d959d74573b7dff8e1"
name = "github.com/gorilla/context"
packages = ["."]
pruneopts = "UT"
revision = "08b5f424b9271eedf6f9f0ce86cb9396ed337a42"
version = "v1.1.1"
[[projects]]
digest = "1:e73f5b0152105f18bc131fba127d9949305c8693f8a762588a82a48f61756f5f"
name = "github.com/gorilla/mux"
packages = ["."]
pruneopts = "UT"
revision = "e3702bed27f0d39777b0b37b664b6280e8ef8fbf"
version = "v1.6.2"
[[projects]]
digest = "1:dab83a1bbc7ad3d7a6ba1a1cc1760f25ac38cdf7d96a5cdd55cd915a4f5ceaf9"
name = "github.com/spf13/pflag"
packages = ["."]
pruneopts = "UT"
revision = "9a97c102cda95a86cec2345a6f09f55a939babf5"
version = "v1.0.2"
[[projects]]
branch = "v2"
digest = "1:b6539350da50de0d3c9b83ae587c06b89be9cb5750443bdd887f1c4077f57776"
name = "gopkg.in/validator.v2"
packages = ["."]
pruneopts = "UT"
revision = "135c24b11c19e52befcae2ec3fca5d9b78c4e98e"
[[projects]]
digest = "1:342378ac4dcb378a5448dd723f0784ae519383532f5e70ade24132c4c8693202"
name = "gopkg.in/yaml.v2"
packages = ["."]
pruneopts = "UT"
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
version = "v2.2.1"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/Luzifer/rconfig",
"github.com/gorilla/mux",
]
solver-name = "gps-cdcl"
solver-version = 1

38
Gopkg.toml Normal file
View file

@ -0,0 +1,38 @@
# Gopkg.toml example
#
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
#
# [prune]
# non-go = false
# go-tests = true
# unused-packages = true
[[constraint]]
name = "github.com/Luzifer/rconfig"
version = "2.1.0"
[[constraint]]
name = "github.com/gorilla/mux"
version = "1.6.2"
[prune]
go-tests = true
unused-packages = true

View file

@ -1,32 +0,0 @@
{
"ImportPath": "github.com/Luzifer/go-holidays/cmd/holiday-api",
"GoVersion": "go1.7",
"GodepVersion": "v78",
"Deps": [
{
"ImportPath": "github.com/Luzifer/go-holidays",
"Rev": "025b87d81e0ce8649eeba2d11c9aa75aaf2e0aa3"
},
{
"ImportPath": "github.com/Luzifer/rconfig",
"Comment": "v1.1.0",
"Rev": "c27bd3a64b5b19556914d9fec69922cf3852d585"
},
{
"ImportPath": "github.com/gorilla/context",
"Rev": "1c83b3eabd45b6d76072b66b746c20815fb2872d"
},
{
"ImportPath": "github.com/gorilla/mux",
"Rev": "49c024275504f0341e5a9971eb7ba7fa3dc7af40"
},
{
"ImportPath": "github.com/spf13/pflag",
"Rev": "c7e63cf4530bcd3ba943729cee0efeff2ebea63f"
},
{
"ImportPath": "gopkg.in/yaml.v2",
"Rev": "31c299268d302dd0aa9a0dcf765a3d58971ac83f"
}
]
}

View file

@ -1,5 +0,0 @@
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.

File diff suppressed because one or more lines are too long

View file

@ -12,7 +12,7 @@ import (
"strings" "strings"
"time" "time"
holidays "github.com/Luzifer/go-holidays" "github.com/Luzifer/go-holidays/holidays"
"github.com/Luzifer/rconfig" "github.com/Luzifer/rconfig"
"github.com/gorilla/mux" "github.com/gorilla/mux"
) )

View file

@ -1,5 +0,0 @@
# 1.1.0 / 2016-06-28
* Support time.Duration config parameters
* Added goreportcard badge
* Added testcase for using bool with ENV and default

View file

@ -1,13 +0,0 @@
Copyright 2015 Knut Ahlers <knut@ahlers.me>
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.

View file

@ -1,8 +0,0 @@
language: go
sudo: false
go:
- 1.3
- 1.4
- 1.5
- tip

View file

@ -1,7 +0,0 @@
context
=======
[![Build Status](https://travis-ci.org/gorilla/context.png?branch=master)](https://travis-ci.org/gorilla/context)
gorilla/context is a general purpose registry for global request variables.
Read the full documentation here: http://www.gorillatoolkit.org/pkg/context

View file

@ -1,8 +0,0 @@
language: go
sudo: false
go:
- 1.3
- 1.4
- 1.5
- tip

View file

@ -1,235 +0,0 @@
mux
===
[![GoDoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux)
[![Build Status](https://travis-ci.org/gorilla/mux.png?branch=master)](https://travis-ci.org/gorilla/mux)
Package gorilla/mux implements a request router and dispatcher.
The name mux stands for "HTTP request multiplexer". Like the standard
http.ServeMux, mux.Router matches incoming requests against a list of
registered routes and calls a handler for the route that matches the URL
or other conditions. The main features are:
* Requests can be matched based on URL host, path, path prefix, schemes,
header and query values, HTTP methods or using custom matchers.
* URL hosts and paths can have variables with an optional regular
expression.
* Registered URLs can be built, or "reversed", which helps maintaining
references to resources.
* Routes can be used as subrouters: nested routes are only tested if the
parent route matches. This is useful to define groups of routes that
share common conditions like a host, a path prefix or other repeated
attributes. As a bonus, this optimizes request matching.
* It implements the http.Handler interface so it is compatible with the
standard http.ServeMux.
Let's start registering a couple of URL paths and handlers:
func main() {
r := mux.NewRouter()
r.HandleFunc("/", HomeHandler)
r.HandleFunc("/products", ProductsHandler)
r.HandleFunc("/articles", ArticlesHandler)
http.Handle("/", r)
}
Here we register three routes mapping URL paths to handlers. This is
equivalent to how http.HandleFunc() works: if an incoming request URL matches
one of the paths, the corresponding handler is called passing
(http.ResponseWriter, *http.Request) as parameters.
Paths can have variables. They are defined using the format {name} or
{name:pattern}. If a regular expression pattern is not defined, the matched
variable will be anything until the next slash. For example:
r := mux.NewRouter()
r.HandleFunc("/products/{key}", ProductHandler)
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
The names are used to create a map of route variables which can be retrieved
calling mux.Vars():
vars := mux.Vars(request)
category := vars["category"]
And this is all you need to know about the basic usage. More advanced options
are explained below.
Routes can also be restricted to a domain or subdomain. Just define a host
pattern to be matched. They can also have variables:
r := mux.NewRouter()
// Only matches if domain is "www.example.com".
r.Host("www.example.com")
// Matches a dynamic subdomain.
r.Host("{subdomain:[a-z]+}.domain.com")
There are several other matchers that can be added. To match path prefixes:
r.PathPrefix("/products/")
...or HTTP methods:
r.Methods("GET", "POST")
...or URL schemes:
r.Schemes("https")
...or header values:
r.Headers("X-Requested-With", "XMLHttpRequest")
...or query values:
r.Queries("key", "value")
...or to use a custom matcher function:
r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
return r.ProtoMajor == 0
})
...and finally, it is possible to combine several matchers in a single route:
r.HandleFunc("/products", ProductsHandler).
Host("www.example.com").
Methods("GET").
Schemes("http")
Setting the same matching conditions again and again can be boring, so we have
a way to group several routes that share the same requirements.
We call it "subrouting".
For example, let's say we have several URLs that should only match when the
host is `www.example.com`. Create a route for that host and get a "subrouter"
from it:
r := mux.NewRouter()
s := r.Host("www.example.com").Subrouter()
Then register routes in the subrouter:
s.HandleFunc("/products/", ProductsHandler)
s.HandleFunc("/products/{key}", ProductHandler)
s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
The three URL paths we registered above will only be tested if the domain is
`www.example.com`, because the subrouter is tested first. This is not
only convenient, but also optimizes request matching. You can create
subrouters combining any attribute matchers accepted by a route.
Subrouters can be used to create domain or path "namespaces": you define
subrouters in a central place and then parts of the app can register its
paths relatively to a given subrouter.
There's one more thing about subroutes. When a subrouter has a path prefix,
the inner routes use it as base for their paths:
r := mux.NewRouter()
s := r.PathPrefix("/products").Subrouter()
// "/products/"
s.HandleFunc("/", ProductsHandler)
// "/products/{key}/"
s.HandleFunc("/{key}/", ProductHandler)
// "/products/{key}/details"
s.HandleFunc("/{key}/details", ProductDetailsHandler)
Now let's see how to build registered URLs.
Routes can be named. All routes that define a name can have their URLs built,
or "reversed". We define a name calling Name() on a route. For example:
r := mux.NewRouter()
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
Name("article")
To build a URL, get the route and call the URL() method, passing a sequence of
key/value pairs for the route variables. For the previous route, we would do:
url, err := r.Get("article").URL("category", "technology", "id", "42")
...and the result will be a url.URL with the following path:
"/articles/technology/42"
This also works for host variables:
r := mux.NewRouter()
r.Host("{subdomain}.domain.com").
Path("/articles/{category}/{id:[0-9]+}").
HandlerFunc(ArticleHandler).
Name("article")
// url.String() will be "http://news.domain.com/articles/technology/42"
url, err := r.Get("article").URL("subdomain", "news",
"category", "technology",
"id", "42")
All variables defined in the route are required, and their values must
conform to the corresponding patterns. These requirements guarantee that a
generated URL will always match a registered route -- the only exception is
for explicitly defined "build-only" routes which never match.
Regex support also exists for matching Headers within a route. For example, we could do:
r.HeadersRegexp("Content-Type", "application/(text|json)")
...and the route will match both requests with a Content-Type of `application/json` as well as
`application/text`
There's also a way to build only the URL host or path for a route:
use the methods URLHost() or URLPath() instead. For the previous route,
we would do:
// "http://news.domain.com/"
host, err := r.Get("article").URLHost("subdomain", "news")
// "/articles/technology/42"
path, err := r.Get("article").URLPath("category", "technology", "id", "42")
And if you use subrouters, host and path defined separately can be built
as well:
r := mux.NewRouter()
s := r.Host("{subdomain}.domain.com").Subrouter()
s.Path("/articles/{category}/{id:[0-9]+}").
HandlerFunc(ArticleHandler).
Name("article")
// "http://news.domain.com/articles/technology/42"
url, err := r.Get("article").URL("subdomain", "news",
"category", "technology",
"id", "42")
## Full Example
Here's a complete, runnable example of a small mux based server:
```go
package main
import (
"net/http"
"github.com/gorilla/mux"
)
func YourHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Gorilla!\n"))
}
func main() {
r := mux.NewRouter()
// Routes consist of a path and a handler function.
r.HandleFunc("/", YourHandler)
// Bind to a port and pass our router in
http.ListenAndServe(":8000", r)
}
```
## License
BSD licensed. See the LICENSE file for details.

View file

@ -1,21 +0,0 @@
sudo: false
language: go
go:
- 1.5.4
- 1.6.3
- 1.7
- tip
matrix:
allow_failures:
- go: tip
install:
- go get github.com/golang/lint/golint
- export PATH=$GOPATH/bin:$PATH
- go install ./...
script:
- verify/all.sh -v
- go test ./...

View file

@ -1,89 +0,0 @@
package yaml
// Set the writer error and return false.
func yaml_emitter_set_writer_error(emitter *yaml_emitter_t, problem string) bool {
emitter.error = yaml_WRITER_ERROR
emitter.problem = problem
return false
}
// Flush the output buffer.
func yaml_emitter_flush(emitter *yaml_emitter_t) bool {
if emitter.write_handler == nil {
panic("write handler not set")
}
// Check if the buffer is empty.
if emitter.buffer_pos == 0 {
return true
}
// If the output encoding is UTF-8, we don't need to recode the buffer.
if emitter.encoding == yaml_UTF8_ENCODING {
if err := emitter.write_handler(emitter, emitter.buffer[:emitter.buffer_pos]); err != nil {
return yaml_emitter_set_writer_error(emitter, "write error: "+err.Error())
}
emitter.buffer_pos = 0
return true
}
// Recode the buffer into the raw buffer.
var low, high int
if emitter.encoding == yaml_UTF16LE_ENCODING {
low, high = 0, 1
} else {
high, low = 1, 0
}
pos := 0
for pos < emitter.buffer_pos {
// See the "reader.c" code for more details on UTF-8 encoding. Note
// that we assume that the buffer contains a valid UTF-8 sequence.
// Read the next UTF-8 character.
octet := emitter.buffer[pos]
var w int
var value rune
switch {
case octet&0x80 == 0x00:
w, value = 1, rune(octet&0x7F)
case octet&0xE0 == 0xC0:
w, value = 2, rune(octet&0x1F)
case octet&0xF0 == 0xE0:
w, value = 3, rune(octet&0x0F)
case octet&0xF8 == 0xF0:
w, value = 4, rune(octet&0x07)
}
for k := 1; k < w; k++ {
octet = emitter.buffer[pos+k]
value = (value << 6) + (rune(octet) & 0x3F)
}
pos += w
// Write the character.
if value < 0x10000 {
var b [2]byte
b[high] = byte(value >> 8)
b[low] = byte(value & 0xFF)
emitter.raw_buffer = append(emitter.raw_buffer, b[0], b[1])
} else {
// Write the character using a surrogate pair (check "reader.c").
var b [4]byte
value -= 0x10000
b[high] = byte(0xD8 + (value >> 18))
b[low] = byte((value >> 10) & 0xFF)
b[high+2] = byte(0xDC + ((value >> 8) & 0xFF))
b[low+2] = byte(value & 0xFF)
emitter.raw_buffer = append(emitter.raw_buffer, b[0], b[1], b[2], b[3])
}
}
// Write the raw buffer.
if err := emitter.write_handler(emitter, emitter.raw_buffer); err != nil {
return yaml_emitter_set_writer_error(emitter, "write error: "+err.Error())
}
emitter.buffer_pos = 0
emitter.raw_buffer = emitter.raw_buffer[:0]
return true
}

View file

@ -1,42 +0,0 @@
package holidays
import "time"
func GregorianEasterSunday(year int) time.Time {
X := year
// Comments from german Wikipedia: https://de.wikipedia.org/wiki/Osterzyklus
// 1. die Säkularzahl: K = X div 100
K := X / 100
// 2. die säkulare Mondschaltung: M = 15 + (3K + 3) div 4 (8K + 13) div 25
M := 15 + (3*K+3)/4 - (8*K+13)/25
// 3. die säkulare Sonnenschaltung: S = 2 (3K + 3) div 4
S := 2 - (3*K+3)/4
// 4. den Mondparameter: A = X mod 19
A := X % 19
// 5. den Keim für den ersten Frühlingsvollmond: D = (19A + M) mod 30
D := (19*A + M) % 30
// 6. die kalendarische Korrekturgröße: R = D div 29 + (D div 28 D div 29) (A div 11)
R := D/29 + (D/28-D/29)*(A/11)
// 7. die Ostergrenze: OG = 21 + D R
OG := 21 + D - R
// 8. den ersten Sonntag im März: SZ = 7 (X + X div 4 + S) mod 7
SZ := 7 - (X+X/4+S)%7
// 9. die Entfernung des Ostersonntags von der
// Ostergrenze (Osterentfernung in Tagen): OE = 7 (OG SZ) mod 7
OE := 7 - (OG-SZ)%7
// 10. das Datum des Ostersonntags als Märzdatum
// (32. März = 1. April usw.): OS = OG + OE
OS := OG + OE
var month int = 3 // March
var day int = OS
if day > 31 {
day = day % 31
month++
}
return time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.Local)
}

View file

@ -1,82 +0,0 @@
package holidays
import (
"errors"
"fmt"
"sort"
"time"
)
const day = 24 * time.Hour
var (
HolidayDataNotFoundError = errors.New("Holiday data not found for requested country-code")
holidayProviders = map[string]holidayDataSource{}
)
func registerHolidayDataSource(code string, hds holidayDataSource) {
if _, ok := holidayProviders[code]; ok {
panic(fmt.Errorf("Duplicatei definition for country code %q", code))
}
holidayProviders[code] = hds
}
// Holiday contains information about an holiday
type Holiday struct {
// Name contains the english representation of the holidays name
Name string `json:"name"`
// LocalizedName contains a map of localizations of the name, key is a ISO 3166-2 country code without subdivision
LocalizedName map[string]string `json:"localized_name"`
// Date contains the date in YYYY-MM-DD notation
Date string `json:"date"`
// ParsedDate is the Date as a time.Time object
ParsedDate time.Time `json:"parsed_date"`
}
func newHoliday(name string, localizedName map[string]string, parsedDate time.Time) Holiday {
return Holiday{
Name: name,
LocalizedName: localizedName,
Date: parsedDate.Format("2006-01-02"),
ParsedDate: parsedDate,
}
}
func dateFromNumbers(year, month, day int) time.Time {
return time.Date(year, time.Month(month), day, 0, 0, 0, 0, time.Local)
}
type holidayDataSource interface {
GetIncludes() []string
GetHolidays(year int) []Holiday
}
// GetHolidays returns the holidays for the given ISO 3166-2 countryCode and year
func GetHolidays(countryCode string, year int) ([]Holiday, error) {
requiredCodes := []string{countryCode}
result := []Holiday{}
for len(requiredCodes) > 0 {
cc := requiredCodes[0]
hds, ok := holidayProviders[cc]
if !ok {
return nil, HolidayDataNotFoundError
}
requiredCodes = append(requiredCodes, hds.GetIncludes()...)
result = append(result, hds.GetHolidays(year)...)
requiredCodes = requiredCodes[1:]
}
sort.Sort(holidays(result))
return result, nil
}
type holidays []Holiday
func (h holidays) Len() int { return len(h) }
func (h holidays) Less(i, j int) bool { return h[i].Date < h[j].Date }
func (h holidays) Swap(i, j int) { h[i], h[j] = h[j], h[i] }

View file

@ -1,119 +0,0 @@
package holidays
func init() {
registerHolidayDataSource("de", holidaysDENational{})
registerHolidayDataSource("de-bb", holidaysDEBB{})
registerHolidayDataSource("de-be", holidaysDEBE{})
registerHolidayDataSource("de-bw", holidaysDEBW{})
registerHolidayDataSource("de-by", holidaysDEBY{})
registerHolidayDataSource("de-hb", holidaysDEHB{})
registerHolidayDataSource("de-he", holidaysDEHE{})
registerHolidayDataSource("de-hh", holidaysDEHH{})
registerHolidayDataSource("de-mv", holidaysDEMV{})
registerHolidayDataSource("de-ni", holidaysDENI{})
registerHolidayDataSource("de-nw", holidaysDENW{})
registerHolidayDataSource("de-rp", holidaysDERP{})
registerHolidayDataSource("de-sh", holidaysDESH{})
registerHolidayDataSource("de-sl", holidaysDESL{})
registerHolidayDataSource("de-sn", holidaysDESN{})
registerHolidayDataSource("de-st", holidaysDEST{})
registerHolidayDataSource("de-th", holidaysDETH{})
}
type holidaysDENational struct{}
func (h holidaysDENational) GetIncludes() []string { return []string{} }
func (h holidaysDENational) GetHolidays(year int) []Holiday {
return []Holiday{
newHoliday("New Year's Day", map[string]string{"de": "Neujahrstag"}, dateFromNumbers(year, 1, 1)),
newHoliday("Labor Day", map[string]string{"de": "Tag der Arbeit"}, dateFromNumbers(year, 5, 1)),
newHoliday("German Unity Day", map[string]string{"de": "Tag der Deutschen Einheit"}, dateFromNumbers(year, 10, 3)),
newHoliday("Christmas Day", map[string]string{"de": "Weihnachtstag"}, dateFromNumbers(year, 12, 25)),
newHoliday("Boxing Day", map[string]string{"de": "Zweiter Weihnachtsfeiertag"}, dateFromNumbers(year, 12, 26)),
newHoliday("Good Friday", map[string]string{"de": "Karfreitag"}, GregorianEasterSunday(year).Add(-2*day)),
newHoliday("Easter Sunday", map[string]string{"de": "Ostersonntag"}, GregorianEasterSunday(year)),
newHoliday("Easter Monday", map[string]string{"de": "Ostermontag"}, GregorianEasterSunday(year).Add(1*day)),
newHoliday("Ascension Day", map[string]string{"de": "Christi Himmelfahrt"}, GregorianEasterSunday(year).Add(39*day)),
newHoliday("Whit Monday", map[string]string{"de": "Pfingstmontag"}, GregorianEasterSunday(year).Add(50*day)),
}
}
type holidaysDEBB struct{}
func (h holidaysDEBB) GetIncludes() []string { return []string{"de"} }
func (h holidaysDEBB) GetHolidays(year int) []Holiday {
return []Holiday{
newHoliday("Reformation Day", map[string]string{"de": "Reformationstag"}, dateFromNumbers(year, 10, 31)),
}
}
type holidaysDEBE struct{ holidaysDENational }
type holidaysDEBW struct{}
func (h holidaysDEBW) GetIncludes() []string { return []string{"de"} }
func (h holidaysDEBW) GetHolidays(year int) []Holiday {
return []Holiday{
newHoliday("Epiphany", map[string]string{"de": "Heilige Drei Könige"}, dateFromNumbers(year, 1, 6)),
newHoliday("All Saints", map[string]string{"de": "Allerheiligen"}, dateFromNumbers(year, 11, 1)),
newHoliday("Corpus Christi", map[string]string{"de": "Fronleichnam"}, GregorianEasterSunday(year).Add(60*day)),
}
}
type holidaysDEBY struct{ holidaysDEBW }
type holidaysDEHB struct{ holidaysDENational }
type holidaysDEHE struct{}
func (h holidaysDEHE) GetIncludes() []string { return []string{"de"} }
func (h holidaysDEHE) GetHolidays(year int) []Holiday {
return []Holiday{
newHoliday("Corpus Christi", map[string]string{"de": "Fronleichnam"}, GregorianEasterSunday(year).Add(60*day)),
}
}
type holidaysDEHH struct{ holidaysDENational }
type holidaysDEMV struct{ holidaysDEBB }
type holidaysDENI struct{ holidaysDENational }
type holidaysDENW struct{}
func (h holidaysDENW) GetIncludes() []string { return []string{"de"} }
func (h holidaysDENW) GetHolidays(year int) []Holiday {
return []Holiday{
newHoliday("All Saints", map[string]string{"de": "Allerheiligen"}, dateFromNumbers(year, 11, 1)),
newHoliday("Corpus Christi", map[string]string{"de": "Fronleichnam"}, GregorianEasterSunday(year).Add(60*day)),
}
}
type holidaysDERP struct{ holidaysDENW }
type holidaysDESH struct{ holidaysDENational }
type holidaysDESL struct{}
func (h holidaysDESL) GetIncludes() []string { return []string{"de"} }
func (h holidaysDESL) GetHolidays(year int) []Holiday {
return []Holiday{
newHoliday("Assumption Day", map[string]string{"de": "Mariä Himmelfahrt"}, dateFromNumbers(year, 8, 15)),
newHoliday("All Saints", map[string]string{"de": "Allerheiligen"}, dateFromNumbers(year, 11, 1)),
newHoliday("Corpus Christi", map[string]string{"de": "Fronleichnam"}, GregorianEasterSunday(year).Add(60*day)),
}
}
type holidaysDESN struct{ holidaysDEBB }
type holidaysDEST struct{}
func (h holidaysDEST) GetIncludes() []string { return []string{"de"} }
func (h holidaysDEST) GetHolidays(year int) []Holiday {
return []Holiday{
newHoliday("Epiphany", map[string]string{"de": "Heilige Drei Könige"}, dateFromNumbers(year, 1, 6)),
newHoliday("Reformation Day", map[string]string{"de": "Reformationstag"}, dateFromNumbers(year, 10, 31)),
}
}
type holidaysDETH struct{ holidaysDEBB }

View file

@ -1,45 +0,0 @@
package holidays
import "time"
func init() {
registerHolidayDataSource("us", holidaysUSNational{})
}
type holidaysUSNational struct{}
func (h holidaysUSNational) GetIncludes() []string { return []string{} }
func (h holidaysUSNational) GetHolidays(year int) []Holiday {
return []Holiday{
newHoliday("New Year's Day", map[string]string{}, h.fixWeekend(dateFromNumbers(year, 1, 1))), // 1st January
newHoliday("Birthday of Martin Luther King, Jr.", map[string]string{}, h.findNextWeekday(dateFromNumbers(year, 1, 15), time.Monday)), // Third Monday in January
newHoliday("Washington's Birthday", map[string]string{}, h.findNextWeekday(dateFromNumbers(year, 2, 15), time.Monday)), // Third Monday in February
newHoliday("Memorial Day", map[string]string{}, h.findNextWeekday(dateFromNumbers(year, 5, 25), time.Monday)), // Last Monday in May
newHoliday("Independence Day", map[string]string{}, h.fixWeekend(dateFromNumbers(year, 7, 4))), // 4 July
newHoliday("Labor Day", map[string]string{}, h.findNextWeekday(dateFromNumbers(year, 9, 1), time.Monday)), // First Monday in September
newHoliday("Columbus Day", map[string]string{}, h.findNextWeekday(dateFromNumbers(year, 10, 8), time.Monday)), // Second Monday in October
newHoliday("Veterans Day", map[string]string{}, h.fixWeekend(dateFromNumbers(year, 11, 11))), // 11 November
newHoliday("Thanksgiving Day", map[string]string{}, h.findNextWeekday(dateFromNumbers(year, 11, 23), time.Thursday)), // Fourth Thursday in November
newHoliday("Christmas Day", map[string]string{}, h.fixWeekend(dateFromNumbers(year, 12, 25))), // 25 December
}
}
func (h holidaysUSNational) findNextWeekday(in time.Time, wd time.Weekday) time.Time {
for in.Weekday() != wd {
in = in.Add(1 * 24 * time.Hour)
}
return in
}
// If a holiday falls on a Saturday it is celebrated the preceding Friday;
// if a holiday falls on a Sunday it is celebrated the following Monday.
func (h holidaysUSNational) fixWeekend(in time.Time) time.Time {
switch in.Weekday() {
case time.Saturday:
return in.Add(-1 * 24 * time.Hour)
case time.Sunday:
return in.Add(1 * 24 * time.Hour)
default:
return in
}
}

View file

@ -1,8 +1,8 @@
language: go language: go
go: go:
- 1.4 - 1.6
- 1.5 - 1.7
- tip - tip
script: go test -v -race -cover ./... script: go test -v -race -cover ./...

18
vendor/github.com/Luzifer/rconfig/History.md generated vendored Normal file
View file

@ -0,0 +1,18 @@
# 2.1.0 / 2018-08-02
* Add AutoEnv feature
# 2.0.0 / 2018-08-02
* Breaking: Ensure an empty default string does not yield a slice with 1 element
Though this is a just a tiny change it does change the default behaviour, so I'm marking this as a breaking change. You should ensure your code is fine with the changes.
# 1.2.0 / 2017-06-19
* Add ParseAndValidate method
# 1.1.0 / 2016-06-28
* Support time.Duration config parameters
* Added goreportcard badge
* Added testcase for using bool with ENV and default

202
vendor/github.com/Luzifer/rconfig/LICENSE generated vendored Normal file
View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2015- Knut Ahlers <knut@ahlers.me>
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.

View file

@ -1,7 +1,8 @@
[![Build Status](https://travis-ci.org/Luzifer/rconfig.svg?branch=master)](https://travis-ci.org/Luzifer/rconfig) [![Build Status](https://travis-ci.org/Luzifer/rconfig.svg?branch=master)](https://travis-ci.org/Luzifer/rconfig)
[![License: Apache v2.0](https://badge.luzifer.io/v1/badge?color=5d79b5&title=license&text=Apache+v2.0)](http://www.apache.org/licenses/LICENSE-2.0) [![Go Report Card](https://goreportcard.com/badge/github.com/Luzifer/rconfig)](https://goreportcard.com/report/github.com/Luzifer/rconfig)
[![Documentation](https://badge.luzifer.io/v1/badge?title=godoc&text=reference)](https://godoc.org/github.com/Luzifer/rconfig) [![Documentation](https://badges.fyi/static/godoc/reference/5272B4)](https://godoc.org/github.com/Luzifer/rconfig)
[![Go Report](http://goreportcard.com/badge/Luzifer/rconfig)](http://goreportcard.com/report/Luzifer/rconfig) ![](https://badges.fyi/github/license/Luzifer/rconfig)
[![](https://badges.fyi/github/latest-tag/Luzifer/rconfig)](https://gopkg.in/Luzifer/rconfig.v2)
## Description ## Description
@ -18,7 +19,7 @@ go get -u github.com/Luzifer/rconfig
OR fetch a specific version: OR fetch a specific version:
``` ```
go get -u gopkg.in/luzifer/rconfig.v1 go get -u gopkg.in/luzifer/rconfig.v2
``` ```
Run tests by running: Run tests by running:
@ -29,34 +30,31 @@ go test -v -race -cover github.com/Luzifer/rconfig
## Usage ## Usage
As a first step define a struct holding your configuration: A very simple usecase is to just configure a struct inside the vars section of your `main.go` and to parse the commandline flags from the `main()` function:
```go ```go
type config struct { package main
Username string `default:"unknown" flag:"user" description:"Your name"`
Details struct {
Age int `default:"25" flag:"age" env:"age" description:"Your age"`
}
}
```
Next create an instance of that struct and let `rconfig` fill that config: import (
"fmt"
"github.com/Luzifer/rconfig"
)
```go var (
var cfg config cfg = struct {
func init() { Username string `default:"unknown" flag:"user" description:"Your name"`
cfg = config{} Details struct {
rconfig.Parse(&cfg) Age int `default:"25" flag:"age" env:"age" description:"Your age"`
} }
``` }{}
)
You're ready to access your configuration:
```go
func main() { func main() {
rconfig.Parse(&cfg)
fmt.Printf("Hello %s, happy birthday for your %dth birthday.", fmt.Printf("Hello %s, happy birthday for your %dth birthday.",
cfg.Username, cfg.Username,
cfg.Details.Age) cfg.Details.Age)
} }
``` ```
@ -72,18 +70,14 @@ The order of the directives (lower number = higher precedence):
1. `default` tag in the struct 1. `default` tag in the struct
```go ```go
type config struct { var cfg = struct {
Username string `vardefault:"username" flag:"username" description:"Your username"` Username string `vardefault:"username" flag:"username" description:"Your username"`
} }
var cfg = config{} func main() {
func init() {
rconfig.SetVariableDefaults(rconfig.VarDefaultsFromYAMLFile("~/.myapp.yml")) rconfig.SetVariableDefaults(rconfig.VarDefaultsFromYAMLFile("~/.myapp.yml"))
rconfig.Parse(&cfg) rconfig.Parse(&cfg)
}
func main() {
fmt.Printf("Username = %s", cfg.Username) fmt.Printf("Username = %s", cfg.Username)
// Output: Username = luzifer // Output: Username = luzifer
} }

64
vendor/github.com/Luzifer/rconfig/autoenv.go generated vendored Normal file
View file

@ -0,0 +1,64 @@
package rconfig
import "strings"
type characterClass [2]rune
func (c characterClass) Contains(r rune) bool {
return c[0] <= r && c[1] >= r
}
type characterClasses []characterClass
func (c characterClasses) Contains(r rune) bool {
for _, cc := range c {
if cc.Contains(r) {
return true
}
}
return false
}
var (
charGroupUpperLetter = characterClass{'A', 'Z'}
charGroupLowerLetter = characterClass{'a', 'z'}
charGroupNumber = characterClass{'0', '9'}
charGroupLowerNumber = characterClasses{charGroupLowerLetter, charGroupNumber}
)
func deriveEnvVarName(s string) string {
var (
words []string
word []rune
)
for _, l := range s {
switch {
case charGroupUpperLetter.Contains(l):
if len(word) > 0 && charGroupLowerNumber.Contains(word[len(word)-1]) {
words = append(words, string(word))
word = []rune{}
}
word = append(word, l)
case charGroupLowerLetter.Contains(l):
if len(word) > 1 && charGroupUpperLetter.Contains(word[len(word)-1]) {
words = append(words, string(word[0:len(word)-1]))
word = word[len(word)-1:]
}
word = append(word, l)
case charGroupNumber.Contains(l):
word = append(word, l)
default:
if len(word) > 0 {
words = append(words, string(word))
}
word = []rune{}
}
}
words = append(words, string(word))
return strings.ToUpper(strings.Join(words, "_"))
}

View file

@ -13,9 +13,11 @@ import (
"time" "time"
"github.com/spf13/pflag" "github.com/spf13/pflag"
validator "gopkg.in/validator.v2"
) )
var ( var (
autoEnv bool
fs *pflag.FlagSet fs *pflag.FlagSet
variableDefaults map[string]string variableDefaults map[string]string
) )
@ -45,11 +47,27 @@ func Parse(config interface{}) error {
return parse(config, nil) return parse(config, nil)
} }
// ParseAndValidate works exactly like Parse but implements an additional run of
// the go-validator package on the configuration struct. Therefore additonal struct
// tags are supported like described in the readme file of the go-validator package:
//
// https://github.com/go-validator/validator/tree/v2#usage
func ParseAndValidate(config interface{}) error {
return parseAndValidate(config, nil)
}
// Args returns the non-flag command-line arguments. // Args returns the non-flag command-line arguments.
func Args() []string { func Args() []string {
return fs.Args() return fs.Args()
} }
// AutoEnv enables or disables automated env variable guessing. If no `env` struct
// tag was set and AutoEnv is enabled the env variable name is derived from the
// name of the field: `MyFieldName` will get `MY_FIELD_NAME`
func AutoEnv(enable bool) {
autoEnv = enable
}
// Usage prints a basic usage with the corresponding defaults for the flags to // Usage prints a basic usage with the corresponding defaults for the flags to
// os.Stdout. The defaults are derived from the `default` struct-tag and the ENV. // os.Stdout. The defaults are derived from the `default` struct-tag and the ENV.
func Usage() { func Usage() {
@ -65,6 +83,14 @@ func SetVariableDefaults(defaults map[string]string) {
variableDefaults = defaults variableDefaults = defaults
} }
func parseAndValidate(in interface{}, args []string) error {
if err := parse(in, args); err != nil {
return err
}
return validator.Validate(in)
}
func parse(in interface{}, args []string) error { func parse(in interface{}, args []string) error {
if args == nil { if args == nil {
args = os.Args args = os.Args
@ -98,7 +124,7 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
} }
value := varDefault(typeField.Tag.Get("vardefault"), typeField.Tag.Get("default")) value := varDefault(typeField.Tag.Get("vardefault"), typeField.Tag.Get("default"))
value = envDefault(typeField.Tag.Get("env"), value) value = envDefault(typeField, value)
parts := strings.Split(typeField.Tag.Get("flag"), ",") parts := strings.Split(typeField.Tag.Get("flag"), ",")
switch typeField.Type { switch typeField.Type {
@ -219,7 +245,10 @@ func execTags(in interface{}, fs *pflag.FlagSet) error {
if len(del) == 0 { if len(del) == 0 {
del = "," del = ","
} }
def := strings.Split(value, del) var def = []string{}
if value != "" {
def = strings.Split(value, del)
}
if len(parts) == 1 { if len(parts) == 1 {
fs.StringSliceVar(valField.Addr().Interface().(*[]string), parts[0], def, typeField.Tag.Get("description")) fs.StringSliceVar(valField.Addr().Interface().(*[]string), parts[0], def, typeField.Tag.Get("description"))
} else { } else {
@ -313,9 +342,14 @@ func registerFlagUint(t reflect.Kind, fs *pflag.FlagSet, field interface{}, part
} }
} }
func envDefault(env, def string) string { func envDefault(field reflect.StructField, def string) string {
value := def value := def
env := field.Tag.Get("env")
if env == "" && autoEnv {
env = deriveEnvVarName(field.Name)
}
if env != "" { if env != "" {
if e := os.Getenv(env); e != "" { if e := os.Getenv(env); e != "" {
value = e value = e

19
vendor/github.com/gorilla/context/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,19 @@
language: go
sudo: false
matrix:
include:
- go: 1.3
- 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 ./...

10
vendor/github.com/gorilla/context/README.md generated vendored Normal file
View file

@ -0,0 +1,10 @@
context
=======
[![Build Status](https://travis-ci.org/gorilla/context.png?branch=master)](https://travis-ci.org/gorilla/context)
gorilla/context is a general purpose registry for global request variables.
> Note: gorilla/context, having been born well before `context.Context` existed, does not play well
> with the shallow copying of the request that [`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext) (added to net/http Go 1.7 onwards) performs. You should either use *just* gorilla/context, or moving forward, the new `http.Request.Context()`.
Read the full documentation here: http://www.gorillatoolkit.org/pkg/context

View file

@ -5,6 +5,12 @@
/* /*
Package context stores values shared during a request lifetime. Package context stores values shared during a request lifetime.
Note: gorilla/context, having been born well before `context.Context` existed,
does not play well > with the shallow copying of the request that
[`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext)
(added to net/http Go 1.7 onwards) performs. You should either use *just*
gorilla/context, or moving forward, the new `http.Request.Context()`.
For example, a router can set variables extracted from the URL and later For example, a router can set variables extracted from the URL and later
application handlers can access those values, or it can be used to store application handlers can access those values, or it can be used to store
sessions values to be saved at the end of a request. There are several sessions values to be saved at the end of a request. There are several

23
vendor/github.com/gorilla/mux/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,23 @@
language: go
sudo: false
matrix:
include:
- go: 1.5.x
- go: 1.6.x
- go: 1.7.x
- go: 1.8.x
- go: 1.9.x
- go: 1.10.x
- go: tip
allow_failures:
- go: tip
install:
- # Skip
script:
- go get -t -v ./...
- diff -u <(echo -n) <(gofmt -d .)
- go tool vet .
- go test -v -race ./...

11
vendor/github.com/gorilla/mux/ISSUE_TEMPLATE.md generated vendored Normal file
View file

@ -0,0 +1,11 @@
**What version of Go are you running?** (Paste the output of `go version`)
**What version of gorilla/mux are you at?** (Paste the output of `git rev-parse HEAD` inside `$GOPATH/src/github.com/gorilla/mux`)
**Describe your problem** (and what you have tried so far)
**Paste a minimal, runnable, reproduction of your issue below** (use backticks to format it)

649
vendor/github.com/gorilla/mux/README.md generated vendored Normal file
View file

@ -0,0 +1,649 @@
# gorilla/mux
[![GoDoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux)
[![Build Status](https://travis-ci.org/gorilla/mux.svg?branch=master)](https://travis-ci.org/gorilla/mux)
[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge)
![Gorilla Logo](http://www.gorillatoolkit.org/static/images/gorilla-icon-64.png)
http://www.gorillatoolkit.org/pkg/mux
Package `gorilla/mux` implements a request router and dispatcher for matching incoming requests to
their respective handler.
The name mux stands for "HTTP request multiplexer". Like the standard `http.ServeMux`, `mux.Router` matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are:
* It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`.
* Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers.
* URL hosts, paths and query values can have variables with an optional regular expression.
* Registered URLs can be built, or "reversed", which helps maintaining references to resources.
* Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching.
---
* [Install](#install)
* [Examples](#examples)
* [Matching Routes](#matching-routes)
* [Static Files](#static-files)
* [Registered URLs](#registered-urls)
* [Walking Routes](#walking-routes)
* [Graceful Shutdown](#graceful-shutdown)
* [Middleware](#middleware)
* [Testing Handlers](#testing-handlers)
* [Full Example](#full-example)
---
## Install
With a [correctly configured](https://golang.org/doc/install#testing) Go toolchain:
```sh
go get -u github.com/gorilla/mux
```
## Examples
Let's start registering a couple of URL paths and handlers:
```go
func main() {
r := mux.NewRouter()
r.HandleFunc("/", HomeHandler)
r.HandleFunc("/products", ProductsHandler)
r.HandleFunc("/articles", ArticlesHandler)
http.Handle("/", r)
}
```
Here we register three routes mapping URL paths to handlers. This is equivalent to how `http.HandleFunc()` works: if an incoming request URL matches one of the paths, the corresponding handler is called passing (`http.ResponseWriter`, `*http.Request`) as parameters.
Paths can have variables. They are defined using the format `{name}` or `{name:pattern}`. If a regular expression pattern is not defined, the matched variable will be anything until the next slash. For example:
```go
r := mux.NewRouter()
r.HandleFunc("/products/{key}", ProductHandler)
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
```
The names are used to create a map of route variables which can be retrieved calling `mux.Vars()`:
```go
func ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Category: %v\n", vars["category"])
}
```
And this is all you need to know about the basic usage. More advanced options are explained below.
### Matching Routes
Routes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables:
```go
r := mux.NewRouter()
// Only matches if domain is "www.example.com".
r.Host("www.example.com")
// Matches a dynamic subdomain.
r.Host("{subdomain:[a-z]+}.domain.com")
```
There are several other matchers that can be added. To match path prefixes:
```go
r.PathPrefix("/products/")
```
...or HTTP methods:
```go
r.Methods("GET", "POST")
```
...or URL schemes:
```go
r.Schemes("https")
```
...or header values:
```go
r.Headers("X-Requested-With", "XMLHttpRequest")
```
...or query values:
```go
r.Queries("key", "value")
```
...or to use a custom matcher function:
```go
r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
return r.ProtoMajor == 0
})
```
...and finally, it is possible to combine several matchers in a single route:
```go
r.HandleFunc("/products", ProductsHandler).
Host("www.example.com").
Methods("GET").
Schemes("http")
```
Routes are tested in the order they were added to the router. If two routes match, the first one wins:
```go
r := mux.NewRouter()
r.HandleFunc("/specific", specificHandler)
r.PathPrefix("/").Handler(catchAllHandler)
```
Setting the same matching conditions again and again can be boring, so we have a way to group several routes that share the same requirements. We call it "subrouting".
For example, let's say we have several URLs that should only match when the host is `www.example.com`. Create a route for that host and get a "subrouter" from it:
```go
r := mux.NewRouter()
s := r.Host("www.example.com").Subrouter()
```
Then register routes in the subrouter:
```go
s.HandleFunc("/products/", ProductsHandler)
s.HandleFunc("/products/{key}", ProductHandler)
s.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
```
The three URL paths we registered above will only be tested if the domain is `www.example.com`, because the subrouter is tested first. This is not only convenient, but also optimizes request matching. You can create subrouters combining any attribute matchers accepted by a route.
Subrouters can be used to create domain or path "namespaces": you define subrouters in a central place and then parts of the app can register its paths relatively to a given subrouter.
There's one more thing about subroutes. When a subrouter has a path prefix, the inner routes use it as base for their paths:
```go
r := mux.NewRouter()
s := r.PathPrefix("/products").Subrouter()
// "/products/"
s.HandleFunc("/", ProductsHandler)
// "/products/{key}/"
s.HandleFunc("/{key}/", ProductHandler)
// "/products/{key}/details"
s.HandleFunc("/{key}/details", ProductDetailsHandler)
```
### Static Files
Note that the path provided to `PathPrefix()` represents a "wildcard": calling
`PathPrefix("/static/").Handler(...)` means that the handler will be passed any
request that matches "/static/\*". This makes it easy to serve static files with mux:
```go
func main() {
var dir string
flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
flag.Parse()
r := mux.NewRouter()
// This will serve files under http://localhost:8000/static/<filename>
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))
srv := &http.Server{
Handler: r,
Addr: "127.0.0.1:8000",
// Good practice: enforce timeouts for servers you create!
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
log.Fatal(srv.ListenAndServe())
}
```
### Registered URLs
Now let's see how to build registered URLs.
Routes can be named. All routes that define a name can have their URLs built, or "reversed". We define a name calling `Name()` on a route. For example:
```go
r := mux.NewRouter()
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
Name("article")
```
To build a URL, get the route and call the `URL()` method, passing a sequence of key/value pairs for the route variables. For the previous route, we would do:
```go
url, err := r.Get("article").URL("category", "technology", "id", "42")
```
...and the result will be a `url.URL` with the following path:
```
"/articles/technology/42"
```
This also works for host and query value variables:
```go
r := mux.NewRouter()
r.Host("{subdomain}.domain.com").
Path("/articles/{category}/{id:[0-9]+}").
Queries("filter", "{filter}").
HandlerFunc(ArticleHandler).
Name("article")
// url.String() will be "http://news.domain.com/articles/technology/42?filter=gorilla"
url, err := r.Get("article").URL("subdomain", "news",
"category", "technology",
"id", "42",
"filter", "gorilla")
```
All variables defined in the route are required, and their values must conform to the corresponding patterns. These requirements guarantee that a generated URL will always match a registered route -- the only exception is for explicitly defined "build-only" routes which never match.
Regex support also exists for matching Headers within a route. For example, we could do:
```go
r.HeadersRegexp("Content-Type", "application/(text|json)")
```
...and the route will match both requests with a Content-Type of `application/json` as well as `application/text`
There's also a way to build only the URL host or path for a route: use the methods `URLHost()` or `URLPath()` instead. For the previous route, we would do:
```go
// "http://news.domain.com/"
host, err := r.Get("article").URLHost("subdomain", "news")
// "/articles/technology/42"
path, err := r.Get("article").URLPath("category", "technology", "id", "42")
```
And if you use subrouters, host and path defined separately can be built as well:
```go
r := mux.NewRouter()
s := r.Host("{subdomain}.domain.com").Subrouter()
s.Path("/articles/{category}/{id:[0-9]+}").
HandlerFunc(ArticleHandler).
Name("article")
// "http://news.domain.com/articles/technology/42"
url, err := r.Get("article").URL("subdomain", "news",
"category", "technology",
"id", "42")
```
### Walking Routes
The `Walk` function on `mux.Router` can be used to visit all of the routes that are registered on a router. For example,
the following prints all of the registered routes:
```go
package main
import (
"fmt"
"net/http"
"strings"
"github.com/gorilla/mux"
)
func handler(w http.ResponseWriter, r *http.Request) {
return
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/", handler)
r.HandleFunc("/products", handler).Methods("POST")
r.HandleFunc("/articles", handler).Methods("GET")
r.HandleFunc("/articles/{id}", handler).Methods("GET", "PUT")
r.HandleFunc("/authors", handler).Queries("surname", "{surname}")
err := r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
pathTemplate, err := route.GetPathTemplate()
if err == nil {
fmt.Println("ROUTE:", pathTemplate)
}
pathRegexp, err := route.GetPathRegexp()
if err == nil {
fmt.Println("Path regexp:", pathRegexp)
}
queriesTemplates, err := route.GetQueriesTemplates()
if err == nil {
fmt.Println("Queries templates:", strings.Join(queriesTemplates, ","))
}
queriesRegexps, err := route.GetQueriesRegexp()
if err == nil {
fmt.Println("Queries regexps:", strings.Join(queriesRegexps, ","))
}
methods, err := route.GetMethods()
if err == nil {
fmt.Println("Methods:", strings.Join(methods, ","))
}
fmt.Println()
return nil
})
if err != nil {
fmt.Println(err)
}
http.Handle("/", r)
}
```
### Graceful Shutdown
Go 1.8 introduced the ability to [gracefully shutdown](https://golang.org/doc/go1.8#http_shutdown) a `*http.Server`. Here's how to do that alongside `mux`:
```go
package main
import (
"context"
"flag"
"log"
"net/http"
"os"
"os/signal"
"time"
"github.com/gorilla/mux"
)
func main() {
var wait time.Duration
flag.DurationVar(&wait, "graceful-timeout", time.Second * 15, "the duration for which the server gracefully wait for existing connections to finish - e.g. 15s or 1m")
flag.Parse()
r := mux.NewRouter()
// Add your routes as needed
srv := &http.Server{
Addr: "0.0.0.0:8080",
// Good practice to set timeouts to avoid Slowloris attacks.
WriteTimeout: time.Second * 15,
ReadTimeout: time.Second * 15,
IdleTimeout: time.Second * 60,
Handler: r, // Pass our instance of gorilla/mux in.
}
// Run our server in a goroutine so that it doesn't block.
go func() {
if err := srv.ListenAndServe(); err != nil {
log.Println(err)
}
}()
c := make(chan os.Signal, 1)
// We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C)
// SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught.
signal.Notify(c, os.Interrupt)
// Block until we receive our signal.
<-c
// Create a deadline to wait for.
ctx, cancel := context.WithTimeout(context.Background(), wait)
defer cancel()
// Doesn't block if no connections, but will otherwise wait
// until the timeout deadline.
srv.Shutdown(ctx)
// Optionally, you could run srv.Shutdown in a goroutine and block on
// <-ctx.Done() if your application should wait for other services
// to finalize based on context cancellation.
log.Println("shutting down")
os.Exit(0)
}
```
### Middleware
Mux supports the addition of middlewares to a [Router](https://godoc.org/github.com/gorilla/mux#Router), which are executed in the order they are added if a match is found, including its subrouters.
Middlewares are (typically) small pieces of code which take one request, do something with it, and pass it down to another middleware or the final handler. Some common use cases for middleware are request logging, header manipulation, or `ResponseWriter` hijacking.
Mux middlewares are defined using the de facto standard type:
```go
type MiddlewareFunc func(http.Handler) http.Handler
```
Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc. This takes advantage of closures being able access variables from the context where they are created, while retaining the signature enforced by the receivers.
A very basic middleware which logs the URI of the request being handled could be written as:
```go
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Do stuff here
log.Println(r.RequestURI)
// Call the next handler, which can be another middleware in the chain, or the final handler.
next.ServeHTTP(w, r)
})
}
```
Middlewares can be added to a router using `Router.Use()`:
```go
r := mux.NewRouter()
r.HandleFunc("/", handler)
r.Use(loggingMiddleware)
```
A more complex authentication middleware, which maps session token to users, could be written as:
```go
// Define our struct
type authenticationMiddleware struct {
tokenUsers map[string]string
}
// Initialize it somewhere
func (amw *authenticationMiddleware) Populate() {
amw.tokenUsers["00000000"] = "user0"
amw.tokenUsers["aaaaaaaa"] = "userA"
amw.tokenUsers["05f717e5"] = "randomUser"
amw.tokenUsers["deadbeef"] = "user0"
}
// Middleware function, which will be called for each request
func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("X-Session-Token")
if user, found := amw.tokenUsers[token]; found {
// We found the token in our map
log.Printf("Authenticated user %s\n", user)
// Pass down the request to the next middleware (or final handler)
next.ServeHTTP(w, r)
} else {
// Write an error and stop the handler chain
http.Error(w, "Forbidden", http.StatusForbidden)
}
})
}
```
```go
r := mux.NewRouter()
r.HandleFunc("/", handler)
amw := authenticationMiddleware{}
amw.Populate()
r.Use(amw.Middleware)
```
Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. Middlewares _should_ write to `ResponseWriter` if they _are_ going to terminate the request, and they _should not_ write to `ResponseWriter` if they _are not_ going to terminate it.
### Testing Handlers
Testing handlers in a Go web application is straightforward, and _mux_ doesn't complicate this any further. Given two files: `endpoints.go` and `endpoints_test.go`, here's how we'd test an application using _mux_.
First, our simple HTTP handler:
```go
// endpoints.go
package main
func HealthCheckHandler(w http.ResponseWriter, r *http.Request) {
// A very simple health check.
w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json")
// In the future we could report back on the status of our DB, or our cache
// (e.g. Redis) by performing a simple PING, and include them in the response.
io.WriteString(w, `{"alive": true}`)
}
func main() {
r := mux.NewRouter()
r.HandleFunc("/health", HealthCheckHandler)
log.Fatal(http.ListenAndServe("localhost:8080", r))
}
```
Our test code:
```go
// endpoints_test.go
package main
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestHealthCheckHandler(t *testing.T) {
// Create a request to pass to our handler. We don't have any query parameters for now, so we'll
// pass 'nil' as the third parameter.
req, err := http.NewRequest("GET", "/health", nil)
if err != nil {
t.Fatal(err)
}
// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
rr := httptest.NewRecorder()
handler := http.HandlerFunc(HealthCheckHandler)
// Our handlers satisfy http.Handler, so we can call their ServeHTTP method
// directly and pass in our Request and ResponseRecorder.
handler.ServeHTTP(rr, req)
// Check the status code is what we expect.
if status := rr.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v",
status, http.StatusOK)
}
// Check the response body is what we expect.
expected := `{"alive": true}`
if rr.Body.String() != expected {
t.Errorf("handler returned unexpected body: got %v want %v",
rr.Body.String(), expected)
}
}
```
In the case that our routes have [variables](#examples), we can pass those in the request. We could write
[table-driven tests](https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go) to test multiple
possible route variables as needed.
```go
// endpoints.go
func main() {
r := mux.NewRouter()
// A route with a route variable:
r.HandleFunc("/metrics/{type}", MetricsHandler)
log.Fatal(http.ListenAndServe("localhost:8080", r))
}
```
Our test file, with a table-driven test of `routeVariables`:
```go
// endpoints_test.go
func TestMetricsHandler(t *testing.T) {
tt := []struct{
routeVariable string
shouldPass bool
}{
{"goroutines", true},
{"heap", true},
{"counters", true},
{"queries", true},
{"adhadaeqm3k", false},
}
for _, tc := range tt {
path := fmt.Sprintf("/metrics/%s", tc.routeVariable)
req, err := http.NewRequest("GET", path, nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
// Need to create a router that we can pass the request through so that the vars will be added to the context
router := mux.NewRouter()
router.HandleFunc("/metrics/{type}", MetricsHandler)
router.ServeHTTP(rr, req)
// In this case, our MetricsHandler returns a non-200 response
// for a route variable it doesn't know about.
if rr.Code == http.StatusOK && !tc.shouldPass {
t.Errorf("handler should have failed on routeVariable %s: got %v want %v",
tc.routeVariable, rr.Code, http.StatusOK)
}
}
}
```
## Full Example
Here's a complete, runnable example of a small `mux` based server:
```go
package main
import (
"net/http"
"log"
"github.com/gorilla/mux"
)
func YourHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Gorilla!\n"))
}
func main() {
r := mux.NewRouter()
// Routes consist of a path and a handler function.
r.HandleFunc("/", YourHandler)
// Bind to a port and pass our router in
log.Fatal(http.ListenAndServe(":8000", r))
}
```
## License
BSD licensed. See the LICENSE file for details.

26
vendor/github.com/gorilla/mux/context_gorilla.go generated vendored Normal file
View file

@ -0,0 +1,26 @@
// +build !go1.7
package mux
import (
"net/http"
"github.com/gorilla/context"
)
func contextGet(r *http.Request, key interface{}) interface{} {
return context.Get(r, key)
}
func contextSet(r *http.Request, key, val interface{}) *http.Request {
if val == nil {
return r
}
context.Set(r, key, val)
return r
}
func contextClear(r *http.Request) {
context.Clear(r)
}

24
vendor/github.com/gorilla/mux/context_native.go generated vendored Normal file
View file

@ -0,0 +1,24 @@
// +build go1.7
package mux
import (
"context"
"net/http"
)
func contextGet(r *http.Request, key interface{}) interface{} {
return r.Context().Value(key)
}
func contextSet(r *http.Request, key, val interface{}) *http.Request {
if val == nil {
return r
}
return r.WithContext(context.WithValue(r.Context(), key, val))
}
func contextClear(r *http.Request) {
return
}

View file

@ -3,7 +3,7 @@
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
/* /*
Package gorilla/mux implements a request router and dispatcher. Package mux implements a request router and dispatcher.
The name mux stands for "HTTP request multiplexer". Like the standard The name mux stands for "HTTP request multiplexer". Like the standard
http.ServeMux, mux.Router matches incoming requests against a list of http.ServeMux, mux.Router matches incoming requests against a list of
@ -12,8 +12,8 @@ or other conditions. The main features are:
* Requests can be matched based on URL host, path, path prefix, schemes, * Requests can be matched based on URL host, path, path prefix, schemes,
header and query values, HTTP methods or using custom matchers. header and query values, HTTP methods or using custom matchers.
* URL hosts and paths can have variables with an optional regular * URL hosts, paths and query values can have variables with an optional
expression. regular expression.
* Registered URLs can be built, or "reversed", which helps maintaining * Registered URLs can be built, or "reversed", which helps maintaining
references to resources. references to resources.
* Routes can be used as subrouters: nested routes are only tested if the * Routes can be used as subrouters: nested routes are only tested if the
@ -47,12 +47,21 @@ variable will be anything until the next slash. For example:
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler) r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
Groups can be used inside patterns, as long as they are non-capturing (?:re). For example:
r.HandleFunc("/articles/{category}/{sort:(?:asc|desc|new)}", ArticlesCategoryHandler)
The names are used to create a map of route variables which can be retrieved The names are used to create a map of route variables which can be retrieved
calling mux.Vars(): calling mux.Vars():
vars := mux.Vars(request) vars := mux.Vars(request)
category := vars["category"] category := vars["category"]
Note that if any capturing groups are present, mux will panic() during parsing. To prevent
this, convert any capturing groups to non-capturing, e.g. change "/{sort:(asc|desc)}" to
"/{sort:(?:asc|desc)}". This is a change from prior versions which behaved unpredictably
when capturing groups were present.
And this is all you need to know about the basic usage. More advanced options And this is all you need to know about the basic usage. More advanced options
are explained below. are explained below.
@ -136,6 +145,31 @@ the inner routes use it as base for their paths:
// "/products/{key}/details" // "/products/{key}/details"
s.HandleFunc("/{key}/details", ProductDetailsHandler) s.HandleFunc("/{key}/details", ProductDetailsHandler)
Note that the path provided to PathPrefix() represents a "wildcard": calling
PathPrefix("/static/").Handler(...) means that the handler will be passed any
request that matches "/static/*". This makes it easy to serve static files with mux:
func main() {
var dir string
flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
flag.Parse()
r := mux.NewRouter()
// This will serve files under http://localhost:8000/static/<filename>
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))
srv := &http.Server{
Handler: r,
Addr: "127.0.0.1:8000",
// Good practice: enforce timeouts for servers you create!
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
log.Fatal(srv.ListenAndServe())
}
Now let's see how to build registered URLs. Now let's see how to build registered URLs.
Routes can be named. All routes that define a name can have their URLs built, Routes can be named. All routes that define a name can have their URLs built,
@ -154,18 +188,20 @@ key/value pairs for the route variables. For the previous route, we would do:
"/articles/technology/42" "/articles/technology/42"
This also works for host variables: This also works for host and query value variables:
r := mux.NewRouter() r := mux.NewRouter()
r.Host("{subdomain}.domain.com"). r.Host("{subdomain}.domain.com").
Path("/articles/{category}/{id:[0-9]+}"). Path("/articles/{category}/{id:[0-9]+}").
Queries("filter", "{filter}").
HandlerFunc(ArticleHandler). HandlerFunc(ArticleHandler).
Name("article") Name("article")
// url.String() will be "http://news.domain.com/articles/technology/42" // url.String() will be "http://news.domain.com/articles/technology/42?filter=gorilla"
url, err := r.Get("article").URL("subdomain", "news", url, err := r.Get("article").URL("subdomain", "news",
"category", "technology", "category", "technology",
"id", "42") "id", "42",
"filter", "gorilla")
All variables defined in the route are required, and their values must All variables defined in the route are required, and their values must
conform to the corresponding patterns. These requirements guarantee that a conform to the corresponding patterns. These requirements guarantee that a
@ -202,5 +238,69 @@ as well:
url, err := r.Get("article").URL("subdomain", "news", url, err := r.Get("article").URL("subdomain", "news",
"category", "technology", "category", "technology",
"id", "42") "id", "42")
Mux supports the addition of middlewares to a Router, which are executed in the order they are added if a match is found, including its subrouters. Middlewares are (typically) small pieces of code which take one request, do something with it, and pass it down to another middleware or the final handler. Some common use cases for middleware are request logging, header manipulation, or ResponseWriter hijacking.
type MiddlewareFunc func(http.Handler) http.Handler
Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc (closures can access variables from the context where they are created).
A very basic middleware which logs the URI of the request being handled could be written as:
func simpleMw(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Do stuff here
log.Println(r.RequestURI)
// Call the next handler, which can be another middleware in the chain, or the final handler.
next.ServeHTTP(w, r)
})
}
Middlewares can be added to a router using `Router.Use()`:
r := mux.NewRouter()
r.HandleFunc("/", handler)
r.Use(simpleMw)
A more complex authentication middleware, which maps session token to users, could be written as:
// Define our struct
type authenticationMiddleware struct {
tokenUsers map[string]string
}
// Initialize it somewhere
func (amw *authenticationMiddleware) Populate() {
amw.tokenUsers["00000000"] = "user0"
amw.tokenUsers["aaaaaaaa"] = "userA"
amw.tokenUsers["05f717e5"] = "randomUser"
amw.tokenUsers["deadbeef"] = "user0"
}
// Middleware function, which will be called for each request
func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("X-Session-Token")
if user, found := amw.tokenUsers[token]; found {
// We found the token in our map
log.Printf("Authenticated user %s\n", user)
next.ServeHTTP(w, r)
} else {
http.Error(w, "Forbidden", http.StatusForbidden)
}
})
}
r := mux.NewRouter()
r.HandleFunc("/", handler)
amw := authenticationMiddleware{}
amw.Populate()
r.Use(amw.Middleware)
Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to.
*/ */
package mux package mux

72
vendor/github.com/gorilla/mux/middleware.go generated vendored Normal file
View file

@ -0,0 +1,72 @@
package mux
import (
"net/http"
"strings"
)
// MiddlewareFunc is a function which receives an http.Handler and returns another http.Handler.
// Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed
// to it, and then calls the handler passed as parameter to the MiddlewareFunc.
type MiddlewareFunc func(http.Handler) http.Handler
// middleware interface is anything which implements a MiddlewareFunc named Middleware.
type middleware interface {
Middleware(handler http.Handler) http.Handler
}
// Middleware allows MiddlewareFunc to implement the middleware interface.
func (mw MiddlewareFunc) Middleware(handler http.Handler) http.Handler {
return mw(handler)
}
// Use appends a MiddlewareFunc to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Router.
func (r *Router) Use(mwf ...MiddlewareFunc) {
for _, fn := range mwf {
r.middlewares = append(r.middlewares, fn)
}
}
// useInterface appends a middleware to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Router.
func (r *Router) useInterface(mw middleware) {
r.middlewares = append(r.middlewares, mw)
}
// CORSMethodMiddleware sets the Access-Control-Allow-Methods response header
// on a request, by matching routes based only on paths. It also handles
// OPTIONS requests, by settings Access-Control-Allow-Methods, and then
// returning without calling the next http handler.
func CORSMethodMiddleware(r *Router) MiddlewareFunc {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
var allMethods []string
err := r.Walk(func(route *Route, _ *Router, _ []*Route) error {
for _, m := range route.matchers {
if _, ok := m.(*routeRegexp); ok {
if m.Match(req, &RouteMatch{}) {
methods, err := route.GetMethods()
if err != nil {
return err
}
allMethods = append(allMethods, methods...)
}
break
}
}
return nil
})
if err == nil {
w.Header().Set("Access-Control-Allow-Methods", strings.Join(append(allMethods, "OPTIONS"), ","))
if req.Method == "OPTIONS" {
return
}
}
next.ServeHTTP(w, req)
})
}
}

View file

@ -10,8 +10,14 @@ import (
"net/http" "net/http"
"path" "path"
"regexp" "regexp"
)
"github.com/gorilla/context" var (
// ErrMethodMismatch is returned when the method in the request does not match
// the method defined against the route.
ErrMethodMismatch = errors.New("method is not allowed")
// ErrNotFound is returned when no route match is found.
ErrNotFound = errors.New("no matching route was found")
) )
// NewRouter returns a new router instance. // NewRouter returns a new router instance.
@ -40,6 +46,10 @@ func NewRouter() *Router {
type Router struct { type Router struct {
// Configurable Handler to be used when no route matches. // Configurable Handler to be used when no route matches.
NotFoundHandler http.Handler NotFoundHandler http.Handler
// Configurable Handler to be used when the request method does not match the route.
MethodNotAllowedHandler http.Handler
// Parent route, if this is a subrouter. // Parent route, if this is a subrouter.
parent parentRoute parent parentRoute
// Routes to be matched, in order. // Routes to be matched, in order.
@ -48,17 +58,59 @@ type Router struct {
namedRoutes map[string]*Route namedRoutes map[string]*Route
// See Router.StrictSlash(). This defines the flag for new routes. // See Router.StrictSlash(). This defines the flag for new routes.
strictSlash bool strictSlash bool
// If true, do not clear the request context after handling the request // See Router.SkipClean(). This defines the flag for new routes.
skipClean bool
// If true, do not clear the request context after handling the request.
// This has no effect when go1.7+ is used, since the context is stored
// on the request itself.
KeepContext bool KeepContext bool
// see Router.UseEncodedPath(). This defines a flag for all routes.
useEncodedPath bool
// Slice of middlewares to be called after a match is found
middlewares []middleware
} }
// Match matches registered routes against the request. // Match attempts to match the given request against the router's registered routes.
//
// If the request matches a route of this router or one of its subrouters the Route,
// Handler, and Vars fields of the the match argument are filled and this function
// returns true.
//
// If the request does not match any of this router's or its subrouters' routes
// then this function returns false. If available, a reason for the match failure
// will be filled in the match argument's MatchErr field. If the match failure type
// (eg: not found) has a registered handler, the handler is assigned to the Handler
// field of the match argument.
func (r *Router) Match(req *http.Request, match *RouteMatch) bool { func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
for _, route := range r.routes { for _, route := range r.routes {
if route.Match(req, match) { if route.Match(req, match) {
// Build middleware chain if no error was found
if match.MatchErr == nil {
for i := len(r.middlewares) - 1; i >= 0; i-- {
match.Handler = r.middlewares[i].Middleware(match.Handler)
}
}
return true return true
} }
} }
if match.MatchErr == ErrMethodMismatch {
if r.MethodNotAllowedHandler != nil {
match.Handler = r.MethodNotAllowedHandler
return true
}
return false
}
// Closest match for a router (includes sub-routers)
if r.NotFoundHandler != nil {
match.Handler = r.NotFoundHandler
match.MatchErr = ErrNotFound
return true
}
match.MatchErr = ErrNotFound
return false return false
} }
@ -67,36 +119,46 @@ func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
// When there is a match, the route variables can be retrieved calling // When there is a match, the route variables can be retrieved calling
// mux.Vars(request). // mux.Vars(request).
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// Clean path to canonical form and redirect. if !r.skipClean {
if p := cleanPath(req.URL.Path); p != req.URL.Path { path := req.URL.Path
if r.useEncodedPath {
path = req.URL.EscapedPath()
}
// Clean path to canonical form and redirect.
if p := cleanPath(path); p != path {
// Added 3 lines (Philip Schlump) - It was droping the query string and #whatever from query. // Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query.
// This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue: // This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue:
// http://code.google.com/p/go/issues/detail?id=5252 // http://code.google.com/p/go/issues/detail?id=5252
url := *req.URL url := *req.URL
url.Path = p url.Path = p
p = url.String() p = url.String()
w.Header().Set("Location", p) w.Header().Set("Location", p)
w.WriteHeader(http.StatusMovedPermanently) w.WriteHeader(http.StatusMovedPermanently)
return return
}
} }
var match RouteMatch var match RouteMatch
var handler http.Handler var handler http.Handler
if r.Match(req, &match) { if r.Match(req, &match) {
handler = match.Handler handler = match.Handler
setVars(req, match.Vars) req = setVars(req, match.Vars)
setCurrentRoute(req, match.Route) req = setCurrentRoute(req, match.Route)
} }
if handler == nil && match.MatchErr == ErrMethodMismatch {
handler = methodNotAllowedHandler()
}
if handler == nil { if handler == nil {
handler = r.NotFoundHandler handler = http.NotFoundHandler()
if handler == nil {
handler = http.NotFoundHandler()
}
} }
if !r.KeepContext { if !r.KeepContext {
defer context.Clear(req) defer contextClear(req)
} }
handler.ServeHTTP(w, req) handler.ServeHTTP(w, req)
} }
@ -114,13 +176,18 @@ func (r *Router) GetRoute(name string) *Route {
// StrictSlash defines the trailing slash behavior for new routes. The initial // StrictSlash defines the trailing slash behavior for new routes. The initial
// value is false. // value is false.
// //
// When true, if the route path is "/path/", accessing "/path" will redirect // When true, if the route path is "/path/", accessing "/path" will perform a redirect
// to the former and vice versa. In other words, your application will always // to the former and vice versa. In other words, your application will always
// see the path as specified in the route. // see the path as specified in the route.
// //
// When false, if the route path is "/path", accessing "/path/" will not match // When false, if the route path is "/path", accessing "/path/" will not match
// this route and vice versa. // this route and vice versa.
// //
// The re-direct is a HTTP 301 (Moved Permanently). Note that when this is set for
// routes with a non-idempotent method (e.g. POST, PUT), the subsequent re-directed
// request will be made as a GET by most clients. Use middleware or client settings
// to modify this behaviour as needed.
//
// Special case: when a route sets a path prefix using the PathPrefix() method, // Special case: when a route sets a path prefix using the PathPrefix() method,
// strict slash is ignored for that route because the redirect behavior can't // strict slash is ignored for that route because the redirect behavior can't
// be determined from a prefix alone. However, any subrouters created from that // be determined from a prefix alone. However, any subrouters created from that
@ -130,10 +197,41 @@ func (r *Router) StrictSlash(value bool) *Router {
return r return r
} }
// SkipClean defines the path cleaning behaviour for new routes. The initial
// value is false. Users should be careful about which routes are not cleaned
//
// When true, if the route path is "/path//to", it will remain with the double
// slash. This is helpful if you have a route like: /fetch/http://xkcd.com/534/
//
// When false, the path will be cleaned, so /fetch/http://xkcd.com/534/ will
// become /fetch/http/xkcd.com/534
func (r *Router) SkipClean(value bool) *Router {
r.skipClean = value
return r
}
// UseEncodedPath tells the router to match the encoded original path
// to the routes.
// For eg. "/path/foo%2Fbar/to" will match the path "/path/{var}/to".
//
// If not called, the router will match the unencoded path to the routes.
// For eg. "/path/foo%2Fbar/to" will match the path "/path/foo/bar/to"
func (r *Router) UseEncodedPath() *Router {
r.useEncodedPath = true
return r
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// parentRoute // parentRoute
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
func (r *Router) getBuildScheme() string {
if r.parent != nil {
return r.parent.getBuildScheme()
}
return ""
}
// getNamedRoutes returns the map where named routes are registered. // getNamedRoutes returns the map where named routes are registered.
func (r *Router) getNamedRoutes() map[string]*Route { func (r *Router) getNamedRoutes() map[string]*Route {
if r.namedRoutes == nil { if r.namedRoutes == nil {
@ -167,7 +265,7 @@ func (r *Router) buildVars(m map[string]string) map[string]string {
// NewRoute registers an empty route. // NewRoute registers an empty route.
func (r *Router) NewRoute() *Route { func (r *Router) NewRoute() *Route {
route := &Route{parent: r, strictSlash: r.strictSlash} route := &Route{parent: r, strictSlash: r.strictSlash, skipClean: r.skipClean, useEncodedPath: r.useEncodedPath}
r.routes = append(r.routes, route) r.routes = append(r.routes, route)
return route return route
} }
@ -233,7 +331,7 @@ func (r *Router) Schemes(schemes ...string) *Route {
return r.NewRoute().Schemes(schemes...) return r.NewRoute().Schemes(schemes...)
} }
// BuildVars registers a new route with a custom function for modifying // BuildVarsFunc registers a new route with a custom function for modifying
// route variables before building a URL. // route variables before building a URL.
func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route { func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route {
return r.NewRoute().BuildVarsFunc(f) return r.NewRoute().BuildVarsFunc(f)
@ -257,20 +355,21 @@ type WalkFunc func(route *Route, router *Router, ancestors []*Route) error
func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error { func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error {
for _, t := range r.routes { for _, t := range r.routes {
if t.regexp == nil || t.regexp.path == nil || t.regexp.path.template == "" {
continue
}
err := walkFn(t, r, ancestors) err := walkFn(t, r, ancestors)
if err == SkipRouter { if err == SkipRouter {
continue continue
} }
if err != nil {
return err
}
for _, sr := range t.matchers { for _, sr := range t.matchers {
if h, ok := sr.(*Router); ok { if h, ok := sr.(*Router); ok {
ancestors = append(ancestors, t)
err := h.walk(walkFn, ancestors) err := h.walk(walkFn, ancestors)
if err != nil { if err != nil {
return err return err
} }
ancestors = ancestors[:len(ancestors)-1]
} }
} }
if h, ok := t.handler.(*Router); ok { if h, ok := t.handler.(*Router); ok {
@ -294,6 +393,11 @@ type RouteMatch struct {
Route *Route Route *Route
Handler http.Handler Handler http.Handler
Vars map[string]string Vars map[string]string
// MatchErr is set to appropriate matching error
// It is set to ErrMethodMismatch if there is a mismatch in
// the request method and route method
MatchErr error
} }
type contextKey int type contextKey int
@ -305,7 +409,7 @@ const (
// Vars returns the route variables for the current request, if any. // Vars returns the route variables for the current request, if any.
func Vars(r *http.Request) map[string]string { func Vars(r *http.Request) map[string]string {
if rv := context.Get(r, varsKey); rv != nil { if rv := contextGet(r, varsKey); rv != nil {
return rv.(map[string]string) return rv.(map[string]string)
} }
return nil return nil
@ -317,18 +421,18 @@ func Vars(r *http.Request) map[string]string {
// after the handler returns, unless the KeepContext option is set on the // after the handler returns, unless the KeepContext option is set on the
// Router. // Router.
func CurrentRoute(r *http.Request) *Route { func CurrentRoute(r *http.Request) *Route {
if rv := context.Get(r, routeKey); rv != nil { if rv := contextGet(r, routeKey); rv != nil {
return rv.(*Route) return rv.(*Route)
} }
return nil return nil
} }
func setVars(r *http.Request, val interface{}) { func setVars(r *http.Request, val interface{}) *http.Request {
context.Set(r, varsKey, val) return contextSet(r, varsKey, val)
} }
func setCurrentRoute(r *http.Request, val interface{}) { func setCurrentRoute(r *http.Request, val interface{}) *http.Request {
context.Set(r, routeKey, val) return contextSet(r, routeKey, val)
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -350,6 +454,7 @@ func cleanPath(p string) string {
if p[len(p)-1] == '/' && np != "/" { if p[len(p)-1] == '/' && np != "/" {
np += "/" np += "/"
} }
return np return np
} }
@ -365,6 +470,8 @@ func uniqueVars(s1, s2 []string) error {
return nil return nil
} }
// checkPairs returns the count of strings passed in, and an error if
// the count is not an even number.
func checkPairs(pairs ...string) (int, error) { func checkPairs(pairs ...string) (int, error) {
length := len(pairs) length := len(pairs)
if length%2 != 0 { if length%2 != 0 {
@ -374,7 +481,8 @@ func checkPairs(pairs ...string) (int, error) {
return length, nil return length, nil
} }
// mapFromPairs converts variadic string parameters to a string map. // mapFromPairsToString converts variadic string parameters to a
// string to string map.
func mapFromPairsToString(pairs ...string) (map[string]string, error) { func mapFromPairsToString(pairs ...string) (map[string]string, error) {
length, err := checkPairs(pairs...) length, err := checkPairs(pairs...)
if err != nil { if err != nil {
@ -387,6 +495,8 @@ func mapFromPairsToString(pairs ...string) (map[string]string, error) {
return m, nil return m, nil
} }
// mapFromPairsToRegex converts variadic string parameters to a
// string to regex map.
func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) { func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) {
length, err := checkPairs(pairs...) length, err := checkPairs(pairs...)
if err != nil { if err != nil {
@ -467,3 +577,12 @@ func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]s
} }
return true return true
} }
// methodNotAllowed replies to the request with an HTTP status code 405.
func methodNotAllowed(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusMethodNotAllowed)
}
// methodNotAllowedHandler returns a simple request handler
// that replies to each request with a status code 405.
func methodNotAllowedHandler() http.Handler { return http.HandlerFunc(methodNotAllowed) }

View file

@ -14,6 +14,20 @@ import (
"strings" "strings"
) )
type routeRegexpOptions struct {
strictSlash bool
useEncodedPath bool
}
type regexpType int
const (
regexpTypePath regexpType = 0
regexpTypeHost regexpType = 1
regexpTypePrefix regexpType = 2
regexpTypeQuery regexpType = 3
)
// newRouteRegexp parses a route template and returns a routeRegexp, // newRouteRegexp parses a route template and returns a routeRegexp,
// used to match a host, a path or a query string. // used to match a host, a path or a query string.
// //
@ -24,7 +38,7 @@ import (
// Previously we accepted only Python-like identifiers for variable // Previously we accepted only Python-like identifiers for variable
// names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that // names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that
// name and pattern can't be empty, and names can't contain a colon. // name and pattern can't be empty, and names can't contain a colon.
func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash bool) (*routeRegexp, error) { func newRouteRegexp(tpl string, typ regexpType, options routeRegexpOptions) (*routeRegexp, error) {
// Check if it is well-formed. // Check if it is well-formed.
idxs, errBraces := braceIndices(tpl) idxs, errBraces := braceIndices(tpl)
if errBraces != nil { if errBraces != nil {
@ -34,19 +48,18 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash
template := tpl template := tpl
// Now let's parse it. // Now let's parse it.
defaultPattern := "[^/]+" defaultPattern := "[^/]+"
if matchQuery { if typ == regexpTypeQuery {
defaultPattern = "[^?&]*" defaultPattern = ".*"
} else if matchHost { } else if typ == regexpTypeHost {
defaultPattern = "[^.]+" defaultPattern = "[^.]+"
matchPrefix = false
} }
// Only match strict slash if not matching // Only match strict slash if not matching
if matchPrefix || matchHost || matchQuery { if typ != regexpTypePath {
strictSlash = false options.strictSlash = false
} }
// Set a flag for strictSlash. // Set a flag for strictSlash.
endSlash := false endSlash := false
if strictSlash && strings.HasSuffix(tpl, "/") { if options.strictSlash && strings.HasSuffix(tpl, "/") {
tpl = tpl[:len(tpl)-1] tpl = tpl[:len(tpl)-1]
endSlash = true endSlash = true
} }
@ -73,14 +86,14 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash
tpl[idxs[i]:end]) tpl[idxs[i]:end])
} }
// Build the regexp pattern. // Build the regexp pattern.
varIdx := i / 2 fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt)
fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(varIdx), patt)
// Build the reverse template. // Build the reverse template.
fmt.Fprintf(reverse, "%s%%s", raw) fmt.Fprintf(reverse, "%s%%s", raw)
// Append variable name and compiled pattern. // Append variable name and compiled pattern.
varsN[varIdx] = name varsN[i/2] = name
varsR[varIdx], err = regexp.Compile(fmt.Sprintf("^%s$", patt)) varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -88,16 +101,16 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash
// Add the remaining. // Add the remaining.
raw := tpl[end:] raw := tpl[end:]
pattern.WriteString(regexp.QuoteMeta(raw)) pattern.WriteString(regexp.QuoteMeta(raw))
if strictSlash { if options.strictSlash {
pattern.WriteString("[/]?") pattern.WriteString("[/]?")
} }
if matchQuery { if typ == regexpTypeQuery {
// Add the default pattern if the query value is empty // Add the default pattern if the query value is empty
if queryVal := strings.SplitN(template, "=", 2)[1]; queryVal == "" { if queryVal := strings.SplitN(template, "=", 2)[1]; queryVal == "" {
pattern.WriteString(defaultPattern) pattern.WriteString(defaultPattern)
} }
} }
if !matchPrefix { if typ != regexpTypePrefix {
pattern.WriteByte('$') pattern.WriteByte('$')
} }
reverse.WriteString(raw) reverse.WriteString(raw)
@ -109,16 +122,22 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash
if errCompile != nil { if errCompile != nil {
return nil, errCompile return nil, errCompile
} }
// Check for capturing groups which used to work in older versions
if reg.NumSubexp() != len(idxs)/2 {
panic(fmt.Sprintf("route %s contains capture groups in its regexp. ", template) +
"Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)")
}
// Done! // Done!
return &routeRegexp{ return &routeRegexp{
template: template, template: template,
matchHost: matchHost, regexpType: typ,
matchQuery: matchQuery, options: options,
strictSlash: strictSlash, regexp: reg,
regexp: reg, reverse: reverse.String(),
reverse: reverse.String(), varsN: varsN,
varsN: varsN, varsR: varsR,
varsR: varsR,
}, nil }, nil
} }
@ -127,12 +146,10 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash
type routeRegexp struct { type routeRegexp struct {
// The unmodified template. // The unmodified template.
template string template string
// True for host match, false for path or query string match. // The type of match
matchHost bool regexpType regexpType
// True for query string match, false for path and host match. // Options for matching
matchQuery bool options routeRegexpOptions
// The strictSlash value defined on the route, but disabled if PathPrefix was used.
strictSlash bool
// Expanded regexp. // Expanded regexp.
regexp *regexp.Regexp regexp *regexp.Regexp
// Reverse template. // Reverse template.
@ -145,13 +162,17 @@ type routeRegexp struct {
// Match matches the regexp against the URL host or path. // Match matches the regexp against the URL host or path.
func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool { func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
if !r.matchHost { if r.regexpType != regexpTypeHost {
if r.matchQuery { if r.regexpType == regexpTypeQuery {
return r.matchQueryString(req) return r.matchQueryString(req)
} else {
return r.regexp.MatchString(req.URL.Path)
} }
path := req.URL.Path
if r.options.useEncodedPath {
path = req.URL.EscapedPath()
}
return r.regexp.MatchString(path)
} }
return r.regexp.MatchString(getHost(req)) return r.regexp.MatchString(getHost(req))
} }
@ -163,6 +184,9 @@ func (r *routeRegexp) url(values map[string]string) (string, error) {
if !ok { if !ok {
return "", fmt.Errorf("mux: missing route variable %q", v) return "", fmt.Errorf("mux: missing route variable %q", v)
} }
if r.regexpType == regexpTypeQuery {
value = url.QueryEscape(value)
}
urlValues[k] = value urlValues[k] = value
} }
rv := fmt.Sprintf(r.reverse, urlValues...) rv := fmt.Sprintf(r.reverse, urlValues...)
@ -181,11 +205,11 @@ func (r *routeRegexp) url(values map[string]string) (string, error) {
return rv, nil return rv, nil
} }
// getUrlQuery returns a single query parameter from a request URL. // getURLQuery returns a single query parameter from a request URL.
// For a URL with foo=bar&baz=ding, we return only the relevant key // For a URL with foo=bar&baz=ding, we return only the relevant key
// value pair for the routeRegexp. // value pair for the routeRegexp.
func (r *routeRegexp) getUrlQuery(req *http.Request) string { func (r *routeRegexp) getURLQuery(req *http.Request) string {
if !r.matchQuery { if r.regexpType != regexpTypeQuery {
return "" return ""
} }
templateKey := strings.SplitN(r.template, "=", 2)[0] templateKey := strings.SplitN(r.template, "=", 2)[0]
@ -198,14 +222,14 @@ func (r *routeRegexp) getUrlQuery(req *http.Request) string {
} }
func (r *routeRegexp) matchQueryString(req *http.Request) bool { func (r *routeRegexp) matchQueryString(req *http.Request) bool {
return r.regexp.MatchString(r.getUrlQuery(req)) return r.regexp.MatchString(r.getURLQuery(req))
} }
// braceIndices returns the first level curly brace indices from a string. // braceIndices returns the first level curly brace indices from a string.
// It returns an error in case of unbalanced braces. // It returns an error in case of unbalanced braces.
func braceIndices(s string) ([]int, error) { func braceIndices(s string) ([]int, error) {
var level, idx int var level, idx int
idxs := make([]int, 0) var idxs []int
for i := 0; i < len(s); i++ { for i := 0; i < len(s); i++ {
switch s[i] { switch s[i] {
case '{': case '{':
@ -246,33 +270,24 @@ type routeRegexpGroup struct {
func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) { func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) {
// Store host variables. // Store host variables.
if v.host != nil { if v.host != nil {
hostVars := v.host.regexp.FindStringSubmatch(getHost(req)) host := getHost(req)
if hostVars != nil { matches := v.host.regexp.FindStringSubmatchIndex(host)
subexpNames := v.host.regexp.SubexpNames() if len(matches) > 0 {
varName := 0 extractVars(host, matches, v.host.varsN, m.Vars)
for i, name := range subexpNames[1:] {
if name != "" && name == varGroupName(varName) {
m.Vars[v.host.varsN[varName]] = hostVars[i+1]
varName++
}
}
} }
} }
path := req.URL.Path
if r.useEncodedPath {
path = req.URL.EscapedPath()
}
// Store path variables. // Store path variables.
if v.path != nil { if v.path != nil {
pathVars := v.path.regexp.FindStringSubmatch(req.URL.Path) matches := v.path.regexp.FindStringSubmatchIndex(path)
if pathVars != nil { if len(matches) > 0 {
subexpNames := v.path.regexp.SubexpNames() extractVars(path, matches, v.path.varsN, m.Vars)
varName := 0
for i, name := range subexpNames[1:] {
if name != "" && name == varGroupName(varName) {
m.Vars[v.path.varsN[varName]] = pathVars[i+1]
varName++
}
}
// Check if we should redirect. // Check if we should redirect.
if v.path.strictSlash { if v.path.options.strictSlash {
p1 := strings.HasSuffix(req.URL.Path, "/") p1 := strings.HasSuffix(path, "/")
p2 := strings.HasSuffix(v.path.template, "/") p2 := strings.HasSuffix(v.path.template, "/")
if p1 != p2 { if p1 != p2 {
u, _ := url.Parse(req.URL.String()) u, _ := url.Parse(req.URL.String())
@ -288,16 +303,10 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route)
} }
// Store query string variables. // Store query string variables.
for _, q := range v.queries { for _, q := range v.queries {
queryVars := q.regexp.FindStringSubmatch(q.getUrlQuery(req)) queryURL := q.getURLQuery(req)
if queryVars != nil { matches := q.regexp.FindStringSubmatchIndex(queryURL)
subexpNames := q.regexp.SubexpNames() if len(matches) > 0 {
varName := 0 extractVars(queryURL, matches, q.varsN, m.Vars)
for i, name := range subexpNames[1:] {
if name != "" && name == varGroupName(varName) {
m.Vars[q.varsN[varName]] = queryVars[i+1]
varName++
}
}
} }
} }
} }
@ -315,3 +324,9 @@ func getHost(r *http.Request) string {
return host return host
} }
func extractVars(input string, matches []int, names []string, output map[string]string) {
for i, name := range names {
output[name] = input[matches[2*i+2]:matches[2*i+3]]
}
}

View file

@ -26,6 +26,13 @@ type Route struct {
// If true, when the path pattern is "/path/", accessing "/path" will // If true, when the path pattern is "/path/", accessing "/path" will
// redirect to the former and vice versa. // redirect to the former and vice versa.
strictSlash bool strictSlash bool
// If true, when the path pattern is "/path//to", accessing "/path//to"
// will not redirect
skipClean bool
// If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to"
useEncodedPath bool
// The scheme used when building URLs.
buildScheme string
// If true, this route never matches: it is only used to build URLs. // If true, this route never matches: it is only used to build URLs.
buildOnly bool buildOnly bool
// The name used to build URLs. // The name used to build URLs.
@ -36,17 +43,44 @@ type Route struct {
buildVarsFunc BuildVarsFunc buildVarsFunc BuildVarsFunc
} }
// SkipClean reports whether path cleaning is enabled for this route via
// Router.SkipClean.
func (r *Route) SkipClean() bool {
return r.skipClean
}
// Match matches the route against the request. // Match matches the route against the request.
func (r *Route) Match(req *http.Request, match *RouteMatch) bool { func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
if r.buildOnly || r.err != nil { if r.buildOnly || r.err != nil {
return false return false
} }
var matchErr error
// Match everything. // Match everything.
for _, m := range r.matchers { for _, m := range r.matchers {
if matched := m.Match(req, match); !matched { if matched := m.Match(req, match); !matched {
if _, ok := m.(methodMatcher); ok {
matchErr = ErrMethodMismatch
continue
}
matchErr = nil
return false return false
} }
} }
if matchErr != nil {
match.MatchErr = matchErr
return false
}
if match.MatchErr == ErrMethodMismatch {
// We found a route which matches request method, clear MatchErr
match.MatchErr = nil
// Then override the mis-matched handler
match.Handler = r.handler
}
// Yay, we have a match. Let's collect some info about it. // Yay, we have a match. Let's collect some info about it.
if match.Route == nil { if match.Route == nil {
match.Route = r match.Route = r
@ -57,6 +91,7 @@ func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
if match.Vars == nil { if match.Vars == nil {
match.Vars = make(map[string]string) match.Vars = make(map[string]string)
} }
// Set variables. // Set variables.
if r.regexp != nil { if r.regexp != nil {
r.regexp.setMatch(req, match, r) r.regexp.setMatch(req, match, r)
@ -138,20 +173,23 @@ func (r *Route) addMatcher(m matcher) *Route {
} }
// addRegexpMatcher adds a host or path matcher and builder to a route. // addRegexpMatcher adds a host or path matcher and builder to a route.
func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery bool) error { func (r *Route) addRegexpMatcher(tpl string, typ regexpType) error {
if r.err != nil { if r.err != nil {
return r.err return r.err
} }
r.regexp = r.getRegexpGroup() r.regexp = r.getRegexpGroup()
if !matchHost && !matchQuery { if typ == regexpTypePath || typ == regexpTypePrefix {
if len(tpl) == 0 || tpl[0] != '/' { if len(tpl) > 0 && tpl[0] != '/' {
return fmt.Errorf("mux: path must start with a slash, got %q", tpl) return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
} }
if r.regexp.path != nil { if r.regexp.path != nil {
tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl
} }
} }
rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash) rr, err := newRouteRegexp(tpl, typ, routeRegexpOptions{
strictSlash: r.strictSlash,
useEncodedPath: r.useEncodedPath,
})
if err != nil { if err != nil {
return err return err
} }
@ -160,7 +198,7 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery
return err return err
} }
} }
if matchHost { if typ == regexpTypeHost {
if r.regexp.path != nil { if r.regexp.path != nil {
if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil { if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil {
return err return err
@ -173,7 +211,7 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery
return err return err
} }
} }
if matchQuery { if typ == regexpTypeQuery {
r.regexp.queries = append(r.regexp.queries, rr) r.regexp.queries = append(r.regexp.queries, rr)
} else { } else {
r.regexp.path = rr r.regexp.path = rr
@ -217,14 +255,16 @@ func (m headerRegexMatcher) Match(r *http.Request, match *RouteMatch) bool {
return matchMapWithRegex(m, r.Header, true) return matchMapWithRegex(m, r.Header, true)
} }
// Regular expressions can be used with headers as well. // HeadersRegexp accepts a sequence of key/value pairs, where the value has regex
// It accepts a sequence of key/value pairs, where the value has regex support. For example // support. For example:
//
// r := mux.NewRouter() // r := mux.NewRouter()
// r.HeadersRegexp("Content-Type", "application/(text|json)", // r.HeadersRegexp("Content-Type", "application/(text|json)",
// "X-Requested-With", "XMLHttpRequest") // "X-Requested-With", "XMLHttpRequest")
// //
// The above route will only match if both the request header matches both regular expressions. // The above route will only match if both the request header matches both regular expressions.
// It the value is an empty string, it will match any value if the key is set. // If the value is an empty string, it will match any value if the key is set.
// Use the start and end of string anchors (^ and $) to match an exact value.
func (r *Route) HeadersRegexp(pairs ...string) *Route { func (r *Route) HeadersRegexp(pairs ...string) *Route {
if r.err == nil { if r.err == nil {
var headers map[string]*regexp.Regexp var headers map[string]*regexp.Regexp
@ -254,7 +294,7 @@ func (r *Route) HeadersRegexp(pairs ...string) *Route {
// Variable names must be unique in a given route. They can be retrieved // Variable names must be unique in a given route. They can be retrieved
// calling mux.Vars(request). // calling mux.Vars(request).
func (r *Route) Host(tpl string) *Route { func (r *Route) Host(tpl string) *Route {
r.err = r.addRegexpMatcher(tpl, true, false, false) r.err = r.addRegexpMatcher(tpl, regexpTypeHost)
return r return r
} }
@ -263,6 +303,7 @@ func (r *Route) Host(tpl string) *Route {
// MatcherFunc is the function signature used by custom matchers. // MatcherFunc is the function signature used by custom matchers.
type MatcherFunc func(*http.Request, *RouteMatch) bool type MatcherFunc func(*http.Request, *RouteMatch) bool
// Match returns the match for a given request.
func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool { func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool {
return m(r, match) return m(r, match)
} }
@ -313,7 +354,7 @@ func (r *Route) Methods(methods ...string) *Route {
// Variable names must be unique in a given route. They can be retrieved // Variable names must be unique in a given route. They can be retrieved
// calling mux.Vars(request). // calling mux.Vars(request).
func (r *Route) Path(tpl string) *Route { func (r *Route) Path(tpl string) *Route {
r.err = r.addRegexpMatcher(tpl, false, false, false) r.err = r.addRegexpMatcher(tpl, regexpTypePath)
return r return r
} }
@ -329,7 +370,7 @@ func (r *Route) Path(tpl string) *Route {
// Also note that the setting of Router.StrictSlash() has no effect on routes // Also note that the setting of Router.StrictSlash() has no effect on routes
// with a PathPrefix matcher. // with a PathPrefix matcher.
func (r *Route) PathPrefix(tpl string) *Route { func (r *Route) PathPrefix(tpl string) *Route {
r.err = r.addRegexpMatcher(tpl, false, true, false) r.err = r.addRegexpMatcher(tpl, regexpTypePrefix)
return r return r
} }
@ -360,7 +401,7 @@ func (r *Route) Queries(pairs ...string) *Route {
return nil return nil
} }
for i := 0; i < length; i += 2 { for i := 0; i < length; i += 2 {
if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], false, false, true); r.err != nil { if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], regexpTypeQuery); r.err != nil {
return r return r
} }
} }
@ -383,6 +424,9 @@ func (r *Route) Schemes(schemes ...string) *Route {
for k, v := range schemes { for k, v := range schemes {
schemes[k] = strings.ToLower(v) schemes[k] = strings.ToLower(v)
} }
if r.buildScheme == "" && len(schemes) > 0 {
r.buildScheme = schemes[0]
}
return r.addMatcher(schemeMatcher(schemes)) return r.addMatcher(schemeMatcher(schemes))
} }
@ -466,22 +510,33 @@ func (r *Route) URL(pairs ...string) (*url.URL, error) {
return nil, err return nil, err
} }
var scheme, host, path string var scheme, host, path string
queries := make([]string, 0, len(r.regexp.queries))
if r.regexp.host != nil { if r.regexp.host != nil {
// Set a default scheme.
scheme = "http"
if host, err = r.regexp.host.url(values); err != nil { if host, err = r.regexp.host.url(values); err != nil {
return nil, err return nil, err
} }
scheme = "http"
if s := r.getBuildScheme(); s != "" {
scheme = s
}
} }
if r.regexp.path != nil { if r.regexp.path != nil {
if path, err = r.regexp.path.url(values); err != nil { if path, err = r.regexp.path.url(values); err != nil {
return nil, err return nil, err
} }
} }
for _, q := range r.regexp.queries {
var query string
if query, err = q.url(values); err != nil {
return nil, err
}
queries = append(queries, query)
}
return &url.URL{ return &url.URL{
Scheme: scheme, Scheme: scheme,
Host: host, Host: host,
Path: path, Path: path,
RawQuery: strings.Join(queries, "&"),
}, nil }, nil
} }
@ -503,10 +558,14 @@ func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &url.URL{ u := &url.URL{
Scheme: "http", Scheme: "http",
Host: host, Host: host,
}, nil }
if s := r.getBuildScheme(); s != "" {
u.Scheme = s
}
return u, nil
} }
// URLPath builds the path part of the URL for a route. See Route.URL(). // URLPath builds the path part of the URL for a route. See Route.URL().
@ -532,6 +591,104 @@ func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
}, nil }, nil
} }
// GetPathTemplate returns the template used to build the
// route match.
// This is useful for building simple REST API documentation and for instrumentation
// against third-party services.
// An error will be returned if the route does not define a path.
func (r *Route) GetPathTemplate() (string, error) {
if r.err != nil {
return "", r.err
}
if r.regexp == nil || r.regexp.path == nil {
return "", errors.New("mux: route doesn't have a path")
}
return r.regexp.path.template, nil
}
// GetPathRegexp returns the expanded regular expression used to match route path.
// This is useful for building simple REST API documentation and for instrumentation
// against third-party services.
// An error will be returned if the route does not define a path.
func (r *Route) GetPathRegexp() (string, error) {
if r.err != nil {
return "", r.err
}
if r.regexp == nil || r.regexp.path == nil {
return "", errors.New("mux: route does not have a path")
}
return r.regexp.path.regexp.String(), nil
}
// GetQueriesRegexp returns the expanded regular expressions used to match the
// route queries.
// This is useful for building simple REST API documentation and for instrumentation
// against third-party services.
// An error will be returned if the route does not have queries.
func (r *Route) GetQueriesRegexp() ([]string, error) {
if r.err != nil {
return nil, r.err
}
if r.regexp == nil || r.regexp.queries == nil {
return nil, errors.New("mux: route doesn't have queries")
}
var queries []string
for _, query := range r.regexp.queries {
queries = append(queries, query.regexp.String())
}
return queries, nil
}
// GetQueriesTemplates returns the templates used to build the
// query matching.
// This is useful for building simple REST API documentation and for instrumentation
// against third-party services.
// An error will be returned if the route does not define queries.
func (r *Route) GetQueriesTemplates() ([]string, error) {
if r.err != nil {
return nil, r.err
}
if r.regexp == nil || r.regexp.queries == nil {
return nil, errors.New("mux: route doesn't have queries")
}
var queries []string
for _, query := range r.regexp.queries {
queries = append(queries, query.template)
}
return queries, nil
}
// GetMethods returns the methods the route matches against
// This is useful for building simple REST API documentation and for instrumentation
// against third-party services.
// An error will be returned if route does not have methods.
func (r *Route) GetMethods() ([]string, error) {
if r.err != nil {
return nil, r.err
}
for _, m := range r.matchers {
if methods, ok := m.(methodMatcher); ok {
return []string(methods), nil
}
}
return nil, errors.New("mux: route doesn't have methods")
}
// GetHostTemplate returns the template used to build the
// route match.
// This is useful for building simple REST API documentation and for instrumentation
// against third-party services.
// An error will be returned if the route does not define a host.
func (r *Route) GetHostTemplate() (string, error) {
if r.err != nil {
return "", r.err
}
if r.regexp == nil || r.regexp.host == nil {
return "", errors.New("mux: route doesn't have a host")
}
return r.regexp.host.template, nil
}
// prepareVars converts the route variable pairs into a map. If the route has a // prepareVars converts the route variable pairs into a map. If the route has a
// BuildVarsFunc, it is invoked. // BuildVarsFunc, it is invoked.
func (r *Route) prepareVars(pairs ...string) (map[string]string, error) { func (r *Route) prepareVars(pairs ...string) (map[string]string, error) {
@ -558,11 +715,22 @@ func (r *Route) buildVars(m map[string]string) map[string]string {
// parentRoute allows routes to know about parent host and path definitions. // parentRoute allows routes to know about parent host and path definitions.
type parentRoute interface { type parentRoute interface {
getBuildScheme() string
getNamedRoutes() map[string]*Route getNamedRoutes() map[string]*Route
getRegexpGroup() *routeRegexpGroup getRegexpGroup() *routeRegexpGroup
buildVars(map[string]string) map[string]string buildVars(map[string]string) map[string]string
} }
func (r *Route) getBuildScheme() string {
if r.buildScheme != "" {
return r.buildScheme
}
if r.parent != nil {
return r.parent.getBuildScheme()
}
return ""
}
// getNamedRoutes returns the map where named routes are registered. // getNamedRoutes returns the map where named routes are registered.
func (r *Route) getNamedRoutes() map[string]*Route { func (r *Route) getNamedRoutes() map[string]*Route {
if r.parent == nil { if r.parent == nil {

19
vendor/github.com/gorilla/mux/test_helpers.go generated vendored Normal file
View file

@ -0,0 +1,19 @@
// Copyright 2012 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 mux
import "net/http"
// SetURLVars sets the URL variables for the given request, to be accessed via
// mux.Vars for testing route behaviour. Arguments are not modified, a shallow
// copy is returned.
//
// This API should only be used for testing purposes; it provides a way to
// inject variables into the request context. Alternatively, URL variables
// can be set by making a route that captures the required variables,
// starting a server and sending the request to that server.
func SetURLVars(r *http.Request, val map[string]string) *http.Request {
return setVars(r, val)
}

2
vendor/github.com/spf13/pflag/.gitignore generated vendored Normal file
View file

@ -0,0 +1,2 @@
.idea/*

21
vendor/github.com/spf13/pflag/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,21 @@
sudo: false
language: go
go:
- 1.7.3
- 1.8.1
- tip
matrix:
allow_failures:
- go: tip
install:
- go get github.com/golang/lint/golint
- export PATH=$GOPATH/bin:$PATH
- go install ./...
script:
- verify/all.sh -v
- go test ./...

View file

@ -1,4 +1,6 @@
[![Build Status](https://travis-ci.org/spf13/pflag.svg?branch=master)](https://travis-ci.org/spf13/pflag) [![Build Status](https://travis-ci.org/spf13/pflag.svg?branch=master)](https://travis-ci.org/spf13/pflag)
[![Go Report Card](https://goreportcard.com/badge/github.com/spf13/pflag)](https://goreportcard.com/report/github.com/spf13/pflag)
[![GoDoc](https://godoc.org/github.com/spf13/pflag?status.svg)](https://godoc.org/github.com/spf13/pflag)
## Description ## Description
@ -106,9 +108,9 @@ that give one-letter shorthands for flags. You can use these by appending
var ip = flag.IntP("flagname", "f", 1234, "help message") var ip = flag.IntP("flagname", "f", 1234, "help message")
var flagvar bool var flagvar bool
func init() { func init() {
flag.BoolVarP("boolname", "b", true, "help message") flag.BoolVarP(&flagvar, "boolname", "b", true, "help message")
} }
flag.VarP(&flagVar, "varname", "v", 1234, "help message") flag.VarP(&flagVal, "varname", "v", "help message")
``` ```
Shorthand letters can be used with single dashes on the command line. Shorthand letters can be used with single dashes on the command line.
@ -244,6 +246,25 @@ It is possible to mark a flag as hidden, meaning it will still function as norma
flags.MarkHidden("secretFlag") flags.MarkHidden("secretFlag")
``` ```
## Disable sorting of flags
`pflag` allows you to disable sorting of flags for help and usage message.
**Example**:
```go
flags.BoolP("verbose", "v", false, "verbose output")
flags.String("coolflag", "yeaah", "it's really cool flag")
flags.Int("usefulflag", 777, "sometimes it's very useful")
flags.SortFlags = false
flags.PrintDefaults()
```
**Output**:
```
-v, --verbose verbose output
--coolflag string it's really cool flag (default "yeaah")
--usefulflag int sometimes it's very useful (default 777)
```
## Supporting Go flags when using pflag ## Supporting Go flags when using pflag
In order to support flags defined using Go's `flag` package, they must be added to the `pflag` flagset. This is usually necessary In order to support flags defined using Go's `flag` package, they must be added to the `pflag` flagset. This is usually necessary
to support flags defined by third-party dependencies (e.g. `golang/glog`). to support flags defined by third-party dependencies (e.g. `golang/glog`).
@ -268,8 +289,8 @@ func main() {
You can see the full reference documentation of the pflag package You can see the full reference documentation of the pflag package
[at godoc.org][3], or through go's standard documentation system by [at godoc.org][3], or through go's standard documentation system by
running `godoc -http=:6060` and browsing to running `godoc -http=:6060` and browsing to
[http://localhost:6060/pkg/github.com/ogier/pflag][2] after [http://localhost:6060/pkg/github.com/spf13/pflag][2] after
installation. installation.
[2]: http://localhost:6060/pkg/github.com/ogier/pflag [2]: http://localhost:6060/pkg/github.com/spf13/pflag
[3]: http://godoc.org/github.com/ogier/pflag [3]: http://godoc.org/github.com/spf13/pflag

147
vendor/github.com/spf13/pflag/bool_slice.go generated vendored Normal file
View file

@ -0,0 +1,147 @@
package pflag
import (
"io"
"strconv"
"strings"
)
// -- boolSlice Value
type boolSliceValue struct {
value *[]bool
changed bool
}
func newBoolSliceValue(val []bool, p *[]bool) *boolSliceValue {
bsv := new(boolSliceValue)
bsv.value = p
*bsv.value = val
return bsv
}
// Set converts, and assigns, the comma-separated boolean argument string representation as the []bool value of this flag.
// If Set is called on a flag that already has a []bool assigned, the newly converted values will be appended.
func (s *boolSliceValue) Set(val string) error {
// remove all quote characters
rmQuote := strings.NewReplacer(`"`, "", `'`, "", "`", "")
// read flag arguments with CSV parser
boolStrSlice, err := readAsCSV(rmQuote.Replace(val))
if err != nil && err != io.EOF {
return err
}
// parse boolean values into slice
out := make([]bool, 0, len(boolStrSlice))
for _, boolStr := range boolStrSlice {
b, err := strconv.ParseBool(strings.TrimSpace(boolStr))
if err != nil {
return err
}
out = append(out, b)
}
if !s.changed {
*s.value = out
} else {
*s.value = append(*s.value, out...)
}
s.changed = true
return nil
}
// Type returns a string that uniquely represents this flag's type.
func (s *boolSliceValue) Type() string {
return "boolSlice"
}
// String defines a "native" format for this boolean slice flag value.
func (s *boolSliceValue) String() string {
boolStrSlice := make([]string, len(*s.value))
for i, b := range *s.value {
boolStrSlice[i] = strconv.FormatBool(b)
}
out, _ := writeAsCSV(boolStrSlice)
return "[" + out + "]"
}
func boolSliceConv(val string) (interface{}, error) {
val = strings.Trim(val, "[]")
// Empty string would cause a slice with one (empty) entry
if len(val) == 0 {
return []bool{}, nil
}
ss := strings.Split(val, ",")
out := make([]bool, len(ss))
for i, t := range ss {
var err error
out[i], err = strconv.ParseBool(t)
if err != nil {
return nil, err
}
}
return out, nil
}
// GetBoolSlice returns the []bool value of a flag with the given name.
func (f *FlagSet) GetBoolSlice(name string) ([]bool, error) {
val, err := f.getFlagType(name, "boolSlice", boolSliceConv)
if err != nil {
return []bool{}, err
}
return val.([]bool), nil
}
// BoolSliceVar defines a boolSlice flag with specified name, default value, and usage string.
// The argument p points to a []bool variable in which to store the value of the flag.
func (f *FlagSet) BoolSliceVar(p *[]bool, name string, value []bool, usage string) {
f.VarP(newBoolSliceValue(value, p), name, "", usage)
}
// BoolSliceVarP is like BoolSliceVar, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) BoolSliceVarP(p *[]bool, name, shorthand string, value []bool, usage string) {
f.VarP(newBoolSliceValue(value, p), name, shorthand, usage)
}
// BoolSliceVar defines a []bool flag with specified name, default value, and usage string.
// The argument p points to a []bool variable in which to store the value of the flag.
func BoolSliceVar(p *[]bool, name string, value []bool, usage string) {
CommandLine.VarP(newBoolSliceValue(value, p), name, "", usage)
}
// BoolSliceVarP is like BoolSliceVar, but accepts a shorthand letter that can be used after a single dash.
func BoolSliceVarP(p *[]bool, name, shorthand string, value []bool, usage string) {
CommandLine.VarP(newBoolSliceValue(value, p), name, shorthand, usage)
}
// BoolSlice defines a []bool flag with specified name, default value, and usage string.
// The return value is the address of a []bool variable that stores the value of the flag.
func (f *FlagSet) BoolSlice(name string, value []bool, usage string) *[]bool {
p := []bool{}
f.BoolSliceVarP(&p, name, "", value, usage)
return &p
}
// BoolSliceP is like BoolSlice, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) BoolSliceP(name, shorthand string, value []bool, usage string) *[]bool {
p := []bool{}
f.BoolSliceVarP(&p, name, shorthand, value, usage)
return &p
}
// BoolSlice defines a []bool flag with specified name, default value, and usage string.
// The return value is the address of a []bool variable that stores the value of the flag.
func BoolSlice(name string, value []bool, usage string) *[]bool {
return CommandLine.BoolSliceP(name, "", value, usage)
}
// BoolSliceP is like BoolSlice, but accepts a shorthand letter that can be used after a single dash.
func BoolSliceP(name, shorthand string, value []bool, usage string) *[]bool {
return CommandLine.BoolSliceP(name, shorthand, value, usage)
}

209
vendor/github.com/spf13/pflag/bytes.go generated vendored Normal file
View file

@ -0,0 +1,209 @@
package pflag
import (
"encoding/base64"
"encoding/hex"
"fmt"
"strings"
)
// BytesHex adapts []byte for use as a flag. Value of flag is HEX encoded
type bytesHexValue []byte
// String implements pflag.Value.String.
func (bytesHex bytesHexValue) String() string {
return fmt.Sprintf("%X", []byte(bytesHex))
}
// Set implements pflag.Value.Set.
func (bytesHex *bytesHexValue) Set(value string) error {
bin, err := hex.DecodeString(strings.TrimSpace(value))
if err != nil {
return err
}
*bytesHex = bin
return nil
}
// Type implements pflag.Value.Type.
func (*bytesHexValue) Type() string {
return "bytesHex"
}
func newBytesHexValue(val []byte, p *[]byte) *bytesHexValue {
*p = val
return (*bytesHexValue)(p)
}
func bytesHexConv(sval string) (interface{}, error) {
bin, err := hex.DecodeString(sval)
if err == nil {
return bin, nil
}
return nil, fmt.Errorf("invalid string being converted to Bytes: %s %s", sval, err)
}
// GetBytesHex return the []byte value of a flag with the given name
func (f *FlagSet) GetBytesHex(name string) ([]byte, error) {
val, err := f.getFlagType(name, "bytesHex", bytesHexConv)
if err != nil {
return []byte{}, err
}
return val.([]byte), nil
}
// BytesHexVar defines an []byte flag with specified name, default value, and usage string.
// The argument p points to an []byte variable in which to store the value of the flag.
func (f *FlagSet) BytesHexVar(p *[]byte, name string, value []byte, usage string) {
f.VarP(newBytesHexValue(value, p), name, "", usage)
}
// BytesHexVarP is like BytesHexVar, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) BytesHexVarP(p *[]byte, name, shorthand string, value []byte, usage string) {
f.VarP(newBytesHexValue(value, p), name, shorthand, usage)
}
// BytesHexVar defines an []byte flag with specified name, default value, and usage string.
// The argument p points to an []byte variable in which to store the value of the flag.
func BytesHexVar(p *[]byte, name string, value []byte, usage string) {
CommandLine.VarP(newBytesHexValue(value, p), name, "", usage)
}
// BytesHexVarP is like BytesHexVar, but accepts a shorthand letter that can be used after a single dash.
func BytesHexVarP(p *[]byte, name, shorthand string, value []byte, usage string) {
CommandLine.VarP(newBytesHexValue(value, p), name, shorthand, usage)
}
// BytesHex defines an []byte flag with specified name, default value, and usage string.
// The return value is the address of an []byte variable that stores the value of the flag.
func (f *FlagSet) BytesHex(name string, value []byte, usage string) *[]byte {
p := new([]byte)
f.BytesHexVarP(p, name, "", value, usage)
return p
}
// BytesHexP is like BytesHex, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) BytesHexP(name, shorthand string, value []byte, usage string) *[]byte {
p := new([]byte)
f.BytesHexVarP(p, name, shorthand, value, usage)
return p
}
// BytesHex defines an []byte flag with specified name, default value, and usage string.
// The return value is the address of an []byte variable that stores the value of the flag.
func BytesHex(name string, value []byte, usage string) *[]byte {
return CommandLine.BytesHexP(name, "", value, usage)
}
// BytesHexP is like BytesHex, but accepts a shorthand letter that can be used after a single dash.
func BytesHexP(name, shorthand string, value []byte, usage string) *[]byte {
return CommandLine.BytesHexP(name, shorthand, value, usage)
}
// BytesBase64 adapts []byte for use as a flag. Value of flag is Base64 encoded
type bytesBase64Value []byte
// String implements pflag.Value.String.
func (bytesBase64 bytesBase64Value) String() string {
return base64.StdEncoding.EncodeToString([]byte(bytesBase64))
}
// Set implements pflag.Value.Set.
func (bytesBase64 *bytesBase64Value) Set(value string) error {
bin, err := base64.StdEncoding.DecodeString(strings.TrimSpace(value))
if err != nil {
return err
}
*bytesBase64 = bin
return nil
}
// Type implements pflag.Value.Type.
func (*bytesBase64Value) Type() string {
return "bytesBase64"
}
func newBytesBase64Value(val []byte, p *[]byte) *bytesBase64Value {
*p = val
return (*bytesBase64Value)(p)
}
func bytesBase64ValueConv(sval string) (interface{}, error) {
bin, err := base64.StdEncoding.DecodeString(sval)
if err == nil {
return bin, nil
}
return nil, fmt.Errorf("invalid string being converted to Bytes: %s %s", sval, err)
}
// GetBytesBase64 return the []byte value of a flag with the given name
func (f *FlagSet) GetBytesBase64(name string) ([]byte, error) {
val, err := f.getFlagType(name, "bytesBase64", bytesBase64ValueConv)
if err != nil {
return []byte{}, err
}
return val.([]byte), nil
}
// BytesBase64Var defines an []byte flag with specified name, default value, and usage string.
// The argument p points to an []byte variable in which to store the value of the flag.
func (f *FlagSet) BytesBase64Var(p *[]byte, name string, value []byte, usage string) {
f.VarP(newBytesBase64Value(value, p), name, "", usage)
}
// BytesBase64VarP is like BytesBase64Var, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) BytesBase64VarP(p *[]byte, name, shorthand string, value []byte, usage string) {
f.VarP(newBytesBase64Value(value, p), name, shorthand, usage)
}
// BytesBase64Var defines an []byte flag with specified name, default value, and usage string.
// The argument p points to an []byte variable in which to store the value of the flag.
func BytesBase64Var(p *[]byte, name string, value []byte, usage string) {
CommandLine.VarP(newBytesBase64Value(value, p), name, "", usage)
}
// BytesBase64VarP is like BytesBase64Var, but accepts a shorthand letter that can be used after a single dash.
func BytesBase64VarP(p *[]byte, name, shorthand string, value []byte, usage string) {
CommandLine.VarP(newBytesBase64Value(value, p), name, shorthand, usage)
}
// BytesBase64 defines an []byte flag with specified name, default value, and usage string.
// The return value is the address of an []byte variable that stores the value of the flag.
func (f *FlagSet) BytesBase64(name string, value []byte, usage string) *[]byte {
p := new([]byte)
f.BytesBase64VarP(p, name, "", value, usage)
return p
}
// BytesBase64P is like BytesBase64, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) BytesBase64P(name, shorthand string, value []byte, usage string) *[]byte {
p := new([]byte)
f.BytesBase64VarP(p, name, shorthand, value, usage)
return p
}
// BytesBase64 defines an []byte flag with specified name, default value, and usage string.
// The return value is the address of an []byte variable that stores the value of the flag.
func BytesBase64(name string, value []byte, usage string) *[]byte {
return CommandLine.BytesBase64P(name, "", value, usage)
}
// BytesBase64P is like BytesBase64, but accepts a shorthand letter that can be used after a single dash.
func BytesBase64P(name, shorthand string, value []byte, usage string) *[]byte {
return CommandLine.BytesBase64P(name, shorthand, value, usage)
}

View file

@ -11,13 +11,13 @@ func newCountValue(val int, p *int) *countValue {
} }
func (i *countValue) Set(s string) error { func (i *countValue) Set(s string) error {
v, err := strconv.ParseInt(s, 0, 64) // "+1" means that no specific value was passed, so increment
// -1 means that no specific value was passed, so increment if s == "+1" {
if v == -1 {
*i = countValue(*i + 1) *i = countValue(*i + 1)
} else { return nil
*i = countValue(v)
} }
v, err := strconv.ParseInt(s, 0, 0)
*i = countValue(v)
return err return err
} }
@ -54,7 +54,7 @@ func (f *FlagSet) CountVar(p *int, name string, usage string) {
// CountVarP is like CountVar only take a shorthand for the flag name. // CountVarP is like CountVar only take a shorthand for the flag name.
func (f *FlagSet) CountVarP(p *int, name, shorthand string, usage string) { func (f *FlagSet) CountVarP(p *int, name, shorthand string, usage string) {
flag := f.VarPF(newCountValue(0, p), name, shorthand, usage) flag := f.VarPF(newCountValue(0, p), name, shorthand, usage)
flag.NoOptDefVal = "-1" flag.NoOptDefVal = "+1"
} }
// CountVar like CountVar only the flag is placed on the CommandLine instead of a given flag set // CountVar like CountVar only the flag is placed on the CommandLine instead of a given flag set
@ -83,7 +83,9 @@ func (f *FlagSet) CountP(name, shorthand string, usage string) *int {
return p return p
} }
// Count like Count only the flag is placed on the CommandLine isntead of a given flag set // Count defines a count flag with specified name, default value, and usage string.
// The return value is the address of an int variable that stores the value of the flag.
// A count flag will add 1 to its value evey time it is found on the command line
func Count(name string, usage string) *int { func Count(name string, usage string) *int {
return CommandLine.CountP(name, "", usage) return CommandLine.CountP(name, "", usage)
} }

128
vendor/github.com/spf13/pflag/duration_slice.go generated vendored Normal file
View file

@ -0,0 +1,128 @@
package pflag
import (
"fmt"
"strings"
"time"
)
// -- durationSlice Value
type durationSliceValue struct {
value *[]time.Duration
changed bool
}
func newDurationSliceValue(val []time.Duration, p *[]time.Duration) *durationSliceValue {
dsv := new(durationSliceValue)
dsv.value = p
*dsv.value = val
return dsv
}
func (s *durationSliceValue) Set(val string) error {
ss := strings.Split(val, ",")
out := make([]time.Duration, len(ss))
for i, d := range ss {
var err error
out[i], err = time.ParseDuration(d)
if err != nil {
return err
}
}
if !s.changed {
*s.value = out
} else {
*s.value = append(*s.value, out...)
}
s.changed = true
return nil
}
func (s *durationSliceValue) Type() string {
return "durationSlice"
}
func (s *durationSliceValue) String() string {
out := make([]string, len(*s.value))
for i, d := range *s.value {
out[i] = fmt.Sprintf("%s", d)
}
return "[" + strings.Join(out, ",") + "]"
}
func durationSliceConv(val string) (interface{}, error) {
val = strings.Trim(val, "[]")
// Empty string would cause a slice with one (empty) entry
if len(val) == 0 {
return []time.Duration{}, nil
}
ss := strings.Split(val, ",")
out := make([]time.Duration, len(ss))
for i, d := range ss {
var err error
out[i], err = time.ParseDuration(d)
if err != nil {
return nil, err
}
}
return out, nil
}
// GetDurationSlice returns the []time.Duration value of a flag with the given name
func (f *FlagSet) GetDurationSlice(name string) ([]time.Duration, error) {
val, err := f.getFlagType(name, "durationSlice", durationSliceConv)
if err != nil {
return []time.Duration{}, err
}
return val.([]time.Duration), nil
}
// DurationSliceVar defines a durationSlice flag with specified name, default value, and usage string.
// The argument p points to a []time.Duration variable in which to store the value of the flag.
func (f *FlagSet) DurationSliceVar(p *[]time.Duration, name string, value []time.Duration, usage string) {
f.VarP(newDurationSliceValue(value, p), name, "", usage)
}
// DurationSliceVarP is like DurationSliceVar, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) DurationSliceVarP(p *[]time.Duration, name, shorthand string, value []time.Duration, usage string) {
f.VarP(newDurationSliceValue(value, p), name, shorthand, usage)
}
// DurationSliceVar defines a duration[] flag with specified name, default value, and usage string.
// The argument p points to a duration[] variable in which to store the value of the flag.
func DurationSliceVar(p *[]time.Duration, name string, value []time.Duration, usage string) {
CommandLine.VarP(newDurationSliceValue(value, p), name, "", usage)
}
// DurationSliceVarP is like DurationSliceVar, but accepts a shorthand letter that can be used after a single dash.
func DurationSliceVarP(p *[]time.Duration, name, shorthand string, value []time.Duration, usage string) {
CommandLine.VarP(newDurationSliceValue(value, p), name, shorthand, usage)
}
// DurationSlice defines a []time.Duration flag with specified name, default value, and usage string.
// The return value is the address of a []time.Duration variable that stores the value of the flag.
func (f *FlagSet) DurationSlice(name string, value []time.Duration, usage string) *[]time.Duration {
p := []time.Duration{}
f.DurationSliceVarP(&p, name, "", value, usage)
return &p
}
// DurationSliceP is like DurationSlice, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) DurationSliceP(name, shorthand string, value []time.Duration, usage string) *[]time.Duration {
p := []time.Duration{}
f.DurationSliceVarP(&p, name, shorthand, value, usage)
return &p
}
// DurationSlice defines a []time.Duration flag with specified name, default value, and usage string.
// The return value is the address of a []time.Duration variable that stores the value of the flag.
func DurationSlice(name string, value []time.Duration, usage string) *[]time.Duration {
return CommandLine.DurationSliceP(name, "", value, usage)
}
// DurationSliceP is like DurationSlice, but accepts a shorthand letter that can be used after a single dash.
func DurationSliceP(name, shorthand string, value []time.Duration, usage string) *[]time.Duration {
return CommandLine.DurationSliceP(name, shorthand, value, usage)
}

View file

@ -16,9 +16,9 @@ pflag is a drop-in replacement of Go's native flag package. If you import
pflag under the name "flag" then all code should continue to function pflag under the name "flag" then all code should continue to function
with no changes. with no changes.
import flag "github.com/ogier/pflag" import flag "github.com/spf13/pflag"
There is one exception to this: if you directly instantiate the Flag struct There is one exception to this: if you directly instantiate the Flag struct
there is one more field "Shorthand" that you will need to set. there is one more field "Shorthand" that you will need to set.
Most code never instantiates this struct directly, and instead uses Most code never instantiates this struct directly, and instead uses
functions such as String(), BoolVar(), and Var(), and is therefore functions such as String(), BoolVar(), and Var(), and is therefore
@ -101,6 +101,7 @@ package pflag
import ( import (
"bytes" "bytes"
"errors" "errors"
goflag "flag"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -123,6 +124,12 @@ const (
PanicOnError PanicOnError
) )
// ParseErrorsWhitelist defines the parsing errors that can be ignored
type ParseErrorsWhitelist struct {
// UnknownFlags will ignore unknown flags errors and continue parsing rest of the flags
UnknownFlags bool
}
// NormalizedName is a flag name that has been normalized according to rules // NormalizedName is a flag name that has been normalized according to rules
// for the FlagSet (e.g. making '-' and '_' equivalent). // for the FlagSet (e.g. making '-' and '_' equivalent).
type NormalizedName string type NormalizedName string
@ -134,18 +141,30 @@ type FlagSet struct {
// a custom error handler. // a custom error handler.
Usage func() Usage func()
// SortFlags is used to indicate, if user wants to have sorted flags in
// help/usage messages.
SortFlags bool
// ParseErrorsWhitelist is used to configure a whitelist of errors
ParseErrorsWhitelist ParseErrorsWhitelist
name string name string
parsed bool parsed bool
actual map[NormalizedName]*Flag actual map[NormalizedName]*Flag
orderedActual []*Flag
sortedActual []*Flag
formal map[NormalizedName]*Flag formal map[NormalizedName]*Flag
orderedFormal []*Flag
sortedFormal []*Flag
shorthands map[byte]*Flag shorthands map[byte]*Flag
args []string // arguments after flags args []string // arguments after flags
argsLenAtDash int // len(args) when a '--' was located when parsing, or -1 if no -- argsLenAtDash int // len(args) when a '--' was located when parsing, or -1 if no --
exitOnError bool // does the program exit if there's an error?
errorHandling ErrorHandling errorHandling ErrorHandling
output io.Writer // nil means stderr; use out() accessor output io.Writer // nil means stderr; use out() accessor
interspersed bool // allow interspersed option/non-option args interspersed bool // allow interspersed option/non-option args
normalizeNameFunc func(f *FlagSet, name string) NormalizedName normalizeNameFunc func(f *FlagSet, name string) NormalizedName
addedGoFlagSets []*goflag.FlagSet
} }
// A Flag represents the state of a flag. // A Flag represents the state of a flag.
@ -156,7 +175,7 @@ type Flag struct {
Value Value // value as set Value Value // value as set
DefValue string // default value (as text); for usage message DefValue string // default value (as text); for usage message
Changed bool // If the user set the value (or if left to default) Changed bool // If the user set the value (or if left to default)
NoOptDefVal string //default value (as text); if the flag is on the command line without any options NoOptDefVal string // default value (as text); if the flag is on the command line without any options
Deprecated string // If this flag is deprecated, this string is the new or now thing to use Deprecated string // If this flag is deprecated, this string is the new or now thing to use
Hidden bool // used by cobra.Command to allow flags to be hidden from help/usage text Hidden bool // used by cobra.Command to allow flags to be hidden from help/usage text
ShorthandDeprecated string // If the shorthand of this flag is deprecated, this string is the new or now thing to use ShorthandDeprecated string // If the shorthand of this flag is deprecated, this string is the new or now thing to use
@ -194,11 +213,19 @@ func sortFlags(flags map[NormalizedName]*Flag) []*Flag {
// "--getUrl" which may also be translated to "geturl" and everything will work. // "--getUrl" which may also be translated to "geturl" and everything will work.
func (f *FlagSet) SetNormalizeFunc(n func(f *FlagSet, name string) NormalizedName) { func (f *FlagSet) SetNormalizeFunc(n func(f *FlagSet, name string) NormalizedName) {
f.normalizeNameFunc = n f.normalizeNameFunc = n
for k, v := range f.formal { f.sortedFormal = f.sortedFormal[:0]
delete(f.formal, k) for fname, flag := range f.formal {
nname := f.normalizeFlagName(string(k)) nname := f.normalizeFlagName(flag.Name)
f.formal[nname] = v if fname == nname {
v.Name = string(nname) continue
}
flag.Name = string(nname)
delete(f.formal, fname)
f.formal[nname] = flag
if _, set := f.actual[fname]; set {
delete(f.actual, fname)
f.actual[nname] = flag
}
} }
} }
@ -229,46 +256,78 @@ func (f *FlagSet) SetOutput(output io.Writer) {
f.output = output f.output = output
} }
// VisitAll visits the flags in lexicographical order, calling fn for each. // VisitAll visits the flags in lexicographical order or
// in primordial order if f.SortFlags is false, calling fn for each.
// It visits all flags, even those not set. // It visits all flags, even those not set.
func (f *FlagSet) VisitAll(fn func(*Flag)) { func (f *FlagSet) VisitAll(fn func(*Flag)) {
for _, flag := range sortFlags(f.formal) { if len(f.formal) == 0 {
return
}
var flags []*Flag
if f.SortFlags {
if len(f.formal) != len(f.sortedFormal) {
f.sortedFormal = sortFlags(f.formal)
}
flags = f.sortedFormal
} else {
flags = f.orderedFormal
}
for _, flag := range flags {
fn(flag) fn(flag)
} }
} }
// HasFlags returns a bool to indicate if the FlagSet has any flags definied. // HasFlags returns a bool to indicate if the FlagSet has any flags defined.
func (f *FlagSet) HasFlags() bool { func (f *FlagSet) HasFlags() bool {
return len(f.formal) > 0 return len(f.formal) > 0
} }
// HasAvailableFlags returns a bool to indicate if the FlagSet has any flags // HasAvailableFlags returns a bool to indicate if the FlagSet has any flags
// definied that are not hidden or deprecated. // that are not hidden.
func (f *FlagSet) HasAvailableFlags() bool { func (f *FlagSet) HasAvailableFlags() bool {
for _, flag := range f.formal { for _, flag := range f.formal {
if !flag.Hidden && len(flag.Deprecated) == 0 { if !flag.Hidden {
return true return true
} }
} }
return false return false
} }
// VisitAll visits the command-line flags in lexicographical order, calling // VisitAll visits the command-line flags in lexicographical order or
// fn for each. It visits all flags, even those not set. // in primordial order if f.SortFlags is false, calling fn for each.
// It visits all flags, even those not set.
func VisitAll(fn func(*Flag)) { func VisitAll(fn func(*Flag)) {
CommandLine.VisitAll(fn) CommandLine.VisitAll(fn)
} }
// Visit visits the flags in lexicographical order, calling fn for each. // Visit visits the flags in lexicographical order or
// in primordial order if f.SortFlags is false, calling fn for each.
// It visits only those flags that have been set. // It visits only those flags that have been set.
func (f *FlagSet) Visit(fn func(*Flag)) { func (f *FlagSet) Visit(fn func(*Flag)) {
for _, flag := range sortFlags(f.actual) { if len(f.actual) == 0 {
return
}
var flags []*Flag
if f.SortFlags {
if len(f.actual) != len(f.sortedActual) {
f.sortedActual = sortFlags(f.actual)
}
flags = f.sortedActual
} else {
flags = f.orderedActual
}
for _, flag := range flags {
fn(flag) fn(flag)
} }
} }
// Visit visits the command-line flags in lexicographical order, calling fn // Visit visits the command-line flags in lexicographical order or
// for each. It visits only those flags that have been set. // in primordial order if f.SortFlags is false, calling fn for each.
// It visits only those flags that have been set.
func Visit(fn func(*Flag)) { func Visit(fn func(*Flag)) {
CommandLine.Visit(fn) CommandLine.Visit(fn)
} }
@ -278,6 +337,22 @@ func (f *FlagSet) Lookup(name string) *Flag {
return f.lookup(f.normalizeFlagName(name)) return f.lookup(f.normalizeFlagName(name))
} }
// ShorthandLookup returns the Flag structure of the short handed flag,
// returning nil if none exists.
// It panics, if len(name) > 1.
func (f *FlagSet) ShorthandLookup(name string) *Flag {
if name == "" {
return nil
}
if len(name) > 1 {
msg := fmt.Sprintf("can not look up shorthand which is more than one ASCII character: %q", name)
fmt.Fprintf(f.out(), msg)
panic(msg)
}
c := name[0]
return f.shorthands[c]
}
// lookup returns the Flag structure of the named flag, returning nil if none exists. // lookup returns the Flag structure of the named flag, returning nil if none exists.
func (f *FlagSet) lookup(name NormalizedName) *Flag { func (f *FlagSet) lookup(name NormalizedName) *Flag {
return f.formal[name] return f.formal[name]
@ -319,10 +394,11 @@ func (f *FlagSet) MarkDeprecated(name string, usageMessage string) error {
if flag == nil { if flag == nil {
return fmt.Errorf("flag %q does not exist", name) return fmt.Errorf("flag %q does not exist", name)
} }
if len(usageMessage) == 0 { if usageMessage == "" {
return fmt.Errorf("deprecated message for flag %q must be set", name) return fmt.Errorf("deprecated message for flag %q must be set", name)
} }
flag.Deprecated = usageMessage flag.Deprecated = usageMessage
flag.Hidden = true
return nil return nil
} }
@ -334,7 +410,7 @@ func (f *FlagSet) MarkShorthandDeprecated(name string, usageMessage string) erro
if flag == nil { if flag == nil {
return fmt.Errorf("flag %q does not exist", name) return fmt.Errorf("flag %q does not exist", name)
} }
if len(usageMessage) == 0 { if usageMessage == "" {
return fmt.Errorf("deprecated message for flag %q must be set", name) return fmt.Errorf("deprecated message for flag %q must be set", name)
} }
flag.ShorthandDeprecated = usageMessage flag.ShorthandDeprecated = usageMessage
@ -358,6 +434,12 @@ func Lookup(name string) *Flag {
return CommandLine.Lookup(name) return CommandLine.Lookup(name)
} }
// ShorthandLookup returns the Flag structure of the short handed flag,
// returning nil if none exists.
func ShorthandLookup(name string) *Flag {
return CommandLine.ShorthandLookup(name)
}
// Set sets the value of the named flag. // Set sets the value of the named flag.
func (f *FlagSet) Set(name, value string) error { func (f *FlagSet) Set(name, value string) error {
normalName := f.normalizeFlagName(name) normalName := f.normalizeFlagName(name)
@ -365,17 +447,30 @@ func (f *FlagSet) Set(name, value string) error {
if !ok { if !ok {
return fmt.Errorf("no such flag -%v", name) return fmt.Errorf("no such flag -%v", name)
} }
err := flag.Value.Set(value) err := flag.Value.Set(value)
if err != nil { if err != nil {
return err var flagName string
if flag.Shorthand != "" && flag.ShorthandDeprecated == "" {
flagName = fmt.Sprintf("-%s, --%s", flag.Shorthand, flag.Name)
} else {
flagName = fmt.Sprintf("--%s", flag.Name)
}
return fmt.Errorf("invalid argument %q for %q flag: %v", value, flagName, err)
} }
if f.actual == nil {
f.actual = make(map[NormalizedName]*Flag) if !flag.Changed {
if f.actual == nil {
f.actual = make(map[NormalizedName]*Flag)
}
f.actual[normalName] = flag
f.orderedActual = append(f.orderedActual, flag)
flag.Changed = true
} }
f.actual[normalName] = flag
flag.Changed = true if flag.Deprecated != "" {
if len(flag.Deprecated) > 0 { fmt.Fprintf(f.out(), "Flag --%s has been deprecated, %s\n", flag.Name, flag.Deprecated)
fmt.Fprintf(os.Stderr, "Flag --%s has been deprecated, %s\n", flag.Name, flag.Deprecated)
} }
return nil return nil
} }
@ -416,7 +511,7 @@ func Set(name, value string) error {
// otherwise, the default values of all defined flags in the set. // otherwise, the default values of all defined flags in the set.
func (f *FlagSet) PrintDefaults() { func (f *FlagSet) PrintDefaults() {
usages := f.FlagUsages() usages := f.FlagUsages()
fmt.Fprintf(f.out(), "%s", usages) fmt.Fprint(f.out(), usages)
} }
// defaultIsZeroValue returns true if the default value for this flag represents // defaultIsZeroValue returns true if the default value for this flag represents
@ -482,43 +577,125 @@ func UnquoteUsage(flag *Flag) (name string, usage string) {
name = "int" name = "int"
case "uint64": case "uint64":
name = "uint" name = "uint"
case "stringSlice":
name = "strings"
case "intSlice":
name = "ints"
case "uintSlice":
name = "uints"
case "boolSlice":
name = "bools"
} }
return return
} }
// FlagUsages Returns a string containing the usage information for all flags in // Splits the string `s` on whitespace into an initial substring up to
// the FlagSet // `i` runes in length and the remainder. Will go `slop` over `i` if
func (f *FlagSet) FlagUsages() string { // that encompasses the entire string (which allows the caller to
x := new(bytes.Buffer) // avoid short orphan words on the final line).
func wrapN(i, slop int, s string) (string, string) {
if i+slop > len(s) {
return s, ""
}
w := strings.LastIndexAny(s[:i], " \t\n")
if w <= 0 {
return s, ""
}
nlPos := strings.LastIndex(s[:i], "\n")
if nlPos > 0 && nlPos < w {
return s[:nlPos], s[nlPos+1:]
}
return s[:w], s[w+1:]
}
// Wraps the string `s` to a maximum width `w` with leading indent
// `i`. The first line is not indented (this is assumed to be done by
// caller). Pass `w` == 0 to do no wrapping
func wrap(i, w int, s string) string {
if w == 0 {
return strings.Replace(s, "\n", "\n"+strings.Repeat(" ", i), -1)
}
// space between indent i and end of line width w into which
// we should wrap the text.
wrap := w - i
var r, l string
// Not enough space for sensible wrapping. Wrap as a block on
// the next line instead.
if wrap < 24 {
i = 16
wrap = w - i
r += "\n" + strings.Repeat(" ", i)
}
// If still not enough space then don't even try to wrap.
if wrap < 24 {
return strings.Replace(s, "\n", r, -1)
}
// Try to avoid short orphan words on the final line, by
// allowing wrapN to go a bit over if that would fit in the
// remainder of the line.
slop := 5
wrap = wrap - slop
// Handle first line, which is indented by the caller (or the
// special case above)
l, s = wrapN(wrap, slop, s)
r = r + strings.Replace(l, "\n", "\n"+strings.Repeat(" ", i), -1)
// Now wrap the rest
for s != "" {
var t string
t, s = wrapN(wrap, slop, s)
r = r + "\n" + strings.Repeat(" ", i) + strings.Replace(t, "\n", "\n"+strings.Repeat(" ", i), -1)
}
return r
}
// FlagUsagesWrapped returns a string containing the usage information
// for all flags in the FlagSet. Wrapped to `cols` columns (0 for no
// wrapping)
func (f *FlagSet) FlagUsagesWrapped(cols int) string {
buf := new(bytes.Buffer)
lines := make([]string, 0, len(f.formal)) lines := make([]string, 0, len(f.formal))
maxlen := 0 maxlen := 0
f.VisitAll(func(flag *Flag) { f.VisitAll(func(flag *Flag) {
if len(flag.Deprecated) > 0 || flag.Hidden { if flag.Hidden {
return return
} }
line := "" line := ""
if len(flag.Shorthand) > 0 && len(flag.ShorthandDeprecated) == 0 { if flag.Shorthand != "" && flag.ShorthandDeprecated == "" {
line = fmt.Sprintf(" -%s, --%s", flag.Shorthand, flag.Name) line = fmt.Sprintf(" -%s, --%s", flag.Shorthand, flag.Name)
} else { } else {
line = fmt.Sprintf(" --%s", flag.Name) line = fmt.Sprintf(" --%s", flag.Name)
} }
varname, usage := UnquoteUsage(flag) varname, usage := UnquoteUsage(flag)
if len(varname) > 0 { if varname != "" {
line += " " + varname line += " " + varname
} }
if len(flag.NoOptDefVal) > 0 { if flag.NoOptDefVal != "" {
switch flag.Value.Type() { switch flag.Value.Type() {
case "string": case "string":
line += fmt.Sprintf("[=%q]", flag.NoOptDefVal) line += fmt.Sprintf("[=\"%s\"]", flag.NoOptDefVal)
case "bool": case "bool":
if flag.NoOptDefVal != "true" { if flag.NoOptDefVal != "true" {
line += fmt.Sprintf("[=%s]", flag.NoOptDefVal) line += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
} }
case "count":
if flag.NoOptDefVal != "+1" {
line += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
}
default: default:
line += fmt.Sprintf("[=%s]", flag.NoOptDefVal) line += fmt.Sprintf("[=%s]", flag.NoOptDefVal)
} }
@ -539,6 +716,9 @@ func (f *FlagSet) FlagUsages() string {
line += fmt.Sprintf(" (default %s)", flag.DefValue) line += fmt.Sprintf(" (default %s)", flag.DefValue)
} }
} }
if len(flag.Deprecated) != 0 {
line += fmt.Sprintf(" (DEPRECATED: %s)", flag.Deprecated)
}
lines = append(lines, line) lines = append(lines, line)
}) })
@ -546,10 +726,17 @@ func (f *FlagSet) FlagUsages() string {
for _, line := range lines { for _, line := range lines {
sidx := strings.Index(line, "\x00") sidx := strings.Index(line, "\x00")
spacing := strings.Repeat(" ", maxlen-sidx) spacing := strings.Repeat(" ", maxlen-sidx)
fmt.Fprintln(x, line[:sidx], spacing, line[sidx+1:]) // maxlen + 2 comes from + 1 for the \x00 and + 1 for the (deliberate) off-by-one in maxlen-sidx
fmt.Fprintln(buf, line[:sidx], spacing, wrap(maxlen+2, cols, line[sidx+1:]))
} }
return x.String() return buf.String()
}
// FlagUsages returns a string containing the usage information for all flags in
// the FlagSet
func (f *FlagSet) FlagUsages() string {
return f.FlagUsagesWrapped(0)
} }
// PrintDefaults prints to standard error the default values of all defined command-line flags. // PrintDefaults prints to standard error the default values of all defined command-line flags.
@ -635,16 +822,15 @@ func (f *FlagSet) VarPF(value Value, name, shorthand, usage string) *Flag {
// VarP is like Var, but accepts a shorthand letter that can be used after a single dash. // VarP is like Var, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) VarP(value Value, name, shorthand, usage string) { func (f *FlagSet) VarP(value Value, name, shorthand, usage string) {
_ = f.VarPF(value, name, shorthand, usage) f.VarPF(value, name, shorthand, usage)
} }
// AddFlag will add the flag to the FlagSet // AddFlag will add the flag to the FlagSet
func (f *FlagSet) AddFlag(flag *Flag) { func (f *FlagSet) AddFlag(flag *Flag) {
// Call normalizeFlagName function only once
normalizedFlagName := f.normalizeFlagName(flag.Name) normalizedFlagName := f.normalizeFlagName(flag.Name)
_, alreadythere := f.formal[normalizedFlagName] _, alreadyThere := f.formal[normalizedFlagName]
if alreadythere { if alreadyThere {
msg := fmt.Sprintf("%s flag redefined: %s", f.name, flag.Name) msg := fmt.Sprintf("%s flag redefined: %s", f.name, flag.Name)
fmt.Fprintln(f.out(), msg) fmt.Fprintln(f.out(), msg)
panic(msg) // Happens only if flags are declared with identical names panic(msg) // Happens only if flags are declared with identical names
@ -655,28 +841,31 @@ func (f *FlagSet) AddFlag(flag *Flag) {
flag.Name = string(normalizedFlagName) flag.Name = string(normalizedFlagName)
f.formal[normalizedFlagName] = flag f.formal[normalizedFlagName] = flag
f.orderedFormal = append(f.orderedFormal, flag)
if len(flag.Shorthand) == 0 { if flag.Shorthand == "" {
return return
} }
if len(flag.Shorthand) > 1 { if len(flag.Shorthand) > 1 {
fmt.Fprintf(f.out(), "%s shorthand more than ASCII character: %s\n", f.name, flag.Shorthand) msg := fmt.Sprintf("%q shorthand is more than one ASCII character", flag.Shorthand)
panic("shorthand is more than one character") fmt.Fprintf(f.out(), msg)
panic(msg)
} }
if f.shorthands == nil { if f.shorthands == nil {
f.shorthands = make(map[byte]*Flag) f.shorthands = make(map[byte]*Flag)
} }
c := flag.Shorthand[0] c := flag.Shorthand[0]
old, alreadythere := f.shorthands[c] used, alreadyThere := f.shorthands[c]
if alreadythere { if alreadyThere {
fmt.Fprintf(f.out(), "%s shorthand reused: %q for %s already used for %s\n", f.name, c, flag.Name, old.Name) msg := fmt.Sprintf("unable to redefine %q shorthand in %q flagset: it's already used for %q flag", c, f.name, used.Name)
panic("shorthand redefinition") fmt.Fprintf(f.out(), msg)
panic(msg)
} }
f.shorthands[c] = flag f.shorthands[c] = flag
} }
// AddFlagSet adds one FlagSet to another. If a flag is already present in f // AddFlagSet adds one FlagSet to another. If a flag is already present in f
// the flag from newSet will be ignored // the flag from newSet will be ignored.
func (f *FlagSet) AddFlagSet(newSet *FlagSet) { func (f *FlagSet) AddFlagSet(newSet *FlagSet) {
if newSet == nil { if newSet == nil {
return return
@ -707,8 +896,10 @@ func VarP(value Value, name, shorthand, usage string) {
// returns the error. // returns the error.
func (f *FlagSet) failf(format string, a ...interface{}) error { func (f *FlagSet) failf(format string, a ...interface{}) error {
err := fmt.Errorf(format, a...) err := fmt.Errorf(format, a...)
fmt.Fprintln(f.out(), err) if f.errorHandling != ContinueOnError {
f.usage() fmt.Fprintln(f.out(), err)
f.usage()
}
return err return err
} }
@ -724,57 +915,61 @@ func (f *FlagSet) usage() {
} }
} }
func (f *FlagSet) setFlag(flag *Flag, value string, origArg string) error { //--unknown (args will be empty)
if err := flag.Value.Set(value); err != nil { //--unknown --next-flag ... (args will be --next-flag ...)
return f.failf("invalid argument %q for %s: %v", value, origArg, err) //--unknown arg ... (args will be arg ...)
func stripUnknownFlagValue(args []string) []string {
if len(args) == 0 {
//--unknown
return args
} }
// mark as visited for Visit()
if f.actual == nil { first := args[0]
f.actual = make(map[NormalizedName]*Flag) if first[0] == '-' {
//--unknown --next-flag ...
return args
} }
f.actual[f.normalizeFlagName(flag.Name)] = flag
flag.Changed = true //--unknown arg ... (args will be arg ...)
if len(flag.Deprecated) > 0 { return args[1:]
fmt.Fprintf(os.Stderr, "Flag --%s has been deprecated, %s\n", flag.Name, flag.Deprecated)
}
if len(flag.ShorthandDeprecated) > 0 && containsShorthand(origArg, flag.Shorthand) {
fmt.Fprintf(os.Stderr, "Flag shorthand -%s has been deprecated, %s\n", flag.Shorthand, flag.ShorthandDeprecated)
}
return nil
} }
func containsShorthand(arg, shorthand string) bool { func (f *FlagSet) parseLongArg(s string, args []string, fn parseFunc) (a []string, err error) {
// filter out flags --<flag_name>
if strings.HasPrefix(arg, "-") {
return false
}
arg = strings.SplitN(arg, "=", 2)[0]
return strings.Contains(arg, shorthand)
}
func (f *FlagSet) parseLongArg(s string, args []string) (a []string, err error) {
a = args a = args
name := s[2:] name := s[2:]
if len(name) == 0 || name[0] == '-' || name[0] == '=' { if len(name) == 0 || name[0] == '-' || name[0] == '=' {
err = f.failf("bad flag syntax: %s", s) err = f.failf("bad flag syntax: %s", s)
return return
} }
split := strings.SplitN(name, "=", 2) split := strings.SplitN(name, "=", 2)
name = split[0] name = split[0]
flag, alreadythere := f.formal[f.normalizeFlagName(name)] flag, exists := f.formal[f.normalizeFlagName(name)]
if !alreadythere {
if name == "help" { // special case for nice help message. if !exists {
switch {
case name == "help":
f.usage() f.usage()
return a, ErrHelp return a, ErrHelp
case f.ParseErrorsWhitelist.UnknownFlags:
// --unknown=unknownval arg ...
// we do not want to lose arg in this case
if len(split) >= 2 {
return a, nil
}
return stripUnknownFlagValue(a), nil
default:
err = f.failf("unknown flag: --%s", name)
return
} }
err = f.failf("unknown flag: --%s", name)
return
} }
var value string var value string
if len(split) == 2 { if len(split) == 2 {
// '--flag=arg' // '--flag=arg'
value = split[1] value = split[1]
} else if len(flag.NoOptDefVal) > 0 { } else if flag.NoOptDefVal != "" {
// '--flag' (arg was optional) // '--flag' (arg was optional)
value = flag.NoOptDefVal value = flag.NoOptDefVal
} else if len(a) > 0 { } else if len(a) > 0 {
@ -786,55 +981,87 @@ func (f *FlagSet) parseLongArg(s string, args []string) (a []string, err error)
err = f.failf("flag needs an argument: %s", s) err = f.failf("flag needs an argument: %s", s)
return return
} }
err = f.setFlag(flag, value, s)
err = fn(flag, value)
if err != nil {
f.failf(err.Error())
}
return return
} }
func (f *FlagSet) parseSingleShortArg(shorthands string, args []string) (outShorts string, outArgs []string, err error) { func (f *FlagSet) parseSingleShortArg(shorthands string, args []string, fn parseFunc) (outShorts string, outArgs []string, err error) {
outArgs = args
if strings.HasPrefix(shorthands, "test.") { if strings.HasPrefix(shorthands, "test.") {
return return
} }
outArgs = args
outShorts = shorthands[1:] outShorts = shorthands[1:]
c := shorthands[0] c := shorthands[0]
flag, alreadythere := f.shorthands[c] flag, exists := f.shorthands[c]
if !alreadythere { if !exists {
if c == 'h' { // special case for nice help message. switch {
case c == 'h':
f.usage() f.usage()
err = ErrHelp err = ErrHelp
return return
case f.ParseErrorsWhitelist.UnknownFlags:
// '-f=arg arg ...'
// we do not want to lose arg in this case
if len(shorthands) > 2 && shorthands[1] == '=' {
outShorts = ""
return
}
outArgs = stripUnknownFlagValue(outArgs)
return
default:
err = f.failf("unknown shorthand flag: %q in -%s", c, shorthands)
return
} }
//TODO continue on error
err = f.failf("unknown shorthand flag: %q in -%s", c, shorthands)
return
} }
var value string var value string
if len(shorthands) > 2 && shorthands[1] == '=' { if len(shorthands) > 2 && shorthands[1] == '=' {
// '-f=arg'
value = shorthands[2:] value = shorthands[2:]
outShorts = "" outShorts = ""
} else if len(flag.NoOptDefVal) > 0 { } else if flag.NoOptDefVal != "" {
// '-f' (arg was optional)
value = flag.NoOptDefVal value = flag.NoOptDefVal
} else if len(shorthands) > 1 { } else if len(shorthands) > 1 {
// '-farg'
value = shorthands[1:] value = shorthands[1:]
outShorts = "" outShorts = ""
} else if len(args) > 0 { } else if len(args) > 0 {
// '-f arg'
value = args[0] value = args[0]
outArgs = args[1:] outArgs = args[1:]
} else { } else {
// '-f' (arg was required)
err = f.failf("flag needs an argument: %q in -%s", c, shorthands) err = f.failf("flag needs an argument: %q in -%s", c, shorthands)
return return
} }
err = f.setFlag(flag, value, shorthands)
if flag.ShorthandDeprecated != "" {
fmt.Fprintf(f.out(), "Flag shorthand -%s has been deprecated, %s\n", flag.Shorthand, flag.ShorthandDeprecated)
}
err = fn(flag, value)
if err != nil {
f.failf(err.Error())
}
return return
} }
func (f *FlagSet) parseShortArg(s string, args []string) (a []string, err error) { func (f *FlagSet) parseShortArg(s string, args []string, fn parseFunc) (a []string, err error) {
a = args a = args
shorthands := s[1:] shorthands := s[1:]
// "shorthands" can be a series of shorthand letters of flags (e.g. "-vvv").
for len(shorthands) > 0 { for len(shorthands) > 0 {
shorthands, a, err = f.parseSingleShortArg(shorthands, args) shorthands, a, err = f.parseSingleShortArg(shorthands, args, fn)
if err != nil { if err != nil {
return return
} }
@ -843,7 +1070,7 @@ func (f *FlagSet) parseShortArg(s string, args []string) (a []string, err error)
return return
} }
func (f *FlagSet) parseArgs(args []string) (err error) { func (f *FlagSet) parseArgs(args []string, fn parseFunc) (err error) {
for len(args) > 0 { for len(args) > 0 {
s := args[0] s := args[0]
args = args[1:] args = args[1:]
@ -863,9 +1090,9 @@ func (f *FlagSet) parseArgs(args []string) (err error) {
f.args = append(f.args, args...) f.args = append(f.args, args...)
break break
} }
args, err = f.parseLongArg(s, args) args, err = f.parseLongArg(s, args, fn)
} else { } else {
args, err = f.parseShortArg(s, args) args, err = f.parseShortArg(s, args, fn)
} }
if err != nil { if err != nil {
return return
@ -879,9 +1106,50 @@ func (f *FlagSet) parseArgs(args []string) (err error) {
// are defined and before flags are accessed by the program. // are defined and before flags are accessed by the program.
// The return value will be ErrHelp if -help was set but not defined. // The return value will be ErrHelp if -help was set but not defined.
func (f *FlagSet) Parse(arguments []string) error { func (f *FlagSet) Parse(arguments []string) error {
if f.addedGoFlagSets != nil {
for _, goFlagSet := range f.addedGoFlagSets {
goFlagSet.Parse(nil)
}
}
f.parsed = true
if len(arguments) < 0 {
return nil
}
f.args = make([]string, 0, len(arguments))
set := func(flag *Flag, value string) error {
return f.Set(flag.Name, value)
}
err := f.parseArgs(arguments, set)
if err != nil {
switch f.errorHandling {
case ContinueOnError:
return err
case ExitOnError:
fmt.Println(err)
os.Exit(2)
case PanicOnError:
panic(err)
}
}
return nil
}
type parseFunc func(flag *Flag, value string) error
// ParseAll parses flag definitions from the argument list, which should not
// include the command name. The arguments for fn are flag and value. Must be
// called after all flags in the FlagSet are defined and before flags are
// accessed by the program. The return value will be ErrHelp if -help was set
// but not defined.
func (f *FlagSet) ParseAll(arguments []string, fn func(flag *Flag, value string) error) error {
f.parsed = true f.parsed = true
f.args = make([]string, 0, len(arguments)) f.args = make([]string, 0, len(arguments))
err := f.parseArgs(arguments)
err := f.parseArgs(arguments, fn)
if err != nil { if err != nil {
switch f.errorHandling { switch f.errorHandling {
case ContinueOnError: case ContinueOnError:
@ -907,6 +1175,14 @@ func Parse() {
CommandLine.Parse(os.Args[1:]) CommandLine.Parse(os.Args[1:])
} }
// ParseAll parses the command-line flags from os.Args[1:] and called fn for each.
// The arguments for fn are flag and value. Must be called after all flags are
// defined and before flags are accessed by the program.
func ParseAll(fn func(flag *Flag, value string) error) {
// Ignore errors; CommandLine is set for ExitOnError.
CommandLine.ParseAll(os.Args[1:], fn)
}
// SetInterspersed sets whether to support interspersed option/non-option arguments. // SetInterspersed sets whether to support interspersed option/non-option arguments.
func SetInterspersed(interspersed bool) { func SetInterspersed(interspersed bool) {
CommandLine.SetInterspersed(interspersed) CommandLine.SetInterspersed(interspersed)
@ -920,14 +1196,15 @@ func Parsed() bool {
// CommandLine is the default set of command-line flags, parsed from os.Args. // CommandLine is the default set of command-line flags, parsed from os.Args.
var CommandLine = NewFlagSet(os.Args[0], ExitOnError) var CommandLine = NewFlagSet(os.Args[0], ExitOnError)
// NewFlagSet returns a new, empty flag set with the specified name and // NewFlagSet returns a new, empty flag set with the specified name,
// error handling property. // error handling property and SortFlags set to true.
func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet { func NewFlagSet(name string, errorHandling ErrorHandling) *FlagSet {
f := &FlagSet{ f := &FlagSet{
name: name, name: name,
errorHandling: errorHandling, errorHandling: errorHandling,
argsLenAtDash: -1, argsLenAtDash: -1,
interspersed: true, interspersed: true,
SortFlags: true,
} }
return f return f
} }

View file

@ -6,13 +6,10 @@ package pflag
import ( import (
goflag "flag" goflag "flag"
"fmt"
"reflect" "reflect"
"strings" "strings"
) )
var _ = fmt.Print
// flagValueWrapper implements pflag.Value around a flag.Value. The main // flagValueWrapper implements pflag.Value around a flag.Value. The main
// difference here is the addition of the Type method that returns a string // difference here is the addition of the Type method that returns a string
// name of the type. As this is generally unknown, we approximate that with // name of the type. As this is generally unknown, we approximate that with
@ -101,4 +98,8 @@ func (f *FlagSet) AddGoFlagSet(newSet *goflag.FlagSet) {
newSet.VisitAll(func(goflag *goflag.Flag) { newSet.VisitAll(func(goflag *goflag.Flag) {
f.AddGoFlag(goflag) f.AddGoFlag(goflag)
}) })
if f.addedGoFlagSets == nil {
f.addedGoFlagSets = make([]*goflag.FlagSet, 0)
}
f.addedGoFlagSets = append(f.addedGoFlagSets, newSet)
} }

88
vendor/github.com/spf13/pflag/int16.go generated vendored Normal file
View file

@ -0,0 +1,88 @@
package pflag
import "strconv"
// -- int16 Value
type int16Value int16
func newInt16Value(val int16, p *int16) *int16Value {
*p = val
return (*int16Value)(p)
}
func (i *int16Value) Set(s string) error {
v, err := strconv.ParseInt(s, 0, 16)
*i = int16Value(v)
return err
}
func (i *int16Value) Type() string {
return "int16"
}
func (i *int16Value) String() string { return strconv.FormatInt(int64(*i), 10) }
func int16Conv(sval string) (interface{}, error) {
v, err := strconv.ParseInt(sval, 0, 16)
if err != nil {
return 0, err
}
return int16(v), nil
}
// GetInt16 returns the int16 value of a flag with the given name
func (f *FlagSet) GetInt16(name string) (int16, error) {
val, err := f.getFlagType(name, "int16", int16Conv)
if err != nil {
return 0, err
}
return val.(int16), nil
}
// Int16Var defines an int16 flag with specified name, default value, and usage string.
// The argument p points to an int16 variable in which to store the value of the flag.
func (f *FlagSet) Int16Var(p *int16, name string, value int16, usage string) {
f.VarP(newInt16Value(value, p), name, "", usage)
}
// Int16VarP is like Int16Var, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) Int16VarP(p *int16, name, shorthand string, value int16, usage string) {
f.VarP(newInt16Value(value, p), name, shorthand, usage)
}
// Int16Var defines an int16 flag with specified name, default value, and usage string.
// The argument p points to an int16 variable in which to store the value of the flag.
func Int16Var(p *int16, name string, value int16, usage string) {
CommandLine.VarP(newInt16Value(value, p), name, "", usage)
}
// Int16VarP is like Int16Var, but accepts a shorthand letter that can be used after a single dash.
func Int16VarP(p *int16, name, shorthand string, value int16, usage string) {
CommandLine.VarP(newInt16Value(value, p), name, shorthand, usage)
}
// Int16 defines an int16 flag with specified name, default value, and usage string.
// The return value is the address of an int16 variable that stores the value of the flag.
func (f *FlagSet) Int16(name string, value int16, usage string) *int16 {
p := new(int16)
f.Int16VarP(p, name, "", value, usage)
return p
}
// Int16P is like Int16, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) Int16P(name, shorthand string, value int16, usage string) *int16 {
p := new(int16)
f.Int16VarP(p, name, shorthand, value, usage)
return p
}
// Int16 defines an int16 flag with specified name, default value, and usage string.
// The return value is the address of an int16 variable that stores the value of the flag.
func Int16(name string, value int16, usage string) *int16 {
return CommandLine.Int16P(name, "", value, usage)
}
// Int16P is like Int16, but accepts a shorthand letter that can be used after a single dash.
func Int16P(name, shorthand string, value int16, usage string) *int16 {
return CommandLine.Int16P(name, shorthand, value, usage)
}

View file

@ -6,8 +6,6 @@ import (
"strings" "strings"
) )
var _ = strings.TrimSpace
// -- net.IP value // -- net.IP value
type ipValue net.IP type ipValue net.IP

148
vendor/github.com/spf13/pflag/ip_slice.go generated vendored Normal file
View file

@ -0,0 +1,148 @@
package pflag
import (
"fmt"
"io"
"net"
"strings"
)
// -- ipSlice Value
type ipSliceValue struct {
value *[]net.IP
changed bool
}
func newIPSliceValue(val []net.IP, p *[]net.IP) *ipSliceValue {
ipsv := new(ipSliceValue)
ipsv.value = p
*ipsv.value = val
return ipsv
}
// Set converts, and assigns, the comma-separated IP argument string representation as the []net.IP value of this flag.
// If Set is called on a flag that already has a []net.IP assigned, the newly converted values will be appended.
func (s *ipSliceValue) Set(val string) error {
// remove all quote characters
rmQuote := strings.NewReplacer(`"`, "", `'`, "", "`", "")
// read flag arguments with CSV parser
ipStrSlice, err := readAsCSV(rmQuote.Replace(val))
if err != nil && err != io.EOF {
return err
}
// parse ip values into slice
out := make([]net.IP, 0, len(ipStrSlice))
for _, ipStr := range ipStrSlice {
ip := net.ParseIP(strings.TrimSpace(ipStr))
if ip == nil {
return fmt.Errorf("invalid string being converted to IP address: %s", ipStr)
}
out = append(out, ip)
}
if !s.changed {
*s.value = out
} else {
*s.value = append(*s.value, out...)
}
s.changed = true
return nil
}
// Type returns a string that uniquely represents this flag's type.
func (s *ipSliceValue) Type() string {
return "ipSlice"
}
// String defines a "native" format for this net.IP slice flag value.
func (s *ipSliceValue) String() string {
ipStrSlice := make([]string, len(*s.value))
for i, ip := range *s.value {
ipStrSlice[i] = ip.String()
}
out, _ := writeAsCSV(ipStrSlice)
return "[" + out + "]"
}
func ipSliceConv(val string) (interface{}, error) {
val = strings.Trim(val, "[]")
// Emtpy string would cause a slice with one (empty) entry
if len(val) == 0 {
return []net.IP{}, nil
}
ss := strings.Split(val, ",")
out := make([]net.IP, len(ss))
for i, sval := range ss {
ip := net.ParseIP(strings.TrimSpace(sval))
if ip == nil {
return nil, fmt.Errorf("invalid string being converted to IP address: %s", sval)
}
out[i] = ip
}
return out, nil
}
// GetIPSlice returns the []net.IP value of a flag with the given name
func (f *FlagSet) GetIPSlice(name string) ([]net.IP, error) {
val, err := f.getFlagType(name, "ipSlice", ipSliceConv)
if err != nil {
return []net.IP{}, err
}
return val.([]net.IP), nil
}
// IPSliceVar defines a ipSlice flag with specified name, default value, and usage string.
// The argument p points to a []net.IP variable in which to store the value of the flag.
func (f *FlagSet) IPSliceVar(p *[]net.IP, name string, value []net.IP, usage string) {
f.VarP(newIPSliceValue(value, p), name, "", usage)
}
// IPSliceVarP is like IPSliceVar, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) IPSliceVarP(p *[]net.IP, name, shorthand string, value []net.IP, usage string) {
f.VarP(newIPSliceValue(value, p), name, shorthand, usage)
}
// IPSliceVar defines a []net.IP flag with specified name, default value, and usage string.
// The argument p points to a []net.IP variable in which to store the value of the flag.
func IPSliceVar(p *[]net.IP, name string, value []net.IP, usage string) {
CommandLine.VarP(newIPSliceValue(value, p), name, "", usage)
}
// IPSliceVarP is like IPSliceVar, but accepts a shorthand letter that can be used after a single dash.
func IPSliceVarP(p *[]net.IP, name, shorthand string, value []net.IP, usage string) {
CommandLine.VarP(newIPSliceValue(value, p), name, shorthand, usage)
}
// IPSlice defines a []net.IP flag with specified name, default value, and usage string.
// The return value is the address of a []net.IP variable that stores the value of that flag.
func (f *FlagSet) IPSlice(name string, value []net.IP, usage string) *[]net.IP {
p := []net.IP{}
f.IPSliceVarP(&p, name, "", value, usage)
return &p
}
// IPSliceP is like IPSlice, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) IPSliceP(name, shorthand string, value []net.IP, usage string) *[]net.IP {
p := []net.IP{}
f.IPSliceVarP(&p, name, shorthand, value, usage)
return &p
}
// IPSlice defines a []net.IP flag with specified name, default value, and usage string.
// The return value is the address of a []net.IP variable that stores the value of the flag.
func IPSlice(name string, value []net.IP, usage string) *[]net.IP {
return CommandLine.IPSliceP(name, "", value, usage)
}
// IPSliceP is like IPSlice, but accepts a shorthand letter that can be used after a single dash.
func IPSliceP(name, shorthand string, value []net.IP, usage string) *[]net.IP {
return CommandLine.IPSliceP(name, shorthand, value, usage)
}

View file

@ -27,8 +27,6 @@ func (*ipNetValue) Type() string {
return "ipNet" return "ipNet"
} }
var _ = strings.TrimSpace
func newIPNetValue(val net.IPNet, p *net.IPNet) *ipNetValue { func newIPNetValue(val net.IPNet, p *net.IPNet) *ipNetValue {
*p = val *p = val
return (*ipNetValue)(p) return (*ipNetValue)(p)

View file

@ -1,12 +1,5 @@
package pflag package pflag
import (
"fmt"
"strings"
)
var _ = fmt.Fprint
// -- stringArray Value // -- stringArray Value
type stringArrayValue struct { type stringArrayValue struct {
value *[]string value *[]string
@ -40,7 +33,7 @@ func (s *stringArrayValue) String() string {
} }
func stringArrayConv(sval string) (interface{}, error) { func stringArrayConv(sval string) (interface{}, error) {
sval = strings.Trim(sval, "[]") sval = sval[1 : len(sval)-1]
// An empty string would cause a array with one (empty) string // An empty string would cause a array with one (empty) string
if len(sval) == 0 { if len(sval) == 0 {
return []string{}, nil return []string{}, nil
@ -59,7 +52,7 @@ func (f *FlagSet) GetStringArray(name string) ([]string, error) {
// StringArrayVar defines a string flag with specified name, default value, and usage string. // StringArrayVar defines a string flag with specified name, default value, and usage string.
// The argument p points to a []string variable in which to store the values of the multiple flags. // The argument p points to a []string variable in which to store the values of the multiple flags.
// The value of each argument will not try to be separated by comma // The value of each argument will not try to be separated by comma. Use a StringSlice for that.
func (f *FlagSet) StringArrayVar(p *[]string, name string, value []string, usage string) { func (f *FlagSet) StringArrayVar(p *[]string, name string, value []string, usage string) {
f.VarP(newStringArrayValue(value, p), name, "", usage) f.VarP(newStringArrayValue(value, p), name, "", usage)
} }
@ -71,7 +64,7 @@ func (f *FlagSet) StringArrayVarP(p *[]string, name, shorthand string, value []s
// StringArrayVar defines a string flag with specified name, default value, and usage string. // StringArrayVar defines a string flag with specified name, default value, and usage string.
// The argument p points to a []string variable in which to store the value of the flag. // The argument p points to a []string variable in which to store the value of the flag.
// The value of each argument will not try to be separated by comma // The value of each argument will not try to be separated by comma. Use a StringSlice for that.
func StringArrayVar(p *[]string, name string, value []string, usage string) { func StringArrayVar(p *[]string, name string, value []string, usage string) {
CommandLine.VarP(newStringArrayValue(value, p), name, "", usage) CommandLine.VarP(newStringArrayValue(value, p), name, "", usage)
} }
@ -83,7 +76,7 @@ func StringArrayVarP(p *[]string, name, shorthand string, value []string, usage
// StringArray defines a string flag with specified name, default value, and usage string. // StringArray defines a string flag with specified name, default value, and usage string.
// The return value is the address of a []string variable that stores the value of the flag. // The return value is the address of a []string variable that stores the value of the flag.
// The value of each argument will not try to be separated by comma // The value of each argument will not try to be separated by comma. Use a StringSlice for that.
func (f *FlagSet) StringArray(name string, value []string, usage string) *[]string { func (f *FlagSet) StringArray(name string, value []string, usage string) *[]string {
p := []string{} p := []string{}
f.StringArrayVarP(&p, name, "", value, usage) f.StringArrayVarP(&p, name, "", value, usage)
@ -99,7 +92,7 @@ func (f *FlagSet) StringArrayP(name, shorthand string, value []string, usage str
// StringArray defines a string flag with specified name, default value, and usage string. // StringArray defines a string flag with specified name, default value, and usage string.
// The return value is the address of a []string variable that stores the value of the flag. // The return value is the address of a []string variable that stores the value of the flag.
// The value of each argument will not try to be separated by comma // The value of each argument will not try to be separated by comma. Use a StringSlice for that.
func StringArray(name string, value []string, usage string) *[]string { func StringArray(name string, value []string, usage string) *[]string {
return CommandLine.StringArrayP(name, "", value, usage) return CommandLine.StringArrayP(name, "", value, usage)
} }

View file

@ -3,12 +3,9 @@ package pflag
import ( import (
"bytes" "bytes"
"encoding/csv" "encoding/csv"
"fmt"
"strings" "strings"
) )
var _ = fmt.Fprint
// -- stringSlice Value // -- stringSlice Value
type stringSliceValue struct { type stringSliceValue struct {
value *[]string value *[]string
@ -39,7 +36,7 @@ func writeAsCSV(vals []string) (string, error) {
return "", err return "", err
} }
w.Flush() w.Flush()
return strings.TrimSuffix(b.String(), fmt.Sprintln()), nil return strings.TrimSuffix(b.String(), "\n"), nil
} }
func (s *stringSliceValue) Set(val string) error { func (s *stringSliceValue) Set(val string) error {
@ -66,7 +63,7 @@ func (s *stringSliceValue) String() string {
} }
func stringSliceConv(sval string) (interface{}, error) { func stringSliceConv(sval string) (interface{}, error) {
sval = strings.Trim(sval, "[]") sval = sval[1 : len(sval)-1]
// An empty string would cause a slice with one (empty) string // An empty string would cause a slice with one (empty) string
if len(sval) == 0 { if len(sval) == 0 {
return []string{}, nil return []string{}, nil
@ -85,6 +82,11 @@ func (f *FlagSet) GetStringSlice(name string) ([]string, error) {
// StringSliceVar defines a string flag with specified name, default value, and usage string. // StringSliceVar defines a string flag with specified name, default value, and usage string.
// The argument p points to a []string variable in which to store the value of the flag. // The argument p points to a []string variable in which to store the value of the flag.
// Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly.
// For example:
// --ss="v1,v2" -ss="v3"
// will result in
// []string{"v1", "v2", "v3"}
func (f *FlagSet) StringSliceVar(p *[]string, name string, value []string, usage string) { func (f *FlagSet) StringSliceVar(p *[]string, name string, value []string, usage string) {
f.VarP(newStringSliceValue(value, p), name, "", usage) f.VarP(newStringSliceValue(value, p), name, "", usage)
} }
@ -96,6 +98,11 @@ func (f *FlagSet) StringSliceVarP(p *[]string, name, shorthand string, value []s
// StringSliceVar defines a string flag with specified name, default value, and usage string. // StringSliceVar defines a string flag with specified name, default value, and usage string.
// The argument p points to a []string variable in which to store the value of the flag. // The argument p points to a []string variable in which to store the value of the flag.
// Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly.
// For example:
// --ss="v1,v2" -ss="v3"
// will result in
// []string{"v1", "v2", "v3"}
func StringSliceVar(p *[]string, name string, value []string, usage string) { func StringSliceVar(p *[]string, name string, value []string, usage string) {
CommandLine.VarP(newStringSliceValue(value, p), name, "", usage) CommandLine.VarP(newStringSliceValue(value, p), name, "", usage)
} }
@ -107,6 +114,11 @@ func StringSliceVarP(p *[]string, name, shorthand string, value []string, usage
// StringSlice defines a string flag with specified name, default value, and usage string. // StringSlice defines a string flag with specified name, default value, and usage string.
// The return value is the address of a []string variable that stores the value of the flag. // The return value is the address of a []string variable that stores the value of the flag.
// Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly.
// For example:
// --ss="v1,v2" -ss="v3"
// will result in
// []string{"v1", "v2", "v3"}
func (f *FlagSet) StringSlice(name string, value []string, usage string) *[]string { func (f *FlagSet) StringSlice(name string, value []string, usage string) *[]string {
p := []string{} p := []string{}
f.StringSliceVarP(&p, name, "", value, usage) f.StringSliceVarP(&p, name, "", value, usage)
@ -122,6 +134,11 @@ func (f *FlagSet) StringSliceP(name, shorthand string, value []string, usage str
// StringSlice defines a string flag with specified name, default value, and usage string. // StringSlice defines a string flag with specified name, default value, and usage string.
// The return value is the address of a []string variable that stores the value of the flag. // The return value is the address of a []string variable that stores the value of the flag.
// Compared to StringArray flags, StringSlice flags take comma-separated value as arguments and split them accordingly.
// For example:
// --ss="v1,v2" -ss="v3"
// will result in
// []string{"v1", "v2", "v3"}
func StringSlice(name string, value []string, usage string) *[]string { func StringSlice(name string, value []string, usage string) *[]string {
return CommandLine.StringSliceP(name, "", value, usage) return CommandLine.StringSliceP(name, "", value, usage)
} }

126
vendor/github.com/spf13/pflag/uint_slice.go generated vendored Normal file
View file

@ -0,0 +1,126 @@
package pflag
import (
"fmt"
"strconv"
"strings"
)
// -- uintSlice Value
type uintSliceValue struct {
value *[]uint
changed bool
}
func newUintSliceValue(val []uint, p *[]uint) *uintSliceValue {
uisv := new(uintSliceValue)
uisv.value = p
*uisv.value = val
return uisv
}
func (s *uintSliceValue) Set(val string) error {
ss := strings.Split(val, ",")
out := make([]uint, len(ss))
for i, d := range ss {
u, err := strconv.ParseUint(d, 10, 0)
if err != nil {
return err
}
out[i] = uint(u)
}
if !s.changed {
*s.value = out
} else {
*s.value = append(*s.value, out...)
}
s.changed = true
return nil
}
func (s *uintSliceValue) Type() string {
return "uintSlice"
}
func (s *uintSliceValue) String() string {
out := make([]string, len(*s.value))
for i, d := range *s.value {
out[i] = fmt.Sprintf("%d", d)
}
return "[" + strings.Join(out, ",") + "]"
}
func uintSliceConv(val string) (interface{}, error) {
val = strings.Trim(val, "[]")
// Empty string would cause a slice with one (empty) entry
if len(val) == 0 {
return []uint{}, nil
}
ss := strings.Split(val, ",")
out := make([]uint, len(ss))
for i, d := range ss {
u, err := strconv.ParseUint(d, 10, 0)
if err != nil {
return nil, err
}
out[i] = uint(u)
}
return out, nil
}
// GetUintSlice returns the []uint value of a flag with the given name.
func (f *FlagSet) GetUintSlice(name string) ([]uint, error) {
val, err := f.getFlagType(name, "uintSlice", uintSliceConv)
if err != nil {
return []uint{}, err
}
return val.([]uint), nil
}
// UintSliceVar defines a uintSlice flag with specified name, default value, and usage string.
// The argument p points to a []uint variable in which to store the value of the flag.
func (f *FlagSet) UintSliceVar(p *[]uint, name string, value []uint, usage string) {
f.VarP(newUintSliceValue(value, p), name, "", usage)
}
// UintSliceVarP is like UintSliceVar, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) UintSliceVarP(p *[]uint, name, shorthand string, value []uint, usage string) {
f.VarP(newUintSliceValue(value, p), name, shorthand, usage)
}
// UintSliceVar defines a uint[] flag with specified name, default value, and usage string.
// The argument p points to a uint[] variable in which to store the value of the flag.
func UintSliceVar(p *[]uint, name string, value []uint, usage string) {
CommandLine.VarP(newUintSliceValue(value, p), name, "", usage)
}
// UintSliceVarP is like the UintSliceVar, but accepts a shorthand letter that can be used after a single dash.
func UintSliceVarP(p *[]uint, name, shorthand string, value []uint, usage string) {
CommandLine.VarP(newUintSliceValue(value, p), name, shorthand, usage)
}
// UintSlice defines a []uint flag with specified name, default value, and usage string.
// The return value is the address of a []uint variable that stores the value of the flag.
func (f *FlagSet) UintSlice(name string, value []uint, usage string) *[]uint {
p := []uint{}
f.UintSliceVarP(&p, name, "", value, usage)
return &p
}
// UintSliceP is like UintSlice, but accepts a shorthand letter that can be used after a single dash.
func (f *FlagSet) UintSliceP(name, shorthand string, value []uint, usage string) *[]uint {
p := []uint{}
f.UintSliceVarP(&p, name, shorthand, value, usage)
return &p
}
// UintSlice defines a []uint flag with specified name, default value, and usage string.
// The return value is the address of a []uint variable that stores the value of the flag.
func UintSlice(name string, value []uint, usage string) *[]uint {
return CommandLine.UintSliceP(name, "", value, usage)
}
// UintSliceP is like UintSlice, but accepts a shorthand letter that can be used after a single dash.
func UintSliceP(name, shorthand string, value []uint, usage string) *[]uint {
return CommandLine.UintSliceP(name, shorthand, value, usage)
}

23
vendor/gopkg.in/validator.v2/.gitignore generated vendored Normal file
View file

@ -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

10
vendor/gopkg.in/validator.v2/.travis.yml generated vendored Normal file
View file

@ -0,0 +1,10 @@
language: go
go:
- 1.5
- 1.6
- 1.7
go_import_path: gopkg.in/validator.v2
script:
- go test -race -v -bench=.
notifications:
email: false

201
vendor/gopkg.in/validator.v2/LICENSE generated vendored Normal file
View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

188
vendor/gopkg.in/validator.v2/README.md generated vendored Normal file
View file

@ -0,0 +1,188 @@
Package validator
================
Package validator implements variable validations
Installation
============
Just use go get.
```bash
go get gopkg.in/validator.v2
```
And then just import the package into your own code.
```go
import (
"gopkg.in/validator.v2"
)
```
Usage
=====
Please see http://godoc.org/gopkg.in/validator.v2 for detailed usage docs.
A simple example would be.
```go
type NewUserRequest struct {
Username string `validate:"min=3,max=40,regexp=^[a-zA-Z]*$"`
Name string `validate:"nonzero"`
Age int `validate:"min=21"`
Password string `validate:"min=8"`
}
nur := NewUserRequest{Username: "something", Age: 20}
if errs := validator.Validate(nur); errs != nil {
// values not valid, deal with errors here
}
```
Builtin validators
Here is the list of validators buildin in the package. Validators buildin
will check the element pointed to if the value to check is a pointer.
The `nil` pointer is treated as a valid value by validators buildin other
than `nonzero`, so you should to use `nonzero` if you don't want to
accept a `nil` pointer.
```
len
For numeric numbers, len will simply make sure that the
value is equal to the parameter given. For strings, it
checks that the string length is exactly that number of
characters. For slices, arrays, and maps, validates the
number of items. (Usage: len=10)
max
For numeric numbers, max will simply make sure that the
value is lesser or equal to the parameter given. For strings,
it checks that the string length is at most that number of
characters. For slices, arrays, and maps, validates the
number of items. (Usage: max=10)
min
For numeric numbers, min will simply make sure that the value
is greater or equal to the parameter given. For strings, it
checks that the string length is at least that number of
characters. For slices, arrays, and maps, validates the
number of items. (Usage: min=10)
nonzero
This validates that the value is not zero. The appropriate
zero value is given by the Go spec (e.g. for int it's 0, for
string it's "", for pointers is nil, etc.) For structs, it
will not check to see if the struct itself has all zero
values, instead use a pointer or put nonzero on the struct's
keys that you care about. (Usage: nonzero)
regexp
Only valid for string types, it will validator that the
value matches the regular expression provided as parameter.
(Usage: regexp=^a.*b$)
```
Custom validators
It is possible to define custom validators by using SetValidationFunc.
First, one needs to create a validation function.
```go
// Very simple validator
func notZZ(v interface{}, param string) error {
st := reflect.ValueOf(v)
if st.Kind() != reflect.String {
return errors.New("notZZ only validates strings")
}
if st.String() == "ZZ" {
return errors.New("value cannot be ZZ")
}
return nil
}
```
Then one needs to add it to the list of validators and give it a "tag"
name.
```go
validator.SetValidationFunc("notzz", notZZ)
```
Then it is possible to use the notzz validation tag. This will print
"Field A error: value cannot be ZZ"
```go
type T struct {
A string `validate:"nonzero,notzz"`
}
t := T{"ZZ"}
if errs := validator.Validate(t); errs != nil {
fmt.Printf("Field A error: %s\n", errs["A"][0])
}
```
You can also have multiple sets of validator rules with SetTag().
```go
type T struct {
A int `foo:"nonzero" bar:"min=10"`
}
t := T{5}
SetTag("foo")
validator.Validate(t) // valid as it's nonzero
SetTag("bar")
validator.Validate(t) // invalid as it's less than 10
```
SetTag is probably better used with multiple validators.
```go
fooValidator := validator.NewValidator()
fooValidator.SetTag("foo")
barValidator := validator.NewValidator()
barValidator.SetTag("bar")
fooValidator.Validate(t)
barValidator.Validate(t)
```
This keeps the default validator's tag clean. Again, please refer to
godocs for a lot of more examples and different uses.
Pull requests policy
====================
tl;dr. Contributions are welcome.
The repository is organized in version branches. Pull requests to, say, the
`v2` branch that break API compatibility will not be accepted. It is okay to
break the API in master, *not in the branches*.
As for validation functions, the preference is to keep the main code simple
and add most new functions to the validator-contrib repository.
https://github.com/go-validator/validator-contrib
For improvements and/or fixes to the builtin validation functions, please
make sure the behaviour will not break existing functionality in the branches.
If you see a case where the functionality of the builtin will change
significantly, please send a pull request against `master`. We can discuss then
whether the changes should be incorporated in the version branches as well.
License
=======
Copyright 2014 Roberto Teixeira <robteix@robteix.com>
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.

271
vendor/gopkg.in/validator.v2/builtins.go generated vendored Normal file
View file

@ -0,0 +1,271 @@
// Package validator implements value validations
//
// Copyright 2014 Roberto Teixeira <robteix@robteix.com>
//
// 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 validator
import (
"reflect"
"regexp"
"strconv"
)
// nonzero tests whether a variable value non-zero
// as defined by the golang spec.
func nonzero(v interface{}, param string) error {
st := reflect.ValueOf(v)
valid := true
switch st.Kind() {
case reflect.String:
valid = len(st.String()) != 0
case reflect.Ptr, reflect.Interface:
valid = !st.IsNil()
case reflect.Slice, reflect.Map, reflect.Array:
valid = st.Len() != 0
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
valid = st.Int() != 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
valid = st.Uint() != 0
case reflect.Float32, reflect.Float64:
valid = st.Float() != 0
case reflect.Bool:
valid = st.Bool()
case reflect.Invalid:
valid = false // always invalid
case reflect.Struct:
valid = true // always valid since only nil pointers are empty
default:
return ErrUnsupported
}
if !valid {
return ErrZeroValue
}
return nil
}
// length tests whether a variable's length is equal to a given
// value. For strings it tests the number of characters whereas
// for maps and slices it tests the number of items.
func length(v interface{}, param string) error {
st := reflect.ValueOf(v)
valid := true
if st.Kind() == reflect.Ptr {
if st.IsNil() {
return nil
}
st = st.Elem()
}
switch st.Kind() {
case reflect.String:
p, err := asInt(param)
if err != nil {
return ErrBadParameter
}
valid = int64(len(st.String())) == p
case reflect.Slice, reflect.Map, reflect.Array:
p, err := asInt(param)
if err != nil {
return ErrBadParameter
}
valid = int64(st.Len()) == p
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
p, err := asInt(param)
if err != nil {
return ErrBadParameter
}
valid = st.Int() == p
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
p, err := asUint(param)
if err != nil {
return ErrBadParameter
}
valid = st.Uint() == p
case reflect.Float32, reflect.Float64:
p, err := asFloat(param)
if err != nil {
return ErrBadParameter
}
valid = st.Float() == p
default:
return ErrUnsupported
}
if !valid {
return ErrLen
}
return nil
}
// min tests whether a variable value is larger or equal to a given
// number. For number types, it's a simple lesser-than test; for
// strings it tests the number of characters whereas for maps
// and slices it tests the number of items.
func min(v interface{}, param string) error {
st := reflect.ValueOf(v)
invalid := false
if st.Kind() == reflect.Ptr {
if st.IsNil() {
return nil
}
st = st.Elem()
}
switch st.Kind() {
case reflect.String:
p, err := asInt(param)
if err != nil {
return ErrBadParameter
}
invalid = int64(len(st.String())) < p
case reflect.Slice, reflect.Map, reflect.Array:
p, err := asInt(param)
if err != nil {
return ErrBadParameter
}
invalid = int64(st.Len()) < p
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
p, err := asInt(param)
if err != nil {
return ErrBadParameter
}
invalid = st.Int() < p
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
p, err := asUint(param)
if err != nil {
return ErrBadParameter
}
invalid = st.Uint() < p
case reflect.Float32, reflect.Float64:
p, err := asFloat(param)
if err != nil {
return ErrBadParameter
}
invalid = st.Float() < p
default:
return ErrUnsupported
}
if invalid {
return ErrMin
}
return nil
}
// max tests whether a variable value is lesser than a given
// value. For numbers, it's a simple lesser-than test; for
// strings it tests the number of characters whereas for maps
// and slices it tests the number of items.
func max(v interface{}, param string) error {
st := reflect.ValueOf(v)
var invalid bool
if st.Kind() == reflect.Ptr {
if st.IsNil() {
return nil
}
st = st.Elem()
}
switch st.Kind() {
case reflect.String:
p, err := asInt(param)
if err != nil {
return ErrBadParameter
}
invalid = int64(len(st.String())) > p
case reflect.Slice, reflect.Map, reflect.Array:
p, err := asInt(param)
if err != nil {
return ErrBadParameter
}
invalid = int64(st.Len()) > p
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
p, err := asInt(param)
if err != nil {
return ErrBadParameter
}
invalid = st.Int() > p
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
p, err := asUint(param)
if err != nil {
return ErrBadParameter
}
invalid = st.Uint() > p
case reflect.Float32, reflect.Float64:
p, err := asFloat(param)
if err != nil {
return ErrBadParameter
}
invalid = st.Float() > p
default:
return ErrUnsupported
}
if invalid {
return ErrMax
}
return nil
}
// regex is the builtin validation function that checks
// whether the string variable matches a regular expression
func regex(v interface{}, param string) error {
s, ok := v.(string)
if !ok {
sptr, ok := v.(*string)
if !ok {
return ErrUnsupported
}
if sptr == nil {
return nil
}
s = *sptr
}
re, err := regexp.Compile(param)
if err != nil {
return ErrBadParameter
}
if !re.MatchString(s) {
return ErrRegexp
}
return nil
}
// asInt retuns the parameter as a int64
// or panics if it can't convert
func asInt(param string) (int64, error) {
i, err := strconv.ParseInt(param, 0, 64)
if err != nil {
return 0, ErrBadParameter
}
return i, nil
}
// asUint retuns the parameter as a uint64
// or panics if it can't convert
func asUint(param string) (uint64, error) {
i, err := strconv.ParseUint(param, 0, 64)
if err != nil {
return 0, ErrBadParameter
}
return i, nil
}
// asFloat retuns the parameter as a float64
// or panics if it can't convert
func asFloat(param string) (float64, error) {
i, err := strconv.ParseFloat(param, 64)
if err != nil {
return 0.0, ErrBadParameter
}
return i, nil
}

265
vendor/gopkg.in/validator.v2/doc.go generated vendored Normal file
View file

@ -0,0 +1,265 @@
// Package validator implements value validations
//
// Copyright 2014 Roberto Teixeira <robteix@robteix.com>
//
// 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 validator implements value validations based on struct tags.
In code it is often necessary to validate that a given value is valid before
using it for something. A typical example might be something like this.
if age < 18 {
return error.New("age cannot be under 18")
}
This is a simple enough example, but it can get significantly more complex,
especially when dealing with structs.
l := len(strings.Trim(s.Username))
if l < 3 || l > 40 || !regexp.MatchString("^[a-zA-Z]$", s.Username) || s.Age < 18 || s.Password {
return errors.New("Invalid request")
}
You get the idea. Package validator allows one to define valid values as
struct tags when defining a new struct type.
type NewUserRequest struct {
Username string `validate:"min=3,max=40,regexp=^[a-zA-Z]*$"`
Name string `validate:"nonzero"`
Age int `validate:"min=18"`
Password string `validate:"min=8"`
}
Then validating a variable of type NewUserRequest becomes trivial.
nur := NewUserRequest{Username: "something", ...}
if errs := validator.Validate(nur); errs != nil {
// do something
}
Builtin validator functions
Here is the list of validator functions builtin in the package.
len
For numeric numbers, len will simply make sure that the value is
equal to the parameter given. For strings, it checks that
the string length is exactly that number of characters. For slices,
arrays, and maps, validates the number of items. (Usage: len=10)
max
For numeric numbers, max will simply make sure that the value is
lesser or equal to the parameter given. For strings, it checks that
the string length is at most that number of characters. For slices,
arrays, and maps, validates the number of items. (Usage: max=10)
min
For numeric numbers, min will simply make sure that the value is
greater or equal to the parameter given. For strings, it checks that
the string length is at least that number of characters. For slices,
arrays, and maps, validates the number of items. (Usage: min=10)
nonzero
This validates that the value is not zero. The appropriate zero value
is given by the Go spec (e.g. for int it's 0, for string it's "", for
pointers is nil, etc.) Usage: nonzero
regexp
Only valid for string types, it will validate that the value matches
the regular expression provided as parameter. (Usage: regexp=^a.*b$)
Note that there are no tests to prevent conflicting validator parameters. For
instance, these fields will never be valid.
...
A int `validate:"max=0,min=1"`
B string `validate:"len=10,regexp=^$"
...
Custom validation functions
It is possible to define custom validation functions by using SetValidationFunc.
First, one needs to create a validation function.
// Very simple validation func
func notZZ(v interface{}, param string) error {
st := reflect.ValueOf(v)
if st.Kind() != reflect.String {
return validate.ErrUnsupported
}
if st.String() == "ZZ" {
return errors.New("value cannot be ZZ")
}
return nil
}
Then one needs to add it to the list of validation funcs and give it a "tag" name.
validate.SetValidationFunc("notzz", notZZ)
Then it is possible to use the notzz validation tag. This will print
"Field A error: value cannot be ZZ"
type T struct {
A string `validate:"nonzero,notzz"`
}
t := T{"ZZ"}
if errs := validator.Validate(t); errs != nil {
fmt.Printf("Field A error: %s\n", errs["A"][0])
}
To use parameters, it is very similar.
// Very simple validator with parameter
func notSomething(v interface{}, param string) error {
st := reflect.ValueOf(v)
if st.Kind() != reflect.String {
return validate.ErrUnsupported
}
if st.String() == param {
return errors.New("value cannot be " + param)
}
return nil
}
And then the code below should print "Field A error: value cannot be ABC".
validator.SetValidationFunc("notsomething", notSomething)
type T struct {
A string `validate:"notsomething=ABC"`
}
t := T{"ABC"}
if errs := validator.Validate(t); errs != nil {
fmt.Printf("Field A error: %s\n", errs["A"][0])
}
As well, it is possible to overwrite builtin validation functions.
validate.SetValidationFunc("min", myMinFunc)
And you can delete a validation function by setting it to nil.
validate.SetValidationFunc("notzz", nil)
validate.SetValidationFunc("nonzero", nil)
Using a non-existing validation func in a field tag will always return
false and with error validate.ErrUnknownTag.
Finally, package validator also provides a helper function that can be used
to validate simple variables/values.
// errs: nil
errs = validator.Valid(42, "min=10, max=50")
// errs: [validate.ErrZeroValue]
errs = validator.Valid(nil, "nonzero")
// errs: [validate.ErrMin,validate.ErrMax]
errs = validator.Valid("hi", "nonzero,min=3,max=2")
Custom tag name
In case there is a reason why one would not wish to use tag 'validate' (maybe due to
a conflict with a different package), it is possible to tell the package to use
a different tag.
validator.SetTag("valid")
Then.
Type T struct {
A int `valid:"min=8, max=10"`
B string `valid:"nonzero"`
}
SetTag is permanent. The new tag name will be used until it is again changed
with a new call to SetTag. A way to temporarily use a different tag exists.
validator.WithTag("foo").Validate(t)
validator.WithTag("bar").Validate(t)
// But this will go back to using 'validate'
validator.Validate(t)
Multiple validators
You may often need to have a different set of validation
rules for different situations. In all the examples above,
we only used the default validator but you could create a
new one and set specific rules for it.
For instance, you might use the same struct to decode incoming JSON for a REST API
but your needs will change when you're using it to, say, create a new instance
in storage vs. when you need to change something.
type User struct {
Username string `validate:"nonzero"`
Name string `validate:"nonzero"`
Age int `validate:"nonzero"`
Password string `validate:"nonzero"`
}
Maybe when creating a new user, you need to make sure all values in the struct are filled,
but then you use the same struct to handle incoming requests to, say, change the password,
in which case you only need the Username and the Password and don't care for the others.
You might use two different validators.
type User struct {
Username string `creating:"nonzero" chgpw:"nonzero"`
Name string `creating:"nonzero"`
Age int `creating:"nonzero"`
Password string `creating:"nonzero" chgpw:"nonzero"`
}
var (
creationValidator = validator.NewValidator()
chgPwValidator = validator.NewValidator()
)
func init() {
creationValidator.SetTag("creating")
chgPwValidator.SetTag("chgpw")
}
...
func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
var u User
json.NewDecoder(r.Body).Decode(&user)
if errs := creationValidator.Validate(user); errs != nil {
// the request did not include all of the User
// struct fields, so send a http.StatusBadRequest
// back or something
}
// create the new user
}
func SetNewUserPasswordHandler(w http.ResponseWriter, r *http.Request) {
var u User
json.NewDecoder(r.Body).Decode(&user)
if errs := chgPwValidator.Validate(user); errs != nil {
// the request did not Username and Password,
// so send a http.StatusBadRequest
// back or something
}
// save the new password
}
It is also possible to do all of that using only the default validator as long
as SetTag is always called before calling validator.Validate() or you chain the
with WithTag().
*/
package validator

369
vendor/gopkg.in/validator.v2/validator.go generated vendored Normal file
View file

@ -0,0 +1,369 @@
// Package validator implements value validations
//
// Copyright 2014 Roberto Teixeira <robteix@robteix.com>
//
// 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 validator
import (
"errors"
"fmt"
"reflect"
"regexp"
"strings"
"unicode"
)
// TextErr is an error that also implements the TextMarshaller interface for
// serializing out to various plain text encodings. Packages creating their
// own custom errors should use TextErr if they're intending to use serializing
// formats like json, msgpack etc.
type TextErr struct {
Err error
}
// Error implements the error interface.
func (t TextErr) Error() string {
return t.Err.Error()
}
// MarshalText implements the TextMarshaller
func (t TextErr) MarshalText() ([]byte, error) {
return []byte(t.Err.Error()), nil
}
var (
// ErrZeroValue is the error returned when variable has zero valud
// and nonzero was specified
ErrZeroValue = TextErr{errors.New("zero value")}
// ErrMin is the error returned when variable is less than mininum
// value specified
ErrMin = TextErr{errors.New("less than min")}
// ErrMax is the error returned when variable is more than
// maximum specified
ErrMax = TextErr{errors.New("greater than max")}
// ErrLen is the error returned when length is not equal to
// param specified
ErrLen = TextErr{errors.New("invalid length")}
// ErrRegexp is the error returned when the value does not
// match the provided regular expression parameter
ErrRegexp = TextErr{errors.New("regular expression mismatch")}
// ErrUnsupported is the error error returned when a validation rule
// is used with an unsupported variable type
ErrUnsupported = TextErr{errors.New("unsupported type")}
// ErrBadParameter is the error returned when an invalid parameter
// is provided to a validation rule (e.g. a string where an int was
// expected (max=foo,len=bar) or missing a parameter when one is required (len=))
ErrBadParameter = TextErr{errors.New("bad parameter")}
// ErrUnknownTag is the error returned when an unknown tag is found
ErrUnknownTag = TextErr{errors.New("unknown tag")}
// ErrInvalid is the error returned when variable is invalid
// (normally a nil pointer)
ErrInvalid = TextErr{errors.New("invalid value")}
)
// ErrorMap is a map which contains all errors from validating a struct.
type ErrorMap map[string]ErrorArray
// ErrorMap implements the Error interface so we can check error against nil.
// The returned error is if existent the first error which was added to the map.
func (err ErrorMap) Error() string {
for k, errs := range err {
if len(errs) > 0 {
return fmt.Sprintf("%s: %s", k, errs.Error())
}
}
return ""
}
// ErrorArray is a slice of errors returned by the Validate function.
type ErrorArray []error
// ErrorArray implements the Error interface and returns the first error as
// string if existent.
func (err ErrorArray) Error() string {
if len(err) > 0 {
return err[0].Error()
}
return ""
}
// ValidationFunc is a function that receives the value of a
// field and a parameter used for the respective validation tag.
type ValidationFunc func(v interface{}, param string) error
// Validator implements a validator
type Validator struct {
// Tag name being used.
tagName string
// validationFuncs is a map of ValidationFuncs indexed
// by their name.
validationFuncs map[string]ValidationFunc
}
// Helper validator so users can use the
// functions directly from the package
var defaultValidator = NewValidator()
// NewValidator creates a new Validator
func NewValidator() *Validator {
return &Validator{
tagName: "validate",
validationFuncs: map[string]ValidationFunc{
"nonzero": nonzero,
"len": length,
"min": min,
"max": max,
"regexp": regex,
},
}
}
// SetTag allows you to change the tag name used in structs
func SetTag(tag string) {
defaultValidator.SetTag(tag)
}
// SetTag allows you to change the tag name used in structs
func (mv *Validator) SetTag(tag string) {
mv.tagName = tag
}
// WithTag creates a new Validator with the new tag name. It is
// useful to chain-call with Validate so we don't change the tag
// name permanently: validator.WithTag("foo").Validate(t)
func WithTag(tag string) *Validator {
return defaultValidator.WithTag(tag)
}
// WithTag creates a new Validator with the new tag name. It is
// useful to chain-call with Validate so we don't change the tag
// name permanently: validator.WithTag("foo").Validate(t)
func (mv *Validator) WithTag(tag string) *Validator {
v := mv.copy()
v.SetTag(tag)
return v
}
// Copy a validator
func (mv *Validator) copy() *Validator {
newFuncs := map[string]ValidationFunc{}
for k, f := range mv.validationFuncs {
newFuncs[k] = f
}
return &Validator{
tagName: mv.tagName,
validationFuncs: newFuncs,
}
}
// SetValidationFunc sets the function to be used for a given
// validation constraint. Calling this function with nil vf
// is the same as removing the constraint function from the list.
func SetValidationFunc(name string, vf ValidationFunc) error {
return defaultValidator.SetValidationFunc(name, vf)
}
// SetValidationFunc sets the function to be used for a given
// validation constraint. Calling this function with nil vf
// is the same as removing the constraint function from the list.
func (mv *Validator) SetValidationFunc(name string, vf ValidationFunc) error {
if name == "" {
return errors.New("name cannot be empty")
}
if vf == nil {
delete(mv.validationFuncs, name)
return nil
}
mv.validationFuncs[name] = vf
return nil
}
// Validate validates the fields of a struct based
// on 'validator' tags and returns errors found indexed
// by the field name.
func Validate(v interface{}) error {
return defaultValidator.Validate(v)
}
// Validate validates the fields of a struct based
// on 'validator' tags and returns errors found indexed
// by the field name.
func (mv *Validator) Validate(v interface{}) error {
sv := reflect.ValueOf(v)
st := reflect.TypeOf(v)
if sv.Kind() == reflect.Ptr && !sv.IsNil() {
return mv.Validate(sv.Elem().Interface())
}
if sv.Kind() != reflect.Struct && sv.Kind() != reflect.Interface {
return ErrUnsupported
}
nfields := sv.NumField()
m := make(ErrorMap)
for i := 0; i < nfields; i++ {
fname := st.Field(i).Name
if !unicode.IsUpper(rune(fname[0])) {
continue
}
f := sv.Field(i)
// deal with pointers
for f.Kind() == reflect.Ptr && !f.IsNil() {
f = f.Elem()
}
tag := st.Field(i).Tag.Get(mv.tagName)
if tag == "-" {
continue
}
var errs ErrorArray
if tag != "" {
err := mv.Valid(f.Interface(), tag)
if errors, ok := err.(ErrorArray); ok {
errs = errors
} else {
if err != nil {
errs = ErrorArray{err}
}
}
}
mv.deepValidateCollection(f, fname, m) // no-op if field is not a struct, interface, array, slice or map
if len(errs) > 0 {
m[st.Field(i).Name] = errs
}
}
if len(m) > 0 {
return m
}
return nil
}
func (mv *Validator) deepValidateCollection(f reflect.Value, fname string, m ErrorMap) {
switch f.Kind() {
case reflect.Struct, reflect.Interface, reflect.Ptr:
e := mv.Validate(f.Interface())
if e, ok := e.(ErrorMap); ok && len(e) > 0 {
for j, k := range e {
m[fname+"."+j] = k
}
}
case reflect.Array, reflect.Slice:
for i := 0; i < f.Len(); i++ {
mv.deepValidateCollection(f.Index(i), fmt.Sprintf("%s[%d]", fname, i), m)
}
case reflect.Map:
for _, key := range f.MapKeys() {
mv.deepValidateCollection(key, fmt.Sprintf("%s[%+v](key)", fname, key.Interface()), m) // validate the map key
value := f.MapIndex(key)
mv.deepValidateCollection(value, fmt.Sprintf("%s[%+v](value)", fname, key.Interface()), m)
}
}
}
// Valid validates a value based on the provided
// tags and returns errors found or nil.
func Valid(val interface{}, tags string) error {
return defaultValidator.Valid(val, tags)
}
// Valid validates a value based on the provided
// tags and returns errors found or nil.
func (mv *Validator) Valid(val interface{}, tags string) error {
if tags == "-" {
return nil
}
v := reflect.ValueOf(val)
if v.Kind() == reflect.Ptr && !v.IsNil() {
return mv.Valid(v.Elem().Interface(), tags)
}
var err error
switch v.Kind() {
case reflect.Invalid:
err = mv.validateVar(nil, tags)
default:
err = mv.validateVar(val, tags)
}
return err
}
// validateVar validates one single variable
func (mv *Validator) validateVar(v interface{}, tag string) error {
tags, err := mv.parseTags(tag)
if err != nil {
// unknown tag found, give up.
return err
}
errs := make(ErrorArray, 0, len(tags))
for _, t := range tags {
if err := t.Fn(v, t.Param); err != nil {
errs = append(errs, err)
}
}
if len(errs) > 0 {
return errs
}
return nil
}
// tag represents one of the tag items
type tag struct {
Name string // name of the tag
Fn ValidationFunc // validation function to call
Param string // parameter to send to the validation function
}
// separate by no escaped commas
var sepPattern *regexp.Regexp = regexp.MustCompile(`((?:^|[^\\])(?:\\\\)*),`)
func splitUnescapedComma(str string) []string {
ret := []string{}
indexes := sepPattern.FindAllStringIndex(str, -1)
last := 0
for _, is := range indexes {
ret = append(ret, str[last:is[1]-1])
last = is[1]
}
ret = append(ret, str[last:])
return ret
}
// parseTags parses all individual tags found within a struct tag.
func (mv *Validator) parseTags(t string) ([]tag, error) {
tl := splitUnescapedComma(t)
tags := make([]tag, 0, len(tl))
for _, i := range tl {
i = strings.Replace(i, `\,`, ",", -1)
tg := tag{}
v := strings.SplitN(i, "=", 2)
tg.Name = strings.Trim(v[0], " ")
if tg.Name == "" {
return []tag{}, ErrUnknownTag
}
if len(v) > 1 {
tg.Param = strings.Trim(v[1], " ")
}
var found bool
if tg.Fn, found = mv.validationFuncs[tg.Name]; !found {
return []tag{}, ErrUnknownTag
}
tags = append(tags, tg)
}
return tags, nil
}

View file

@ -4,6 +4,9 @@ go:
- 1.4 - 1.4
- 1.5 - 1.5
- 1.6 - 1.6
- 1.7
- 1.8
- 1.9
- tip - tip
go_import_path: gopkg.in/yaml.v2 go_import_path: gopkg.in/yaml.v2

201
vendor/gopkg.in/yaml.v2/LICENSE generated vendored Normal file
View file

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View file

@ -65,6 +65,8 @@ b:
d: [3, 4] d: [3, 4]
` `
// Note: struct fields must be public in order for unmarshal to
// correctly populate the data.
type T struct { type T struct {
A string A string
B struct { B struct {

View file

@ -2,7 +2,6 @@ package yaml
import ( import (
"io" "io"
"os"
) )
func yaml_insert_token(parser *yaml_parser_t, pos int, token *yaml_token_t) { func yaml_insert_token(parser *yaml_parser_t, pos int, token *yaml_token_t) {
@ -48,9 +47,9 @@ func yaml_string_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err
return n, nil return n, nil
} }
// File read handler. // Reader read handler.
func yaml_file_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) { func yaml_reader_read_handler(parser *yaml_parser_t, buffer []byte) (n int, err error) {
return parser.input_file.Read(buffer) return parser.input_reader.Read(buffer)
} }
// Set a string input. // Set a string input.
@ -64,12 +63,12 @@ func yaml_parser_set_input_string(parser *yaml_parser_t, input []byte) {
} }
// Set a file input. // Set a file input.
func yaml_parser_set_input_file(parser *yaml_parser_t, file *os.File) { func yaml_parser_set_input_reader(parser *yaml_parser_t, r io.Reader) {
if parser.read_handler != nil { if parser.read_handler != nil {
panic("must set the input source only once") panic("must set the input source only once")
} }
parser.read_handler = yaml_file_read_handler parser.read_handler = yaml_reader_read_handler
parser.input_file = file parser.input_reader = r
} }
// Set the source encoding. // Set the source encoding.
@ -81,14 +80,13 @@ func yaml_parser_set_encoding(parser *yaml_parser_t, encoding yaml_encoding_t) {
} }
// Create a new emitter object. // Create a new emitter object.
func yaml_emitter_initialize(emitter *yaml_emitter_t) bool { func yaml_emitter_initialize(emitter *yaml_emitter_t) {
*emitter = yaml_emitter_t{ *emitter = yaml_emitter_t{
buffer: make([]byte, output_buffer_size), buffer: make([]byte, output_buffer_size),
raw_buffer: make([]byte, 0, output_raw_buffer_size), raw_buffer: make([]byte, 0, output_raw_buffer_size),
states: make([]yaml_emitter_state_t, 0, initial_stack_size), states: make([]yaml_emitter_state_t, 0, initial_stack_size),
events: make([]yaml_event_t, 0, initial_queue_size), events: make([]yaml_event_t, 0, initial_queue_size),
} }
return true
} }
// Destroy an emitter object. // Destroy an emitter object.
@ -102,9 +100,10 @@ func yaml_string_write_handler(emitter *yaml_emitter_t, buffer []byte) error {
return nil return nil
} }
// File write handler. // yaml_writer_write_handler uses emitter.output_writer to write the
func yaml_file_write_handler(emitter *yaml_emitter_t, buffer []byte) error { // emitted text.
_, err := emitter.output_file.Write(buffer) func yaml_writer_write_handler(emitter *yaml_emitter_t, buffer []byte) error {
_, err := emitter.output_writer.Write(buffer)
return err return err
} }
@ -118,12 +117,12 @@ func yaml_emitter_set_output_string(emitter *yaml_emitter_t, output_buffer *[]by
} }
// Set a file output. // Set a file output.
func yaml_emitter_set_output_file(emitter *yaml_emitter_t, file io.Writer) { func yaml_emitter_set_output_writer(emitter *yaml_emitter_t, w io.Writer) {
if emitter.write_handler != nil { if emitter.write_handler != nil {
panic("must set the output target only once") panic("must set the output target only once")
} }
emitter.write_handler = yaml_file_write_handler emitter.write_handler = yaml_writer_write_handler
emitter.output_file = file emitter.output_writer = w
} }
// Set the output encoding. // Set the output encoding.
@ -252,41 +251,41 @@ func yaml_emitter_set_break(emitter *yaml_emitter_t, line_break yaml_break_t) {
// //
// Create STREAM-START. // Create STREAM-START.
func yaml_stream_start_event_initialize(event *yaml_event_t, encoding yaml_encoding_t) bool { func yaml_stream_start_event_initialize(event *yaml_event_t, encoding yaml_encoding_t) {
*event = yaml_event_t{ *event = yaml_event_t{
typ: yaml_STREAM_START_EVENT, typ: yaml_STREAM_START_EVENT,
encoding: encoding, encoding: encoding,
} }
return true
} }
// Create STREAM-END. // Create STREAM-END.
func yaml_stream_end_event_initialize(event *yaml_event_t) bool { func yaml_stream_end_event_initialize(event *yaml_event_t) {
*event = yaml_event_t{ *event = yaml_event_t{
typ: yaml_STREAM_END_EVENT, typ: yaml_STREAM_END_EVENT,
} }
return true
} }
// Create DOCUMENT-START. // Create DOCUMENT-START.
func yaml_document_start_event_initialize(event *yaml_event_t, version_directive *yaml_version_directive_t, func yaml_document_start_event_initialize(
tag_directives []yaml_tag_directive_t, implicit bool) bool { event *yaml_event_t,
version_directive *yaml_version_directive_t,
tag_directives []yaml_tag_directive_t,
implicit bool,
) {
*event = yaml_event_t{ *event = yaml_event_t{
typ: yaml_DOCUMENT_START_EVENT, typ: yaml_DOCUMENT_START_EVENT,
version_directive: version_directive, version_directive: version_directive,
tag_directives: tag_directives, tag_directives: tag_directives,
implicit: implicit, implicit: implicit,
} }
return true
} }
// Create DOCUMENT-END. // Create DOCUMENT-END.
func yaml_document_end_event_initialize(event *yaml_event_t, implicit bool) bool { func yaml_document_end_event_initialize(event *yaml_event_t, implicit bool) {
*event = yaml_event_t{ *event = yaml_event_t{
typ: yaml_DOCUMENT_END_EVENT, typ: yaml_DOCUMENT_END_EVENT,
implicit: implicit, implicit: implicit,
} }
return true
} }
///* ///*
@ -348,7 +347,7 @@ func yaml_sequence_end_event_initialize(event *yaml_event_t) bool {
} }
// Create MAPPING-START. // Create MAPPING-START.
func yaml_mapping_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_mapping_style_t) bool { func yaml_mapping_start_event_initialize(event *yaml_event_t, anchor, tag []byte, implicit bool, style yaml_mapping_style_t) {
*event = yaml_event_t{ *event = yaml_event_t{
typ: yaml_MAPPING_START_EVENT, typ: yaml_MAPPING_START_EVENT,
anchor: anchor, anchor: anchor,
@ -356,15 +355,13 @@ func yaml_mapping_start_event_initialize(event *yaml_event_t, anchor, tag []byte
implicit: implicit, implicit: implicit,
style: yaml_style_t(style), style: yaml_style_t(style),
} }
return true
} }
// Create MAPPING-END. // Create MAPPING-END.
func yaml_mapping_end_event_initialize(event *yaml_event_t) bool { func yaml_mapping_end_event_initialize(event *yaml_event_t) {
*event = yaml_event_t{ *event = yaml_event_t{
typ: yaml_MAPPING_END_EVENT, typ: yaml_MAPPING_END_EVENT,
} }
return true
} }
// Destroy an event object. // Destroy an event object.
@ -471,7 +468,7 @@ func yaml_event_delete(event *yaml_event_t) {
// } context // } context
// tag_directive *yaml_tag_directive_t // tag_directive *yaml_tag_directive_t
// //
// context.error = YAML_NO_ERROR // Eliminate a compliler warning. // context.error = YAML_NO_ERROR // Eliminate a compiler warning.
// //
// assert(document) // Non-NULL document object is expected. // assert(document) // Non-NULL document object is expected.
// //

View file

@ -4,6 +4,7 @@ import (
"encoding" "encoding"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"io"
"math" "math"
"reflect" "reflect"
"strconv" "strconv"
@ -22,19 +23,22 @@ type node struct {
kind int kind int
line, column int line, column int
tag string tag string
value string // For an alias node, alias holds the resolved alias.
implicit bool alias *node
children []*node value string
anchors map[string]*node implicit bool
children []*node
anchors map[string]*node
} }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Parser, produces a node tree out of a libyaml event stream. // Parser, produces a node tree out of a libyaml event stream.
type parser struct { type parser struct {
parser yaml_parser_t parser yaml_parser_t
event yaml_event_t event yaml_event_t
doc *node doc *node
doneInit bool
} }
func newParser(b []byte) *parser { func newParser(b []byte) *parser {
@ -42,21 +46,30 @@ func newParser(b []byte) *parser {
if !yaml_parser_initialize(&p.parser) { if !yaml_parser_initialize(&p.parser) {
panic("failed to initialize YAML emitter") panic("failed to initialize YAML emitter")
} }
if len(b) == 0 { if len(b) == 0 {
b = []byte{'\n'} b = []byte{'\n'}
} }
yaml_parser_set_input_string(&p.parser, b) yaml_parser_set_input_string(&p.parser, b)
p.skip()
if p.event.typ != yaml_STREAM_START_EVENT {
panic("expected stream start event, got " + strconv.Itoa(int(p.event.typ)))
}
p.skip()
return &p return &p
} }
func newParserFromReader(r io.Reader) *parser {
p := parser{}
if !yaml_parser_initialize(&p.parser) {
panic("failed to initialize YAML emitter")
}
yaml_parser_set_input_reader(&p.parser, r)
return &p
}
func (p *parser) init() {
if p.doneInit {
return
}
p.expect(yaml_STREAM_START_EVENT)
p.doneInit = true
}
func (p *parser) destroy() { func (p *parser) destroy() {
if p.event.typ != yaml_NO_EVENT { if p.event.typ != yaml_NO_EVENT {
yaml_event_delete(&p.event) yaml_event_delete(&p.event)
@ -64,16 +77,35 @@ func (p *parser) destroy() {
yaml_parser_delete(&p.parser) yaml_parser_delete(&p.parser)
} }
func (p *parser) skip() { // expect consumes an event from the event stream and
if p.event.typ != yaml_NO_EVENT { // checks that it's of the expected type.
if p.event.typ == yaml_STREAM_END_EVENT { func (p *parser) expect(e yaml_event_type_t) {
failf("attempted to go past the end of stream; corrupted value?") if p.event.typ == yaml_NO_EVENT {
if !yaml_parser_parse(&p.parser, &p.event) {
p.fail()
} }
yaml_event_delete(&p.event) }
if p.event.typ == yaml_STREAM_END_EVENT {
failf("attempted to go past the end of stream; corrupted value?")
}
if p.event.typ != e {
p.parser.problem = fmt.Sprintf("expected %s event but got %s", e, p.event.typ)
p.fail()
}
yaml_event_delete(&p.event)
p.event.typ = yaml_NO_EVENT
}
// peek peeks at the next event in the event stream,
// puts the results into p.event and returns the event type.
func (p *parser) peek() yaml_event_type_t {
if p.event.typ != yaml_NO_EVENT {
return p.event.typ
} }
if !yaml_parser_parse(&p.parser, &p.event) { if !yaml_parser_parse(&p.parser, &p.event) {
p.fail() p.fail()
} }
return p.event.typ
} }
func (p *parser) fail() { func (p *parser) fail() {
@ -81,6 +113,10 @@ func (p *parser) fail() {
var line int var line int
if p.parser.problem_mark.line != 0 { if p.parser.problem_mark.line != 0 {
line = p.parser.problem_mark.line line = p.parser.problem_mark.line
// Scanner errors don't iterate line before returning error
if p.parser.error == yaml_SCANNER_ERROR {
line++
}
} else if p.parser.context_mark.line != 0 { } else if p.parser.context_mark.line != 0 {
line = p.parser.context_mark.line line = p.parser.context_mark.line
} }
@ -103,7 +139,8 @@ func (p *parser) anchor(n *node, anchor []byte) {
} }
func (p *parser) parse() *node { func (p *parser) parse() *node {
switch p.event.typ { p.init()
switch p.peek() {
case yaml_SCALAR_EVENT: case yaml_SCALAR_EVENT:
return p.scalar() return p.scalar()
case yaml_ALIAS_EVENT: case yaml_ALIAS_EVENT:
@ -118,9 +155,8 @@ func (p *parser) parse() *node {
// Happens when attempting to decode an empty buffer. // Happens when attempting to decode an empty buffer.
return nil return nil
default: default:
panic("attempted to parse unknown event: " + strconv.Itoa(int(p.event.typ))) panic("attempted to parse unknown event: " + p.event.typ.String())
} }
panic("unreachable")
} }
func (p *parser) node(kind int) *node { func (p *parser) node(kind int) *node {
@ -135,19 +171,20 @@ func (p *parser) document() *node {
n := p.node(documentNode) n := p.node(documentNode)
n.anchors = make(map[string]*node) n.anchors = make(map[string]*node)
p.doc = n p.doc = n
p.skip() p.expect(yaml_DOCUMENT_START_EVENT)
n.children = append(n.children, p.parse()) n.children = append(n.children, p.parse())
if p.event.typ != yaml_DOCUMENT_END_EVENT { p.expect(yaml_DOCUMENT_END_EVENT)
panic("expected end of document event but got " + strconv.Itoa(int(p.event.typ)))
}
p.skip()
return n return n
} }
func (p *parser) alias() *node { func (p *parser) alias() *node {
n := p.node(aliasNode) n := p.node(aliasNode)
n.value = string(p.event.anchor) n.value = string(p.event.anchor)
p.skip() n.alias = p.doc.anchors[n.value]
if n.alias == nil {
failf("unknown anchor '%s' referenced", n.value)
}
p.expect(yaml_ALIAS_EVENT)
return n return n
} }
@ -157,29 +194,29 @@ func (p *parser) scalar() *node {
n.tag = string(p.event.tag) n.tag = string(p.event.tag)
n.implicit = p.event.implicit n.implicit = p.event.implicit
p.anchor(n, p.event.anchor) p.anchor(n, p.event.anchor)
p.skip() p.expect(yaml_SCALAR_EVENT)
return n return n
} }
func (p *parser) sequence() *node { func (p *parser) sequence() *node {
n := p.node(sequenceNode) n := p.node(sequenceNode)
p.anchor(n, p.event.anchor) p.anchor(n, p.event.anchor)
p.skip() p.expect(yaml_SEQUENCE_START_EVENT)
for p.event.typ != yaml_SEQUENCE_END_EVENT { for p.peek() != yaml_SEQUENCE_END_EVENT {
n.children = append(n.children, p.parse()) n.children = append(n.children, p.parse())
} }
p.skip() p.expect(yaml_SEQUENCE_END_EVENT)
return n return n
} }
func (p *parser) mapping() *node { func (p *parser) mapping() *node {
n := p.node(mappingNode) n := p.node(mappingNode)
p.anchor(n, p.event.anchor) p.anchor(n, p.event.anchor)
p.skip() p.expect(yaml_MAPPING_START_EVENT)
for p.event.typ != yaml_MAPPING_END_EVENT { for p.peek() != yaml_MAPPING_END_EVENT {
n.children = append(n.children, p.parse(), p.parse()) n.children = append(n.children, p.parse(), p.parse())
} }
p.skip() p.expect(yaml_MAPPING_END_EVENT)
return n return n
} }
@ -188,9 +225,10 @@ func (p *parser) mapping() *node {
type decoder struct { type decoder struct {
doc *node doc *node
aliases map[string]bool aliases map[*node]bool
mapType reflect.Type mapType reflect.Type
terrors []string terrors []string
strict bool
} }
var ( var (
@ -198,11 +236,13 @@ var (
durationType = reflect.TypeOf(time.Duration(0)) durationType = reflect.TypeOf(time.Duration(0))
defaultMapType = reflect.TypeOf(map[interface{}]interface{}{}) defaultMapType = reflect.TypeOf(map[interface{}]interface{}{})
ifaceType = defaultMapType.Elem() ifaceType = defaultMapType.Elem()
timeType = reflect.TypeOf(time.Time{})
ptrTimeType = reflect.TypeOf(&time.Time{})
) )
func newDecoder() *decoder { func newDecoder(strict bool) *decoder {
d := &decoder{mapType: defaultMapType} d := &decoder{mapType: defaultMapType, strict: strict}
d.aliases = make(map[string]bool) d.aliases = make(map[*node]bool)
return d return d
} }
@ -251,7 +291,7 @@ func (d *decoder) callUnmarshaler(n *node, u Unmarshaler) (good bool) {
// //
// If n holds a null value, prepare returns before doing anything. // If n holds a null value, prepare returns before doing anything.
func (d *decoder) prepare(n *node, out reflect.Value) (newout reflect.Value, unmarshaled, good bool) { func (d *decoder) prepare(n *node, out reflect.Value) (newout reflect.Value, unmarshaled, good bool) {
if n.tag == yaml_NULL_TAG || n.kind == scalarNode && n.tag == "" && (n.value == "null" || n.value == "") { if n.tag == yaml_NULL_TAG || n.kind == scalarNode && n.tag == "" && (n.value == "null" || n.value == "~" || n.value == "" && n.implicit) {
return out, false, false return out, false, false
} }
again := true again := true
@ -308,16 +348,13 @@ func (d *decoder) document(n *node, out reflect.Value) (good bool) {
} }
func (d *decoder) alias(n *node, out reflect.Value) (good bool) { func (d *decoder) alias(n *node, out reflect.Value) (good bool) {
an, ok := d.doc.anchors[n.value] if d.aliases[n] {
if !ok { // TODO this could actually be allowed in some circumstances.
failf("unknown anchor '%s' referenced", n.value)
}
if d.aliases[n.value] {
failf("anchor '%s' value contains itself", n.value) failf("anchor '%s' value contains itself", n.value)
} }
d.aliases[n.value] = true d.aliases[n] = true
good = d.unmarshal(an, out) good = d.unmarshal(n.alias, out)
delete(d.aliases, n.value) delete(d.aliases, n)
return good return good
} }
@ -329,7 +366,7 @@ func resetMap(out reflect.Value) {
} }
} }
func (d *decoder) scalar(n *node, out reflect.Value) (good bool) { func (d *decoder) scalar(n *node, out reflect.Value) bool {
var tag string var tag string
var resolved interface{} var resolved interface{}
if n.tag == "" && !n.implicit { if n.tag == "" && !n.implicit {
@ -353,9 +390,26 @@ func (d *decoder) scalar(n *node, out reflect.Value) (good bool) {
} }
return true return true
} }
if s, ok := resolved.(string); ok && out.CanAddr() { if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() {
if u, ok := out.Addr().Interface().(encoding.TextUnmarshaler); ok { // We've resolved to exactly the type we want, so use that.
err := u.UnmarshalText([]byte(s)) out.Set(resolvedv)
return true
}
// Perhaps we can use the value as a TextUnmarshaler to
// set its value.
if out.CanAddr() {
u, ok := out.Addr().Interface().(encoding.TextUnmarshaler)
if ok {
var text []byte
if tag == yaml_BINARY_TAG {
text = []byte(resolved.(string))
} else {
// We let any value be unmarshaled into TextUnmarshaler.
// That might be more lax than we'd like, but the
// TextUnmarshaler itself should bowl out any dubious values.
text = []byte(n.value)
}
err := u.UnmarshalText(text)
if err != nil { if err != nil {
fail(err) fail(err)
} }
@ -366,46 +420,54 @@ func (d *decoder) scalar(n *node, out reflect.Value) (good bool) {
case reflect.String: case reflect.String:
if tag == yaml_BINARY_TAG { if tag == yaml_BINARY_TAG {
out.SetString(resolved.(string)) out.SetString(resolved.(string))
good = true return true
} else if resolved != nil { }
if resolved != nil {
out.SetString(n.value) out.SetString(n.value)
good = true return true
} }
case reflect.Interface: case reflect.Interface:
if resolved == nil { if resolved == nil {
out.Set(reflect.Zero(out.Type())) out.Set(reflect.Zero(out.Type()))
} else if tag == yaml_TIMESTAMP_TAG {
// It looks like a timestamp but for backward compatibility
// reasons we set it as a string, so that code that unmarshals
// timestamp-like values into interface{} will continue to
// see a string and not a time.Time.
// TODO(v3) Drop this.
out.Set(reflect.ValueOf(n.value))
} else { } else {
out.Set(reflect.ValueOf(resolved)) out.Set(reflect.ValueOf(resolved))
} }
good = true return true
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
switch resolved := resolved.(type) { switch resolved := resolved.(type) {
case int: case int:
if !out.OverflowInt(int64(resolved)) { if !out.OverflowInt(int64(resolved)) {
out.SetInt(int64(resolved)) out.SetInt(int64(resolved))
good = true return true
} }
case int64: case int64:
if !out.OverflowInt(resolved) { if !out.OverflowInt(resolved) {
out.SetInt(resolved) out.SetInt(resolved)
good = true return true
} }
case uint64: case uint64:
if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) { if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) {
out.SetInt(int64(resolved)) out.SetInt(int64(resolved))
good = true return true
} }
case float64: case float64:
if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) { if resolved <= math.MaxInt64 && !out.OverflowInt(int64(resolved)) {
out.SetInt(int64(resolved)) out.SetInt(int64(resolved))
good = true return true
} }
case string: case string:
if out.Type() == durationType { if out.Type() == durationType {
d, err := time.ParseDuration(resolved) d, err := time.ParseDuration(resolved)
if err == nil { if err == nil {
out.SetInt(int64(d)) out.SetInt(int64(d))
good = true return true
} }
} }
} }
@ -414,44 +476,49 @@ func (d *decoder) scalar(n *node, out reflect.Value) (good bool) {
case int: case int:
if resolved >= 0 && !out.OverflowUint(uint64(resolved)) { if resolved >= 0 && !out.OverflowUint(uint64(resolved)) {
out.SetUint(uint64(resolved)) out.SetUint(uint64(resolved))
good = true return true
} }
case int64: case int64:
if resolved >= 0 && !out.OverflowUint(uint64(resolved)) { if resolved >= 0 && !out.OverflowUint(uint64(resolved)) {
out.SetUint(uint64(resolved)) out.SetUint(uint64(resolved))
good = true return true
} }
case uint64: case uint64:
if !out.OverflowUint(uint64(resolved)) { if !out.OverflowUint(uint64(resolved)) {
out.SetUint(uint64(resolved)) out.SetUint(uint64(resolved))
good = true return true
} }
case float64: case float64:
if resolved <= math.MaxUint64 && !out.OverflowUint(uint64(resolved)) { if resolved <= math.MaxUint64 && !out.OverflowUint(uint64(resolved)) {
out.SetUint(uint64(resolved)) out.SetUint(uint64(resolved))
good = true return true
} }
} }
case reflect.Bool: case reflect.Bool:
switch resolved := resolved.(type) { switch resolved := resolved.(type) {
case bool: case bool:
out.SetBool(resolved) out.SetBool(resolved)
good = true return true
} }
case reflect.Float32, reflect.Float64: case reflect.Float32, reflect.Float64:
switch resolved := resolved.(type) { switch resolved := resolved.(type) {
case int: case int:
out.SetFloat(float64(resolved)) out.SetFloat(float64(resolved))
good = true return true
case int64: case int64:
out.SetFloat(float64(resolved)) out.SetFloat(float64(resolved))
good = true return true
case uint64: case uint64:
out.SetFloat(float64(resolved)) out.SetFloat(float64(resolved))
good = true return true
case float64: case float64:
out.SetFloat(resolved) out.SetFloat(resolved)
good = true return true
}
case reflect.Struct:
if resolvedv := reflect.ValueOf(resolved); out.Type() == resolvedv.Type() {
out.Set(resolvedv)
return true
} }
case reflect.Ptr: case reflect.Ptr:
if out.Type().Elem() == reflect.TypeOf(resolved) { if out.Type().Elem() == reflect.TypeOf(resolved) {
@ -459,13 +526,11 @@ func (d *decoder) scalar(n *node, out reflect.Value) (good bool) {
elem := reflect.New(out.Type().Elem()) elem := reflect.New(out.Type().Elem())
elem.Elem().Set(reflect.ValueOf(resolved)) elem.Elem().Set(reflect.ValueOf(resolved))
out.Set(elem) out.Set(elem)
good = true return true
} }
} }
if !good { d.terror(n, tag, out)
d.terror(n, tag, out) return false
}
return good
} }
func settableValueOf(i interface{}) reflect.Value { func settableValueOf(i interface{}) reflect.Value {
@ -482,6 +547,10 @@ func (d *decoder) sequence(n *node, out reflect.Value) (good bool) {
switch out.Kind() { switch out.Kind() {
case reflect.Slice: case reflect.Slice:
out.Set(reflect.MakeSlice(out.Type(), l, l)) out.Set(reflect.MakeSlice(out.Type(), l, l))
case reflect.Array:
if l != out.Len() {
failf("invalid array: want %d elements but got %d", out.Len(), l)
}
case reflect.Interface: case reflect.Interface:
// No type hints. Will have to use a generic sequence. // No type hints. Will have to use a generic sequence.
iface = out iface = out
@ -500,7 +569,9 @@ func (d *decoder) sequence(n *node, out reflect.Value) (good bool) {
j++ j++
} }
} }
out.Set(out.Slice(0, j)) if out.Kind() != reflect.Array {
out.Set(out.Slice(0, j))
}
if iface.IsValid() { if iface.IsValid() {
iface.Set(out) iface.Set(out)
} }
@ -561,7 +632,7 @@ func (d *decoder) mapping(n *node, out reflect.Value) (good bool) {
} }
e := reflect.New(et).Elem() e := reflect.New(et).Elem()
if d.unmarshal(n.children[i+1], e) { if d.unmarshal(n.children[i+1], e) {
out.SetMapIndex(k, e) d.setMapIndex(n.children[i+1], out, k, e)
} }
} }
} }
@ -569,6 +640,14 @@ func (d *decoder) mapping(n *node, out reflect.Value) (good bool) {
return true return true
} }
func (d *decoder) setMapIndex(n *node, out, k, v reflect.Value) {
if d.strict && out.MapIndex(k) != zeroValue {
d.terrors = append(d.terrors, fmt.Sprintf("line %d: key %#v already set in map", n.line+1, k.Interface()))
return
}
out.SetMapIndex(k, v)
}
func (d *decoder) mappingSlice(n *node, out reflect.Value) (good bool) { func (d *decoder) mappingSlice(n *node, out reflect.Value) (good bool) {
outt := out.Type() outt := out.Type()
if outt.Elem() != mapItemType { if outt.Elem() != mapItemType {
@ -616,6 +695,10 @@ func (d *decoder) mappingStruct(n *node, out reflect.Value) (good bool) {
elemType = inlineMap.Type().Elem() elemType = inlineMap.Type().Elem()
} }
var doneFields []bool
if d.strict {
doneFields = make([]bool, len(sinfo.FieldsList))
}
for i := 0; i < l; i += 2 { for i := 0; i < l; i += 2 {
ni := n.children[i] ni := n.children[i]
if isMerge(ni) { if isMerge(ni) {
@ -626,6 +709,13 @@ func (d *decoder) mappingStruct(n *node, out reflect.Value) (good bool) {
continue continue
} }
if info, ok := sinfo.FieldsMap[name.String()]; ok { if info, ok := sinfo.FieldsMap[name.String()]; ok {
if d.strict {
if doneFields[info.Id] {
d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s already set in type %s", ni.line+1, name.String(), out.Type()))
continue
}
doneFields[info.Id] = true
}
var field reflect.Value var field reflect.Value
if info.Inline == nil { if info.Inline == nil {
field = out.Field(info.Num) field = out.Field(info.Num)
@ -639,7 +729,9 @@ func (d *decoder) mappingStruct(n *node, out reflect.Value) (good bool) {
} }
value := reflect.New(elemType).Elem() value := reflect.New(elemType).Elem()
d.unmarshal(n.children[i+1], value) d.unmarshal(n.children[i+1], value)
inlineMap.SetMapIndex(name, value) d.setMapIndex(n.children[i+1], inlineMap, name, value)
} else if d.strict {
d.terrors = append(d.terrors, fmt.Sprintf("line %d: field %s not found in type %s", ni.line+1, name.String(), out.Type()))
} }
} }
return true return true

View file

@ -2,6 +2,7 @@ package yaml
import ( import (
"bytes" "bytes"
"fmt"
) )
// Flush the buffer if needed. // Flush the buffer if needed.
@ -664,9 +665,8 @@ func yaml_emitter_emit_node(emitter *yaml_emitter_t, event *yaml_event_t,
return yaml_emitter_emit_mapping_start(emitter, event) return yaml_emitter_emit_mapping_start(emitter, event)
default: default:
return yaml_emitter_set_emitter_error(emitter, return yaml_emitter_set_emitter_error(emitter,
"expected SCALAR, SEQUENCE-START, MAPPING-START, or ALIAS") fmt.Sprintf("expected SCALAR, SEQUENCE-START, MAPPING-START, or ALIAS, but got %v", event.typ))
} }
return false
} }
// Expect ALIAS. // Expect ALIAS.
@ -843,7 +843,7 @@ func yaml_emitter_select_scalar_style(emitter *yaml_emitter_t, event *yaml_event
return true return true
} }
// Write an achor. // Write an anchor.
func yaml_emitter_process_anchor(emitter *yaml_emitter_t) bool { func yaml_emitter_process_anchor(emitter *yaml_emitter_t) bool {
if emitter.anchor_data.anchor == nil { if emitter.anchor_data.anchor == nil {
return true return true
@ -995,10 +995,10 @@ func yaml_emitter_analyze_scalar(emitter *yaml_emitter_t, value []byte) bool {
break_space = false break_space = false
space_break = false space_break = false
preceeded_by_whitespace = false preceded_by_whitespace = false
followed_by_whitespace = false followed_by_whitespace = false
previous_space = false previous_space = false
previous_break = false previous_break = false
) )
emitter.scalar_data.value = value emitter.scalar_data.value = value
@ -1017,7 +1017,7 @@ func yaml_emitter_analyze_scalar(emitter *yaml_emitter_t, value []byte) bool {
flow_indicators = true flow_indicators = true
} }
preceeded_by_whitespace = true preceded_by_whitespace = true
for i, w := 0, 0; i < len(value); i += w { for i, w := 0, 0; i < len(value); i += w {
w = width(value[i]) w = width(value[i])
followed_by_whitespace = i+w >= len(value) || is_blank(value, i+w) followed_by_whitespace = i+w >= len(value) || is_blank(value, i+w)
@ -1048,7 +1048,7 @@ func yaml_emitter_analyze_scalar(emitter *yaml_emitter_t, value []byte) bool {
block_indicators = true block_indicators = true
} }
case '#': case '#':
if preceeded_by_whitespace { if preceded_by_whitespace {
flow_indicators = true flow_indicators = true
block_indicators = true block_indicators = true
} }
@ -1089,7 +1089,7 @@ func yaml_emitter_analyze_scalar(emitter *yaml_emitter_t, value []byte) bool {
} }
// [Go]: Why 'z'? Couldn't be the end of the string as that's the loop condition. // [Go]: Why 'z'? Couldn't be the end of the string as that's the loop condition.
preceeded_by_whitespace = is_blankz(value, i) preceded_by_whitespace = is_blankz(value, i)
} }
emitter.scalar_data.multiline = line_breaks emitter.scalar_data.multiline = line_breaks

View file

@ -3,12 +3,14 @@ package yaml
import ( import (
"encoding" "encoding"
"fmt" "fmt"
"io"
"reflect" "reflect"
"regexp" "regexp"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"unicode/utf8"
) )
type encoder struct { type encoder struct {
@ -16,25 +18,39 @@ type encoder struct {
event yaml_event_t event yaml_event_t
out []byte out []byte
flow bool flow bool
// doneInit holds whether the initial stream_start_event has been
// emitted.
doneInit bool
} }
func newEncoder() (e *encoder) { func newEncoder() *encoder {
e = &encoder{} e := &encoder{}
e.must(yaml_emitter_initialize(&e.emitter)) yaml_emitter_initialize(&e.emitter)
yaml_emitter_set_output_string(&e.emitter, &e.out) yaml_emitter_set_output_string(&e.emitter, &e.out)
yaml_emitter_set_unicode(&e.emitter, true) yaml_emitter_set_unicode(&e.emitter, true)
e.must(yaml_stream_start_event_initialize(&e.event, yaml_UTF8_ENCODING))
e.emit()
e.must(yaml_document_start_event_initialize(&e.event, nil, nil, true))
e.emit()
return e return e
} }
func (e *encoder) finish() { func newEncoderWithWriter(w io.Writer) *encoder {
e.must(yaml_document_end_event_initialize(&e.event, true)) e := &encoder{}
yaml_emitter_initialize(&e.emitter)
yaml_emitter_set_output_writer(&e.emitter, w)
yaml_emitter_set_unicode(&e.emitter, true)
return e
}
func (e *encoder) init() {
if e.doneInit {
return
}
yaml_stream_start_event_initialize(&e.event, yaml_UTF8_ENCODING)
e.emit() e.emit()
e.doneInit = true
}
func (e *encoder) finish() {
e.emitter.open_ended = false e.emitter.open_ended = false
e.must(yaml_stream_end_event_initialize(&e.event)) yaml_stream_end_event_initialize(&e.event)
e.emit() e.emit()
} }
@ -44,9 +60,7 @@ func (e *encoder) destroy() {
func (e *encoder) emit() { func (e *encoder) emit() {
// This will internally delete the e.event value. // This will internally delete the e.event value.
if !yaml_emitter_emit(&e.emitter, &e.event) && e.event.typ != yaml_DOCUMENT_END_EVENT && e.event.typ != yaml_STREAM_END_EVENT { e.must(yaml_emitter_emit(&e.emitter, &e.event))
e.must(false)
}
} }
func (e *encoder) must(ok bool) { func (e *encoder) must(ok bool) {
@ -59,13 +73,28 @@ func (e *encoder) must(ok bool) {
} }
} }
func (e *encoder) marshalDoc(tag string, in reflect.Value) {
e.init()
yaml_document_start_event_initialize(&e.event, nil, nil, true)
e.emit()
e.marshal(tag, in)
yaml_document_end_event_initialize(&e.event, true)
e.emit()
}
func (e *encoder) marshal(tag string, in reflect.Value) { func (e *encoder) marshal(tag string, in reflect.Value) {
if !in.IsValid() { if !in.IsValid() || in.Kind() == reflect.Ptr && in.IsNil() {
e.nilv() e.nilv()
return return
} }
iface := in.Interface() iface := in.Interface()
if m, ok := iface.(Marshaler); ok { switch m := iface.(type) {
case time.Time, *time.Time:
// Although time.Time implements TextMarshaler,
// we don't want to treat it as a string for YAML
// purposes because YAML has special support for
// timestamps.
case Marshaler:
v, err := m.MarshalYAML() v, err := m.MarshalYAML()
if err != nil { if err != nil {
fail(err) fail(err)
@ -75,31 +104,34 @@ func (e *encoder) marshal(tag string, in reflect.Value) {
return return
} }
in = reflect.ValueOf(v) in = reflect.ValueOf(v)
} else if m, ok := iface.(encoding.TextMarshaler); ok { case encoding.TextMarshaler:
text, err := m.MarshalText() text, err := m.MarshalText()
if err != nil { if err != nil {
fail(err) fail(err)
} }
in = reflect.ValueOf(string(text)) in = reflect.ValueOf(string(text))
case nil:
e.nilv()
return
} }
switch in.Kind() { switch in.Kind() {
case reflect.Interface: case reflect.Interface:
if in.IsNil() { e.marshal(tag, in.Elem())
e.nilv()
} else {
e.marshal(tag, in.Elem())
}
case reflect.Map: case reflect.Map:
e.mapv(tag, in) e.mapv(tag, in)
case reflect.Ptr: case reflect.Ptr:
if in.IsNil() { if in.Type() == ptrTimeType {
e.nilv() e.timev(tag, in.Elem())
} else { } else {
e.marshal(tag, in.Elem()) e.marshal(tag, in.Elem())
} }
case reflect.Struct: case reflect.Struct:
e.structv(tag, in) if in.Type() == timeType {
case reflect.Slice: e.timev(tag, in)
} else {
e.structv(tag, in)
}
case reflect.Slice, reflect.Array:
if in.Type().Elem() == mapItemType { if in.Type().Elem() == mapItemType {
e.itemsv(tag, in) e.itemsv(tag, in)
} else { } else {
@ -191,10 +223,10 @@ func (e *encoder) mappingv(tag string, f func()) {
e.flow = false e.flow = false
style = yaml_FLOW_MAPPING_STYLE style = yaml_FLOW_MAPPING_STYLE
} }
e.must(yaml_mapping_start_event_initialize(&e.event, nil, []byte(tag), implicit, style)) yaml_mapping_start_event_initialize(&e.event, nil, []byte(tag), implicit, style)
e.emit() e.emit()
f() f()
e.must(yaml_mapping_end_event_initialize(&e.event)) yaml_mapping_end_event_initialize(&e.event)
e.emit() e.emit()
} }
@ -240,23 +272,36 @@ var base60float = regexp.MustCompile(`^[-+]?[0-9][0-9_]*(?::[0-5]?[0-9])+(?:\.[0
func (e *encoder) stringv(tag string, in reflect.Value) { func (e *encoder) stringv(tag string, in reflect.Value) {
var style yaml_scalar_style_t var style yaml_scalar_style_t
s := in.String() s := in.String()
rtag, rs := resolve("", s) canUsePlain := true
if rtag == yaml_BINARY_TAG { switch {
if tag == "" || tag == yaml_STR_TAG { case !utf8.ValidString(s):
tag = rtag if tag == yaml_BINARY_TAG {
s = rs.(string)
} else if tag == yaml_BINARY_TAG {
failf("explicitly tagged !!binary data must be base64-encoded") failf("explicitly tagged !!binary data must be base64-encoded")
} else { }
if tag != "" {
failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag)) failf("cannot marshal invalid UTF-8 data as %s", shortTag(tag))
} }
// It can't be encoded directly as YAML so use a binary tag
// and encode it as base64.
tag = yaml_BINARY_TAG
s = encodeBase64(s)
case tag == "":
// Check to see if it would resolve to a specific
// tag when encoded unquoted. If it doesn't,
// there's no need to quote it.
rtag, _ := resolve("", s)
canUsePlain = rtag == yaml_STR_TAG && !isBase60Float(s)
} }
if tag == "" && (rtag != yaml_STR_TAG || isBase60Float(s)) { // Note: it's possible for user code to emit invalid YAML
style = yaml_DOUBLE_QUOTED_SCALAR_STYLE // if they explicitly specify a tag and a string containing
} else if strings.Contains(s, "\n") { // text that's incompatible with that tag.
switch {
case strings.Contains(s, "\n"):
style = yaml_LITERAL_SCALAR_STYLE style = yaml_LITERAL_SCALAR_STYLE
} else { case canUsePlain:
style = yaml_PLAIN_SCALAR_STYLE style = yaml_PLAIN_SCALAR_STYLE
default:
style = yaml_DOUBLE_QUOTED_SCALAR_STYLE
} }
e.emitScalar(s, "", tag, style) e.emitScalar(s, "", tag, style)
} }
@ -281,9 +326,20 @@ func (e *encoder) uintv(tag string, in reflect.Value) {
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE) e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
} }
func (e *encoder) timev(tag string, in reflect.Value) {
t := in.Interface().(time.Time)
s := t.Format(time.RFC3339Nano)
e.emitScalar(s, "", tag, yaml_PLAIN_SCALAR_STYLE)
}
func (e *encoder) floatv(tag string, in reflect.Value) { func (e *encoder) floatv(tag string, in reflect.Value) {
// FIXME: Handle 64 bits here. // Issue #352: When formatting, use the precision of the underlying value
s := strconv.FormatFloat(float64(in.Float()), 'g', -1, 32) precision := 64
if in.Kind() == reflect.Float32 {
precision = 32
}
s := strconv.FormatFloat(in.Float(), 'g', -1, precision)
switch s { switch s {
case "+Inf": case "+Inf":
s = ".inf" s = ".inf"

5
vendor/gopkg.in/yaml.v2/go.mod generated vendored Normal file
View file

@ -0,0 +1,5 @@
module "gopkg.in/yaml.v2"
require (
"gopkg.in/check.v1" v0.0.0-20161208181325-20d25e280405
)

View file

@ -166,7 +166,6 @@ func yaml_parser_state_machine(parser *yaml_parser_t, event *yaml_event_t) bool
default: default:
panic("invalid parser state") panic("invalid parser state")
} }
return false
} }
// Parse the production: // Parse the production:

View file

@ -93,9 +93,18 @@ func yaml_parser_update_buffer(parser *yaml_parser_t, length int) bool {
panic("read handler must be set") panic("read handler must be set")
} }
// [Go] This function was changed to guarantee the requested length size at EOF.
// The fact we need to do this is pretty awful, but the description above implies
// for that to be the case, and there are tests
// If the EOF flag is set and the raw buffer is empty, do nothing. // If the EOF flag is set and the raw buffer is empty, do nothing.
if parser.eof && parser.raw_buffer_pos == len(parser.raw_buffer) { if parser.eof && parser.raw_buffer_pos == len(parser.raw_buffer) {
return true // [Go] ACTUALLY! Read the documentation of this function above.
// This is just broken. To return true, we need to have the
// given length in the buffer. Not doing that means every single
// check that calls this function to make sure the buffer has a
// given length is Go) panicking; or C) accessing invalid memory.
//return true
} }
// Return if the buffer contains enough characters. // Return if the buffer contains enough characters.
@ -389,6 +398,15 @@ func yaml_parser_update_buffer(parser *yaml_parser_t, length int) bool {
break break
} }
} }
// [Go] Read the documentation of this function above. To return true,
// we need to have the given length in the buffer. Not doing that means
// every single check that calls this function to make sure the buffer
// has a given length is Go) panicking; or C) accessing invalid memory.
// This happens here due to the EOF above breaking early.
for buffer_len < length {
parser.buffer[buffer_len] = 0
buffer_len++
}
parser.buffer = parser.buffer[:buffer_len] parser.buffer = parser.buffer[:buffer_len]
return true return true
} }

Some files were not shown because too many files have changed in this diff Show more