Merge pull request #520 from fkautz/pr_out_updating_godep_deps

This commit is contained in:
Frederick F. Kautz IV 2015-04-27 21:11:42 -07:00
commit f22390ccef
9 changed files with 406 additions and 18 deletions

6
Godeps/Godeps.json generated
View File

@ -7,7 +7,7 @@
"Deps": [ "Deps": [
{ {
"ImportPath": "github.com/dustin/go-humanize", "ImportPath": "github.com/dustin/go-humanize",
"Rev": "8cc1aaa2d955ee82833337cfb10babc42be6bce6" "Rev": "00897f070f09f194c26d65afae734ba4c32404e8"
}, },
{ {
"ImportPath": "github.com/golang/groupcache/lru", "ImportPath": "github.com/golang/groupcache/lru",
@ -15,11 +15,11 @@
}, },
{ {
"ImportPath": "github.com/gorilla/context", "ImportPath": "github.com/gorilla/context",
"Rev": "50c25fb3b2b3b3cc724e9b6ac75fb44b3bccd0da" "Rev": "215affda49addc4c8ef7e2534915df2c8c35c6cd"
}, },
{ {
"ImportPath": "github.com/gorilla/mux", "ImportPath": "github.com/gorilla/mux",
"Rev": "e444e69cbd2e2e3e0749a2f3c717cec491552bbf" "Rev": "660d31f8602b95058fed6833debf113e85350868"
}, },
{ {
"ImportPath": "github.com/minio-io/check", "ImportPath": "github.com/minio-io/check",

View File

@ -0,0 +1,192 @@
package humanize
/*
Slightly adapted from the source to fit go-humanize.
Author: https://github.com/gorhill
Source: https://gist.github.com/gorhill/5285193
*/
import (
"math"
"strconv"
)
var (
renderFloatPrecisionMultipliers = [...]float64{
1,
10,
100,
1000,
10000,
100000,
1000000,
10000000,
100000000,
1000000000,
}
renderFloatPrecisionRounders = [...]float64{
0.5,
0.05,
0.005,
0.0005,
0.00005,
0.000005,
0.0000005,
0.00000005,
0.000000005,
0.0000000005,
}
)
// FormatFloat produces a formatted number as string based on the following user-specified criteria:
// * thousands separator
// * decimal separator
// * decimal precision
//
// Usage: s := RenderFloat(format, n)
// The format parameter tells how to render the number n.
//
// See examples: http://play.golang.org/p/LXc1Ddm1lJ
//
// Examples of format strings, given n = 12345.6789:
// "#,###.##" => "12,345.67"
// "#,###." => "12,345"
// "#,###" => "12345,678"
// "#\u202F###,##" => "12345,68"
// "#.###,###### => 12.345,678900
// "" (aka default format) => 12,345.67
//
// The highest precision allowed is 9 digits after the decimal symbol.
// There is also a version for integer number, FormatInteger(),
// which is convenient for calls within template.
func FormatFloat(format string, n float64) string {
// Special cases:
// NaN = "NaN"
// +Inf = "+Infinity"
// -Inf = "-Infinity"
if math.IsNaN(n) {
return "NaN"
}
if n > math.MaxFloat64 {
return "Infinity"
}
if n < -math.MaxFloat64 {
return "-Infinity"
}
// default format
precision := 2
decimalStr := "."
thousandStr := ","
positiveStr := ""
negativeStr := "-"
if len(format) > 0 {
format := []rune(format)
// If there is an explicit format directive,
// then default values are these:
precision = 9
thousandStr = ""
// collect indices of meaningful formatting directives
formatIndx := []int{}
for i, char := range format {
if char != '#' && char != '0' {
formatIndx = append(formatIndx, i)
}
}
if len(formatIndx) > 0 {
// Directive at index 0:
// Must be a '+'
// Raise an error if not the case
// index: 0123456789
// +0.000,000
// +000,000.0
// +0000.00
// +0000
if formatIndx[0] == 0 {
if format[formatIndx[0]] != '+' {
panic("RenderFloat(): invalid positive sign directive")
}
positiveStr = "+"
formatIndx = formatIndx[1:]
}
// Two directives:
// First is thousands separator
// Raise an error if not followed by 3-digit
// 0123456789
// 0.000,000
// 000,000.00
if len(formatIndx) == 2 {
if (formatIndx[1] - formatIndx[0]) != 4 {
panic("RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers")
}
thousandStr = string(format[formatIndx[0]])
formatIndx = formatIndx[1:]
}
// One directive:
// Directive is decimal separator
// The number of digit-specifier following the separator indicates wanted precision
// 0123456789
// 0.00
// 000,0000
if len(formatIndx) == 1 {
decimalStr = string(format[formatIndx[0]])
precision = len(format) - formatIndx[0] - 1
}
}
}
// generate sign part
var signStr string
if n >= 0.000000001 {
signStr = positiveStr
} else if n <= -0.000000001 {
signStr = negativeStr
n = -n
} else {
signStr = ""
n = 0.0
}
// split number into integer and fractional parts
intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision])
// generate integer part string
intStr := strconv.Itoa(int(intf))
// add thousand separator if required
if len(thousandStr) > 0 {
for i := len(intStr); i > 3; {
i -= 3
intStr = intStr[:i] + thousandStr + intStr[i:]
}
}
// no fractional part, we can leave now
if precision == 0 {
return signStr + intStr
}
// generate fractional part
fracStr := strconv.Itoa(int(fracf * renderFloatPrecisionMultipliers[precision]))
// may need padding
if len(fracStr) < precision {
fracStr = "000000000000000"[:precision-len(fracStr)] + fracStr
}
return signStr + intStr + decimalStr + fracStr
}
// FormatInteger produces a formatted number as string.
// See FormatFloat.
func FormatInteger(format string, n int) string {
return FormatFloat(format, float64(n))
}

View File

@ -0,0 +1,78 @@
package humanize
import (
"math"
"testing"
)
type TestStruct struct {
name string
format string
num float64
formatted string
}
func TestFormatFloat(t *testing.T) {
tests := []TestStruct{
{"default", "", 12345.6789, "12,345.68"},
{"#", "#", 12345.6789, "12345.678900000"},
{"#.", "#.", 12345.6789, "12346"},
{"#,#", "#,#", 12345.6789, "12345,7"},
{"#,##", "#,##", 12345.6789, "12345,68"},
{"#,###", "#,###", 12345.6789, "12345,679"},
{"#,###.", "#,###.", 12345.6789, "12,346"},
{"#,###.##", "#,###.##", 12345.6789, "12,345.68"},
{"#,###.###", "#,###.###", 12345.6789, "12,345.679"},
{"#,###.####", "#,###.####", 12345.6789, "12,345.6789"},
{"#.###,######", "#.###,######", 12345.6789, "12.345,678900"},
{"#\u202f###,##", "#\u202f###,##", 12345.6789, "12345,68"},
// special cases
{"NaN", "#", math.NaN(), "NaN"},
{"+Inf", "#", math.Inf(1), "Infinity"},
{"-Inf", "#", math.Inf(-1), "-Infinity"},
{"signStr <= -0.000000001", "", -0.000000002, "-0.00"},
{"signStr = 0", "", 0, "0.00"},
{"Format directive must start with +", "+000", 12345.6789, "+12345.678900000"},
}
for _, test := range tests {
got := FormatFloat(test.format, test.num)
if got != test.formatted {
t.Errorf("On %v (%v, %v), got %v, wanted %v",
test.name, test.format, test.num, got, test.formatted)
}
}
// Test a single integer
got := FormatInteger("#", 12345)
if got != "12345.000000000" {
t.Errorf("On %v (%v, %v), got %v, wanted %v",
"integerTest", "#", 12345, got, "12345.000000000")
}
// Test the things that could panic
panictests := []TestStruct{
{"RenderFloat(): invalid positive sign directive", "-", 12345.6789, "12,345.68"},
{"RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers", "0.01", 12345.6789, "12,345.68"},
}
for _, test := range panictests {
didPanic := false
var message interface{}
func() {
defer func() {
if message = recover(); message != nil {
didPanic = true
}
}()
// call the target function
_ = FormatFloat(test.format, test.num)
}()
if didPanic != true {
t.Errorf("On %v, should have panic and did not.",
test.name)
}
}
}

View File

@ -4,4 +4,6 @@ go:
- 1.0 - 1.0
- 1.1 - 1.1
- 1.2 - 1.2
- 1.3
- 1.4
- tip - tip

View File

@ -89,7 +89,7 @@ There are several other matchers that can be added. To match path prefixes:
r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool { r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
return r.ProtoMajor == 0 return r.ProtoMajor == 0
}) })
...and finally, it is possible to combine several matchers in a single route: ...and finally, it is possible to combine several matchers in a single route:
@ -164,8 +164,8 @@ This also works for host variables:
// url.String() will be "http://news.domain.com/articles/technology/42" // url.String() will be "http://news.domain.com/articles/technology/42"
url, err := r.Get("article").URL("subdomain", "news", url, err := r.Get("article").URL("subdomain", "news",
"category", "technology", "category", "technology",
"id", "42") "id", "42")
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
@ -193,7 +193,7 @@ as well:
// "http://news.domain.com/articles/technology/42" // "http://news.domain.com/articles/technology/42"
url, err := r.Get("article").URL("subdomain", "news", url, err := r.Get("article").URL("subdomain", "news",
"category", "technology", "category", "technology",
"id", "42") "id", "42")
*/ */
package mux package mux

