diff --git a/CHANGELOG.md b/CHANGELOG.md
index 23cc05810eb..0ade6b78d33 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,7 @@
- [Issue #171](https://github.com/grafana/grafana/issues/171). Panel: Different time periods, panels can override dashboard relative time and/or add a time shift
- [Issue #1488](https://github.com/grafana/grafana/issues/1488). Dashboard: Clone dashboard / Save as
- [Issue #1458](https://github.com/grafana/grafana/issues/1458). User: persisted user option for dark or light theme (no longer an option on a dashboard)
+- [Issue #452](https://github.com/grafana/grafana/issues/452). Graph: Adds logarithmic scale option (log base 10)
**Enhancements**
- [Issue #1366](https://github.com/grafana/grafana/issues/1366). Graph & Singlestat: Support for additional units, Fahrenheit (°F) and Celsius (°C), Humidity (%H), kW, watt-hour (Wh), kilowatt-hour (kWh), velocities (m/s, km/h, mpg, knot)
diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json
index 323c3acfa79..79b49db2f0a 100644
--- a/Godeps/Godeps.json
+++ b/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"
}
]
}
diff --git a/Godeps/_workspace/src/github.com/Unknwon/macaron/README.md b/Godeps/_workspace/src/github.com/Unknwon/macaron/README.md
index 551790a7f6a..8b201624f61 100644
--- a/Godeps/_workspace/src/github.com/Unknwon/macaron/README.md
+++ b/Godeps/_workspace/src/github.com/Unknwon/macaron/README.md
@@ -1,11 +1,11 @@
Macaron [](https://drone.io/github.com/Unknwon/macaron/latest) [](http://gocover.io/github.com/Unknwon/macaron)
=======================
-
+
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)
+- [](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.
\ No newline at end of file
+This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.
diff --git a/Godeps/_workspace/src/github.com/Unknwon/macaron/bpool/README.md b/Godeps/_workspace/src/github.com/Unknwon/macaron/bpool/README.md
deleted file mode 100644
index b51601c90b2..00000000000
--- a/Godeps/_workspace/src/github.com/Unknwon/macaron/bpool/README.md
+++ /dev/null
@@ -1,37 +0,0 @@
-# bpool [](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
-}
-```
diff --git a/Godeps/_workspace/src/github.com/Unknwon/macaron/bpool/bufferpool.go b/Godeps/_workspace/src/github.com/Unknwon/macaron/bpool/bufferpool.go
deleted file mode 100644
index 93981b1907d..00000000000
--- a/Godeps/_workspace/src/github.com/Unknwon/macaron/bpool/bufferpool.go
+++ /dev/null
@@ -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
-}
diff --git a/Godeps/_workspace/src/github.com/Unknwon/macaron/context.go b/Godeps/_workspace/src/github.com/Unknwon/macaron/context.go
index fdaaf9d6775..abf7ac90257 100644
--- a/Godeps/_workspace/src/github.com/Unknwon/macaron/context.go
+++ b/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
diff --git a/Godeps/_workspace/src/github.com/Unknwon/macaron/context_test.go b/Godeps/_workspace/src/github.com/Unknwon/macaron/context_test.go
index e5d8dd2dc02..c4b4752e12f 100644
--- a/Godeps/_workspace/src/github.com/Unknwon/macaron/context_test.go
+++ b/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, "
Hello Unknwon
")
})
- resp := httptest.NewRecorder()
- req, err := http.NewRequest("GET", "/html", nil)
- So(err, ShouldBeNil)
- m.ServeHTTP(resp, req)
- So(resp.Body.String(), ShouldEqual, "Hello Unknwon
")
+ 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, "Hello Unknwon
")
})
- resp = httptest.NewRecorder()
- req, err = http.NewRequest("GET", "/html2", nil)
- So(err, ShouldBeNil)
- m.ServeHTTP(resp, req)
- So(resp.Body.String(), ShouldEqual, "Hello Unknwon
")
+ 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, "headHello Unknwon
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!")))
diff --git a/Godeps/_workspace/src/github.com/Unknwon/macaron/inject/inject_test.go b/Godeps/_workspace/src/github.com/Unknwon/macaron/inject/inject_test.go
index a6d85ef5483..748572ac2bf 100644
--- a/Godeps/_workspace/src/github.com/Unknwon/macaron/inject/inject_test.go
+++ b/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
diff --git a/Godeps/_workspace/src/github.com/Unknwon/macaron/logger_test.go b/Godeps/_workspace/src/github.com/Unknwon/macaron/logger_test.go
index e4951dc28cc..c81f70237f9 100644
--- a/Godeps/_workspace/src/github.com/Unknwon/macaron/logger_test.go
+++ b/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) {
diff --git a/Godeps/_workspace/src/github.com/Unknwon/macaron/macaron.go b/Godeps/_workspace/src/github.com/Unknwon/macaron/macaron.go
index 414e8e80b65..adbe9e3692d 100644
--- a/Godeps/_workspace/src/github.com/Unknwon/macaron/macaron.go
+++ b/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
}
diff --git a/Godeps/_workspace/src/github.com/Unknwon/macaron/render.go b/Godeps/_workspace/src/github.com/Unknwon/macaron/render.go
index bc13888dcc6..b0558c95742 100644
--- a/Godeps/_workspace/src/github.com/Unknwon/macaron/render.go
+++ b/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 {
diff --git a/Godeps/_workspace/src/github.com/Unknwon/macaron/return_handler.go b/Godeps/_workspace/src/github.com/Unknwon/macaron/return_handler.go
index 8a2c5063e2d..ea1e0447646 100644
--- a/Godeps/_workspace/src/github.com/Unknwon/macaron/return_handler.go
+++ b/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 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
- if len(vals) > 1 && vals[0].Kind() == reflect.Int {
- res.WriteHeader(int(vals[0].Int()))
- responseVal = vals[1]
- } else if len(vals) > 0 {
- responseVal = vals[0]
- }
- if canDeref(responseVal) {
- responseVal = responseVal.Elem()
- }
- if isByteSlice(responseVal) {
- res.Write(responseVal.Bytes())
- } else {
- res.Write([]byte(responseVal.String()))
- }
- }
+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 canDeref(val reflect.Value) bool {
- return val.Kind() == reflect.Interface || val.Kind() == reflect.Ptr
+func defaultReturnHandler() ReturnHandler {
+ return func(ctx *Context, vals []reflect.Value) {
+ rv := ctx.GetVal(inject.InterfaceOf((*http.ResponseWriter)(nil)))
+ res := rv.Interface().(http.ResponseWriter)
+ var respVal reflect.Value
+ if len(vals) > 1 && vals[0].Kind() == reflect.Int {
+ res.WriteHeader(int(vals[0].Int()))
+ respVal = vals[1]
+ } else if len(vals) > 0 {
+ respVal = vals[0]
+ }
+ if canDeref(respVal) {
+ respVal = respVal.Elem()
+ }
+ if isByteSlice(respVal) {
+ res.Write(respVal.Bytes())
+ } else {
+ res.Write([]byte(respVal.String()))
+ }
+ }
}
diff --git a/Godeps/_workspace/src/github.com/Unknwon/macaron/return_handler_test.go b/Godeps/_workspace/src/github.com/Unknwon/macaron/return_handler_test.go
index 27cc9d953f0..02325b218b0 100644
--- a/Godeps/_workspace/src/github.com/Unknwon/macaron/return_handler_test.go
+++ b/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
diff --git a/Godeps/_workspace/src/gopkg.in/ini.v1/.gitignore b/Godeps/_workspace/src/gopkg.in/ini.v1/.gitignore
index 610ecf3242c..53b64215a3b 100644
--- a/Godeps/_workspace/src/gopkg.in/ini.v1/.gitignore
+++ b/Godeps/_workspace/src/gopkg.in/ini.v1/.gitignore
@@ -1 +1,3 @@
-testdata/conf_out.ini
\ No newline at end of file
+testdata/conf_out.ini
+ini.sublime-project
+ini.sublime-workspace
diff --git a/Godeps/_workspace/src/gopkg.in/ini.v1/README.md b/Godeps/_workspace/src/gopkg.in/ini.v1/README.md
index 626a6ae5c53..6d771819656 100644
--- a/Godeps/_workspace/src/gopkg.in/ini.v1/README.md
+++ b/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.
diff --git a/Godeps/_workspace/src/gopkg.in/ini.v1/README_ZH.md b/Godeps/_workspace/src/gopkg.in/ini.v1/README_ZH.md
index 2dc38df4a42..c455cb67297 100644
--- a/Godeps/_workspace/src/gopkg.in/ini.v1/README_ZH.md
+++ b/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) 的名称映射器,该映射器负责结构字段名与分区名和键名之间的映射。
diff --git a/Godeps/_workspace/src/gopkg.in/ini.v1/ini.go b/Godeps/_workspace/src/gopkg.in/ini.v1/ini.go
index be45f25e4ac..6674baf0b00 100644
--- a/Godeps/_workspace/src/gopkg.in/ini.v1/ini.go
+++ b/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)
diff --git a/Godeps/_workspace/src/gopkg.in/ini.v1/ini_test.go b/Godeps/_workspace/src/gopkg.in/ini.v1/ini_test.go
index aafe91826e5..c6daf81ca52 100644
--- a/Godeps/_workspace/src/gopkg.in/ini.v1/ini_test.go
+++ b/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)
diff --git a/Godeps/_workspace/src/gopkg.in/ini.v1/struct.go b/Godeps/_workspace/src/gopkg.in/ini.v1/struct.go
index 1875faf7695..09ea816442b 100644
--- a/Godeps/_workspace/src/gopkg.in/ini.v1/struct.go
+++ b/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:
diff --git a/Godeps/_workspace/src/gopkg.in/ini.v1/struct_test.go b/Godeps/_workspace/src/gopkg.in/ini.v1/struct_test.go
index 97cf7452705..f6fad196ae0 100644
--- a/Godeps/_workspace/src/gopkg.in/ini.v1/struct_test.go
+++ b/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")
})
}
diff --git a/pkg/cmd/web.go b/pkg/cmd/web.go
index d8f8295d874..6619e5b1e0e 100644
--- a/pkg/cmd/web.go
+++ b/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)
+ },
},
))
}
diff --git a/pkg/services/sqlstore/migrations/apikey_mig.go b/pkg/services/sqlstore/migrations/apikey_mig.go
index e25b57c77ef..a02e759f551 100644
--- a/pkg/services/sqlstore/migrations/apikey_mig.go
+++ b/pkg/services/sqlstore/migrations/apikey_mig.go
@@ -42,7 +42,7 @@ func addApiKeyMigrations(mg *Migrator) {
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "org_id", Type: DB_BigInt, Nullable: false},
{Name: "name", Type: DB_NVarchar, Length: 255, Nullable: false},
- {Name: "key", Type: DB_Varchar, Length: 64, Nullable: false},
+ {Name: "key", Type: DB_Varchar, Length: 255, Nullable: false},
{Name: "role", Type: DB_NVarchar, Length: 255, Nullable: false},
{Name: "created", Type: DB_DateTime, Nullable: false},
{Name: "updated", Type: DB_DateTime, Nullable: false},
diff --git a/src/app/features/panel/panelDirective.js b/src/app/features/panel/panelDirective.js
index f8048d53637..7330bb627de 100644
--- a/src/app/features/panel/panelDirective.js
+++ b/src/app/features/panel/panelDirective.js
@@ -30,8 +30,8 @@ function (angular, $, config) {
link: function(scope, elem) {
var panelContainer = elem.find('.panel-container');
- scope.$watchGroup(['fullscreen', 'panel.height', 'row.height'], function() {
- panelContainer.css({ minHeight: scope.panel.height || scope.row.height, display: 'block' });
+ scope.$watchGroup(['fullscreen', 'height', 'panel.height', 'row.height'], function() {
+ panelContainer.css({ minHeight: scope.height || scope.panel.height || scope.row.height, display: 'block' });
elem.toggleClass('panel-fullscreen', scope.fullscreen ? true : false);
});
}
diff --git a/src/app/panels/graph/axisEditor.html b/src/app/panels/graph/axisEditor.html
index edf61a04cd3..40391995fc4 100644
--- a/src/app/panels/graph/axisEditor.html
+++ b/src/app/panels/graph/axisEditor.html
@@ -30,6 +30,12 @@
empty-to-null ng-model="panel.grid.leftMin"
ng-change="render()" ng-model-onblur>
+
+ Scale type
+
+
+
+
Label
@@ -69,6 +75,12 @@
empty-to-null ng-model="panel.grid.rightMin"
ng-change="render()" ng-model-onblur>
+
+ Scale type
+
+
+
+
Label
diff --git a/src/app/panels/graph/graph.js b/src/app/panels/graph/graph.js
index 967991e5da8..5e2066466ca 100755
--- a/src/app/panels/graph/graph.js
+++ b/src/app/panels/graph/graph.js
@@ -27,6 +27,7 @@ function (angular, $, kbn, moment, _, GraphTooltip) {
var dashboard = scope.dashboard;
var data, annotations;
var sortedSeries;
+ var graphHeight;
var legendSideLastValue = null;
scope.crosshairEmiter = false;
@@ -64,19 +65,19 @@ function (angular, $, kbn, moment, _, GraphTooltip) {
function setElementHeight() {
try {
- var height = scope.height || scope.panel.height || scope.row.height;
- if (_.isString(height)) {
- height = parseInt(height.replace('px', ''), 10);
+ graphHeight = scope.height || scope.panel.height || scope.row.height;
+ if (_.isString(graphHeight)) {
+ graphHeight = parseInt(graphHeight.replace('px', ''), 10);
}
- height -= 5; // padding
- height -= scope.panel.title ? 24 : 9; // subtract panel title bar
+ graphHeight -= 5; // padding
+ graphHeight -= scope.panel.title ? 24 : 9; // subtract panel title bar
if (scope.panel.legend.show && !scope.panel.legend.rightSide) {
- height = height - 26; // subtract one line legend
+ graphHeight = graphHeight - 26; // subtract one line legend
}
- elem.css('height', height + 'px');
+ elem.css('height', graphHeight + 'px');
return true;
} catch(e) { // IE throws errors sometimes
@@ -349,6 +350,8 @@ function (angular, $, kbn, moment, _, GraphTooltip) {
position: 'left',
show: scope.panel['y-axis'],
min: scope.panel.grid.leftMin,
+ index: 1,
+ scale: scope.panel.grid.leftScale,
max: scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.leftMax,
};
@@ -356,16 +359,59 @@ function (angular, $, kbn, moment, _, GraphTooltip) {
if (_.findWhere(data, {yaxis: 2})) {
var secondY = _.clone(defaults);
+ secondY.index = 2,
+ secondY.scale = scope.panel.grid.rightScale;
secondY.position = 'right';
secondY.min = scope.panel.grid.rightMin;
secondY.max = scope.panel.percentage && scope.panel.stack ? 100 : scope.panel.grid.rightMax;
options.yaxes.push(secondY);
+
+ applyLogScale(options.yaxes[1], data);
configureAxisMode(options.yaxes[1], scope.panel.y_formats[1]);
}
+ applyLogScale(options.yaxes[0], data);
configureAxisMode(options.yaxes[0], scope.panel.y_formats[0]);
}
+ function applyLogScale(axis, data) {
+ if (axis.scale !== 2) {
+ return;
+ }
+
+ var series, i;
+ var max = axis.max;
+
+ if (max === null) {
+ for (i = 0; i < data.length; i++) {
+ series = data[i];
+ if (series.yaxis === axis.index) {
+ if (max < series.stats.max) {
+ max = series.stats.max;
+ }
+ }
+ }
+
+ if (max === null) {
+ max = 10000000000;
+ }
+ }
+
+ axis.ticks = [0, 1];
+ var tick = 1;
+
+ while (true) {
+ tick = tick*10;
+ axis.ticks.push(tick);
+ if (tick > max) {
+ break;
+ }
+ }
+
+ axis.transform = function(v) { return Math.log(v+0.1); };
+ axis.inverseTransform = function (v) { return Math.pow(10,v); };
+ }
+
function configureAxisMode(axis, format) {
axis.tickFormatter = function(val, axis) {
return kbn.valueFormats[format](val, axis.tickDecimals, axis.scaledDecimals);
@@ -411,44 +457,44 @@ function (angular, $, kbn, moment, _, GraphTooltip) {
url += scope.panel['y-axis'] ? '' : '&hideYAxis=true';
switch(scope.panel.y_formats[0]) {
- case 'bytes':
- url += '&yUnitSystem=binary';
- break;
- case 'bits':
- url += '&yUnitSystem=binary';
- break;
- case 'bps':
- url += '&yUnitSystem=si';
- break;
- case 'Bps':
- url += '&yUnitSystem=si';
- break;
- case 'short':
- url += '&yUnitSystem=si';
- break;
- case 'joule':
- url += '&yUnitSystem=si';
- break;
- case 'watt':
- url += '&yUnitSystem=si';
- break;
- case 'ev':
- url += '&yUnitSystem=si';
- break;
- case 'none':
- url += '&yUnitSystem=none';
- break;
+ case 'bytes':
+ url += '&yUnitSystem=binary';
+ break;
+ case 'bits':
+ url += '&yUnitSystem=binary';
+ break;
+ case 'bps':
+ url += '&yUnitSystem=si';
+ break;
+ case 'Bps':
+ url += '&yUnitSystem=si';
+ break;
+ case 'short':
+ url += '&yUnitSystem=si';
+ break;
+ case 'joule':
+ url += '&yUnitSystem=si';
+ break;
+ case 'watt':
+ url += '&yUnitSystem=si';
+ break;
+ case 'ev':
+ url += '&yUnitSystem=si';
+ break;
+ case 'none':
+ url += '&yUnitSystem=none';
+ break;
}
switch(scope.panel.nullPointMode) {
- case 'connected':
- url += '&lineMode=connected';
- break;
- case 'null':
- break; // graphite default lineMode
- case 'null as zero':
- url += "&drawNullAsZero=true";
- break;
+ case 'connected':
+ url += '&lineMode=connected';
+ break;
+ case 'null':
+ break; // graphite default lineMode
+ case 'null as zero':
+ url += "&drawNullAsZero=true";
+ break;
}
url += scope.panel.steppedLine ? '&lineMode=staircase' : '';
diff --git a/src/app/panels/graph/graph.tooltip.js b/src/app/panels/graph/graph.tooltip.js
index d4690bea96d..724588c4fd5 100644
--- a/src/app/panels/graph/graph.tooltip.js
+++ b/src/app/panels/graph/graph.tooltip.js
@@ -99,7 +99,7 @@ function ($) {
var group, value, timestamp, hoverInfo, i, series, seriesHtml;
if(dashboard.sharedCrosshair){
- scope.appEvent('setCrosshair', { pos: pos, scope: scope });
+ scope.appEvent('setCrosshair', { pos: pos, scope: scope });
}
if (seriesList.length === 0) {
diff --git a/src/app/panels/graph/module.js b/src/app/panels/graph/module.js
index d14d30fd300..485e7593b73 100644
--- a/src/app/panels/graph/module.js
+++ b/src/app/panels/graph/module.js
@@ -53,10 +53,12 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) {
y_formats : ['short', 'short'],
// grid options
grid : {
+ leftScale: 1,
leftMax: null,
rightMax: null,
leftMin: null,
rightMin: null,
+ rightScale: 1,
threshold1: null,
threshold2: null,
threshold1Color: 'rgba(216, 200, 27, 0.27)',
@@ -95,7 +97,7 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) {
// tooltip options
tooltip : {
value_type: 'cumulative',
- shared: false,
+ shared: true,
},
// time overrides
timeFrom: null,
@@ -114,6 +116,8 @@ function (angular, app, $, _, kbn, moment, TimeSeries, PanelMeta) {
_.defaults($scope.panel.grid, _d.grid);
_.defaults($scope.panel.legend, _d.legend);
+ $scope.scaleTypes = {'linear': 1, 'log (base 10)': 2};
+
$scope.hiddenSeries = {};
$scope.seriesList = [];
$scope.unitFormats = kbn.getUnitFormats();
diff --git a/src/app/partials/dasheditor.html b/src/app/partials/dasheditor.html
index a9de9af2acb..e6ab6070f9f 100644
--- a/src/app/partials/dasheditor.html
+++ b/src/app/partials/dasheditor.html
@@ -31,7 +31,6 @@
-
diff --git a/src/css/less/grafana.less b/src/css/less/grafana.less
index b6c127c5319..579415f17f8 100644
--- a/src/css/less/grafana.less
+++ b/src/css/less/grafana.less
@@ -62,6 +62,7 @@
.main-view-container {
overflow: hidden;
height: 0;
+ padding: 0;
.row-control-inner {
display: none;
}
diff --git a/src/css/less/variables.dark.less b/src/css/less/variables.dark.less
index 6aab758ad82..9a7613ff311 100644
--- a/src/css/less/variables.dark.less
+++ b/src/css/less/variables.dark.less
@@ -28,7 +28,7 @@
// grafana Variables
// -------------------------
@grafanaPanelBackground: @grayDarker;
-@grafanaPanelBorder: solid 1px @grayDark;
+@grafanaPanelBorder: solid 1px @grayDark;
@grafanaTriggerBorder: solid 1px #555;
// Graphite Target Editor