Просмотр исходного кода

Updated macaron and ini package

Torkel Ödegaard 10 лет назад
Родитель
Сommit
30047e6a9f

+ 3 - 3
Godeps/Godeps.json

@@ -11,7 +11,7 @@
 		},
 		{
 			"ImportPath": "github.com/Unknwon/macaron",
-			"Rev": "da7cbddc50b9d33e076fb1eabff13b55c3b85fc5"
+			"Rev": "93de4f3fad97bf246b838f828e2348f46f21f20a"
 		},
 		{
 			"ImportPath": "github.com/codegangsta/cli",
@@ -72,8 +72,8 @@
 		},
 		{
 			"ImportPath": "gopkg.in/ini.v1",
-			"Comment": "v0-10-g28ad8c4",
-			"Rev": "28ad8c408ba20e5c86b06d64cd2cc9248f640a83"
+			"Comment": "v0-16-g1772191",
+			"Rev": "177219109c97e7920c933e21c9b25f874357b237"
 		}
 	]
 }

+ 7 - 4
Godeps/_workspace/src/github.com/Unknwon/macaron/README.md

@@ -1,11 +1,11 @@
 Macaron [![Build Status](https://drone.io/github.com/Unknwon/macaron/status.png)](https://drone.io/github.com/Unknwon/macaron/latest) [![](http://gocover.io/_badge/github.com/Unknwon/macaron)](http://gocover.io/github.com/Unknwon/macaron)
 =======================
 
-![Macaron Logo](macaronlogo.png)
+![Macaron Logo](https://raw.githubusercontent.com/Unknwon/macaron/master/macaronlogo.png)
 
 Package macaron is a high productive and modular design web framework in Go.
 
-##### Current version: 0.5.0
+##### Current version: 0.5.4
 
 ## Getting Started
 
@@ -41,7 +41,7 @@ func main() {
 - Handy dependency injection powered by [inject](https://github.com/codegangsta/inject).
 - Better router layer and less reflection make faster speed.
 
-## Middlewares 
+## Middlewares
 
 Middlewares allow you easily plugin/unplugin features for your Macaron applications.
 
@@ -70,15 +70,18 @@ There are already many [middlewares](https://github.com/macaron-contrib) to simp
 
 - [Gogs](https://github.com/gogits/gogs): Go Git Service
 - [Gogs Web](https://github.com/gogits/gogsweb): Gogs official website
+- [Go Walker](https://gowalker.org): Go online API documentation
 - [Switch](https://github.com/gpmgo/switch): Gopm registry
 - [YouGam](http://yougam.com): Online Forum
 - [Car Girl](http://qcnl.gzsy.com/): Online campaign
+- [Critical Stack Intel](https://intel.criticalstack.com/): A 100% free intel marketplace from Critical Stack, Inc.
 
 ## Getting Help
 
 - [API Reference](https://gowalker.org/github.com/Unknwon/macaron)
 - [Documentation](http://macaron.gogs.io)
 - [FAQs](http://macaron.gogs.io/docs/faqs)
+- [![Join the chat at https://gitter.im/Unknwon/macaron](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Unknwon/macaron?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
 
 ## Credits
 
@@ -88,4 +91,4 @@ There are already many [middlewares](https://github.com/macaron-contrib) to simp
 
 ## License
 
-This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.
+This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.

+ 0 - 37
Godeps/_workspace/src/github.com/Unknwon/macaron/bpool/README.md

@@ -1,37 +0,0 @@
-# bpool [![GoDoc](https://godoc.org/github.com/oxtoacart/bpool?status.png)](https://godoc.org/github.com/oxtoacart/bpool)
-
-Package bpool implements leaky pools of byte arrays and Buffers as bounded channels. It is based on the leaky buffer example from the Effective Go documentation: http://golang.org/doc/effective_go.html#leaky_buffer
-
-## Install
-
-`go get github.com/oxtoacart/bpool`
-
-## Documentation
-
-See [godoc.org](http://godoc.org/github.com/oxtoacart/bpool) or use `godoc github.com/oxtoacart/bpool`
-
-## Example
-
-```go
-
-var bufpool *bpol.BufferPool
-
-func main() {
-
-    bufpool = bpool.NewBufferPool(48)
-
-}
-
-func someFunction() error {
-
-     // Get a buffer from the pool
-     buf := bufpool.Get()
-     ...
-     ...
-     ...
-     // Return the buffer to the pool
-     bufpool.Put(buf)
-     
-     return nil
-}
-```

+ 0 - 45
Godeps/_workspace/src/github.com/Unknwon/macaron/bpool/bufferpool.go

@@ -1,45 +0,0 @@
-package bpool
-
-import (
-	"bytes"
-)
-
-/*
-BufferPool implements a pool of bytes.Buffers in the form of a bounded
-channel.
-*/
-type BufferPool struct {
-	c chan *bytes.Buffer
-}
-
-/*
-NewBufferPool creates a new BufferPool bounded to the given size.
-*/
-func NewBufferPool(size int) (bp *BufferPool) {
-	return &BufferPool{
-		c: make(chan *bytes.Buffer, size),
-	}
-}
-
-/*
-Get gets a Buffer from the BufferPool, or creates a new one if none are available
-in the pool.
-*/
-func (bp *BufferPool) Get() (b *bytes.Buffer) {
-	select {
-	case b = <-bp.c:
-	// reuse existing buffer
-	default:
-		// create new buffer
-		b = bytes.NewBuffer([]byte{})
-	}
-	return
-}
-
-/*
-Put returns the given Buffer to the BufferPool.
-*/
-func (bp *BufferPool) Put(b *bytes.Buffer) {
-	b.Reset()
-	bp.c <- b
-}

+ 69 - 12
Godeps/_workspace/src/github.com/Unknwon/macaron/context.go

@@ -23,9 +23,11 @@ import (
 	"mime/multipart"
 	"net/http"
 	"net/url"
+	"os"
 	"path"
 	"path/filepath"
 	"reflect"
+	"strconv"
 	"strings"
 	"time"
 
@@ -182,6 +184,11 @@ func (ctx *Context) Query(name string) string {
 	return ctx.Req.Form.Get(name)
 }
 
+// QueryTrim querys and trims spaces form parameter.
+func (ctx *Context) QueryTrim(name string) string {
+	return strings.TrimSpace(ctx.Query(name))
+}
+
 // QueryStrings returns a list of results by given query name.
 func (ctx *Context) QueryStrings(name string) []string {
 	if ctx.Req.Form == nil {
@@ -210,9 +217,21 @@ func (ctx *Context) QueryInt64(name string) int64 {
 	return com.StrTo(ctx.Query(name)).MustInt64()
 }
 
+// QueryFloat64 returns query result in float64 type.
+func (ctx *Context) QueryFloat64(name string) float64 {
+	v, _ := strconv.ParseFloat(ctx.Query(name), 64)
+	return v
+}
+
 // Params returns value of given param name.
-// e.g. ctx.Params(":uid")
+// e.g. ctx.Params(":uid") or ctx.Params("uid")
 func (ctx *Context) Params(name string) string {
+	if len(name) == 0 {
+		return ""
+	}
+	if name[0] != '*' && name[0] != ':' {
+		name = ":" + name
+	}
 	return ctx.params[name]
 }
 
@@ -242,12 +261,20 @@ func (ctx *Context) ParamsInt64(name string) int64 {
 	return com.StrTo(ctx.Params(name)).MustInt64()
 }
 
+// ParamsFloat64 returns params result in int64 type.
+// e.g. ctx.ParamsFloat64(":uid")
+func (ctx *Context) ParamsFloat64(name string) float64 {
+	v, _ := strconv.ParseFloat(ctx.Params(name), 64)
+	return v
+}
+
 // GetFile returns information about user upload file by given form field name.
 func (ctx *Context) GetFile(name string) (multipart.File, *multipart.FileHeader, error) {
 	return ctx.Req.FormFile(name)
 }
 
 // SetCookie sets given cookie value to response header.
+// FIXME: IE support? http://golanghome.com/post/620#reply2
 func (ctx *Context) SetCookie(name string, value string, others ...interface{}) {
 	cookie := http.Cookie{}
 	cookie.Name = name
@@ -264,23 +291,19 @@ func (ctx *Context) SetCookie(name string, value string, others ...interface{})
 		}
 	}
 
-	// default "/"
+	cookie.Path = "/"
 	if len(others) > 1 {
 		if v, ok := others[1].(string); ok && len(v) > 0 {
 			cookie.Path = v
 		}
-	} else {
-		cookie.Path = "/"
 	}
 
-	// default empty
 	if len(others) > 2 {
 		if v, ok := others[2].(string); ok && len(v) > 0 {
 			cookie.Domain = v
 		}
 	}
 
-	// default empty
 	if len(others) > 3 {
 		switch v := others[3].(type) {
 		case bool:
@@ -292,7 +315,6 @@ func (ctx *Context) SetCookie(name string, value string, others ...interface{})
 		}
 	}
 
-	// default false. for session cookie default true
 	if len(others) > 4 {
 		if v, ok := others[4].(bool); ok && v {
 			cookie.HttpOnly = true
@@ -322,6 +344,12 @@ func (ctx *Context) GetCookieInt64(name string) int64 {
 	return com.StrTo(ctx.GetCookie(name)).MustInt64()
 }
 
+// GetCookieFloat64 returns cookie result in float64 type.
+func (ctx *Context) GetCookieFloat64(name string) float64 {
+	v, _ := strconv.ParseFloat(ctx.GetCookie(name), 64)
+	return v
+}
+
 var defaultCookieSecret string
 
 // SetDefaultCookieSecret sets global default secure cookie secret.
@@ -368,6 +396,14 @@ func (ctx *Context) GetSuperSecureCookie(secret, key string) (string, bool) {
 	return string(text), err == nil
 }
 
+func (ctx *Context) setRawContentHeader() {
+	ctx.Resp.Header().Set("Content-Description", "Raw content")
+	ctx.Resp.Header().Set("Content-Type", "text/plain")
+	ctx.Resp.Header().Set("Expires", "0")
+	ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
+	ctx.Resp.Header().Set("Pragma", "public")
+}
+
 // ServeContent serves given content to response.
 func (ctx *Context) ServeContent(name string, r io.ReadSeeker, params ...interface{}) {
 	modtime := time.Now()
@@ -377,14 +413,35 @@ func (ctx *Context) ServeContent(name string, r io.ReadSeeker, params ...interfa
 			modtime = v
 		}
 	}
-	ctx.Resp.Header().Set("Content-Description", "Raw content")
-	ctx.Resp.Header().Set("Content-Type", "text/plain")
-	ctx.Resp.Header().Set("Expires", "0")
-	ctx.Resp.Header().Set("Cache-Control", "must-revalidate")
-	ctx.Resp.Header().Set("Pragma", "public")
+
+	ctx.setRawContentHeader()
 	http.ServeContent(ctx.Resp, ctx.Req.Request, name, modtime, r)
 }
 
+// ServeFileContent serves given file as content to response.
+func (ctx *Context) ServeFileContent(file string, names ...string) {
+	var name string
+	if len(names) > 0 {
+		name = names[0]
+	} else {
+		name = path.Base(file)
+	}
+
+	f, err := os.Open(file)
+	if err != nil {
+		if Env == PROD {
+			http.Error(ctx.Resp, "Internal Server Error", 500)
+		} else {
+			http.Error(ctx.Resp, err.Error(), 500)
+		}
+		return
+	}
+	defer f.Close()
+
+	ctx.setRawContentHeader()
+	http.ServeContent(ctx.Resp, ctx.Req.Request, name, time.Now(), f)
+}
+
 // ServeFile serves given file to response.
 func (ctx *Context) ServeFile(file string, names ...string) {
 	var name string

+ 81 - 25
Godeps/_workspace/src/github.com/Unknwon/macaron/context_test.go

@@ -76,35 +76,53 @@ func Test_Context(t *testing.T) {
 		})
 
 		Convey("Render HTML", func() {
-			m.Get("/html", func(ctx *Context) {
-				ctx.HTML(304, "hello", "Unknwon") // 304 for logger test.
+
+			Convey("Normal HTML", func() {
+				m.Get("/html", func(ctx *Context) {
+					ctx.HTML(304, "hello", "Unknwon") // 304 for logger test.
+				})
+
+				resp := httptest.NewRecorder()
+				req, err := http.NewRequest("GET", "/html", nil)
+				So(err, ShouldBeNil)
+				m.ServeHTTP(resp, req)
+				So(resp.Body.String(), ShouldEqual, "<h1>Hello Unknwon</h1>")
 			})
 
-			resp := httptest.NewRecorder()
-			req, err := http.NewRequest("GET", "/html", nil)
-			So(err, ShouldBeNil)
-			m.ServeHTTP(resp, req)
-			So(resp.Body.String(), ShouldEqual, "<h1>Hello Unknwon</h1>")
+			Convey("HTML template set", func() {
+				m.Get("/html2", func(ctx *Context) {
+					ctx.Data["Name"] = "Unknwon"
+					ctx.HTMLSet(200, "basic2", "hello2")
+				})
 
-			m.Get("/html2", func(ctx *Context) {
-				ctx.Data["Name"] = "Unknwon"
-				ctx.HTMLSet(200, "basic2", "hello2")
+				resp := httptest.NewRecorder()
+				req, err := http.NewRequest("GET", "/html2", nil)
+				So(err, ShouldBeNil)
+				m.ServeHTTP(resp, req)
+				So(resp.Body.String(), ShouldEqual, "<h1>Hello Unknwon</h1>")
 			})
 
-			resp = httptest.NewRecorder()
-			req, err = http.NewRequest("GET", "/html2", nil)
-			So(err, ShouldBeNil)
-			m.ServeHTTP(resp, req)
-			So(resp.Body.String(), ShouldEqual, "<h1>Hello Unknwon</h1>")
+			Convey("With layout", func() {
+				m.Get("/layout", func(ctx *Context) {
+					ctx.HTML(200, "hello", "Unknwon", HTMLOptions{"layout"})
+				})
+
+				resp := httptest.NewRecorder()
+				req, err := http.NewRequest("GET", "/layout", nil)
+				So(err, ShouldBeNil)
+				m.ServeHTTP(resp, req)
+				So(resp.Body.String(), ShouldEqual, "head<h1>Hello Unknwon</h1>foot")
+			})
 		})
 
 		Convey("Parse from and query", func() {
 			m.Get("/query", func(ctx *Context) string {
 				var buf bytes.Buffer
-				buf.WriteString(ctx.Query("name") + " ")
+				buf.WriteString(ctx.QueryTrim("name") + " ")
 				buf.WriteString(ctx.QueryEscape("name") + " ")
 				buf.WriteString(com.ToStr(ctx.QueryInt("int")) + " ")
 				buf.WriteString(com.ToStr(ctx.QueryInt64("int64")) + " ")
+				buf.WriteString(com.ToStr(ctx.QueryFloat64("float64")) + " ")
 				return buf.String()
 			})
 			m.Get("/query2", func(ctx *Context) string {
@@ -115,10 +133,10 @@ func Test_Context(t *testing.T) {
 			})
 
 			resp := httptest.NewRecorder()
-			req, err := http.NewRequest("GET", "/query?name=Unknwon&int=12&int64=123", nil)
+			req, err := http.NewRequest("GET", "/query?name=Unknwon&int=12&int64=123&float64=1.25", nil)
 			So(err, ShouldBeNil)
 			m.ServeHTTP(resp, req)
-			So(resp.Body.String(), ShouldEqual, "Unknwon Unknwon 12 123 ")
+			So(resp.Body.String(), ShouldEqual, "Unknwon Unknwon 12 123 1.25 ")
 
 			resp = httptest.NewRecorder()
 			req, err = http.NewRequest("GET", "/query2?list=item1&list=item2", nil)
@@ -128,21 +146,23 @@ func Test_Context(t *testing.T) {
 		})
 
 		Convey("URL parameter", func() {
-			m.Get("/:name/:int/:int64", func(ctx *Context) string {
+			m.Get("/:name/:int/:int64/:float64", func(ctx *Context) string {
 				var buf bytes.Buffer
-				ctx.SetParams(":name", ctx.Params(":name"))
+				ctx.SetParams("name", ctx.Params("name"))
+				buf.WriteString(ctx.Params(""))
 				buf.WriteString(ctx.Params(":name") + " ")
 				buf.WriteString(ctx.ParamsEscape(":name") + " ")
 				buf.WriteString(com.ToStr(ctx.ParamsInt(":int")) + " ")
 				buf.WriteString(com.ToStr(ctx.ParamsInt64(":int64")) + " ")
+				buf.WriteString(com.ToStr(ctx.ParamsFloat64(":float64")) + " ")
 				return buf.String()
 			})
 
 			resp := httptest.NewRecorder()
-			req, err := http.NewRequest("GET", "/user/1/13", nil)
+			req, err := http.NewRequest("GET", "/user/1/13/1.24", nil)
 			So(err, ShouldBeNil)
 			m.ServeHTTP(resp, req)
-			So(resp.Body.String(), ShouldEqual, "user user 1 13 ")
+			So(resp.Body.String(), ShouldEqual, "user user 1 13 1.24 ")
 		})
 
 		Convey("Get file", func() {
@@ -158,26 +178,29 @@ func Test_Context(t *testing.T) {
 
 		Convey("Set and get cookie", func() {
 			m.Get("/set", func(ctx *Context) {
-				ctx.SetCookie("user", "Unknwon", 1)
+				ctx.SetCookie("user", "Unknwon", 1, "/", "localhost", true, true)
+				ctx.SetCookie("user", "Unknwon", int32(1), "/", "localhost", 1)
+				ctx.SetCookie("user", "Unknwon", int64(1))
 			})
 
 			resp := httptest.NewRecorder()
 			req, err := http.NewRequest("GET", "/set", nil)
 			So(err, ShouldBeNil)
 			m.ServeHTTP(resp, req)
-			So(resp.Header().Get("Set-Cookie"), ShouldEqual, "user=Unknwon; Path=/; Max-Age=1")
+			So(resp.Header().Get("Set-Cookie"), ShouldEqual, "user=Unknwon; Path=/; Domain=localhost; Max-Age=1; HttpOnly; Secure")
 
 			m.Get("/get", func(ctx *Context) string {
 				ctx.GetCookie("404")
 				So(ctx.GetCookieInt("uid"), ShouldEqual, 1)
 				So(ctx.GetCookieInt64("uid"), ShouldEqual, 1)
+				So(ctx.GetCookieFloat64("balance"), ShouldEqual, 1.25)
 				return ctx.GetCookie("user")
 			})
 
 			resp = httptest.NewRecorder()
 			req, err = http.NewRequest("GET", "/get", nil)
 			So(err, ShouldBeNil)
-			req.Header.Set("Cookie", "user=Unknwon; uid=1")
+			req.Header.Set("Cookie", "user=Unknwon; uid=1; balance=1.25")
 			m.ServeHTTP(resp, req)
 			So(resp.Body.String(), ShouldEqual, "Unknwon")
 		})
@@ -231,6 +254,39 @@ func Test_Context(t *testing.T) {
 			So(resp.Body.String(), ShouldEqual, "{{ myCustomFunc }}")
 		})
 
+		Convey("Serve file content", func() {
+			m.Get("/file", func(ctx *Context) {
+				ctx.ServeFileContent("fixtures/custom_funcs/index.tmpl")
+			})
+
+			resp := httptest.NewRecorder()
+			req, err := http.NewRequest("GET", "/file", nil)
+			So(err, ShouldBeNil)
+			m.ServeHTTP(resp, req)
+			So(resp.Body.String(), ShouldEqual, "{{ myCustomFunc }}")
+
+			m.Get("/file2", func(ctx *Context) {
+				ctx.ServeFileContent("fixtures/custom_funcs/index.tmpl", "ok.tmpl")
+			})
+
+			resp = httptest.NewRecorder()
+			req, err = http.NewRequest("GET", "/file2", nil)
+			So(err, ShouldBeNil)
+			m.ServeHTTP(resp, req)
+			So(resp.Body.String(), ShouldEqual, "{{ myCustomFunc }}")
+
+			m.Get("/file3", func(ctx *Context) {
+				ctx.ServeFileContent("404.tmpl")
+			})
+
+			resp = httptest.NewRecorder()
+			req, err = http.NewRequest("GET", "/file3", nil)
+			So(err, ShouldBeNil)
+			m.ServeHTTP(resp, req)
+			So(resp.Body.String(), ShouldEqual, "open 404.tmpl: no such file or directory\n")
+			So(resp.Code, ShouldEqual, 500)
+		})
+
 		Convey("Serve content", func() {
 			m.Get("/content", func(ctx *Context) {
 				ctx.ServeContent("content1", bytes.NewReader([]byte("Hello world!")))

+ 1 - 1
Godeps/_workspace/src/github.com/Unknwon/macaron/inject/inject_test.go

@@ -1,5 +1,5 @@
 // Copyright 2013 Martini Authors
-// Copyright 2014 Unknown
+// Copyright 2014 Unknwon
 //
 // 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

+ 1 - 1
Godeps/_workspace/src/github.com/Unknwon/macaron/logger_test.go

@@ -46,7 +46,7 @@ func Test_Logger(t *testing.T) {
 		So(len(buf.String()), ShouldBeGreaterThan, 0)
 	})
 
-	if !isWindows {
+	if ColorLog {
 		Convey("Color console output", t, func() {
 			m := Classic()
 			m.Get("/:code:int", func(ctx *Context) (int, string) {

+ 2 - 2
Godeps/_workspace/src/github.com/Unknwon/macaron/macaron.go

@@ -29,7 +29,7 @@ import (
 	"github.com/Unknwon/macaron/inject"
 )
 
-const _VERSION = "0.5.0.0116"
+const _VERSION = "0.5.4.0318"
 
 func Version() string {
 	return _VERSION
@@ -267,7 +267,7 @@ func SetConfig(source interface{}, others ...interface{}) (_ *ini.File, err erro
 // It returns an empty object if there is no one available.
 func Config() *ini.File {
 	if cfg == nil {
-		return &ini.File{}
+		return ini.Empty()
 	}
 	return cfg
 }

+ 37 - 5
Godeps/_workspace/src/github.com/Unknwon/macaron/render.go

@@ -1,4 +1,5 @@
 // Copyright 2013 Martini Authors
+// Copyright 2013 oxtoacart
 // Copyright 2014 Unknwon
 //
 // Licensed under the Apache License, Version 2.0 (the "License"): you may
@@ -32,10 +33,39 @@ import (
 	"time"
 
 	"github.com/Unknwon/com"
-
-	"github.com/Unknwon/macaron/bpool"
 )
 
+// BufferPool implements a pool of bytes.Buffers in the form of a bounded channel.
+type BufferPool struct {
+	c chan *bytes.Buffer
+}
+
+// NewBufferPool creates a new BufferPool bounded to the given size.
+func NewBufferPool(size int) (bp *BufferPool) {
+	return &BufferPool{
+		c: make(chan *bytes.Buffer, size),
+	}
+}
+
+// Get gets a Buffer from the BufferPool, or creates a new one if none are available
+// in the pool.
+func (bp *BufferPool) Get() (b *bytes.Buffer) {
+	select {
+	case b = <-bp.c:
+	// reuse existing buffer
+	default:
+		// create new buffer
+		b = bytes.NewBuffer([]byte{})
+	}
+	return
+}
+
+// Put returns the given Buffer to the BufferPool.
+func (bp *BufferPool) Put(b *bytes.Buffer) {
+	b.Reset()
+	bp.c <- b
+}
+
 const (
 	ContentType    = "Content-Type"
 	ContentLength  = "Content-Length"
@@ -50,7 +80,7 @@ const (
 
 var (
 	// Provides a temporary buffer to execute templates into and catch errors.
-	bufpool = bpool.NewBufferPool(64)
+	bufpool = NewBufferPool(64)
 
 	// Included helper functions for use when rendering html
 	helperFuncs = template.FuncMap{
@@ -392,8 +422,10 @@ func (r *TplRender) RW() http.ResponseWriter {
 }
 
 func (r *TplRender) JSON(status int, v interface{}) {
-	var result []byte
-	var err error
+	var (
+		result []byte
+		err    error
+	)
 	if r.Opt.IndentJSON {
 		result, err = json.MarshalIndent(v, "", "  ")
 	} else {

+ 17 - 17
Godeps/_workspace/src/github.com/Unknwon/macaron/return_handler.go

@@ -1,5 +1,5 @@
 // Copyright 2013 Martini Authors
-// Copyright 2014 Unknown
+// Copyright 2014 Unknwon
 //
 // 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
@@ -28,32 +28,32 @@ import (
 // that are passed into this function.
 type ReturnHandler func(*Context, []reflect.Value)
 
+func canDeref(val reflect.Value) bool {
+	return val.Kind() == reflect.Interface || val.Kind() == reflect.Ptr
+}
+
+func isByteSlice(val reflect.Value) bool {
+	return val.Kind() == reflect.Slice && val.Type().Elem().Kind() == reflect.Uint8
+}
+
 func defaultReturnHandler() ReturnHandler {
 	return func(ctx *Context, vals []reflect.Value) {
 		rv := ctx.GetVal(inject.InterfaceOf((*http.ResponseWriter)(nil)))
 		res := rv.Interface().(http.ResponseWriter)
-		var responseVal reflect.Value
+		var respVal reflect.Value
 		if len(vals) > 1 && vals[0].Kind() == reflect.Int {
 			res.WriteHeader(int(vals[0].Int()))
-			responseVal = vals[1]
+			respVal = vals[1]
 		} else if len(vals) > 0 {
-			responseVal = vals[0]
+			respVal = vals[0]
 		}
-		if canDeref(responseVal) {
-			responseVal = responseVal.Elem()
+		if canDeref(respVal) {
+			respVal = respVal.Elem()
 		}
-		if isByteSlice(responseVal) {
-			res.Write(responseVal.Bytes())
+		if isByteSlice(respVal) {
+			res.Write(respVal.Bytes())
 		} else {
-			res.Write([]byte(responseVal.String()))
+			res.Write([]byte(respVal.String()))
 		}
 	}
 }
-
-func isByteSlice(val reflect.Value) bool {
-	return val.Kind() == reflect.Slice && val.Type().Elem().Kind() == reflect.Uint8
-}
-
-func canDeref(val reflect.Value) bool {
-	return val.Kind() == reflect.Interface || val.Kind() == reflect.Ptr
-}

+ 1 - 1
Godeps/_workspace/src/github.com/Unknwon/macaron/return_handler_test.go

@@ -1,4 +1,4 @@
-// Copyright 2014 Unknown
+// Copyright 2014 Unknwon
 //
 // 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

+ 3 - 1
Godeps/_workspace/src/gopkg.in/ini.v1/.gitignore

@@ -1 +1,3 @@
-testdata/conf_out.ini
+testdata/conf_out.ini
+ini.sublime-project
+ini.sublime-workspace

+ 34 - 2
Godeps/_workspace/src/gopkg.in/ini.v1/README.md

@@ -32,6 +32,12 @@ A **Data Source** is either raw data in type `[]byte` or a file name with type `
 cfg, err := ini.Load([]byte("raw data"), "filename")
 ```
 
+Or start with an empty object:
+
+```go
+cfg := ini.Empty()
+```
+
 When you cannot decide how many data sources to load at the beginning, you still able to **Append()** them later.
 
 ```go
@@ -58,7 +64,7 @@ When you're pretty sure the section exists, following code could make your life
 section := cfg.Section("")
 ```
 
-What happens when the section somehow does not exists? Won't panic, it returns an empty section object.
+What happens when the section somehow does not exist? Don't panic, it automatically creates and returns a new section to you.
 
 To create a new section:
 
@@ -117,6 +123,9 @@ val := cfg.Section("").Key("key name").String()
 To get value with types:
 
 ```go
+// For boolean values:
+// true when value is: 1, t, T, TRUE, true, True, YES, yes, Yes, ON, on, On
+// false when value is: 0, f, F, FALSE, false, False, NO, no, No, OFF, off, Off
 v, err = cfg.Section("").Key("BOOL").Bool()
 v, err = cfg.Section("").Key("FLOAT64").Float64()
 v, err = cfg.Section("").Key("INT").Int()
@@ -176,11 +185,22 @@ v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"})
 v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75})
 v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30})
 v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30})
-v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3})
+v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3})
+v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339
 ```
 
 Default value will be presented if value of key is not in candidates you given, and default value does not need be one of candidates.
 
+To validate value in a given range:
+
+```go
+vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2)
+vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20)
+vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20)
+vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime)
+vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339
+```
+
 To auto-split value into slice:
 
 ```go
@@ -295,6 +315,18 @@ func main() {
 }
 ```
 
+Can I have default value for field? Absolutely.
+
+Assign it before you map to struct. It will keep the value as it is if the key is not presented or got wrong type.
+
+```go
+// ...
+p := &Person{
+	Name: "Joe",
+}
+// ...
+```
+
 #### Name Mapper
 
 To save your time and make your code cleaner, this library supports [`NameMapper`](https://gowalker.org/gopkg.in/ini.v1#NameMapper) between struct field and actual secion and key name.

+ 32 - 2
Godeps/_workspace/src/gopkg.in/ini.v1/README_ZH.md

@@ -27,6 +27,12 @@
 cfg, err := ini.Load([]byte("raw data"), "filename")
 ```
 
+或者从一个空白的文件开始:
+
+```go
+cfg := ini.Empty()
+```
+
 当您在一开始无法决定需要加载哪些数据源时,仍可以使用 **Append()** 在需要的时候加载它们。
 
 ```go
@@ -53,7 +59,7 @@ section, err := cfg.GetSection("")
 section := cfg.Section("")
 ```
 
-如果不小心判断错了,要获取的分区其实是不存在的,那会发生什么呢?没事的,它会返回一个空的分区对象
+如果不小心判断错了,要获取的分区其实是不存在的,那会发生什么呢?没事的,它会自动创建并返回一个对应的分区对象给您
 
 创建一个分区:
 
@@ -112,6 +118,9 @@ val := cfg.Section("").Key("key name").String()
 获取其它类型的值:
 
 ```go
+// 布尔值的规则:
+// true 当值为:1, t, T, TRUE, true, True, YES, yes, Yes, ON, on, On
+// false 当值为:0, f, F, FALSE, false, False, NO, no, No, OFF, off, Off
 v, err = cfg.Section("").Key("BOOL").Bool()
 v, err = cfg.Section("").Key("FLOAT64").Float64()
 v, err = cfg.Section("").Key("INT").Int()
@@ -171,11 +180,22 @@ v = cfg.Section("").Key("STRING").In("default", []string{"str", "arr", "types"})
 v = cfg.Section("").Key("FLOAT64").InFloat64(1.1, []float64{1.25, 2.5, 3.75})
 v = cfg.Section("").Key("INT").InInt(5, []int{10, 20, 30})
 v = cfg.Section("").Key("INT64").InInt64(10, []int64{10, 20, 30})
-v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3})
+v = cfg.Section("").Key("TIME").InTimeFormat(time.RFC3339, time.Now(), []time.Time{time1, time2, time3})
+v = cfg.Section("").Key("TIME").InTime(time.Now(), []time.Time{time1, time2, time3}) // RFC3339
 ```
 
 如果获取到的值不是候选值的任意一个,则会返回默认值,而默认值不需要是候选值中的一员。
 
+验证获取的值是否在指定范围内:
+
+```go
+vals = cfg.Section("").Key("FLOAT64").RangeFloat64(0.0, 1.1, 2.2)
+vals = cfg.Section("").Key("INT").RangeInt(0, 10, 20)
+vals = cfg.Section("").Key("INT64").RangeInt64(0, 10, 20)
+vals = cfg.Section("").Key("TIME").RangeTimeFormat(time.RFC3339, time.Now(), minTime, maxTime)
+vals = cfg.Section("").Key("TIME").RangeTime(time.Now(), minTime, maxTime) // RFC3339
+```
+
 自动分割键值为切片(slice):
 
 ```go
@@ -290,6 +310,16 @@ func main() {
 }
 ```
 
+结构的字段怎么设置默认值呢?很简单,只要在映射之前对指定字段进行赋值就可以了。如果键未找到或者类型错误,该值不会发生改变。
+
+```go
+// ...
+p := &Person{
+	Name: "Joe",
+}
+// ...
+```
+
 #### 名称映射器(Name Mapper)
 
 为了节省您的时间并简化代码,本库支持类型为 [`NameMapper`](https://gowalker.org/gopkg.in/ini.v1#NameMapper) 的名称映射器,该映射器负责结构字段名与分区名和键名之间的映射。

+ 99 - 7
Godeps/_workspace/src/gopkg.in/ini.v1/ini.go

@@ -35,7 +35,7 @@ const (
 	// Maximum allowed depth when recursively substituing variable names.
 	_DEPTH_VALUES = 99
 
-	_VERSION = "1.0.1"
+	_VERSION = "1.2.6"
 )
 
 func Version() string {
@@ -144,9 +144,24 @@ func (k *Key) String() string {
 	return val
 }
 
+// parseBool returns the boolean value represented by the string.
+//
+// It accepts 1, t, T, TRUE, true, True, YES, yes, Yes, ON, on, On,
+// 0, f, F, FALSE, false, False, NO, no, No, OFF, off, Off.
+// Any other value returns an error.
+func parseBool(str string) (value bool, err error) {
+	switch str {
+	case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "ON", "on", "On":
+		return true, nil
+	case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "OFF", "off", "Off":
+		return false, nil
+	}
+	return false, fmt.Errorf("parsing \"%s\": invalid syntax", str)
+}
+
 // Bool returns bool type value.
 func (k *Key) Bool() (bool, error) {
-	return strconv.ParseBool(k.String())
+	return parseBool(k.String())
 }
 
 // Float64 returns float64 type value.
@@ -305,6 +320,52 @@ func (k *Key) InTime(defaultVal time.Time, candidates []time.Time) time.Time {
 	return k.InTimeFormat(time.RFC3339, defaultVal, candidates)
 }
 
+// RangeFloat64 checks if value is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeFloat64(defaultVal, min, max float64) float64 {
+	val := k.MustFloat64()
+	if val < min || val > max {
+		return defaultVal
+	}
+	return val
+}
+
+// RangeInt checks if value is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeInt(defaultVal, min, max int) int {
+	val := k.MustInt()
+	if val < min || val > max {
+		return defaultVal
+	}
+	return val
+}
+
+// RangeInt64 checks if value is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeInt64(defaultVal, min, max int64) int64 {
+	val := k.MustInt64()
+	if val < min || val > max {
+		return defaultVal
+	}
+	return val
+}
+
+// RangeTimeFormat checks if value with given format is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeTimeFormat(format string, defaultVal, min, max time.Time) time.Time {
+	val := k.MustTimeFormat(format)
+	if val.Unix() < min.Unix() || val.Unix() > max.Unix() {
+		return defaultVal
+	}
+	return val
+}
+
+// RangeTime checks if value with RFC3339 format is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeTime(defaultVal, min, max time.Time) time.Time {
+	return k.RangeTimeFormat(time.RFC3339, defaultVal, min, max)
+}
+
 // Strings returns list of string devide by given delimiter.
 func (k *Key) Strings(delim string) []string {
 	str := k.String()
@@ -440,7 +501,10 @@ func (s *Section) GetKey(name string) (*Key, error) {
 func (s *Section) Key(name string) *Key {
 	key, err := s.GetKey(name)
 	if err != nil {
-		return &Key{}
+		// It's OK here because the only possible error is empty key name,
+		// but if it's empty, this piece of code won't be executed.
+		key, _ = s.NewKey(name, "")
+		return key
 	}
 	return key
 }
@@ -555,6 +619,13 @@ func Load(source interface{}, others ...interface{}) (_ *File, err error) {
 	return f, f.Reload()
 }
 
+// Empty returns an empty file object.
+func Empty() *File {
+	// Ignore error here, we sure our data is good.
+	f, _ := Load([]byte(""))
+	return f
+}
+
 // NewSection creates a new section.
 func (f *File) NewSection(name string) (*Section, error) {
 	if len(name) == 0 {
@@ -575,6 +646,16 @@ func (f *File) NewSection(name string) (*Section, error) {
 	return f.sections[name], nil
 }
 
+// NewSections creates a list of sections.
+func (f *File) NewSections(names ...string) (err error) {
+	for _, name := range names {
+		if _, err = f.NewSection(name); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
 // GetSection returns section by given name.
 func (f *File) GetSection(name string) (*Section, error) {
 	if len(name) == 0 {
@@ -597,7 +678,10 @@ func (f *File) GetSection(name string) (*Section, error) {
 func (f *File) Section(name string) *Section {
 	sec, err := f.GetSection(name)
 	if err != nil {
-		return newSection(f, name)
+		// It's OK here because the only possible error is empty section name,
+		// but if it's empty, this piece of code won't be executed.
+		sec, _ = f.NewSection(name)
+		return sec
 	}
 	return sec
 }
@@ -638,6 +722,14 @@ func (f *File) DeleteSection(name string) {
 	}
 }
 
+func cutComment(str string) string {
+	i := strings.Index(str, "#")
+	if i == -1 {
+		return str
+	}
+	return str[:i]
+}
+
 // parse parses data through an io.Reader.
 func (f *File) parse(reader io.Reader) error {
 	buf := bufio.NewReader(reader)
@@ -776,7 +868,6 @@ func (f *File) parse(reader io.Reader) error {
 				val = lineRight[qLen:] + "\n"
 				for {
 					next, err := buf.ReadString('\n')
-					val += next
 					if err != nil {
 						if err != io.EOF {
 							return err
@@ -785,9 +876,10 @@ func (f *File) parse(reader io.Reader) error {
 					}
 					pos = strings.LastIndex(next, valQuote)
 					if pos > -1 {
-						val = val[:len(val)-len(valQuote)-1]
+						val += next[:pos]
 						break
 					}
+					val += next
 					if isEnd {
 						return fmt.Errorf("error parsing line: missing closing key quote from '%s' to '%s'", line, next)
 					}
@@ -796,7 +888,7 @@ func (f *File) parse(reader io.Reader) error {
 				val = lineRight[qLen : pos+qLen]
 			}
 		} else {
-			val = strings.TrimSpace(lineRight[0:])
+			val = strings.TrimSpace(cutComment(lineRight[0:]))
 		}
 
 		k, err := section.NewKey(kname, val)

+ 45 - 6
Godeps/_workspace/src/gopkg.in/ini.v1/ini_test.go

@@ -40,13 +40,13 @@ IMPORT_PATH = gopkg.in/%(NAME)s.%(VERSION)s
 # Information about package author
 # Bio can be written in multiple lines.
 [author]
-NAME = Unknwon
+NAME = Unknwon  # Succeeding comment
 E-MAIL = fake@localhost
 GITHUB = https://github.com/%(NAME)s
 BIO = """Gopher.
 Coding addict.
 Good man.
-"""
+"""  # Succeeding comment
 
 [package]
 CLONE_URL = https://%(IMPORT_PATH)s
@@ -62,6 +62,7 @@ UNUSED_KEY = should be deleted
 [types]
 STRING = str
 BOOL = true
+BOOL_FALSE = false
 FLOAT64 = 1.25
 INT = 10
 TIME = 2015-01-01T20:17:05Z
@@ -88,9 +89,7 @@ func Test_Load(t *testing.T) {
 	Convey("Load from data sources", t, func() {
 
 		Convey("Load with empty data", func() {
-			cfg, err := Load([]byte(""))
-			So(err, ShouldBeNil)
-			So(cfg, ShouldNotBeNil)
+			So(Empty(), ShouldNotBeNil)
 		})
 
 		Convey("Load with multiple data sources", func() {
@@ -203,6 +202,10 @@ func Test_Values(t *testing.T) {
 			So(err, ShouldBeNil)
 			So(v1, ShouldBeTrue)
 
+			v1, err = sec.Key("BOOL_FALSE").Bool()
+			So(err, ShouldBeNil)
+			So(v1, ShouldBeFalse)
+
 			v2, err := sec.Key("FLOAT64").Float64()
 			So(err, ShouldBeNil)
 			So(v2, ShouldEqual, 1.25)
@@ -265,6 +268,30 @@ func Test_Values(t *testing.T) {
 			})
 		})
 
+		Convey("Get values in range", func() {
+			sec := cfg.Section("types")
+			So(sec.Key("FLOAT64").RangeFloat64(0, 1, 2), ShouldEqual, 1.25)
+			So(sec.Key("INT").RangeInt(0, 10, 20), ShouldEqual, 10)
+			So(sec.Key("INT").RangeInt64(0, 10, 20), ShouldEqual, 10)
+
+			minT, err := time.Parse(time.RFC3339, "0001-01-01T01:00:00Z")
+			So(err, ShouldBeNil)
+			midT, err := time.Parse(time.RFC3339, "2013-01-01T01:00:00Z")
+			So(err, ShouldBeNil)
+			maxT, err := time.Parse(time.RFC3339, "9999-01-01T01:00:00Z")
+			So(err, ShouldBeNil)
+			t, err := time.Parse(time.RFC3339, "2015-01-01T20:17:05Z")
+			So(err, ShouldBeNil)
+			So(sec.Key("TIME").RangeTime(t, minT, maxT).String(), ShouldEqual, t.String())
+
+			Convey("Get value in range with default value", func() {
+				So(sec.Key("FLOAT64").RangeFloat64(5, 0, 1), ShouldEqual, 5)
+				So(sec.Key("INT").RangeInt(7, 0, 5), ShouldEqual, 7)
+				So(sec.Key("INT").RangeInt64(7, 0, 5), ShouldEqual, 7)
+				So(sec.Key("TIME").RangeTime(t, minT, midT).String(), ShouldEqual, t.String())
+			})
+		})
+
 		Convey("Get values into slice", func() {
 			sec := cfg.Section("array")
 			So(strings.Join(sec.Key("STRINGS").Strings(","), ","), ShouldEqual, "en,zh,de")
@@ -304,7 +331,7 @@ func Test_Values(t *testing.T) {
 		})
 
 		Convey("Get key strings", func() {
-			So(strings.Join(cfg.Section("types").KeyStrings(), ","), ShouldEqual, "STRING,BOOL,FLOAT64,INT,TIME")
+			So(strings.Join(cfg.Section("types").KeyStrings(), ","), ShouldEqual, "STRING,BOOL,BOOL_FALSE,FLOAT64,INT,TIME")
 		})
 
 		Convey("Delete a key", func() {
@@ -321,6 +348,14 @@ func Test_Values(t *testing.T) {
 			cfg.DeleteSection("")
 			So(cfg.SectionStrings()[0], ShouldNotEqual, DEFAULT_SECTION)
 		})
+
+		Convey("Create new sections", func() {
+			cfg.NewSections("test", "test2")
+			_, err := cfg.GetSection("test")
+			So(err, ShouldBeNil)
+			_, err = cfg.GetSection("test2")
+			So(err, ShouldBeNil)
+		})
 	})
 
 	Convey("Test getting and setting bad values", t, func() {
@@ -340,6 +375,10 @@ func Test_Values(t *testing.T) {
 			So(s, ShouldBeNil)
 		})
 
+		Convey("Create new sections with empty name", func() {
+			So(cfg.NewSections(""), ShouldNotBeNil)
+		})
+
 		Convey("Get section that not exists", func() {
 			s, err := cfg.GetSection("404")
 			So(err, ShouldNotBeNil)

+ 12 - 6
Godeps/_workspace/src/gopkg.in/ini.v1/struct.go

@@ -29,7 +29,7 @@ type NameMapper func(string) string
 var (
 	// AllCapsUnderscore converts to format ALL_CAPS_UNDERSCORE.
 	AllCapsUnderscore NameMapper = func(raw string) string {
-		newstr := make([]rune, 0, 10)
+		newstr := make([]rune, 0, len(raw))
 		for i, chr := range raw {
 			if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
 				if i > 0 {
@@ -42,7 +42,7 @@ var (
 	}
 	// TitleUnderscore converts to format title_underscore.
 	TitleUnderscore NameMapper = func(raw string) string {
-		newstr := make([]rune, 0, 10)
+		newstr := make([]rune, 0, len(raw))
 		for i, chr := range raw {
 			if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
 				if i > 0 {
@@ -75,32 +75,38 @@ func parseDelim(actual string) string {
 
 var reflectTime = reflect.TypeOf(time.Now()).Kind()
 
+// setWithProperType sets proper value to field based on its type,
+// but it does not return error for failing parsing,
+// because we want to use default value that is already assigned to strcut.
 func setWithProperType(kind reflect.Kind, key *Key, field reflect.Value, delim string) error {
 	switch kind {
 	case reflect.String:
+		if len(key.String()) == 0 {
+			return nil
+		}
 		field.SetString(key.String())
 	case reflect.Bool:
 		boolVal, err := key.Bool()
 		if err != nil {
-			return err
+			return nil
 		}
 		field.SetBool(boolVal)
 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
 		intVal, err := key.Int64()
 		if err != nil {
-			return err
+			return nil
 		}
 		field.SetInt(intVal)
 	case reflect.Float64:
 		floatVal, err := key.Float64()
 		if err != nil {
-			return err
+			return nil
 		}
 		field.SetFloat(floatVal)
 	case reflectTime:
 		timeVal, err := key.Time()
 		if err != nil {
-			return err
+			return nil
 		}
 		field.Set(reflect.ValueOf(timeVal))
 	case reflect.Slice:

+ 21 - 23
Godeps/_workspace/src/gopkg.in/ini.v1/struct_test.go

@@ -78,27 +78,17 @@ type unsupport4 struct {
 	*unsupport3 `ini:"Others"`
 }
 
-type invalidInt struct {
-	Age int
-}
-
-type invalidBool struct {
-	Male bool
-}
-
-type invalidFloat struct {
-	Money float64
-}
-
-type invalidTime struct {
-	Born time.Time
-}
-
-type emptySlice struct {
+type defaultValue struct {
+	Name   string
+	Age    int
+	Male   bool
+	Money  float64
+	Born   time.Time
 	Cities []string
 }
 
 const _INVALID_DATA_CONF_STRUCT = `
+Name = 
 Age = age
 Male = 123
 Money = money
@@ -154,12 +144,20 @@ func Test_Struct(t *testing.T) {
 		So(MapTo(&testStruct{}, "hi"), ShouldNotBeNil)
 	})
 
-	Convey("Map to wrong types", t, func() {
-		So(MapTo(&invalidInt{}, []byte(_INVALID_DATA_CONF_STRUCT)), ShouldNotBeNil)
-		So(MapTo(&invalidBool{}, []byte(_INVALID_DATA_CONF_STRUCT)), ShouldNotBeNil)
-		So(MapTo(&invalidFloat{}, []byte(_INVALID_DATA_CONF_STRUCT)), ShouldNotBeNil)
-		So(MapTo(&invalidTime{}, []byte(_INVALID_DATA_CONF_STRUCT)), ShouldNotBeNil)
-		So(MapTo(&emptySlice{}, []byte(_INVALID_DATA_CONF_STRUCT)), ShouldBeNil)
+	Convey("Map to wrong types and gain default values", t, func() {
+		cfg, err := Load([]byte(_INVALID_DATA_CONF_STRUCT))
+		So(err, ShouldBeNil)
+
+		t, err := time.Parse(time.RFC3339, "1993-10-07T20:17:05Z")
+		So(err, ShouldBeNil)
+		dv := &defaultValue{"Joe", 10, true, 1.25, t, []string{"HangZhou", "Boston"}}
+		So(cfg.MapTo(dv), ShouldBeNil)
+		So(dv.Name, ShouldEqual, "Joe")
+		So(dv.Age, ShouldEqual, 10)
+		So(dv.Male, ShouldBeTrue)
+		So(dv.Money, ShouldEqual, 1.25)
+		So(dv.Born.String(), ShouldEqual, t.String())
+		So(strings.Join(dv.Cities, ","), ShouldEqual, "HangZhou,Boston")
 	})
 }
 

+ 4 - 0
pkg/cmd/web.go

@@ -11,6 +11,7 @@ import (
 	"path"
 	"path/filepath"
 	"strconv"
+	"time"
 
 	"github.com/Unknwon/macaron"
 	"github.com/codegangsta/cli"
@@ -68,6 +69,9 @@ func mapStatic(m *macaron.Macaron, dir string, prefix string) {
 		macaron.StaticOptions{
 			SkipLogging: true,
 			Prefix:      prefix,
+			Expires: func() string {
+				return time.Now().UTC().Format(http.TimeFormat)
+			},
 		},
 	))
 }