View File

@ -152,6 +152,13 @@ func (r *Router) getRegexpGroup() *routeRegexpGroup {
return nil return nil
} }
func (r *Router) buildVars(m map[string]string) map[string]string {
if r.parent != nil {
m = r.parent.buildVars(m)
}
return m
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Route factories // Route factories
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -224,6 +231,12 @@ 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
// route variables before building a URL.
func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route {
return r.NewRoute().BuildVarsFunc(f)
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Context // Context
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------

View File

@ -135,6 +135,33 @@ func TestHost(t *testing.T) {
path: "", path: "",
shouldMatch: false, shouldMatch: false,
}, },
{
title: "Path route with single pattern with pipe, match",
route: new(Route).Path("/{category:a|b/c}"),
request: newRequest("GET", "http://localhost/a"),
vars: map[string]string{"category": "a"},
host: "",
path: "/a",
shouldMatch: true,
},
{
title: "Path route with single pattern with pipe, match",
route: new(Route).Path("/{category:a|b/c}"),
request: newRequest("GET", "http://localhost/b/c"),
vars: map[string]string{"category": "b/c"},
host: "",
path: "/b/c",
shouldMatch: true,
},
{
title: "Path route with multiple patterns with pipe, match",
route: new(Route).Path("/{category:a|b/c}/{product}/{id:[0-9]+}"),
request: newRequest("GET", "http://localhost/a/product_name/1"),
vars: map[string]string{"category": "a", "product": "product_name", "id": "1"},
host: "",
path: "/a/product_name/1",
shouldMatch: true,
},
} }
for _, test := range tests { for _, test := range tests {
testRoute(t, test) testRoute(t, test)
@ -593,6 +620,39 @@ func TestMatcherFunc(t *testing.T) {
} }
} }
func TestBuildVarsFunc(t *testing.T) {
tests := []routeTest{
{
title: "BuildVarsFunc set on route",
route: new(Route).Path(`/111/{v1:\d}{v2:.*}`).BuildVarsFunc(func(vars map[string]string) map[string]string {
vars["v1"] = "3"
vars["v2"] = "a"
return vars
}),
request: newRequest("GET", "http://localhost/111/2"),
path: "/111/3a",
shouldMatch: true,
},
{
title: "BuildVarsFunc set on route and parent route",
route: new(Route).PathPrefix(`/{v1:\d}`).BuildVarsFunc(func(vars map[string]string) map[string]string {
vars["v1"] = "2"
return vars
}).Subrouter().Path(`/{v2:\w}`).BuildVarsFunc(func(vars map[string]string) map[string]string {
vars["v2"] = "b"
return vars
}),
request: newRequest("GET", "http://localhost/1/a"),
path: "/2/b",
shouldMatch: true,
},
}
for _, test := range tests {
testRoute(t, test)
}
}
func TestSubRouter(t *testing.T) { func TestSubRouter(t *testing.T) {
subrouter1 := new(Route).Host("{v1:[a-z]+}.google.com").Subrouter() subrouter1 := new(Route).Host("{v1:[a-z]+}.google.com").Subrouter()
subrouter2 := new(Route).PathPrefix("/foo/{v1}").Subrouter() subrouter2 := new(Route).PathPrefix("/foo/{v1}").Subrouter()

View File

@ -150,11 +150,7 @@ func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
} }
// url builds a URL part using the given values. // url builds a URL part using the given values.
func (r *routeRegexp) url(pairs ...string) (string, error) { func (r *routeRegexp) url(values map[string]string) (string, error) {
values, err := mapFromPairs(pairs...)
if err != nil {
return "", err
}
urlValues := make([]interface{}, len(r.varsN)) urlValues := make([]interface{}, len(r.varsN))
for k, v := range r.varsN { for k, v := range r.varsN {
value, ok := values[v] value, ok := values[v]

View File

@ -31,6 +31,8 @@ type Route struct {
name string name string
// Error resulted from building a route. // Error resulted from building a route.
err error err error
buildVarsFunc BuildVarsFunc
} }
// Match matches the route against the request. // Match matches the route against the request.
@ -360,6 +362,19 @@ func (r *Route) Schemes(schemes ...string) *Route {
return r.addMatcher(schemeMatcher(schemes)) return r.addMatcher(schemeMatcher(schemes))
} }
// BuildVarsFunc --------------------------------------------------------------
// BuildVarsFunc is the function signature used by custom build variable
// functions (which can modify route variables before a route's URL is built).
type BuildVarsFunc func(map[string]string) map[string]string
// BuildVarsFunc adds a custom function to be used to modify build variables
// before a route's URL is built.
func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route {
r.buildVarsFunc = f
return r
}
// Subrouter ------------------------------------------------------------------ // Subrouter ------------------------------------------------------------------
// Subrouter creates a subrouter for the route. // Subrouter creates a subrouter for the route.
@ -422,17 +437,20 @@ func (r *Route) URL(pairs ...string) (*url.URL, error) {
if r.regexp == nil { if r.regexp == nil {
return nil, errors.New("mux: route doesn't have a host or path") return nil, errors.New("mux: route doesn't have a host or path")
} }
values, err := r.prepareVars(pairs...)
if err != nil {
return nil, err
}
var scheme, host, path string var scheme, host, path string
var err error
if r.regexp.host != nil { if r.regexp.host != nil {
// Set a default scheme. // Set a default scheme.
scheme = "http" scheme = "http"
if host, err = r.regexp.host.url(pairs...); err != nil { if host, err = r.regexp.host.url(values); err != nil {
return nil, err return nil, err
} }
} }
if r.regexp.path != nil { if r.regexp.path != nil {
if path, err = r.regexp.path.url(pairs...); err != nil { if path, err = r.regexp.path.url(values); err != nil {
return nil, err return nil, err
} }
} }
@ -453,7 +471,11 @@ func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
if r.regexp == nil || r.regexp.host == nil { if r.regexp == nil || r.regexp.host == nil {
return nil, errors.New("mux: route doesn't have a host") return nil, errors.New("mux: route doesn't have a host")
} }
host, err := r.regexp.host.url(pairs...) values, err := r.prepareVars(pairs...)
if err != nil {
return nil, err
}
host, err := r.regexp.host.url(values)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -473,7 +495,11 @@ func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
if r.regexp == nil || r.regexp.path == nil { if r.regexp == nil || r.regexp.path == nil {
return nil, errors.New("mux: route doesn't have a path") return nil, errors.New("mux: route doesn't have a path")
} }
path, err := r.regexp.path.url(pairs...) values, err := r.prepareVars(pairs...)
if err != nil {
return nil, err
}
path, err := r.regexp.path.url(values)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -482,6 +508,26 @@ func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
}, nil }, nil
} }
// prepareVars converts the route variable pairs into a map. If the route has a
// BuildVarsFunc, it is invoked.
func (r *Route) prepareVars(pairs ...string) (map[string]string, error) {
m, err := mapFromPairs(pairs...)
if err != nil {
return nil, err
}
return r.buildVars(m), nil
}
func (r *Route) buildVars(m map[string]string) map[string]string {
if r.parent != nil {
m = r.parent.buildVars(m)
}
if r.buildVarsFunc != nil {
m = r.buildVarsFunc(m)
}
return m
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// parentRoute // parentRoute
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -490,6 +536,7 @@ func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
type parentRoute interface { type parentRoute interface {
getNamedRoutes() map[string]*Route getNamedRoutes() map[string]*Route
getRegexpGroup() *routeRegexpGroup getRegexpGroup() *routeRegexpGroup
buildVars(map[string]string) map[string]string
} }
// getNamedRoutes returns the map where named routes are registered. // getNamedRoutes returns the map where named routes are registered.