diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index b75fdfb574a..bc1cb68bb5b 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -64,6 +64,11 @@ "Comment": "v1.0.0", "Rev": "abb928e07c4108683d6b4d0b6ca08fe6bc0eee5f" }, + { + "ImportPath": "github.com/bmizerany/assert", + "Comment": "release.r60-6-ge17e998", + "Rev": "e17e99893cb6509f428e1728281c2ad60a6b31e3" + }, { "ImportPath": "github.com/bradfitz/gomemcache/memcache", "Comment": "release.r60-40-g72a6864", @@ -123,10 +128,6 @@ "Comment": "v0.4.4-44-gf561133", "Rev": "f56113384f2c63dfe4cd8e768e349f1c35122b58" }, - { - "ImportPath": "github.com/gopherjs/gopherjs/js", - "Rev": "14d893dca2e4adb93a5ccc9494040acc0821cd8d" - }, { "ImportPath": "github.com/gosimple/slug", "Rev": "8d258463b4459f161f51d6a357edacd3eef9d663" @@ -160,6 +161,15 @@ "ImportPath": "github.com/klauspost/crc32", "Rev": "6834731faf32e62a2dd809d99fb24d1e4ae5a92d" }, + { + "ImportPath": "github.com/kr/pretty", + "Comment": "go.weekly.2011-12-22-27-ge6ac2fc", + "Rev": "e6ac2fc51e89a3249e82157fa0bb7a18ef9dd5bb" + }, + { + "ImportPath": "github.com/kr/text", + "Rev": "bb797dc4fb8320488f47bf11de07a733d7233e1f" + }, { "ImportPath": "github.com/lib/pq", "Comment": "go1.0-cutoff-13-g19eeca3", diff --git a/Godeps/_workspace/src/github.com/bmizerany/assert/.gitignore b/Godeps/_workspace/src/github.com/bmizerany/assert/.gitignore new file mode 100644 index 00000000000..b6fadf4ebe7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bmizerany/assert/.gitignore @@ -0,0 +1,7 @@ +_go_.* +_gotest_.* +_obj +_test +_testmain.go +*.out +*.[568] diff --git a/Godeps/_workspace/src/github.com/bmizerany/assert/README.md b/Godeps/_workspace/src/github.com/bmizerany/assert/README.md new file mode 100644 index 00000000000..8b6b6fc4fc6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bmizerany/assert/README.md @@ -0,0 +1,45 @@ +# Assert (c) Blake Mizerany and Keith Rarick -- MIT LICENCE + +## Assertions for Go tests + +## Install + + $ go get github.com/bmizerany/assert + +## Use + +**point.go** + + package point + + type Point struct { + x, y int + } + +**point_test.go** + + + package point + + import ( + "testing" + "github.com/bmizerany/assert" + ) + + func TestAsserts(t *testing.T) { + p1 := Point{1, 1} + p2 := Point{2, 1} + + assert.Equal(t, p1, p2) + } + +**output** + $ go test + --- FAIL: TestAsserts (0.00 seconds) + assert.go:15: /Users/flavio.barbosa/dev/stewie/src/point_test.go:12 + assert.go:24: ! X: 1 != 2 + FAIL + +## Docs + + http://github.com/bmizerany/assert diff --git a/Godeps/_workspace/src/github.com/bmizerany/assert/assert.go b/Godeps/_workspace/src/github.com/bmizerany/assert/assert.go new file mode 100644 index 00000000000..7409f985e80 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bmizerany/assert/assert.go @@ -0,0 +1,76 @@ +package assert +// Testing helpers for doozer. + +import ( + "github.com/kr/pretty" + "reflect" + "testing" + "runtime" + "fmt" +) + +func assert(t *testing.T, result bool, f func(), cd int) { + if !result { + _, file, line, _ := runtime.Caller(cd + 1) + t.Errorf("%s:%d", file, line) + f() + t.FailNow() + } +} + +func equal(t *testing.T, exp, got interface{}, cd int, args ...interface{}) { + fn := func() { + for _, desc := range pretty.Diff(exp, got) { + t.Error("!", desc) + } + if len(args) > 0 { + t.Error("!", " -", fmt.Sprint(args...)) + } + } + result := reflect.DeepEqual(exp, got) + assert(t, result, fn, cd+1) +} + +func tt(t *testing.T, result bool, cd int, args ...interface{}) { + fn := func() { + t.Errorf("! Failure") + if len(args) > 0 { + t.Error("!", " -", fmt.Sprint(args...)) + } + } + assert(t, result, fn, cd+1) +} + +func T(t *testing.T, result bool, args ...interface{}) { + tt(t, result, 1, args...) +} + +func Tf(t *testing.T, result bool, format string, args ...interface{}) { + tt(t, result, 1, fmt.Sprintf(format, args...)) +} + +func Equal(t *testing.T, exp, got interface{}, args ...interface{}) { + equal(t, exp, got, 1, args...) +} + +func Equalf(t *testing.T, exp, got interface{}, format string, args ...interface{}) { + equal(t, exp, got, 1, fmt.Sprintf(format, args...)) +} + +func NotEqual(t *testing.T, exp, got interface{}, args ...interface{}) { + fn := func() { + t.Errorf("! Unexpected: <%#v>", exp) + if len(args) > 0 { + t.Error("!", " -", fmt.Sprint(args...)) + } + } + result := !reflect.DeepEqual(exp, got) + assert(t, result, fn, 1) +} + +func Panic(t *testing.T, err interface{}, fn func()) { + defer func() { + equal(t, err, recover(), 3) + }() + fn() +} diff --git a/Godeps/_workspace/src/github.com/bmizerany/assert/assert_test.go b/Godeps/_workspace/src/github.com/bmizerany/assert/assert_test.go new file mode 100644 index 00000000000..162a590c62f --- /dev/null +++ b/Godeps/_workspace/src/github.com/bmizerany/assert/assert_test.go @@ -0,0 +1,15 @@ +package assert + +import ( + "testing" +) + +func TestLineNumbers(t *testing.T) { + Equal(t, "foo", "foo", "msg!") + //Equal(t, "foo", "bar", "this should blow up") +} + +func TestNotEqual(t *testing.T) { + NotEqual(t, "foo", "bar", "msg!") + //NotEqual(t, "foo", "foo", "this should blow up") +} diff --git a/Godeps/_workspace/src/github.com/bmizerany/assert/example/point.go b/Godeps/_workspace/src/github.com/bmizerany/assert/example/point.go new file mode 100644 index 00000000000..15789fe10f4 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bmizerany/assert/example/point.go @@ -0,0 +1,5 @@ +package point + +type Point struct { + X, Y int +} diff --git a/Godeps/_workspace/src/github.com/bmizerany/assert/example/point_test.go b/Godeps/_workspace/src/github.com/bmizerany/assert/example/point_test.go new file mode 100644 index 00000000000..34e791a43c9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/bmizerany/assert/example/point_test.go @@ -0,0 +1,13 @@ +package point + +import ( + "testing" + "assert" +) + +func TestAsserts(t *testing.T) { + p1 := Point{1, 1} + p2 := Point{2, 1} + + assert.Equal(t, p1, p2) +} diff --git a/Godeps/_workspace/src/github.com/gopherjs/gopherjs/js/js.go b/Godeps/_workspace/src/github.com/gopherjs/gopherjs/js/js.go deleted file mode 100644 index 5367d3d0fa7..00000000000 --- a/Godeps/_workspace/src/github.com/gopherjs/gopherjs/js/js.go +++ /dev/null @@ -1,168 +0,0 @@ -// Package js provides functions for interacting with native JavaScript APIs. Calls to these functions are treated specially by GopherJS and translated directly to their corresponding JavaScript syntax. -// -// Use MakeWrapper to expose methods to JavaScript. When passing values directly, the following type conversions are performed: -// -// | Go type | JavaScript type | Conversions back to interface{} | -// | --------------------- | --------------------- | ------------------------------- | -// | bool | Boolean | bool | -// | integers and floats | Number | float64 | -// | string | String | string | -// | []int8 | Int8Array | []int8 | -// | []int16 | Int16Array | []int16 | -// | []int32, []int | Int32Array | []int | -// | []uint8 | Uint8Array | []uint8 | -// | []uint16 | Uint16Array | []uint16 | -// | []uint32, []uint | Uint32Array | []uint | -// | []float32 | Float32Array | []float32 | -// | []float64 | Float64Array | []float64 | -// | all other slices | Array | []interface{} | -// | arrays | see slice type | see slice type | -// | functions | Function | func(...interface{}) *js.Object | -// | time.Time | Date | time.Time | -// | - | instanceof Node | *js.Object | -// | maps, structs | instanceof Object | map[string]interface{} | -// -// Additionally, for a struct containing a *js.Object field, only the content of the field will be passed to JavaScript and vice versa. -package js - -// Object is a container for a native JavaScript object. Calls to its methods are treated specially by GopherJS and translated directly to their JavaScript syntax. A nil pointer to Object is equal to JavaScript's "null". Object can not be used as a map key. -type Object struct{ object *Object } - -// Get returns the object's property with the given key. -func (o *Object) Get(key string) *Object { return o.object.Get(key) } - -// Set assigns the value to the object's property with the given key. -func (o *Object) Set(key string, value interface{}) { o.object.Set(key, value) } - -// Delete removes the object's property with the given key. -func (o *Object) Delete(key string) { o.object.Delete(key) } - -// Length returns the object's "length" property, converted to int. -func (o *Object) Length() int { return o.object.Length() } - -// Index returns the i'th element of an array. -func (o *Object) Index(i int) *Object { return o.object.Index(i) } - -// SetIndex sets the i'th element of an array. -func (o *Object) SetIndex(i int, value interface{}) { o.object.SetIndex(i, value) } - -// Call calls the object's method with the given name. -func (o *Object) Call(name string, args ...interface{}) *Object { return o.object.Call(name, args...) } - -// Invoke calls the object itself. This will fail if it is not a function. -func (o *Object) Invoke(args ...interface{}) *Object { return o.object.Invoke(args...) } - -// New creates a new instance of this type object. This will fail if it not a function (constructor). -func (o *Object) New(args ...interface{}) *Object { return o.object.New(args...) } - -// Bool returns the object converted to bool according to JavaScript type conversions. -func (o *Object) Bool() bool { return o.object.Bool() } - -// String returns the object converted to string according to JavaScript type conversions. -func (o *Object) String() string { return o.object.String() } - -// Int returns the object converted to int according to JavaScript type conversions (parseInt). -func (o *Object) Int() int { return o.object.Int() } - -// Int64 returns the object converted to int64 according to JavaScript type conversions (parseInt). -func (o *Object) Int64() int64 { return o.object.Int64() } - -// Uint64 returns the object converted to uint64 according to JavaScript type conversions (parseInt). -func (o *Object) Uint64() uint64 { return o.object.Uint64() } - -// Float returns the object converted to float64 according to JavaScript type conversions (parseFloat). -func (o *Object) Float() float64 { return o.object.Float() } - -// Interface returns the object converted to interface{}. See GopherJS' README for details. -func (o *Object) Interface() interface{} { return o.object.Interface() } - -// Unsafe returns the object as an uintptr, which can be converted via unsafe.Pointer. Not intended for public use. -func (o *Object) Unsafe() uintptr { return o.object.Unsafe() } - -// Error encapsulates JavaScript errors. Those are turned into a Go panic and may be recovered, giving an *Error that holds the JavaScript error object. -type Error struct { - *Object -} - -// Error returns the message of the encapsulated JavaScript error object. -func (err *Error) Error() string { - return "JavaScript error: " + err.Get("message").String() -} - -// Stack returns the stack property of the encapsulated JavaScript error object. -func (err *Error) Stack() string { - return err.Get("stack").String() -} - -// Global gives JavaScript's global object ("window" for browsers and "GLOBAL" for Node.js). -var Global *Object - -// Module gives the value of the "module" variable set by Node.js. Hint: Set a module export with 'js.Module.Get("exports").Set("exportName", ...)'. -var Module *Object - -// Undefined gives the JavaScript value "undefined". -var Undefined *Object - -// Debugger gets compiled to JavaScript's "debugger;" statement. -func Debugger() {} - -// InternalObject returns the internal JavaScript object that represents i. Not intended for public use. -func InternalObject(i interface{}) *Object { - return nil -} - -// MakeFunc wraps a function and gives access to the values of JavaScript's "this" and "arguments" keywords. -func MakeFunc(func(this *Object, arguments []*Object) interface{}) *Object { - return nil -} - -// Keys returns the keys of the given JavaScript object. -func Keys(o *Object) []string { - if o == nil || o == Undefined { - return nil - } - a := Global.Get("Object").Call("keys", o) - s := make([]string, a.Length()) - for i := 0; i < a.Length(); i++ { - s[i] = a.Index(i).String() - } - return s -} - -// MakeWrapper creates a JavaScript object which has wrappers for the exported methods of i. Use explicit getter and setter methods to expose struct fields to JavaScript. -func MakeWrapper(i interface{}) *Object { - v := InternalObject(i) - o := Global.Get("Object").New() - o.Set("__internal_object__", v) - methods := v.Get("constructor").Get("methods") - for i := 0; i < methods.Length(); i++ { - m := methods.Index(i) - if m.Get("pkg").String() != "" { // not exported - continue - } - o.Set(m.Get("name").String(), func(args ...*Object) *Object { - return Global.Call("$externalizeFunction", v.Get(m.Get("prop").String()), m.Get("typ"), true).Call("apply", v, args) - }) - } - return o -} - -// NewArrayBuffer creates a JavaScript ArrayBuffer from a byte slice. -func NewArrayBuffer(b []byte) *Object { - slice := InternalObject(b) - offset := slice.Get("$offset").Int() - length := slice.Get("$length").Int() - return slice.Get("$array").Get("buffer").Call("slice", offset, offset+length) -} - -// M is a simple map type. It is intended as a shorthand for JavaScript objects (before conversion). -type M map[string]interface{} - -// S is a simple slice type. It is intended as a shorthand for JavaScript arrays (before conversion). -type S []interface{} - -func init() { - // avoid dead code elimination - e := Error{} - _ = e -} diff --git a/Godeps/_workspace/src/github.com/kr/pretty/.gitignore b/Godeps/_workspace/src/github.com/kr/pretty/.gitignore new file mode 100644 index 00000000000..1f0a99f2f2b --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/pretty/.gitignore @@ -0,0 +1,4 @@ +[568].out +_go* +_test* +_obj diff --git a/Godeps/_workspace/src/github.com/kr/pretty/License b/Godeps/_workspace/src/github.com/kr/pretty/License new file mode 100644 index 00000000000..05c783ccf68 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/pretty/License @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright 2012 Keith Rarick + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/kr/pretty/Readme b/Godeps/_workspace/src/github.com/kr/pretty/Readme new file mode 100644 index 00000000000..c589fc622b3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/pretty/Readme @@ -0,0 +1,9 @@ +package pretty + + import "github.com/kr/pretty" + + Package pretty provides pretty-printing for Go values. + +Documentation + + http://godoc.org/github.com/kr/pretty diff --git a/Godeps/_workspace/src/github.com/kr/pretty/diff.go b/Godeps/_workspace/src/github.com/kr/pretty/diff.go new file mode 100644 index 00000000000..8fe8e2405a0 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/pretty/diff.go @@ -0,0 +1,158 @@ +package pretty + +import ( + "fmt" + "io" + "reflect" +) + +type sbuf []string + +func (s *sbuf) Write(b []byte) (int, error) { + *s = append(*s, string(b)) + return len(b), nil +} + +// Diff returns a slice where each element describes +// a difference between a and b. +func Diff(a, b interface{}) (desc []string) { + Fdiff((*sbuf)(&desc), a, b) + return desc +} + +// Fdiff writes to w a description of the differences between a and b. +func Fdiff(w io.Writer, a, b interface{}) { + diffWriter{w: w}.diff(reflect.ValueOf(a), reflect.ValueOf(b)) +} + +type diffWriter struct { + w io.Writer + l string // label +} + +func (w diffWriter) printf(f string, a ...interface{}) { + var l string + if w.l != "" { + l = w.l + ": " + } + fmt.Fprintf(w.w, l+f, a...) +} + +func (w diffWriter) diff(av, bv reflect.Value) { + if !av.IsValid() && bv.IsValid() { + w.printf("nil != %#v", bv.Interface()) + return + } + if av.IsValid() && !bv.IsValid() { + w.printf("%#v != nil", av.Interface()) + return + } + if !av.IsValid() && !bv.IsValid() { + return + } + + at := av.Type() + bt := bv.Type() + if at != bt { + w.printf("%v != %v", at, bt) + return + } + + // numeric types, including bool + if at.Kind() < reflect.Array { + a, b := av.Interface(), bv.Interface() + if a != b { + w.printf("%#v != %#v", a, b) + } + return + } + + switch at.Kind() { + case reflect.String: + a, b := av.Interface(), bv.Interface() + if a != b { + w.printf("%q != %q", a, b) + } + case reflect.Ptr: + switch { + case av.IsNil() && !bv.IsNil(): + w.printf("nil != %v", bv.Interface()) + case !av.IsNil() && bv.IsNil(): + w.printf("%v != nil", av.Interface()) + case !av.IsNil() && !bv.IsNil(): + w.diff(av.Elem(), bv.Elem()) + } + case reflect.Struct: + for i := 0; i < av.NumField(); i++ { + w.relabel(at.Field(i).Name).diff(av.Field(i), bv.Field(i)) + } + case reflect.Slice: + lenA := av.Len() + lenB := bv.Len() + if lenA != lenB { + w.printf("%s[%d] != %s[%d]", av.Type(), lenA, bv.Type(), lenB) + break + } + for i := 0; i < lenA; i++ { + w.relabel(fmt.Sprintf("[%d]", i)).diff(av.Index(i), bv.Index(i)) + } + case reflect.Map: + ak, both, bk := keyDiff(av.MapKeys(), bv.MapKeys()) + for _, k := range ak { + w := w.relabel(fmt.Sprintf("[%#v]", k.Interface())) + w.printf("%q != (missing)", av.MapIndex(k)) + } + for _, k := range both { + w := w.relabel(fmt.Sprintf("[%#v]", k.Interface())) + w.diff(av.MapIndex(k), bv.MapIndex(k)) + } + for _, k := range bk { + w := w.relabel(fmt.Sprintf("[%#v]", k.Interface())) + w.printf("(missing) != %q", bv.MapIndex(k)) + } + case reflect.Interface: + w.diff(reflect.ValueOf(av.Interface()), reflect.ValueOf(bv.Interface())) + default: + if !reflect.DeepEqual(av.Interface(), bv.Interface()) { + w.printf("%# v != %# v", Formatter(av.Interface()), Formatter(bv.Interface())) + } + } +} + +func (d diffWriter) relabel(name string) (d1 diffWriter) { + d1 = d + if d.l != "" && name[0] != '[' { + d1.l += "." + } + d1.l += name + return d1 +} + +func keyDiff(a, b []reflect.Value) (ak, both, bk []reflect.Value) { + for _, av := range a { + inBoth := false + for _, bv := range b { + if reflect.DeepEqual(av.Interface(), bv.Interface()) { + inBoth = true + both = append(both, av) + break + } + } + if !inBoth { + ak = append(ak, av) + } + } + for _, bv := range b { + inBoth := false + for _, av := range a { + if reflect.DeepEqual(av.Interface(), bv.Interface()) { + inBoth = true + break + } + } + if !inBoth { + bk = append(bk, bv) + } + } + return +} diff --git a/Godeps/_workspace/src/github.com/kr/pretty/diff_test.go b/Godeps/_workspace/src/github.com/kr/pretty/diff_test.go new file mode 100644 index 00000000000..3c388f13ca7 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/pretty/diff_test.go @@ -0,0 +1,74 @@ +package pretty + +import ( + "testing" +) + +type difftest struct { + a interface{} + b interface{} + exp []string +} + +type S struct { + A int + S *S + I interface{} + C []int +} + +var diffs = []difftest{ + {a: nil, b: nil}, + {a: S{A: 1}, b: S{A: 1}}, + + {0, "", []string{`int != string`}}, + {0, 1, []string{`0 != 1`}}, + {S{}, new(S), []string{`pretty.S != *pretty.S`}}, + {"a", "b", []string{`"a" != "b"`}}, + {S{}, S{A: 1}, []string{`A: 0 != 1`}}, + {new(S), &S{A: 1}, []string{`A: 0 != 1`}}, + {S{S: new(S)}, S{S: &S{A: 1}}, []string{`S.A: 0 != 1`}}, + {S{}, S{I: 0}, []string{`I: nil != 0`}}, + {S{I: 1}, S{I: "x"}, []string{`I: int != string`}}, + {S{}, S{C: []int{1}}, []string{`C: []int[0] != []int[1]`}}, + {S{C: []int{}}, S{C: []int{1}}, []string{`C: []int[0] != []int[1]`}}, + {S{C: []int{1, 2, 3}}, S{C: []int{1, 2, 4}}, []string{`C[2]: 3 != 4`}}, + {S{}, S{A: 1, S: new(S)}, []string{`A: 0 != 1`, `S: nil != &{0 []}`}}, +} + +func TestDiff(t *testing.T) { + for _, tt := range diffs { + got := Diff(tt.a, tt.b) + eq := len(got) == len(tt.exp) + if eq { + for i := range got { + eq = eq && got[i] == tt.exp[i] + } + } + if !eq { + t.Errorf("diffing % #v", tt.a) + t.Errorf("with % #v", tt.b) + diffdiff(t, got, tt.exp) + continue + } + } +} + +func diffdiff(t *testing.T, got, exp []string) { + minus(t, "unexpected:", got, exp) + minus(t, "missing:", exp, got) +} + +func minus(t *testing.T, s string, a, b []string) { + var i, j int + for i = 0; i < len(a); i++ { + for j = 0; j < len(b); j++ { + if a[i] == b[j] { + break + } + } + if j == len(b) { + t.Error(s, a[i]) + } + } +} diff --git a/Godeps/_workspace/src/github.com/kr/pretty/example_test.go b/Godeps/_workspace/src/github.com/kr/pretty/example_test.go new file mode 100644 index 00000000000..ecf40f3fcc6 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/pretty/example_test.go @@ -0,0 +1,20 @@ +package pretty_test + +import ( + "fmt" + "github.com/kr/pretty" +) + +func Example() { + type myType struct { + a, b int + } + var x = []myType{{1, 2}, {3, 4}, {5, 6}} + fmt.Printf("%# v", pretty.Formatter(x)) + // output: + // []pretty_test.myType{ + // {a:1, b:2}, + // {a:3, b:4}, + // {a:5, b:6}, + // } +} diff --git a/Godeps/_workspace/src/github.com/kr/pretty/formatter.go b/Godeps/_workspace/src/github.com/kr/pretty/formatter.go new file mode 100644 index 00000000000..8dacda25fa8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/pretty/formatter.go @@ -0,0 +1,337 @@ +package pretty + +import ( + "fmt" + "io" + "reflect" + "strconv" + "text/tabwriter" + + "github.com/kr/text" +) + +const ( + limit = 50 +) + +type formatter struct { + x interface{} + force bool + quote bool +} + +// Formatter makes a wrapper, f, that will format x as go source with line +// breaks and tabs. Object f responds to the "%v" formatting verb when both the +// "#" and " " (space) flags are set, for example: +// +// fmt.Sprintf("%# v", Formatter(x)) +// +// If one of these two flags is not set, or any other verb is used, f will +// format x according to the usual rules of package fmt. +// In particular, if x satisfies fmt.Formatter, then x.Format will be called. +func Formatter(x interface{}) (f fmt.Formatter) { + return formatter{x: x, quote: true} +} + +func (fo formatter) String() string { + return fmt.Sprint(fo.x) // unwrap it +} + +func (fo formatter) passThrough(f fmt.State, c rune) { + s := "%" + for i := 0; i < 128; i++ { + if f.Flag(i) { + s += string(i) + } + } + if w, ok := f.Width(); ok { + s += fmt.Sprintf("%d", w) + } + if p, ok := f.Precision(); ok { + s += fmt.Sprintf(".%d", p) + } + s += string(c) + fmt.Fprintf(f, s, fo.x) +} + +func (fo formatter) Format(f fmt.State, c rune) { + if fo.force || c == 'v' && f.Flag('#') && f.Flag(' ') { + w := tabwriter.NewWriter(f, 4, 4, 1, ' ', 0) + p := &printer{tw: w, Writer: w, visited: make(map[visit]int)} + p.printValue(reflect.ValueOf(fo.x), true, fo.quote) + w.Flush() + return + } + fo.passThrough(f, c) +} + +type printer struct { + io.Writer + tw *tabwriter.Writer + visited map[visit]int + depth int +} + +func (p *printer) indent() *printer { + q := *p + q.tw = tabwriter.NewWriter(p.Writer, 4, 4, 1, ' ', 0) + q.Writer = text.NewIndentWriter(q.tw, []byte{'\t'}) + return &q +} + +func (p *printer) printInline(v reflect.Value, x interface{}, showType bool) { + if showType { + io.WriteString(p, v.Type().String()) + fmt.Fprintf(p, "(%#v)", x) + } else { + fmt.Fprintf(p, "%#v", x) + } +} + +// printValue must keep track of already-printed pointer values to avoid +// infinite recursion. +type visit struct { + v uintptr + typ reflect.Type +} + +func (p *printer) printValue(v reflect.Value, showType, quote bool) { + if p.depth > 10 { + io.WriteString(p, "!%v(DEPTH EXCEEDED)") + return + } + + switch v.Kind() { + case reflect.Bool: + p.printInline(v, v.Bool(), showType) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + p.printInline(v, v.Int(), showType) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + p.printInline(v, v.Uint(), showType) + case reflect.Float32, reflect.Float64: + p.printInline(v, v.Float(), showType) + case reflect.Complex64, reflect.Complex128: + fmt.Fprintf(p, "%#v", v.Complex()) + case reflect.String: + p.fmtString(v.String(), quote) + case reflect.Map: + t := v.Type() + if showType { + io.WriteString(p, t.String()) + } + writeByte(p, '{') + if nonzero(v) { + expand := !canInline(v.Type()) + pp := p + if expand { + writeByte(p, '\n') + pp = p.indent() + } + keys := v.MapKeys() + for i := 0; i < v.Len(); i++ { + showTypeInStruct := true + k := keys[i] + mv := v.MapIndex(k) + pp.printValue(k, false, true) + writeByte(pp, ':') + if expand { + writeByte(pp, '\t') + } + showTypeInStruct = t.Elem().Kind() == reflect.Interface + pp.printValue(mv, showTypeInStruct, true) + if expand { + io.WriteString(pp, ",\n") + } else if i < v.Len()-1 { + io.WriteString(pp, ", ") + } + } + if expand { + pp.tw.Flush() + } + } + writeByte(p, '}') + case reflect.Struct: + t := v.Type() + if v.CanAddr() { + addr := v.UnsafeAddr() + vis := visit{addr, t} + if vd, ok := p.visited[vis]; ok && vd < p.depth { + p.fmtString(t.String()+"{(CYCLIC REFERENCE)}", false) + break // don't print v again + } + p.visited[vis] = p.depth + } + + if showType { + io.WriteString(p, t.String()) + } + writeByte(p, '{') + if nonzero(v) { + expand := !canInline(v.Type()) + pp := p + if expand { + writeByte(p, '\n') + pp = p.indent() + } + for i := 0; i < v.NumField(); i++ { + showTypeInStruct := true + if f := t.Field(i); f.Name != "" { + io.WriteString(pp, f.Name) + writeByte(pp, ':') + if expand { + writeByte(pp, '\t') + } + showTypeInStruct = labelType(f.Type) + } + pp.printValue(getField(v, i), showTypeInStruct, true) + if expand { + io.WriteString(pp, ",\n") + } else if i < v.NumField()-1 { + io.WriteString(pp, ", ") + } + } + if expand { + pp.tw.Flush() + } + } + writeByte(p, '}') + case reflect.Interface: + switch e := v.Elem(); { + case e.Kind() == reflect.Invalid: + io.WriteString(p, "nil") + case e.IsValid(): + pp := *p + pp.depth++ + pp.printValue(e, showType, true) + default: + io.WriteString(p, v.Type().String()) + io.WriteString(p, "(nil)") + } + case reflect.Array, reflect.Slice: + t := v.Type() + if showType { + io.WriteString(p, t.String()) + } + if v.Kind() == reflect.Slice && v.IsNil() && showType { + io.WriteString(p, "(nil)") + break + } + if v.Kind() == reflect.Slice && v.IsNil() { + io.WriteString(p, "nil") + break + } + writeByte(p, '{') + expand := !canInline(v.Type()) + pp := p + if expand { + writeByte(p, '\n') + pp = p.indent() + } + for i := 0; i < v.Len(); i++ { + showTypeInSlice := t.Elem().Kind() == reflect.Interface + pp.printValue(v.Index(i), showTypeInSlice, true) + if expand { + io.WriteString(pp, ",\n") + } else if i < v.Len()-1 { + io.WriteString(pp, ", ") + } + } + if expand { + pp.tw.Flush() + } + writeByte(p, '}') + case reflect.Ptr: + e := v.Elem() + if !e.IsValid() { + writeByte(p, '(') + io.WriteString(p, v.Type().String()) + io.WriteString(p, ")(nil)") + } else { + pp := *p + pp.depth++ + writeByte(pp, '&') + pp.printValue(e, true, true) + } + case reflect.Chan: + x := v.Pointer() + if showType { + writeByte(p, '(') + io.WriteString(p, v.Type().String()) + fmt.Fprintf(p, ")(%#v)", x) + } else { + fmt.Fprintf(p, "%#v", x) + } + case reflect.Func: + io.WriteString(p, v.Type().String()) + io.WriteString(p, " {...}") + case reflect.UnsafePointer: + p.printInline(v, v.Pointer(), showType) + case reflect.Invalid: + io.WriteString(p, "nil") + } +} + +func canInline(t reflect.Type) bool { + switch t.Kind() { + case reflect.Map: + return !canExpand(t.Elem()) + case reflect.Struct: + for i := 0; i < t.NumField(); i++ { + if canExpand(t.Field(i).Type) { + return false + } + } + return true + case reflect.Interface: + return false + case reflect.Array, reflect.Slice: + return !canExpand(t.Elem()) + case reflect.Ptr: + return false + case reflect.Chan, reflect.Func, reflect.UnsafePointer: + return false + } + return true +} + +func canExpand(t reflect.Type) bool { + switch t.Kind() { + case reflect.Map, reflect.Struct, + reflect.Interface, reflect.Array, reflect.Slice, + reflect.Ptr: + return true + } + return false +} + +func labelType(t reflect.Type) bool { + switch t.Kind() { + case reflect.Interface, reflect.Struct: + return true + } + return false +} + +func (p *printer) fmtString(s string, quote bool) { + if quote { + s = strconv.Quote(s) + } + io.WriteString(p, s) +} + +func tryDeepEqual(a, b interface{}) bool { + defer func() { recover() }() + return reflect.DeepEqual(a, b) +} + +func writeByte(w io.Writer, b byte) { + w.Write([]byte{b}) +} + +func getField(v reflect.Value, i int) reflect.Value { + val := v.Field(i) + if val.Kind() == reflect.Interface && !val.IsNil() { + val = val.Elem() + } + return val +} diff --git a/Godeps/_workspace/src/github.com/kr/pretty/formatter_test.go b/Godeps/_workspace/src/github.com/kr/pretty/formatter_test.go new file mode 100644 index 00000000000..5f3204e8e87 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/pretty/formatter_test.go @@ -0,0 +1,261 @@ +package pretty + +import ( + "fmt" + "io" + "strings" + "testing" + "unsafe" +) + +type test struct { + v interface{} + s string +} + +type LongStructTypeName struct { + longFieldName interface{} + otherLongFieldName interface{} +} + +type SA struct { + t *T + v T +} + +type T struct { + x, y int +} + +type F int + +func (f F) Format(s fmt.State, c rune) { + fmt.Fprintf(s, "F(%d)", int(f)) +} + +var long = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + +var gosyntax = []test{ + {nil, `nil`}, + {"", `""`}, + {"a", `"a"`}, + {1, "int(1)"}, + {1.0, "float64(1)"}, + {[]int(nil), "[]int(nil)"}, + {[0]int{}, "[0]int{}"}, + {complex(1, 0), "(1+0i)"}, + //{make(chan int), "(chan int)(0x1234)"}, + {unsafe.Pointer(uintptr(unsafe.Pointer(&long))), fmt.Sprintf("unsafe.Pointer(0x%02x)", uintptr(unsafe.Pointer(&long)))}, + {func(int) {}, "func(int) {...}"}, + {map[int]int{1: 1}, "map[int]int{1:1}"}, + {int32(1), "int32(1)"}, + {io.EOF, `&errors.errorString{s:"EOF"}`}, + {[]string{"a"}, `[]string{"a"}`}, + { + []string{long}, + `[]string{"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"}`, + }, + {F(5), "pretty.F(5)"}, + { + SA{&T{1, 2}, T{3, 4}}, + `pretty.SA{ + t: &pretty.T{x:1, y:2}, + v: pretty.T{x:3, y:4}, +}`, + }, + { + map[int][]byte{1: {}}, + `map[int][]uint8{ + 1: {}, +}`, + }, + { + map[int]T{1: {}}, + `map[int]pretty.T{ + 1: {}, +}`, + }, + { + long, + `"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"`, + }, + { + LongStructTypeName{ + longFieldName: LongStructTypeName{}, + otherLongFieldName: long, + }, + `pretty.LongStructTypeName{ + longFieldName: pretty.LongStructTypeName{}, + otherLongFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", +}`, + }, + { + &LongStructTypeName{ + longFieldName: &LongStructTypeName{}, + otherLongFieldName: (*LongStructTypeName)(nil), + }, + `&pretty.LongStructTypeName{ + longFieldName: &pretty.LongStructTypeName{}, + otherLongFieldName: (*pretty.LongStructTypeName)(nil), +}`, + }, + { + []LongStructTypeName{ + {nil, nil}, + {3, 3}, + {long, nil}, + }, + `[]pretty.LongStructTypeName{ + {}, + { + longFieldName: int(3), + otherLongFieldName: int(3), + }, + { + longFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", + otherLongFieldName: nil, + }, +}`, + }, + { + []interface{}{ + LongStructTypeName{nil, nil}, + []byte{1, 2, 3}, + T{3, 4}, + LongStructTypeName{long, nil}, + }, + `[]interface {}{ + pretty.LongStructTypeName{}, + []uint8{0x1, 0x2, 0x3}, + pretty.T{x:3, y:4}, + pretty.LongStructTypeName{ + longFieldName: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", + otherLongFieldName: nil, + }, +}`, + }, +} + +func TestGoSyntax(t *testing.T) { + for _, tt := range gosyntax { + s := fmt.Sprintf("%# v", Formatter(tt.v)) + if tt.s != s { + t.Errorf("expected %q", tt.s) + t.Errorf("got %q", s) + t.Errorf("expraw\n%s", tt.s) + t.Errorf("gotraw\n%s", s) + } + } +} + +type I struct { + i int + R interface{} +} + +func (i *I) I() *I { return i.R.(*I) } + +func TestCycle(t *testing.T) { + type A struct{ *A } + v := &A{} + v.A = v + + // panics from stack overflow without cycle detection + t.Logf("Example cycle:\n%# v", Formatter(v)) + + p := &A{} + s := fmt.Sprintf("%# v", Formatter([]*A{p, p})) + if strings.Contains(s, "CYCLIC") { + t.Errorf("Repeated address detected as cyclic reference:\n%s", s) + } + + type R struct { + i int + *R + } + r := &R{ + i: 1, + R: &R{ + i: 2, + R: &R{ + i: 3, + }, + }, + } + r.R.R.R = r + t.Logf("Example longer cycle:\n%# v", Formatter(r)) + + r = &R{ + i: 1, + R: &R{ + i: 2, + R: &R{ + i: 3, + R: &R{ + i: 4, + R: &R{ + i: 5, + R: &R{ + i: 6, + R: &R{ + i: 7, + R: &R{ + i: 8, + R: &R{ + i: 9, + R: &R{ + i: 10, + R: &R{ + i: 11, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + // here be pirates + r.R.R.R.R.R.R.R.R.R.R.R = r + t.Logf("Example very long cycle:\n%# v", Formatter(r)) + + i := &I{ + i: 1, + R: &I{ + i: 2, + R: &I{ + i: 3, + R: &I{ + i: 4, + R: &I{ + i: 5, + R: &I{ + i: 6, + R: &I{ + i: 7, + R: &I{ + i: 8, + R: &I{ + i: 9, + R: &I{ + i: 10, + R: &I{ + i: 11, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + iv := i.I().I().I().I().I().I().I().I().I().I() + *iv = *i + t.Logf("Example long interface cycle:\n%# v", Formatter(i)) +} diff --git a/Godeps/_workspace/src/github.com/kr/pretty/pretty.go b/Godeps/_workspace/src/github.com/kr/pretty/pretty.go new file mode 100644 index 00000000000..d3df8686ce8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/pretty/pretty.go @@ -0,0 +1,98 @@ +// Package pretty provides pretty-printing for Go values. This is +// useful during debugging, to avoid wrapping long output lines in +// the terminal. +// +// It provides a function, Formatter, that can be used with any +// function that accepts a format string. It also provides +// convenience wrappers for functions in packages fmt and log. +package pretty + +import ( + "fmt" + "io" + "log" +) + +// Errorf is a convenience wrapper for fmt.Errorf. +// +// Calling Errorf(f, x, y) is equivalent to +// fmt.Errorf(f, Formatter(x), Formatter(y)). +func Errorf(format string, a ...interface{}) error { + return fmt.Errorf(format, wrap(a, false)...) +} + +// Fprintf is a convenience wrapper for fmt.Fprintf. +// +// Calling Fprintf(w, f, x, y) is equivalent to +// fmt.Fprintf(w, f, Formatter(x), Formatter(y)). +func Fprintf(w io.Writer, format string, a ...interface{}) (n int, error error) { + return fmt.Fprintf(w, format, wrap(a, false)...) +} + +// Log is a convenience wrapper for log.Printf. +// +// Calling Log(x, y) is equivalent to +// log.Print(Formatter(x), Formatter(y)), but each operand is +// formatted with "%# v". +func Log(a ...interface{}) { + log.Print(wrap(a, true)...) +} + +// Logf is a convenience wrapper for log.Printf. +// +// Calling Logf(f, x, y) is equivalent to +// log.Printf(f, Formatter(x), Formatter(y)). +func Logf(format string, a ...interface{}) { + log.Printf(format, wrap(a, false)...) +} + +// Logln is a convenience wrapper for log.Printf. +// +// Calling Logln(x, y) is equivalent to +// log.Println(Formatter(x), Formatter(y)), but each operand is +// formatted with "%# v". +func Logln(a ...interface{}) { + log.Println(wrap(a, true)...) +} + +// Print pretty-prints its operands and writes to standard output. +// +// Calling Print(x, y) is equivalent to +// fmt.Print(Formatter(x), Formatter(y)), but each operand is +// formatted with "%# v". +func Print(a ...interface{}) (n int, errno error) { + return fmt.Print(wrap(a, true)...) +} + +// Printf is a convenience wrapper for fmt.Printf. +// +// Calling Printf(f, x, y) is equivalent to +// fmt.Printf(f, Formatter(x), Formatter(y)). +func Printf(format string, a ...interface{}) (n int, errno error) { + return fmt.Printf(format, wrap(a, false)...) +} + +// Println pretty-prints its operands and writes to standard output. +// +// Calling Print(x, y) is equivalent to +// fmt.Println(Formatter(x), Formatter(y)), but each operand is +// formatted with "%# v". +func Println(a ...interface{}) (n int, errno error) { + return fmt.Println(wrap(a, true)...) +} + +// Sprintf is a convenience wrapper for fmt.Sprintf. +// +// Calling Sprintf(f, x, y) is equivalent to +// fmt.Sprintf(f, Formatter(x), Formatter(y)). +func Sprintf(format string, a ...interface{}) string { + return fmt.Sprintf(format, wrap(a, false)...) +} + +func wrap(a []interface{}, force bool) []interface{} { + w := make([]interface{}, len(a)) + for i, x := range a { + w[i] = formatter{x: x, force: force} + } + return w +} diff --git a/Godeps/_workspace/src/github.com/kr/pretty/zero.go b/Godeps/_workspace/src/github.com/kr/pretty/zero.go new file mode 100644 index 00000000000..abb5b6fc14c --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/pretty/zero.go @@ -0,0 +1,41 @@ +package pretty + +import ( + "reflect" +) + +func nonzero(v reflect.Value) bool { + switch v.Kind() { + case reflect.Bool: + return v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() != 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() != 0 + case reflect.Float32, reflect.Float64: + return v.Float() != 0 + case reflect.Complex64, reflect.Complex128: + return v.Complex() != complex(0, 0) + case reflect.String: + return v.String() != "" + case reflect.Struct: + for i := 0; i < v.NumField(); i++ { + if nonzero(getField(v, i)) { + return true + } + } + return false + case reflect.Array: + for i := 0; i < v.Len(); i++ { + if nonzero(v.Index(i)) { + return true + } + } + return false + case reflect.Map, reflect.Interface, reflect.Slice, reflect.Ptr, reflect.Chan, reflect.Func: + return !v.IsNil() + case reflect.UnsafePointer: + return v.Pointer() != 0 + } + return true +} diff --git a/Godeps/_workspace/src/github.com/kr/text/License b/Godeps/_workspace/src/github.com/kr/text/License new file mode 100644 index 00000000000..480a3280599 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/text/License @@ -0,0 +1,19 @@ +Copyright 2012 Keith Rarick + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/kr/text/Readme b/Godeps/_workspace/src/github.com/kr/text/Readme new file mode 100644 index 00000000000..7e6e7c0687b --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/text/Readme @@ -0,0 +1,3 @@ +This is a Go package for manipulating paragraphs of text. + +See http://go.pkgdoc.org/github.com/kr/text for full documentation. diff --git a/Godeps/_workspace/src/github.com/kr/text/colwriter/Readme b/Godeps/_workspace/src/github.com/kr/text/colwriter/Readme new file mode 100644 index 00000000000..1c1f4e68393 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/text/colwriter/Readme @@ -0,0 +1,5 @@ +Package colwriter provides a write filter that formats +input lines in multiple columns. + +The package is a straightforward translation from +/src/cmd/draw/mc.c in Plan 9 from User Space. diff --git a/Godeps/_workspace/src/github.com/kr/text/colwriter/column.go b/Godeps/_workspace/src/github.com/kr/text/colwriter/column.go new file mode 100644 index 00000000000..7302ce9f7a8 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/text/colwriter/column.go @@ -0,0 +1,147 @@ +// Package colwriter provides a write filter that formats +// input lines in multiple columns. +// +// The package is a straightforward translation from +// /src/cmd/draw/mc.c in Plan 9 from User Space. +package colwriter + +import ( + "bytes" + "io" + "unicode/utf8" +) + +const ( + tab = 4 +) + +const ( + // Print each input line ending in a colon ':' separately. + BreakOnColon uint = 1 << iota +) + +// A Writer is a filter that arranges input lines in as many columns as will +// fit in its width. Tab '\t' chars in the input are translated to sequences +// of spaces ending at multiples of 4 positions. +// +// If BreakOnColon is set, each input line ending in a colon ':' is written +// separately. +// +// The Writer assumes that all Unicode code points have the same width; this +// may not be true in some fonts. +type Writer struct { + w io.Writer + buf []byte + width int + flag uint +} + +// NewWriter allocates and initializes a new Writer writing to w. +// Parameter width controls the total number of characters on each line +// across all columns. +func NewWriter(w io.Writer, width int, flag uint) *Writer { + return &Writer{ + w: w, + width: width, + flag: flag, + } +} + +// Write writes p to the writer w. The only errors returned are ones +// encountered while writing to the underlying output stream. +func (w *Writer) Write(p []byte) (n int, err error) { + var linelen int + var lastWasColon bool + for i, c := range p { + w.buf = append(w.buf, c) + linelen++ + if c == '\t' { + w.buf[len(w.buf)-1] = ' ' + for linelen%tab != 0 { + w.buf = append(w.buf, ' ') + linelen++ + } + } + if w.flag&BreakOnColon != 0 && c == ':' { + lastWasColon = true + } else if lastWasColon { + if c == '\n' { + pos := bytes.LastIndex(w.buf[:len(w.buf)-1], []byte{'\n'}) + if pos < 0 { + pos = 0 + } + line := w.buf[pos:] + w.buf = w.buf[:pos] + if err = w.columnate(); err != nil { + if len(line) < i { + return i - len(line), err + } + return 0, err + } + if n, err := w.w.Write(line); err != nil { + if r := len(line) - n; r < i { + return i - r, err + } + return 0, err + } + } + lastWasColon = false + } + if c == '\n' { + linelen = 0 + } + } + return len(p), nil +} + +// Flush should be called after the last call to Write to ensure that any data +// buffered in the Writer is written to output. +func (w *Writer) Flush() error { + return w.columnate() +} + +func (w *Writer) columnate() error { + words := bytes.Split(w.buf, []byte{'\n'}) + w.buf = nil + if len(words[len(words)-1]) == 0 { + words = words[:len(words)-1] + } + maxwidth := 0 + for _, wd := range words { + if n := utf8.RuneCount(wd); n > maxwidth { + maxwidth = n + } + } + maxwidth++ // space char + wordsPerLine := w.width / maxwidth + if wordsPerLine <= 0 { + wordsPerLine = 1 + } + nlines := (len(words) + wordsPerLine - 1) / wordsPerLine + for i := 0; i < nlines; i++ { + col := 0 + endcol := 0 + for j := i; j < len(words); j += nlines { + endcol += maxwidth + _, err := w.w.Write(words[j]) + if err != nil { + return err + } + col += utf8.RuneCount(words[j]) + if j+nlines < len(words) { + for col < endcol { + _, err := w.w.Write([]byte{' '}) + if err != nil { + return err + } + col++ + } + } + } + _, err := w.w.Write([]byte{'\n'}) + if err != nil { + return err + } + } + return nil +} diff --git a/Godeps/_workspace/src/github.com/kr/text/colwriter/column_test.go b/Godeps/_workspace/src/github.com/kr/text/colwriter/column_test.go new file mode 100644 index 00000000000..ce388f5a260 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/text/colwriter/column_test.go @@ -0,0 +1,90 @@ +package colwriter + +import ( + "bytes" + "testing" +) + +var src = ` +.git +.gitignore +.godir +Procfile: +README.md +api.go +apps.go +auth.go +darwin.go +data.go +dyno.go: +env.go +git.go +help.go +hkdist +linux.go +ls.go +main.go +plugin.go +run.go +scale.go +ssh.go +tail.go +term +unix.go +update.go +version.go +windows.go +`[1:] + +var tests = []struct { + wid int + flag uint + src string + want string +}{ + {80, 0, "", ""}, + {80, 0, src, ` +.git README.md darwin.go git.go ls.go scale.go unix.go +.gitignore api.go data.go help.go main.go ssh.go update.go +.godir apps.go dyno.go: hkdist plugin.go tail.go version.go +Procfile: auth.go env.go linux.go run.go term windows.go +`[1:]}, + {80, BreakOnColon, src, ` +.git .gitignore .godir + +Procfile: +README.md api.go apps.go auth.go darwin.go data.go + +dyno.go: +env.go hkdist main.go scale.go term version.go +git.go linux.go plugin.go ssh.go unix.go windows.go +help.go ls.go run.go tail.go update.go +`[1:]}, + {20, 0, ` +Hello +Γειά σου +안녕 +今日は +`[1:], ` +Hello 안녕 +Γειά σου 今日は +`[1:]}, +} + +func TestWriter(t *testing.T) { + for _, test := range tests { + b := new(bytes.Buffer) + w := NewWriter(b, test.wid, test.flag) + if _, err := w.Write([]byte(test.src)); err != nil { + t.Error(err) + } + if err := w.Flush(); err != nil { + t.Error(err) + } + if g := b.String(); test.want != g { + t.Log("\n" + test.want) + t.Log("\n" + g) + t.Errorf("%q != %q", test.want, g) + } + } +} diff --git a/Godeps/_workspace/src/github.com/kr/text/doc.go b/Godeps/_workspace/src/github.com/kr/text/doc.go new file mode 100644 index 00000000000..cf4c198f955 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/text/doc.go @@ -0,0 +1,3 @@ +// Package text provides rudimentary functions for manipulating text in +// paragraphs. +package text diff --git a/Godeps/_workspace/src/github.com/kr/text/indent.go b/Godeps/_workspace/src/github.com/kr/text/indent.go new file mode 100644 index 00000000000..4ebac45c092 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/text/indent.go @@ -0,0 +1,74 @@ +package text + +import ( + "io" +) + +// Indent inserts prefix at the beginning of each non-empty line of s. The +// end-of-line marker is NL. +func Indent(s, prefix string) string { + return string(IndentBytes([]byte(s), []byte(prefix))) +} + +// IndentBytes inserts prefix at the beginning of each non-empty line of b. +// The end-of-line marker is NL. +func IndentBytes(b, prefix []byte) []byte { + var res []byte + bol := true + for _, c := range b { + if bol && c != '\n' { + res = append(res, prefix...) + } + res = append(res, c) + bol = c == '\n' + } + return res +} + +// Writer indents each line of its input. +type indentWriter struct { + w io.Writer + bol bool + pre [][]byte + sel int + off int +} + +// NewIndentWriter makes a new write filter that indents the input +// lines. Each line is prefixed in order with the corresponding +// element of pre. If there are more lines than elements, the last +// element of pre is repeated for each subsequent line. +func NewIndentWriter(w io.Writer, pre ...[]byte) io.Writer { + return &indentWriter{ + w: w, + pre: pre, + bol: true, + } +} + +// The only errors returned are from the underlying indentWriter. +func (w *indentWriter) Write(p []byte) (n int, err error) { + for _, c := range p { + if w.bol { + var i int + i, err = w.w.Write(w.pre[w.sel][w.off:]) + w.off += i + if err != nil { + return n, err + } + } + _, err = w.w.Write([]byte{c}) + if err != nil { + return n, err + } + n++ + w.bol = c == '\n' + if w.bol { + w.off = 0 + if w.sel < len(w.pre)-1 { + w.sel++ + } + } + } + return n, nil +} diff --git a/Godeps/_workspace/src/github.com/kr/text/indent_test.go b/Godeps/_workspace/src/github.com/kr/text/indent_test.go new file mode 100644 index 00000000000..5c723eee855 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/text/indent_test.go @@ -0,0 +1,119 @@ +package text + +import ( + "bytes" + "testing" +) + +type T struct { + inp, exp, pre string +} + +var tests = []T{ + { + "The quick brown fox\njumps over the lazy\ndog.\nBut not quickly.\n", + "xxxThe quick brown fox\nxxxjumps over the lazy\nxxxdog.\nxxxBut not quickly.\n", + "xxx", + }, + { + "The quick brown fox\njumps over the lazy\ndog.\n\nBut not quickly.", + "xxxThe quick brown fox\nxxxjumps over the lazy\nxxxdog.\n\nxxxBut not quickly.", + "xxx", + }, +} + +func TestIndent(t *testing.T) { + for _, test := range tests { + got := Indent(test.inp, test.pre) + if got != test.exp { + t.Errorf("mismatch %q != %q", got, test.exp) + } + } +} + +type IndentWriterTest struct { + inp, exp string + pre []string +} + +var ts = []IndentWriterTest{ + { + ` +The quick brown fox +jumps over the lazy +dog. +But not quickly. +`[1:], + ` +xxxThe quick brown fox +xxxjumps over the lazy +xxxdog. +xxxBut not quickly. +`[1:], + []string{"xxx"}, + }, + { + ` +The quick brown fox +jumps over the lazy +dog. +But not quickly. +`[1:], + ` +xxaThe quick brown fox +xxxjumps over the lazy +xxxdog. +xxxBut not quickly. +`[1:], + []string{"xxa", "xxx"}, + }, + { + ` +The quick brown fox +jumps over the lazy +dog. +But not quickly. +`[1:], + ` +xxaThe quick brown fox +xxbjumps over the lazy +xxcdog. +xxxBut not quickly. +`[1:], + []string{"xxa", "xxb", "xxc", "xxx"}, + }, + { + ` +The quick brown fox +jumps over the lazy +dog. + +But not quickly.`[1:], + ` +xxaThe quick brown fox +xxxjumps over the lazy +xxxdog. +xxx +xxxBut not quickly.`[1:], + []string{"xxa", "xxx"}, + }, +} + +func TestIndentWriter(t *testing.T) { + for _, test := range ts { + b := new(bytes.Buffer) + pre := make([][]byte, len(test.pre)) + for i := range test.pre { + pre[i] = []byte(test.pre[i]) + } + w := NewIndentWriter(b, pre...) + if _, err := w.Write([]byte(test.inp)); err != nil { + t.Error(err) + } + if got := b.String(); got != test.exp { + t.Errorf("mismatch %q != %q", got, test.exp) + t.Log(got) + t.Log(test.exp) + } + } +} diff --git a/Godeps/_workspace/src/github.com/kr/text/mc/Readme b/Godeps/_workspace/src/github.com/kr/text/mc/Readme new file mode 100644 index 00000000000..519ddc00a13 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/text/mc/Readme @@ -0,0 +1,9 @@ +Command mc prints in multiple columns. + + Usage: mc [-] [-N] [file...] + +Mc splits the input into as many columns as will fit in N +print positions. If the output is a tty, the default N is +the number of characters in a terminal line; otherwise the +default N is 80. Under option - each input line ending in +a colon ':' is printed separately. diff --git a/Godeps/_workspace/src/github.com/kr/text/mc/mc.go b/Godeps/_workspace/src/github.com/kr/text/mc/mc.go new file mode 100644 index 00000000000..00169a30f16 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/text/mc/mc.go @@ -0,0 +1,62 @@ +// Command mc prints in multiple columns. +// +// Usage: mc [-] [-N] [file...] +// +// Mc splits the input into as many columns as will fit in N +// print positions. If the output is a tty, the default N is +// the number of characters in a terminal line; otherwise the +// default N is 80. Under option - each input line ending in +// a colon ':' is printed separately. +package main + +import ( + "github.com/kr/pty" + "github.com/kr/text/colwriter" + "io" + "log" + "os" + "strconv" +) + +func main() { + var width int + var flag uint + args := os.Args[1:] + for len(args) > 0 && len(args[0]) > 0 && args[0][0] == '-' { + if len(args[0]) > 1 { + width, _ = strconv.Atoi(args[0][1:]) + } else { + flag |= colwriter.BreakOnColon + } + args = args[1:] + } + if width < 1 { + _, width, _ = pty.Getsize(os.Stdout) + } + if width < 1 { + width = 80 + } + + w := colwriter.NewWriter(os.Stdout, width, flag) + if len(args) > 0 { + for _, s := range args { + if f, err := os.Open(s); err == nil { + copyin(w, f) + f.Close() + } else { + log.Println(err) + } + } + } else { + copyin(w, os.Stdin) + } +} + +func copyin(w *colwriter.Writer, r io.Reader) { + if _, err := io.Copy(w, r); err != nil { + log.Println(err) + } + if err := w.Flush(); err != nil { + log.Println(err) + } +} diff --git a/Godeps/_workspace/src/github.com/kr/text/wrap.go b/Godeps/_workspace/src/github.com/kr/text/wrap.go new file mode 100644 index 00000000000..b09bb03736d --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/text/wrap.go @@ -0,0 +1,86 @@ +package text + +import ( + "bytes" + "math" +) + +var ( + nl = []byte{'\n'} + sp = []byte{' '} +) + +const defaultPenalty = 1e5 + +// Wrap wraps s into a paragraph of lines of length lim, with minimal +// raggedness. +func Wrap(s string, lim int) string { + return string(WrapBytes([]byte(s), lim)) +} + +// WrapBytes wraps b into a paragraph of lines of length lim, with minimal +// raggedness. +func WrapBytes(b []byte, lim int) []byte { + words := bytes.Split(bytes.Replace(bytes.TrimSpace(b), nl, sp, -1), sp) + var lines [][]byte + for _, line := range WrapWords(words, 1, lim, defaultPenalty) { + lines = append(lines, bytes.Join(line, sp)) + } + return bytes.Join(lines, nl) +} + +// WrapWords is the low-level line-breaking algorithm, useful if you need more +// control over the details of the text wrapping process. For most uses, either +// Wrap or WrapBytes will be sufficient and more convenient. +// +// WrapWords splits a list of words into lines with minimal "raggedness", +// treating each byte as one unit, accounting for spc units between adjacent +// words on each line, and attempting to limit lines to lim units. Raggedness +// is the total error over all lines, where error is the square of the +// difference of the length of the line and lim. Too-long lines (which only +// happen when a single word is longer than lim units) have pen penalty units +// added to the error. +func WrapWords(words [][]byte, spc, lim, pen int) [][][]byte { + n := len(words) + + length := make([][]int, n) + for i := 0; i < n; i++ { + length[i] = make([]int, n) + length[i][i] = len(words[i]) + for j := i + 1; j < n; j++ { + length[i][j] = length[i][j-1] + spc + len(words[j]) + } + } + + nbrk := make([]int, n) + cost := make([]int, n) + for i := range cost { + cost[i] = math.MaxInt32 + } + for i := n - 1; i >= 0; i-- { + if length[i][n-1] <= lim || i == n-1 { + cost[i] = 0 + nbrk[i] = n + } else { + for j := i + 1; j < n; j++ { + d := lim - length[i][j-1] + c := d*d + cost[j] + if length[i][j-1] > lim { + c += pen // too-long lines get a worse penalty + } + if c < cost[i] { + cost[i] = c + nbrk[i] = j + } + } + } + } + + var lines [][][]byte + i := 0 + for i < n { + lines = append(lines, words[i:nbrk[i]]) + i = nbrk[i] + } + return lines +} diff --git a/Godeps/_workspace/src/github.com/kr/text/wrap_test.go b/Godeps/_workspace/src/github.com/kr/text/wrap_test.go new file mode 100644 index 00000000000..634b6e8ebb9 --- /dev/null +++ b/Godeps/_workspace/src/github.com/kr/text/wrap_test.go @@ -0,0 +1,62 @@ +package text + +import ( + "bytes" + "testing" +) + +var text = "The quick brown fox jumps over the lazy dog." + +func TestWrap(t *testing.T) { + exp := [][]string{ + {"The", "quick", "brown", "fox"}, + {"jumps", "over", "the", "lazy", "dog."}, + } + words := bytes.Split([]byte(text), sp) + got := WrapWords(words, 1, 24, defaultPenalty) + if len(exp) != len(got) { + t.Fail() + } + for i := range exp { + if len(exp[i]) != len(got[i]) { + t.Fail() + } + for j := range exp[i] { + if exp[i][j] != string(got[i][j]) { + t.Fatal(i, exp[i][j], got[i][j]) + } + } + } +} + +func TestWrapNarrow(t *testing.T) { + exp := "The\nquick\nbrown\nfox\njumps\nover\nthe\nlazy\ndog." + if Wrap(text, 5) != exp { + t.Fail() + } +} + +func TestWrapOneLine(t *testing.T) { + exp := "The quick brown fox jumps over the lazy dog." + if Wrap(text, 500) != exp { + t.Fail() + } +} + +func TestWrapBug1(t *testing.T) { + cases := []struct { + limit int + text string + want string + }{ + {4, "aaaaa", "aaaaa"}, + {4, "a aaaaa", "a\naaaaa"}, + } + + for _, test := range cases { + got := Wrap(test.text, test.limit) + if got != test.want { + t.Errorf("Wrap(%q, %d) = %q want %q", test.text, test.limit, got, test.want) + } + } +} diff --git a/pkg/api/api.go b/pkg/api/api.go index d4bb22149f8..c350b278679 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -176,7 +176,7 @@ func Register(r *macaron.Macaron) { r.Get("/", wrap(GetPluginList)) r.Get("/dashboards/:pluginId", wrap(GetPluginDashboards)) - r.Post("/dashboards/install", bind(dtos.InstallPluginDashboardCmd{}), wrap(InstallPluginDashboard)) + r.Post("/dashboards/import", bind(dtos.ImportDashboardCommand{}), wrap(ImportDashboard)) r.Get("/:pluginId/settings", wrap(GetPluginSettingById)) r.Post("/:pluginId/settings", bind(m.UpdatePluginSettingCmd{}), wrap(UpdatePluginSetting)) diff --git a/pkg/api/cloudwatch/metrics.go b/pkg/api/cloudwatch/metrics.go index 54ab565bede..f5e5274a202 100644 --- a/pkg/api/cloudwatch/metrics.go +++ b/pkg/api/cloudwatch/metrics.go @@ -130,11 +130,14 @@ func handleGetNamespaces(req *cwRequest, c *middleware.Context) { for key := range metricsMap { keys = append(keys, key) } - if customMetricsNamespaces, ok := req.DataSource.JsonData["customMetricsNamespaces"].(string); ok { - for _, key := range strings.Split(customMetricsNamespaces, ",") { + + customNamespaces := req.DataSource.JsonData.Get("customMetricsNamespaces").MustString() + if customNamespaces != "" { + for _, key := range strings.Split(customNamespaces, ",") { keys = append(keys, key) } } + sort.Sort(sort.StringSlice(keys)) result := []interface{}{} diff --git a/pkg/api/dashboard_snapshot.go b/pkg/api/dashboard_snapshot.go index 8ed848a7b11..3c369ca5b7f 100644 --- a/pkg/api/dashboard_snapshot.go +++ b/pkg/api/dashboard_snapshot.go @@ -53,7 +53,6 @@ func CreateDashboardSnapshot(c *middleware.Context, cmd m.CreateDashboardSnapsho } func GetDashboardSnapshot(c *middleware.Context) { - key := c.Params(":key") query := &m.GetDashboardSnapshotQuery{Key: key} @@ -136,5 +135,4 @@ func SearchDashboardSnapshots(c *middleware.Context) Response { } return Json(200, dtos) - //return Json(200, searchQuery.Result) } diff --git a/pkg/api/dtos/models.go b/pkg/api/dtos/models.go index 36e51fdc28a..26295dd3d3c 100644 --- a/pkg/api/dtos/models.go +++ b/pkg/api/dtos/models.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/grafana/grafana/pkg/components/simplejson" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/setting" ) @@ -52,26 +53,26 @@ type DashboardMeta struct { } type DashboardFullWithMeta struct { - Meta DashboardMeta `json:"meta"` - Dashboard map[string]interface{} `json:"dashboard"` + Meta DashboardMeta `json:"meta"` + Dashboard *simplejson.Json `json:"dashboard"` } type DataSource struct { - Id int64 `json:"id"` - OrgId int64 `json:"orgId"` - Name string `json:"name"` - Type string `json:"type"` - Access m.DsAccess `json:"access"` - Url string `json:"url"` - Password string `json:"password"` - User string `json:"user"` - Database string `json:"database"` - BasicAuth bool `json:"basicAuth"` - BasicAuthUser string `json:"basicAuthUser"` - BasicAuthPassword string `json:"basicAuthPassword"` - WithCredentials bool `json:"withCredentials"` - IsDefault bool `json:"isDefault"` - JsonData map[string]interface{} `json:"jsonData,omitempty"` + Id int64 `json:"id"` + OrgId int64 `json:"orgId"` + Name string `json:"name"` + Type string `json:"type"` + Access m.DsAccess `json:"access"` + Url string `json:"url"` + Password string `json:"password"` + User string `json:"user"` + Database string `json:"database"` + BasicAuth bool `json:"basicAuth"` + BasicAuthUser string `json:"basicAuthUser"` + BasicAuthPassword string `json:"basicAuthPassword"` + WithCredentials bool `json:"withCredentials"` + IsDefault bool `json:"isDefault"` + JsonData *simplejson.Json `json:"jsonData,omitempty"` } type MetricQueryResultDto struct { diff --git a/pkg/api/dtos/plugins.go b/pkg/api/dtos/plugins.go index 5f3dac76296..7008c939aef 100644 --- a/pkg/api/dtos/plugins.go +++ b/pkg/api/dtos/plugins.go @@ -26,9 +26,9 @@ type PluginListItem struct { Info *plugins.PluginInfo `json:"info"` } -type InstallPluginDashboardCmd struct { - PluginId string `json:"pluginId"` - Path string `json:"path"` - Reinstall bool `json:"reinstall"` - Inputs map[string]interface{} `json:"inputs"` +type ImportDashboardCommand struct { + PluginId string `json:"pluginId"` + Path string `json:"path"` + Reinstall bool `json:"reinstall"` + Inputs []plugins.ImportDashboardInput `json:"inputs"` } diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 8c86f98220a..c84d7faccff 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -59,7 +59,7 @@ func getFrontendSettingsMap(c *middleware.Context) (map[string]interface{}, erro defaultDatasource = ds.Name } - if len(ds.JsonData) > 0 { + if len(ds.JsonData.MustMap()) > 0 { dsMap["jsonData"] = ds.JsonData } diff --git a/pkg/api/plugins.go b/pkg/api/plugins.go index 1ba0dde6c9f..2eb27c3c8ed 100644 --- a/pkg/api/plugins.go +++ b/pkg/api/plugins.go @@ -122,9 +122,9 @@ func GetPluginDashboards(c *middleware.Context) Response { } } -func InstallPluginDashboard(c *middleware.Context, apiCmd dtos.InstallPluginDashboardCmd) Response { +func ImportDashboard(c *middleware.Context, apiCmd dtos.ImportDashboardCommand) Response { - cmd := plugins.InstallPluginDashboardCommand{ + cmd := plugins.ImportDashboardCommand{ OrgId: c.OrgId, UserId: c.UserId, PluginId: apiCmd.PluginId, diff --git a/pkg/components/dynmap/dynmap.go b/pkg/components/dynmap/dynmap.go new file mode 100644 index 00000000000..797694845cd --- /dev/null +++ b/pkg/components/dynmap/dynmap.go @@ -0,0 +1,817 @@ +// uses code from https://github.com/antonholmquist/jason/blob/master/jason.go +// MIT Licence + +package dynmap + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "strings" +) + +// Error values returned when validation functions fail +var ( + ErrNotNull = errors.New("is not null") + ErrNotArray = errors.New("Not an array") + ErrNotNumber = errors.New("not a number") + ErrNotBool = errors.New("no bool") + ErrNotObject = errors.New("not an object") + ErrNotObjectArray = errors.New("not an object array") + ErrNotString = errors.New("not a string") +) + +type KeyNotFoundError struct { + Key string +} + +func (k KeyNotFoundError) Error() string { + if k.Key != "" { + return fmt.Sprintf("key '%s' not found", k.Key) + } + + return "key not found" +} + +// Value represents an arbitrary JSON value. +// It may contain a bool, number, string, object, array or null. +type Value struct { + data interface{} + exists bool // Used to separate nil and non-existing values +} + +// Object represents an object JSON object. +// It inherets from Value but with an additional method to access +// a map representation of it's content. It's useful when iterating. +type Object struct { + Value + m map[string]*Value + valid bool +} + +// Returns the golang map. +// Needed when iterating through the values of the object. +func (v *Object) Map() map[string]*Value { + return v.m +} + +func NewFromMap(data map[string]interface{}) *Object { + val := &Value{data: data, exists: true} + obj, _ := val.Object() + return obj +} + +func NewObject() *Object { + val := &Value{data: make(map[string]interface{}), exists: true} + obj, _ := val.Object() + return obj +} + +// Creates a new value from an io.reader. +// Returns an error if the reader does not contain valid json. +// Useful for parsing the body of a net/http response. +// Example: NewFromReader(res.Body) +func NewValueFromReader(reader io.Reader) (*Value, error) { + j := new(Value) + d := json.NewDecoder(reader) + d.UseNumber() + err := d.Decode(&j.data) + return j, err +} + +// Creates a new value from bytes. +// Returns an error if the bytes are not valid json. +func NewValueFromBytes(b []byte) (*Value, error) { + r := bytes.NewReader(b) + return NewValueFromReader(r) +} + +func objectFromValue(v *Value, err error) (*Object, error) { + if err != nil { + return nil, err + } + + o, err := v.Object() + + if err != nil { + return nil, err + } + + return o, nil +} + +func NewObjectFromBytes(b []byte) (*Object, error) { + return objectFromValue(NewValueFromBytes(b)) +} + +func NewObjectFromReader(reader io.Reader) (*Object, error) { + return objectFromValue(NewValueFromReader(reader)) +} + +// Marshal into bytes. +func (v *Value) Marshal() ([]byte, error) { + return json.Marshal(v.data) +} + +// Get the interyling data as interface +func (v *Value) Interface() interface{} { + return v.data +} + +func (v *Value) StringMap() map[string]interface{} { + return v.data.(map[string]interface{}) +} + +// Private Get +func (v *Value) get(key string) (*Value, error) { + + // Assume this is an object + obj, err := v.Object() + + if err == nil { + child, ok := obj.Map()[key] + if ok { + return child, nil + } else { + return nil, KeyNotFoundError{key} + } + } + + return nil, err +} + +// Private get path +func (v *Value) getPath(keys []string) (*Value, error) { + current := v + var err error + for _, key := range keys { + current, err = current.get(key) + + if err != nil { + return nil, err + } + } + return current, nil +} + +// Gets the value at key path. +// Returns error if the value does not exist. +// Consider using the more specific Get(..) methods instead. +// Example: +// value, err := GetValue("address", "street") +func (v *Object) GetValue(keys ...string) (*Value, error) { + return v.getPath(keys) +} + +// Gets the value at key path and attempts to typecast the value into an object. +// Returns error if the value is not a json object. +// Example: +// object, err := GetObject("person", "address") +func (v *Object) GetObject(keys ...string) (*Object, error) { + child, err := v.getPath(keys) + + if err != nil { + return nil, err + } else { + + obj, err := child.Object() + + if err != nil { + return nil, err + } else { + return obj, nil + } + + } +} + +// Gets the value at key path and attempts to typecast the value into a string. +// Returns error if the value is not a json string. +// Example: +// string, err := GetString("address", "street") +func (v *Object) GetString(keys ...string) (string, error) { + child, err := v.getPath(keys) + + if err != nil { + return "", err + } else { + return child.String() + } +} + +func (v *Object) MustGetString(path string, def string) string { + keys := strings.Split(path, ".") + if str, err := v.GetString(keys...); err != nil { + return def + } else { + return str + } +} + +// Gets the value at key path and attempts to typecast the value into null. +// Returns error if the value is not json null. +// Example: +// err := GetNull("address", "street") +func (v *Object) GetNull(keys ...string) error { + child, err := v.getPath(keys) + + if err != nil { + return err + } + + return child.Null() +} + +// Gets the value at key path and attempts to typecast the value into a number. +// Returns error if the value is not a json number. +// Example: +// n, err := GetNumber("address", "street_number") +func (v *Object) GetNumber(keys ...string) (json.Number, error) { + child, err := v.getPath(keys) + + if err != nil { + return "", err + } else { + + n, err := child.Number() + + if err != nil { + return "", err + } else { + return n, nil + } + } +} + +// Gets the value at key path and attempts to typecast the value into a float64. +// Returns error if the value is not a json number. +// Example: +// n, err := GetNumber("address", "street_number") +func (v *Object) GetFloat64(keys ...string) (float64, error) { + child, err := v.getPath(keys) + + if err != nil { + return 0, err + } else { + + n, err := child.Float64() + + if err != nil { + return 0, err + } else { + return n, nil + } + } +} + +// Gets the value at key path and attempts to typecast the value into a float64. +// Returns error if the value is not a json number. +// Example: +// n, err := GetNumber("address", "street_number") +func (v *Object) GetInt64(keys ...string) (int64, error) { + child, err := v.getPath(keys) + + if err != nil { + return 0, err + } else { + + n, err := child.Int64() + + if err != nil { + return 0, err + } else { + return n, nil + } + } +} + +// Gets the value at key path and attempts to typecast the value into a float64. +// Returns error if the value is not a json number. +// Example: +// v, err := GetInterface("address", "anything") +func (v *Object) GetInterface(keys ...string) (interface{}, error) { + child, err := v.getPath(keys) + + if err != nil { + return nil, err + } else { + return child.Interface(), nil + } +} + +// Gets the value at key path and attempts to typecast the value into a bool. +// Returns error if the value is not a json boolean. +// Example: +// married, err := GetBoolean("person", "married") +func (v *Object) GetBoolean(keys ...string) (bool, error) { + child, err := v.getPath(keys) + + if err != nil { + return false, err + } + + return child.Boolean() +} + +// Gets the value at key path and attempts to typecast the value into an array. +// Returns error if the value is not a json array. +// Consider using the more specific GetArray() since it may reduce later type casts. +// Example: +// friends, err := GetValueArray("person", "friends") +// for i, friend := range friends { +// ... // friend will be of type Value here +// } +func (v *Object) GetValueArray(keys ...string) ([]*Value, error) { + child, err := v.getPath(keys) + + if err != nil { + return nil, err + } else { + + return child.Array() + + } +} + +// Gets the value at key path and attempts to typecast the value into an array of objects. +// Returns error if the value is not a json array or if any of the contained objects are not objects. +// Example: +// friends, err := GetObjectArray("person", "friends") +// for i, friend := range friends { +// ... // friend will be of type Object here +// } +func (v *Object) GetObjectArray(keys ...string) ([]*Object, error) { + child, err := v.getPath(keys) + + if err != nil { + return nil, err + } else { + + array, err := child.Array() + + if err != nil { + return nil, err + } else { + + typedArray := make([]*Object, len(array)) + + for index, arrayItem := range array { + typedArrayItem, err := arrayItem. + Object() + + if err != nil { + return nil, err + } else { + typedArray[index] = typedArrayItem + } + + } + return typedArray, nil + } + } +} + +// Gets the value at key path and attempts to typecast the value into an array of string. +// Returns error if the value is not a json array or if any of the contained objects are not strings. +// Gets the value at key path and attempts to typecast the value into an array of objects. +// Returns error if the value is not a json array or if any of the contained objects are not objects. +// Example: +// friendNames, err := GetStringArray("person", "friend_names") +// for i, friendName := range friendNames { +// ... // friendName will be of type string here +// } +func (v *Object) GetStringArray(keys ...string) ([]string, error) { + child, err := v.getPath(keys) + + if err != nil { + return nil, err + } else { + + array, err := child.Array() + + if err != nil { + return nil, err + } else { + + typedArray := make([]string, len(array)) + + for index, arrayItem := range array { + typedArrayItem, err := arrayItem.String() + + if err != nil { + return nil, err + } else { + typedArray[index] = typedArrayItem + } + + } + return typedArray, nil + } + } +} + +// Gets the value at key path and attempts to typecast the value into an array of numbers. +// Returns error if the value is not a json array or if any of the contained objects are not numbers. +// Example: +// friendAges, err := GetNumberArray("person", "friend_ages") +// for i, friendAge := range friendAges { +// ... // friendAge will be of type float64 here +// } +func (v *Object) GetNumberArray(keys ...string) ([]json.Number, error) { + child, err := v.getPath(keys) + + if err != nil { + return nil, err + } else { + + array, err := child.Array() + + if err != nil { + return nil, err + } else { + + typedArray := make([]json.Number, len(array)) + + for index, arrayItem := range array { + typedArrayItem, err := arrayItem.Number() + + if err != nil { + return nil, err + } else { + typedArray[index] = typedArrayItem + } + + } + return typedArray, nil + } + } +} + +// Gets the value at key path and attempts to typecast the value into an array of floats. +// Returns error if the value is not a json array or if any of the contained objects are not numbers. +func (v *Object) GetFloat64Array(keys ...string) ([]float64, error) { + child, err := v.getPath(keys) + + if err != nil { + return nil, err + } else { + + array, err := child.Array() + + if err != nil { + return nil, err + } else { + + typedArray := make([]float64, len(array)) + + for index, arrayItem := range array { + typedArrayItem, err := arrayItem.Float64() + + if err != nil { + return nil, err + } else { + typedArray[index] = typedArrayItem + } + + } + return typedArray, nil + } + } +} + +// Gets the value at key path and attempts to typecast the value into an array of ints. +// Returns error if the value is not a json array or if any of the contained objects are not numbers. +func (v *Object) GetInt64Array(keys ...string) ([]int64, error) { + child, err := v.getPath(keys) + + if err != nil { + return nil, err + } else { + + array, err := child.Array() + + if err != nil { + return nil, err + } else { + + typedArray := make([]int64, len(array)) + + for index, arrayItem := range array { + typedArrayItem, err := arrayItem.Int64() + + if err != nil { + return nil, err + } else { + typedArray[index] = typedArrayItem + } + + } + return typedArray, nil + } + } +} + +// Gets the value at key path and attempts to typecast the value into an array of bools. +// Returns error if the value is not a json array or if any of the contained objects are not booleans. +func (v *Object) GetBooleanArray(keys ...string) ([]bool, error) { + child, err := v.getPath(keys) + + if err != nil { + return nil, err + } else { + + array, err := child.Array() + + if err != nil { + return nil, err + } else { + + typedArray := make([]bool, len(array)) + + for index, arrayItem := range array { + typedArrayItem, err := arrayItem.Boolean() + + if err != nil { + return nil, err + } else { + typedArray[index] = typedArrayItem + } + + } + return typedArray, nil + } + } +} + +// Gets the value at key path and attempts to typecast the value into an array of nulls. +// Returns length, or an error if the value is not a json array or if any of the contained objects are not nulls. +func (v *Object) GetNullArray(keys ...string) (int64, error) { + child, err := v.getPath(keys) + + if err != nil { + return 0, err + } else { + + array, err := child.Array() + + if err != nil { + return 0, err + } else { + + var length int64 = 0 + + for _, arrayItem := range array { + err := arrayItem.Null() + + if err != nil { + return 0, err + } else { + length++ + } + + } + return length, nil + } + } +} + +// Returns an error if the value is not actually null +func (v *Value) Null() error { + var valid bool + + // Check the type of this data + switch v.data.(type) { + case nil: + valid = v.exists // Valid only if j also exists, since other values could possibly also be nil + break + } + + if valid { + return nil + } + + return ErrNotNull + +} + +// Attempts to typecast the current value into an array. +// Returns error if the current value is not a json array. +// Example: +// friendsArray, err := friendsValue.Array() +func (v *Value) Array() ([]*Value, error) { + var valid bool + + // Check the type of this data + switch v.data.(type) { + case []interface{}: + valid = true + break + } + + // Unsure if this is a good way to use slices, it's probably not + var slice []*Value + + if valid { + + for _, element := range v.data.([]interface{}) { + child := Value{element, true} + slice = append(slice, &child) + } + + return slice, nil + } + + return slice, ErrNotArray + +} + +// Attempts to typecast the current value into a number. +// Returns error if the current value is not a json number. +// Example: +// ageNumber, err := ageValue.Number() +func (v *Value) Number() (json.Number, error) { + var valid bool + + // Check the type of this data + switch v.data.(type) { + case json.Number: + valid = true + break + } + + if valid { + return v.data.(json.Number), nil + } + + return "", ErrNotNumber +} + +// Attempts to typecast the current value into a float64. +// Returns error if the current value is not a json number. +// Example: +// percentage, err := v.Float64() +func (v *Value) Float64() (float64, error) { + n, err := v.Number() + + if err != nil { + return 0, err + } + + return n.Float64() +} + +// Attempts to typecast the current value into a int64. +// Returns error if the current value is not a json number. +// Example: +// id, err := v.Int64() +func (v *Value) Int64() (int64, error) { + n, err := v.Number() + + if err != nil { + return 0, err + } + + return n.Int64() +} + +// Attempts to typecast the current value into a bool. +// Returns error if the current value is not a json boolean. +// Example: +// marriedBool, err := marriedValue.Boolean() +func (v *Value) Boolean() (bool, error) { + var valid bool + + // Check the type of this data + switch v.data.(type) { + case bool: + valid = true + break + } + + if valid { + return v.data.(bool), nil + } + + return false, ErrNotBool +} + +// Attempts to typecast the current value into an object. +// Returns error if the current value is not a json object. +// Example: +// friendObject, err := friendValue.Object() +func (v *Value) Object() (*Object, error) { + + var valid bool + + // Check the type of this data + switch v.data.(type) { + case map[string]interface{}: + valid = true + break + } + + if valid { + obj := new(Object) + obj.valid = valid + + m := make(map[string]*Value) + + if valid { + for key, element := range v.data.(map[string]interface{}) { + m[key] = &Value{element, true} + + } + } + + obj.data = v.data + obj.m = m + + return obj, nil + } + + return nil, ErrNotObject +} + +// Attempts to typecast the current value into an object arrau. +// Returns error if the current value is not an array of json objects +// Example: +// friendObjects, err := friendValues.ObjectArray() +func (v *Value) ObjectArray() ([]*Object, error) { + + var valid bool + + // Check the type of this data + switch v.data.(type) { + case []interface{}: + valid = true + break + } + + // Unsure if this is a good way to use slices, it's probably not + var slice []*Object + + if valid { + + for _, element := range v.data.([]interface{}) { + childValue := Value{element, true} + childObject, err := childValue.Object() + + if err != nil { + return nil, ErrNotObjectArray + } + slice = append(slice, childObject) + } + + return slice, nil + } + + return nil, ErrNotObjectArray + +} + +// Attempts to typecast the current value into a string. +// Returns error if the current value is not a json string +// Example: +// nameObject, err := nameValue.String() +func (v *Value) String() (string, error) { + var valid bool + + // Check the type of this data + switch v.data.(type) { + case string: + valid = true + break + } + + if valid { + return v.data.(string), nil + } + + return "", ErrNotString +} + +// Returns the value a json formatted string. +// Note: The method named String() is used by golang's log method for logging. +// Example: +func (v *Object) String() string { + + f, err := json.Marshal(v.data) + if err != nil { + return err.Error() + } + + return string(f) + +} + +func (v *Object) SetValue(key string, value interface{}) *Value { + data := v.Interface().(map[string]interface{}) + data[key] = value + + return &Value{ + data: value, + exists: true, + } +} diff --git a/pkg/components/dynmap/dynmap_test.go b/pkg/components/dynmap/dynmap_test.go new file mode 100644 index 00000000000..cc002ea06e0 --- /dev/null +++ b/pkg/components/dynmap/dynmap_test.go @@ -0,0 +1,313 @@ +// uses code from https://github.com/antonholmquist/jason/blob/master/jason.go +// MIT Licence + +package dynmap + +import ( + "log" + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +type Assert struct { + T *testing.T +} + +func NewAssert(t *testing.T) *Assert { + return &Assert{ + T: t, + } +} + +func (assert *Assert) True(value bool, message string) { + if value == false { + log.Panicln("Assert: ", message) + } +} + +func TestFirst(t *testing.T) { + + assert := NewAssert(t) + + testJSON := `{ + "name": "anton", + "age": 29, + "nothing": null, + "true": true, + "false": false, + "list": [ + "first", + "second" + ], + "list2": [ + { + "street": "Street 42", + "city": "Stockholm" + }, + { + "street": "Street 42", + "city": "Stockholm" + } + ], + "address": { + "street": "Street 42", + "city": "Stockholm" + }, + "country": { + "name": "Sweden" + } + }` + + j, err := NewObjectFromBytes([]byte(testJSON)) + + a, err := j.GetObject("address") + assert.True(a != nil && err == nil, "failed to create json from string") + + assert.True(err == nil, "failed to create json from string") + + s, err := j.GetString("name") + assert.True(s == "anton" && err == nil, "name should be a string") + + s = j.MustGetString("name", "fallback") + assert.True(s == "anton", "must get string") + + s = j.MustGetString("adsasdas", "fallback") + assert.True(s == "fallback", "must get string return fallback") + + s, err = j.GetString("name") + assert.True(s == "anton" && err == nil, "name shoud match") + + s, err = j.GetString("address", "street") + assert.True(s == "Street 42" && err == nil, "street shoud match") + //log.Println("s: ", s.String()) + + _, err = j.GetNumber("age") + assert.True(err == nil, "age should be a number") + + n, err := j.GetInt64("age") + assert.True(n == 29 && err == nil, "age mismatch") + + ageInterface, err := j.GetInterface("age") + assert.True(ageInterface != nil, "should be defined") + assert.True(err == nil, "age interface error") + + invalidInterface, err := j.GetInterface("not_existing") + assert.True(invalidInterface == nil, "should not give error here") + assert.True(err != nil, "should give error here") + + age, err := j.GetValue("age") + assert.True(age != nil && err == nil, "age should exist") + + age2, err := j.GetValue("age2") + assert.True(age2 == nil && err != nil, "age2 should not exist") + + address, err := j.GetObject("address") + assert.True(address != nil && err == nil, "address should be an object") + + //log.Println("address: ", address) + + s, err = address.GetString("street") + + addressAsString, err := j.GetString("address") + assert.True(addressAsString == "" && err != nil, "address should not be an string") + + s, err = j.GetString("address", "street") + assert.True(s == "Street 42" && err == nil, "street mismatching") + + s, err = j.GetString("address", "name2") + assert.True(s == "" && err != nil, "nonexistent string fail") + + b, err := j.GetBoolean("true") + assert.True(b == true && err == nil, "bool true test") + + b, err = j.GetBoolean("false") + assert.True(b == false && err == nil, "bool false test") + + b, err = j.GetBoolean("invalid_field") + assert.True(b == false && err != nil, "bool invalid test") + + list, err := j.GetValueArray("list") + assert.True(list != nil && err == nil, "list should be an array") + + list2, err := j.GetValueArray("list2") + assert.True(list2 != nil && err == nil, "list2 should be an array") + + list2Array, err := j.GetValueArray("list2") + assert.True(err == nil, "List2 should not return error on AsArray") + assert.True(len(list2Array) == 2, "List2 should should have length 2") + + list2Value, err := j.GetValue("list2") + assert.True(err == nil, "List2 should not return error on value") + + list2ObjectArray, err := list2Value.ObjectArray() + assert.True(err == nil, "list2Value should not return error on ObjectArray") + assert.True(len(list2ObjectArray) == 2, "list2ObjectArray should should have length 2") + + for _, elementValue := range list2Array { + //assert.True(element.IsObject() == true, "first fail") + + element, err := elementValue.Object() + + s, err = element.GetString("street") + assert.True(s == "Street 42" && err == nil, "second fail") + } + + obj, err := j.GetObject("country") + assert.True(obj != nil && err == nil, "country should not return error on AsObject") + for key, value := range obj.Map() { + + assert.True(key == "name", "country name key incorrect") + + s, err = value.String() + assert.True(s == "Sweden" && err == nil, "country name should be Sweden") + } +} + +func TestSecond(t *testing.T) { + json := ` + { + "data": [ + { + "id": "X999_Y999", + "from": { + "name": "Tom Brady", "id": "X12" + }, + "message": "Looking forward to 2010!", + "actions": [ + { + "name": "Comment", + "link": "http://www.facebook.com/X999/posts/Y999" + }, + { + "name": "Like", + "link": "http://www.facebook.com/X999/posts/Y999" + } + ], + "type": "status", + "created_time": "2010-08-02T21:27:44+0000", + "updated_time": "2010-08-02T21:27:44+0000" + }, + { + "id": "X998_Y998", + "from": { + "name": "Peyton Manning", "id": "X18" + }, + "message": "Where's my contract?", + "actions": [ + { + "name": "Comment", + "link": "http://www.facebook.com/X998/posts/Y998" + }, + { + "name": "Like", + "link": "http://www.facebook.com/X998/posts/Y998" + } + ], + "type": "status", + "created_time": "2010-08-02T21:27:44+0000", + "updated_time": "2010-08-02T21:27:44+0000" + } + ] + }` + + assert := NewAssert(t) + j, err := NewObjectFromBytes([]byte(json)) + + assert.True(j != nil && err == nil, "failed to parse json") + + dataObject, err := j.GetObject("data") + assert.True(dataObject == nil && err != nil, "data should not be an object") + + dataArray, err := j.GetObjectArray("data") + assert.True(dataArray != nil && err == nil, "data should be an object array") + + for index, dataItem := range dataArray { + + if index == 0 { + id, err := dataItem.GetString("id") + assert.True(id == "X999_Y999" && err == nil, "item id mismatch") + + fromName, err := dataItem.GetString("from", "name") + assert.True(fromName == "Tom Brady" && err == nil, "fromName mismatch") + + actions, err := dataItem.GetObjectArray("actions") + + for index, action := range actions { + + if index == 1 { + name, err := action.GetString("name") + assert.True(name == "Like" && err == nil, "name mismatch") + + link, err := action.GetString("link") + assert.True(link == "http://www.facebook.com/X999/posts/Y999" && err == nil, "Like mismatch") + + } + + } + } else if index == 1 { + id, err := dataItem.GetString("id") + assert.True(id == "X998_Y998" && err == nil, "item id mismatch") + } + + } + +} + +func TestErrors(t *testing.T) { + json := ` + { + "string": "hello", + "number": 1, + "array": [1,2,3] + }` + + errstr := "expected an error getting %s, but got '%s'" + + j, err := NewObjectFromBytes([]byte(json)) + if err != nil { + t.Fatal("failed to parse json") + } + + if _, err = j.GetObject("string"); err != ErrNotObject { + t.Errorf(errstr, "object", err) + } + + if err = j.GetNull("string"); err != ErrNotNull { + t.Errorf(errstr, "null", err) + } + + if _, err = j.GetStringArray("string"); err != ErrNotArray { + t.Errorf(errstr, "array", err) + } + + if _, err = j.GetStringArray("array"); err != ErrNotString { + t.Errorf(errstr, "string array", err) + } + + if _, err = j.GetNumber("array"); err != ErrNotNumber { + t.Errorf(errstr, "number", err) + } + + if _, err = j.GetBoolean("array"); err != ErrNotBool { + t.Errorf(errstr, "boolean", err) + } + + if _, err = j.GetString("number"); err != ErrNotString { + t.Errorf(errstr, "string", err) + } + + _, err = j.GetString("not_found") + if e, ok := err.(KeyNotFoundError); !ok { + t.Errorf(errstr, "key not found error", e) + } + +} + +func TestWriting(t *testing.T) { + Convey("When writing", t, func() { + j, _ := NewObjectFromBytes([]byte(`{}`)) + j.SetValue("prop", "value") + So(j.MustGetString("prop", ""), ShouldEqual, "value") + }) +} diff --git a/pkg/components/simplejson/simplejson.go b/pkg/components/simplejson/simplejson.go new file mode 100644 index 00000000000..85e2f955943 --- /dev/null +++ b/pkg/components/simplejson/simplejson.go @@ -0,0 +1,468 @@ +package simplejson + +import ( + "bytes" + "encoding/json" + "errors" + "log" +) + +// returns the current implementation version +func Version() string { + return "0.5.0" +} + +type Json struct { + data interface{} +} + +func (j *Json) FromDB(data []byte) error { + j.data = make(map[string]interface{}) + + dec := json.NewDecoder(bytes.NewBuffer(data)) + dec.UseNumber() + return dec.Decode(&j.data) +} + +func (j *Json) ToDB() ([]byte, error) { + if j == nil || j.data == nil { + return nil, nil + } + + return j.Encode() +} + +// NewJson returns a pointer to a new `Json` object +// after unmarshaling `body` bytes +func NewJson(body []byte) (*Json, error) { + j := new(Json) + err := j.UnmarshalJSON(body) + if err != nil { + return nil, err + } + return j, nil +} + +// New returns a pointer to a new, empty `Json` object +func New() *Json { + return &Json{ + data: make(map[string]interface{}), + } +} + +// New returns a pointer to a new, empty `Json` object +func NewFromAny(data interface{}) *Json { + return &Json{data: data} +} + +// Interface returns the underlying data +func (j *Json) Interface() interface{} { + return j.data +} + +// Encode returns its marshaled data as `[]byte` +func (j *Json) Encode() ([]byte, error) { + return j.MarshalJSON() +} + +// EncodePretty returns its marshaled data as `[]byte` with indentation +func (j *Json) EncodePretty() ([]byte, error) { + return json.MarshalIndent(&j.data, "", " ") +} + +// Implements the json.Marshaler interface. +func (j *Json) MarshalJSON() ([]byte, error) { + return json.Marshal(&j.data) +} + +// Set modifies `Json` map by `key` and `value` +// Useful for changing single key/value in a `Json` object easily. +func (j *Json) Set(key string, val interface{}) { + m, err := j.Map() + if err != nil { + return + } + m[key] = val +} + +// SetPath modifies `Json`, recursively checking/creating map keys for the supplied path, +// and then finally writing in the value +func (j *Json) SetPath(branch []string, val interface{}) { + if len(branch) == 0 { + j.data = val + return + } + + // in order to insert our branch, we need map[string]interface{} + if _, ok := (j.data).(map[string]interface{}); !ok { + // have to replace with something suitable + j.data = make(map[string]interface{}) + } + curr := j.data.(map[string]interface{}) + + for i := 0; i < len(branch)-1; i++ { + b := branch[i] + // key exists? + if _, ok := curr[b]; !ok { + n := make(map[string]interface{}) + curr[b] = n + curr = n + continue + } + + // make sure the value is the right sort of thing + if _, ok := curr[b].(map[string]interface{}); !ok { + // have to replace with something suitable + n := make(map[string]interface{}) + curr[b] = n + } + + curr = curr[b].(map[string]interface{}) + } + + // add remaining k/v + curr[branch[len(branch)-1]] = val +} + +// Del modifies `Json` map by deleting `key` if it is present. +func (j *Json) Del(key string) { + m, err := j.Map() + if err != nil { + return + } + delete(m, key) +} + +// Get returns a pointer to a new `Json` object +// for `key` in its `map` representation +// +// useful for chaining operations (to traverse a nested JSON): +// js.Get("top_level").Get("dict").Get("value").Int() +func (j *Json) Get(key string) *Json { + m, err := j.Map() + if err == nil { + if val, ok := m[key]; ok { + return &Json{val} + } + } + return &Json{nil} +} + +// GetPath searches for the item as specified by the branch +// without the need to deep dive using Get()'s. +// +// js.GetPath("top_level", "dict") +func (j *Json) GetPath(branch ...string) *Json { + jin := j + for _, p := range branch { + jin = jin.Get(p) + } + return jin +} + +// GetIndex returns a pointer to a new `Json` object +// for `index` in its `array` representation +// +// this is the analog to Get when accessing elements of +// a json array instead of a json object: +// js.Get("top_level").Get("array").GetIndex(1).Get("key").Int() +func (j *Json) GetIndex(index int) *Json { + a, err := j.Array() + if err == nil { + if len(a) > index { + return &Json{a[index]} + } + } + return &Json{nil} +} + +// CheckGet returns a pointer to a new `Json` object and +// a `bool` identifying success or failure +// +// useful for chained operations when success is important: +// if data, ok := js.Get("top_level").CheckGet("inner"); ok { +// log.Println(data) +// } +func (j *Json) CheckGet(key string) (*Json, bool) { + m, err := j.Map() + if err == nil { + if val, ok := m[key]; ok { + return &Json{val}, true + } + } + return nil, false +} + +// Map type asserts to `map` +func (j *Json) Map() (map[string]interface{}, error) { + if m, ok := (j.data).(map[string]interface{}); ok { + return m, nil + } + return nil, errors.New("type assertion to map[string]interface{} failed") +} + +// Array type asserts to an `array` +func (j *Json) Array() ([]interface{}, error) { + if a, ok := (j.data).([]interface{}); ok { + return a, nil + } + return nil, errors.New("type assertion to []interface{} failed") +} + +// Bool type asserts to `bool` +func (j *Json) Bool() (bool, error) { + if s, ok := (j.data).(bool); ok { + return s, nil + } + return false, errors.New("type assertion to bool failed") +} + +// String type asserts to `string` +func (j *Json) String() (string, error) { + if s, ok := (j.data).(string); ok { + return s, nil + } + return "", errors.New("type assertion to string failed") +} + +// Bytes type asserts to `[]byte` +func (j *Json) Bytes() ([]byte, error) { + if s, ok := (j.data).(string); ok { + return []byte(s), nil + } + return nil, errors.New("type assertion to []byte failed") +} + +// StringArray type asserts to an `array` of `string` +func (j *Json) StringArray() ([]string, error) { + arr, err := j.Array() + if err != nil { + return nil, err + } + retArr := make([]string, 0, len(arr)) + for _, a := range arr { + if a == nil { + retArr = append(retArr, "") + continue + } + s, ok := a.(string) + if !ok { + return nil, err + } + retArr = append(retArr, s) + } + return retArr, nil +} + +// MustArray guarantees the return of a `[]interface{}` (with optional default) +// +// useful when you want to interate over array values in a succinct manner: +// for i, v := range js.Get("results").MustArray() { +// fmt.Println(i, v) +// } +func (j *Json) MustArray(args ...[]interface{}) []interface{} { + var def []interface{} + + switch len(args) { + case 0: + case 1: + def = args[0] + default: + log.Panicf("MustArray() received too many arguments %d", len(args)) + } + + a, err := j.Array() + if err == nil { + return a + } + + return def +} + +// MustMap guarantees the return of a `map[string]interface{}` (with optional default) +// +// useful when you want to interate over map values in a succinct manner: +// for k, v := range js.Get("dictionary").MustMap() { +// fmt.Println(k, v) +// } +func (j *Json) MustMap(args ...map[string]interface{}) map[string]interface{} { + var def map[string]interface{} + + switch len(args) { + case 0: + case 1: + def = args[0] + default: + log.Panicf("MustMap() received too many arguments %d", len(args)) + } + + a, err := j.Map() + if err == nil { + return a + } + + return def +} + +// MustString guarantees the return of a `string` (with optional default) +// +// useful when you explicitly want a `string` in a single value return context: +// myFunc(js.Get("param1").MustString(), js.Get("optional_param").MustString("my_default")) +func (j *Json) MustString(args ...string) string { + var def string + + switch len(args) { + case 0: + case 1: + def = args[0] + default: + log.Panicf("MustString() received too many arguments %d", len(args)) + } + + s, err := j.String() + if err == nil { + return s + } + + return def +} + +// MustStringArray guarantees the return of a `[]string` (with optional default) +// +// useful when you want to interate over array values in a succinct manner: +// for i, s := range js.Get("results").MustStringArray() { +// fmt.Println(i, s) +// } +func (j *Json) MustStringArray(args ...[]string) []string { + var def []string + + switch len(args) { + case 0: + case 1: + def = args[0] + default: + log.Panicf("MustStringArray() received too many arguments %d", len(args)) + } + + a, err := j.StringArray() + if err == nil { + return a + } + + return def +} + +// MustInt guarantees the return of an `int` (with optional default) +// +// useful when you explicitly want an `int` in a single value return context: +// myFunc(js.Get("param1").MustInt(), js.Get("optional_param").MustInt(5150)) +func (j *Json) MustInt(args ...int) int { + var def int + + switch len(args) { + case 0: + case 1: + def = args[0] + default: + log.Panicf("MustInt() received too many arguments %d", len(args)) + } + + i, err := j.Int() + if err == nil { + return i + } + + return def +} + +// MustFloat64 guarantees the return of a `float64` (with optional default) +// +// useful when you explicitly want a `float64` in a single value return context: +// myFunc(js.Get("param1").MustFloat64(), js.Get("optional_param").MustFloat64(5.150)) +func (j *Json) MustFloat64(args ...float64) float64 { + var def float64 + + switch len(args) { + case 0: + case 1: + def = args[0] + default: + log.Panicf("MustFloat64() received too many arguments %d", len(args)) + } + + f, err := j.Float64() + if err == nil { + return f + } + + return def +} + +// MustBool guarantees the return of a `bool` (with optional default) +// +// useful when you explicitly want a `bool` in a single value return context: +// myFunc(js.Get("param1").MustBool(), js.Get("optional_param").MustBool(true)) +func (j *Json) MustBool(args ...bool) bool { + var def bool + + switch len(args) { + case 0: + case 1: + def = args[0] + default: + log.Panicf("MustBool() received too many arguments %d", len(args)) + } + + b, err := j.Bool() + if err == nil { + return b + } + + return def +} + +// MustInt64 guarantees the return of an `int64` (with optional default) +// +// useful when you explicitly want an `int64` in a single value return context: +// myFunc(js.Get("param1").MustInt64(), js.Get("optional_param").MustInt64(5150)) +func (j *Json) MustInt64(args ...int64) int64 { + var def int64 + + switch len(args) { + case 0: + case 1: + def = args[0] + default: + log.Panicf("MustInt64() received too many arguments %d", len(args)) + } + + i, err := j.Int64() + if err == nil { + return i + } + + return def +} + +// MustUInt64 guarantees the return of an `uint64` (with optional default) +// +// useful when you explicitly want an `uint64` in a single value return context: +// myFunc(js.Get("param1").MustUint64(), js.Get("optional_param").MustUint64(5150)) +func (j *Json) MustUint64(args ...uint64) uint64 { + var def uint64 + + switch len(args) { + case 0: + case 1: + def = args[0] + default: + log.Panicf("MustUint64() received too many arguments %d", len(args)) + } + + i, err := j.Uint64() + if err == nil { + return i + } + + return def +} diff --git a/pkg/components/simplejson/simplejson_go11.go b/pkg/components/simplejson/simplejson_go11.go new file mode 100644 index 00000000000..1c479532cf0 --- /dev/null +++ b/pkg/components/simplejson/simplejson_go11.go @@ -0,0 +1,89 @@ +// +build go1.1 + +package simplejson + +import ( + "bytes" + "encoding/json" + "errors" + "io" + "reflect" + "strconv" +) + +// Implements the json.Unmarshaler interface. +func (j *Json) UnmarshalJSON(p []byte) error { + dec := json.NewDecoder(bytes.NewBuffer(p)) + dec.UseNumber() + return dec.Decode(&j.data) +} + +// NewFromReader returns a *Json by decoding from an io.Reader +func NewFromReader(r io.Reader) (*Json, error) { + j := new(Json) + dec := json.NewDecoder(r) + dec.UseNumber() + err := dec.Decode(&j.data) + return j, err +} + +// Float64 coerces into a float64 +func (j *Json) Float64() (float64, error) { + switch j.data.(type) { + case json.Number: + return j.data.(json.Number).Float64() + case float32, float64: + return reflect.ValueOf(j.data).Float(), nil + case int, int8, int16, int32, int64: + return float64(reflect.ValueOf(j.data).Int()), nil + case uint, uint8, uint16, uint32, uint64: + return float64(reflect.ValueOf(j.data).Uint()), nil + } + return 0, errors.New("invalid value type") +} + +// Int coerces into an int +func (j *Json) Int() (int, error) { + switch j.data.(type) { + case json.Number: + i, err := j.data.(json.Number).Int64() + return int(i), err + case float32, float64: + return int(reflect.ValueOf(j.data).Float()), nil + case int, int8, int16, int32, int64: + return int(reflect.ValueOf(j.data).Int()), nil + case uint, uint8, uint16, uint32, uint64: + return int(reflect.ValueOf(j.data).Uint()), nil + } + return 0, errors.New("invalid value type") +} + +// Int64 coerces into an int64 +func (j *Json) Int64() (int64, error) { + switch j.data.(type) { + case json.Number: + return j.data.(json.Number).Int64() + case float32, float64: + return int64(reflect.ValueOf(j.data).Float()), nil + case int, int8, int16, int32, int64: + return reflect.ValueOf(j.data).Int(), nil + case uint, uint8, uint16, uint32, uint64: + return int64(reflect.ValueOf(j.data).Uint()), nil + } + return 0, errors.New("invalid value type") +} + +// Uint64 coerces into an uint64 +func (j *Json) Uint64() (uint64, error) { + switch j.data.(type) { + case json.Number: + return strconv.ParseUint(j.data.(json.Number).String(), 10, 64) + case float32, float64: + return uint64(reflect.ValueOf(j.data).Float()), nil + case int, int8, int16, int32, int64: + return uint64(reflect.ValueOf(j.data).Int()), nil + case uint, uint8, uint16, uint32, uint64: + return reflect.ValueOf(j.data).Uint(), nil + } + return 0, errors.New("invalid value type") +} diff --git a/pkg/components/simplejson/simplejson_test.go b/pkg/components/simplejson/simplejson_test.go new file mode 100644 index 00000000000..b46ffff1873 --- /dev/null +++ b/pkg/components/simplejson/simplejson_test.go @@ -0,0 +1,248 @@ +package simplejson + +import ( + "encoding/json" + "testing" + + "github.com/bmizerany/assert" +) + +func TestSimplejson(t *testing.T) { + var ok bool + var err error + + js, err := NewJson([]byte(`{ + "test": { + "string_array": ["asdf", "ghjk", "zxcv"], + "string_array_null": ["abc", null, "efg"], + "array": [1, "2", 3], + "arraywithsubs": [{"subkeyone": 1}, + {"subkeytwo": 2, "subkeythree": 3}], + "int": 10, + "float": 5.150, + "string": "simplejson", + "bool": true, + "sub_obj": {"a": 1} + } + }`)) + + assert.NotEqual(t, nil, js) + assert.Equal(t, nil, err) + + _, ok = js.CheckGet("test") + assert.Equal(t, true, ok) + + _, ok = js.CheckGet("missing_key") + assert.Equal(t, false, ok) + + aws := js.Get("test").Get("arraywithsubs") + assert.NotEqual(t, nil, aws) + var awsval int + awsval, _ = aws.GetIndex(0).Get("subkeyone").Int() + assert.Equal(t, 1, awsval) + awsval, _ = aws.GetIndex(1).Get("subkeytwo").Int() + assert.Equal(t, 2, awsval) + awsval, _ = aws.GetIndex(1).Get("subkeythree").Int() + assert.Equal(t, 3, awsval) + + i, _ := js.Get("test").Get("int").Int() + assert.Equal(t, 10, i) + + f, _ := js.Get("test").Get("float").Float64() + assert.Equal(t, 5.150, f) + + s, _ := js.Get("test").Get("string").String() + assert.Equal(t, "simplejson", s) + + b, _ := js.Get("test").Get("bool").Bool() + assert.Equal(t, true, b) + + mi := js.Get("test").Get("int").MustInt() + assert.Equal(t, 10, mi) + + mi2 := js.Get("test").Get("missing_int").MustInt(5150) + assert.Equal(t, 5150, mi2) + + ms := js.Get("test").Get("string").MustString() + assert.Equal(t, "simplejson", ms) + + ms2 := js.Get("test").Get("missing_string").MustString("fyea") + assert.Equal(t, "fyea", ms2) + + ma2 := js.Get("test").Get("missing_array").MustArray([]interface{}{"1", 2, "3"}) + assert.Equal(t, ma2, []interface{}{"1", 2, "3"}) + + msa := js.Get("test").Get("string_array").MustStringArray() + assert.Equal(t, msa[0], "asdf") + assert.Equal(t, msa[1], "ghjk") + assert.Equal(t, msa[2], "zxcv") + + msa2 := js.Get("test").Get("string_array").MustStringArray([]string{"1", "2", "3"}) + assert.Equal(t, msa2[0], "asdf") + assert.Equal(t, msa2[1], "ghjk") + assert.Equal(t, msa2[2], "zxcv") + + msa3 := js.Get("test").Get("missing_array").MustStringArray([]string{"1", "2", "3"}) + assert.Equal(t, msa3, []string{"1", "2", "3"}) + + mm2 := js.Get("test").Get("missing_map").MustMap(map[string]interface{}{"found": false}) + assert.Equal(t, mm2, map[string]interface{}{"found": false}) + + strs, err := js.Get("test").Get("string_array").StringArray() + assert.Equal(t, err, nil) + assert.Equal(t, strs[0], "asdf") + assert.Equal(t, strs[1], "ghjk") + assert.Equal(t, strs[2], "zxcv") + + strs2, err := js.Get("test").Get("string_array_null").StringArray() + assert.Equal(t, err, nil) + assert.Equal(t, strs2[0], "abc") + assert.Equal(t, strs2[1], "") + assert.Equal(t, strs2[2], "efg") + + gp, _ := js.GetPath("test", "string").String() + assert.Equal(t, "simplejson", gp) + + gp2, _ := js.GetPath("test", "int").Int() + assert.Equal(t, 10, gp2) + + assert.Equal(t, js.Get("test").Get("bool").MustBool(), true) + + js.Set("float2", 300.0) + assert.Equal(t, js.Get("float2").MustFloat64(), 300.0) + + js.Set("test2", "setTest") + assert.Equal(t, "setTest", js.Get("test2").MustString()) + + js.Del("test2") + assert.NotEqual(t, "setTest", js.Get("test2").MustString()) + + js.Get("test").Get("sub_obj").Set("a", 2) + assert.Equal(t, 2, js.Get("test").Get("sub_obj").Get("a").MustInt()) + + js.GetPath("test", "sub_obj").Set("a", 3) + assert.Equal(t, 3, js.GetPath("test", "sub_obj", "a").MustInt()) +} + +func TestStdlibInterfaces(t *testing.T) { + val := new(struct { + Name string `json:"name"` + Params *Json `json:"params"` + }) + val2 := new(struct { + Name string `json:"name"` + Params *Json `json:"params"` + }) + + raw := `{"name":"myobject","params":{"string":"simplejson"}}` + + assert.Equal(t, nil, json.Unmarshal([]byte(raw), val)) + + assert.Equal(t, "myobject", val.Name) + assert.NotEqual(t, nil, val.Params.data) + s, _ := val.Params.Get("string").String() + assert.Equal(t, "simplejson", s) + + p, err := json.Marshal(val) + assert.Equal(t, nil, err) + assert.Equal(t, nil, json.Unmarshal(p, val2)) + assert.Equal(t, val, val2) // stable +} + +func TestSet(t *testing.T) { + js, err := NewJson([]byte(`{}`)) + assert.Equal(t, nil, err) + + js.Set("baz", "bing") + + s, err := js.GetPath("baz").String() + assert.Equal(t, nil, err) + assert.Equal(t, "bing", s) +} + +func TestReplace(t *testing.T) { + js, err := NewJson([]byte(`{}`)) + assert.Equal(t, nil, err) + + err = js.UnmarshalJSON([]byte(`{"baz":"bing"}`)) + assert.Equal(t, nil, err) + + s, err := js.GetPath("baz").String() + assert.Equal(t, nil, err) + assert.Equal(t, "bing", s) +} + +func TestSetPath(t *testing.T) { + js, err := NewJson([]byte(`{}`)) + assert.Equal(t, nil, err) + + js.SetPath([]string{"foo", "bar"}, "baz") + + s, err := js.GetPath("foo", "bar").String() + assert.Equal(t, nil, err) + assert.Equal(t, "baz", s) +} + +func TestSetPathNoPath(t *testing.T) { + js, err := NewJson([]byte(`{"some":"data","some_number":1.0,"some_bool":false}`)) + assert.Equal(t, nil, err) + + f := js.GetPath("some_number").MustFloat64(99.0) + assert.Equal(t, f, 1.0) + + js.SetPath([]string{}, map[string]interface{}{"foo": "bar"}) + + s, err := js.GetPath("foo").String() + assert.Equal(t, nil, err) + assert.Equal(t, "bar", s) + + f = js.GetPath("some_number").MustFloat64(99.0) + assert.Equal(t, f, 99.0) +} + +func TestPathWillAugmentExisting(t *testing.T) { + js, err := NewJson([]byte(`{"this":{"a":"aa","b":"bb","c":"cc"}}`)) + assert.Equal(t, nil, err) + + js.SetPath([]string{"this", "d"}, "dd") + + cases := []struct { + path []string + outcome string + }{ + { + path: []string{"this", "a"}, + outcome: "aa", + }, + { + path: []string{"this", "b"}, + outcome: "bb", + }, + { + path: []string{"this", "c"}, + outcome: "cc", + }, + { + path: []string{"this", "d"}, + outcome: "dd", + }, + } + + for _, tc := range cases { + s, err := js.GetPath(tc.path...).String() + assert.Equal(t, nil, err) + assert.Equal(t, tc.outcome, s) + } +} + +func TestPathWillOverwriteExisting(t *testing.T) { + // notice how "a" is 0.1 - but then we'll try to set at path a, foo + js, err := NewJson([]byte(`{"this":{"a":0.1,"b":"bb","c":"cc"}}`)) + assert.Equal(t, nil, err) + + js.SetPath([]string{"this", "a", "foo"}, "bar") + + s, err := js.GetPath("this", "a", "foo").String() + assert.Equal(t, nil, err) + assert.Equal(t, "bar", s) +} diff --git a/pkg/models/dashboard_snapshot.go b/pkg/models/dashboard_snapshot.go index 9bfbd06c1ef..f920e91f2e4 100644 --- a/pkg/models/dashboard_snapshot.go +++ b/pkg/models/dashboard_snapshot.go @@ -1,6 +1,10 @@ package models -import "time" +import ( + "time" + + "github.com/grafana/grafana/pkg/components/simplejson" +) // DashboardSnapshot model type DashboardSnapshot struct { @@ -17,7 +21,7 @@ type DashboardSnapshot struct { Created time.Time Updated time.Time - Dashboard map[string]interface{} + Dashboard *simplejson.Json } // DashboardSnapshotDTO without dashboard map @@ -40,9 +44,9 @@ type DashboardSnapshotDTO struct { // COMMANDS type CreateDashboardSnapshotCommand struct { - Dashboard map[string]interface{} `json:"dashboard" binding:"Required"` - Name string `json:"name" binding:"Required"` - Expires int64 `json:"expires"` + Dashboard *simplejson.Json `json:"dashboard" binding:"Required"` + Name string `json:"name" binding:"Required"` + Expires int64 `json:"expires"` // these are passed when storing an external snapshot ref External bool `json:"external"` diff --git a/pkg/models/dashboards.go b/pkg/models/dashboards.go index 3e87f504a77..6243c729624 100644 --- a/pkg/models/dashboards.go +++ b/pkg/models/dashboards.go @@ -6,6 +6,7 @@ import ( "time" "github.com/gosimple/slug" + "github.com/grafana/grafana/pkg/components/simplejson" ) // Typed errors @@ -37,14 +38,14 @@ type Dashboard struct { CreatedBy int64 Title string - Data map[string]interface{} + Data *simplejson.Json } // NewDashboard creates a new dashboard func NewDashboard(title string) *Dashboard { dash := &Dashboard{} - dash.Data = make(map[string]interface{}) - dash.Data["title"] = title + dash.Data = simplejson.New() + dash.Data.Set("title", title) dash.Title = title dash.Created = time.Now() dash.Updated = time.Now() @@ -54,34 +55,24 @@ func NewDashboard(title string) *Dashboard { // GetTags turns the tags in data json into go string array func (dash *Dashboard) GetTags() []string { - jsonTags := dash.Data["tags"] - if jsonTags == nil || jsonTags == "" { - return []string{} - } - - arr := jsonTags.([]interface{}) - b := make([]string, len(arr)) - for i := range arr { - b[i] = arr[i].(string) - } - return b + return dash.Data.Get("tags").MustStringArray() } -func NewDashboardFromJson(data map[string]interface{}) *Dashboard { +func NewDashboardFromJson(data *simplejson.Json) *Dashboard { dash := &Dashboard{} dash.Data = data - dash.Title = dash.Data["title"].(string) + dash.Title = dash.Data.Get("title").MustString() dash.UpdateSlug() - if dash.Data["id"] != nil { - dash.Id = int64(dash.Data["id"].(float64)) + if id, err := dash.Data.Get("id").Float64(); err == nil { + dash.Id = int64(id) - if dash.Data["version"] != nil { - dash.Version = int(dash.Data["version"].(float64)) + if version, err := dash.Data.Get("version").Float64(); err == nil { + dash.Version = int(version) dash.Updated = time.Now() } } else { - dash.Data["version"] = 0 + dash.Data.Set("version", 0) dash.Created = time.Now() dash.Updated = time.Now() } @@ -92,9 +83,11 @@ func NewDashboardFromJson(data map[string]interface{}) *Dashboard { // GetDashboardModel turns the command into the savable model func (cmd *SaveDashboardCommand) GetDashboardModel() *Dashboard { dash := NewDashboardFromJson(cmd.Dashboard) - if dash.Data["version"] == 0 { + + if dash.Data.Get("version").MustInt(0) == 0 { dash.CreatedBy = cmd.UserId } + dash.UpdatedBy = cmd.UserId dash.OrgId = cmd.OrgId dash.UpdateSlug() @@ -103,15 +96,12 @@ func (cmd *SaveDashboardCommand) GetDashboardModel() *Dashboard { // GetString a func (dash *Dashboard) GetString(prop string, defaultValue string) string { - if val, exists := dash.Data[prop]; exists { - return val.(string) - } - return defaultValue + return dash.Data.Get(prop).MustString(defaultValue) } // UpdateSlug updates the slug func (dash *Dashboard) UpdateSlug() { - title := strings.ToLower(dash.Data["title"].(string)) + title := strings.ToLower(dash.Data.Get("title").MustString()) dash.Slug = slug.Make(title) } @@ -120,10 +110,10 @@ func (dash *Dashboard) UpdateSlug() { // type SaveDashboardCommand struct { - Dashboard map[string]interface{} `json:"dashboard" binding:"Required"` - UserId int64 `json:"userId"` - OrgId int64 `json:"-"` - Overwrite bool `json:"overwrite"` + Dashboard *simplejson.Json `json:"dashboard" binding:"Required"` + UserId int64 `json:"userId"` + OrgId int64 `json:"-"` + Overwrite bool `json:"overwrite"` Result *Dashboard } diff --git a/pkg/models/dashboards_test.go b/pkg/models/dashboards_test.go index b0b6796c4d8..ee16508dc8a 100644 --- a/pkg/models/dashboards_test.go +++ b/pkg/models/dashboards_test.go @@ -3,6 +3,7 @@ package models import ( "testing" + "github.com/grafana/grafana/pkg/components/simplejson" . "github.com/smartystreets/goconvey/convey" ) @@ -16,12 +17,11 @@ func TestDashboardModel(t *testing.T) { }) Convey("Given a dashboard json", t, func() { - json := map[string]interface{}{ - "title": "test dash", - } + json := simplejson.New() + json.Set("title", "test dash") Convey("With tags as string value", func() { - json["tags"] = "" + json.Set("tags", "") dash := NewDashboardFromJson(json) So(len(dash.GetTags()), ShouldEqual, 0) diff --git a/pkg/models/datasource.go b/pkg/models/datasource.go index 88abd03c319..2e9d98e9700 100644 --- a/pkg/models/datasource.go +++ b/pkg/models/datasource.go @@ -3,6 +3,8 @@ package models import ( "errors" "time" + + "github.com/grafana/grafana/pkg/components/simplejson" ) const ( @@ -42,7 +44,7 @@ type DataSource struct { BasicAuthPassword string WithCredentials bool IsDefault bool - JsonData map[string]interface{} + JsonData *simplejson.Json Created time.Time Updated time.Time @@ -74,19 +76,19 @@ func IsKnownDataSourcePlugin(dsType string) bool { // Also acts as api DTO type AddDataSourceCommand struct { - Name string `json:"name" binding:"Required"` - Type string `json:"type" binding:"Required"` - Access DsAccess `json:"access" binding:"Required"` - Url string `json:"url"` - Password string `json:"password"` - Database string `json:"database"` - User string `json:"user"` - BasicAuth bool `json:"basicAuth"` - BasicAuthUser string `json:"basicAuthUser"` - BasicAuthPassword string `json:"basicAuthPassword"` - WithCredentials bool `json:"withCredentials"` - IsDefault bool `json:"isDefault"` - JsonData map[string]interface{} `json:"jsonData"` + Name string `json:"name" binding:"Required"` + Type string `json:"type" binding:"Required"` + Access DsAccess `json:"access" binding:"Required"` + Url string `json:"url"` + Password string `json:"password"` + Database string `json:"database"` + User string `json:"user"` + BasicAuth bool `json:"basicAuth"` + BasicAuthUser string `json:"basicAuthUser"` + BasicAuthPassword string `json:"basicAuthPassword"` + WithCredentials bool `json:"withCredentials"` + IsDefault bool `json:"isDefault"` + JsonData *simplejson.Json `json:"jsonData"` OrgId int64 `json:"-"` @@ -95,19 +97,19 @@ type AddDataSourceCommand struct { // Also acts as api DTO type UpdateDataSourceCommand struct { - Name string `json:"name" binding:"Required"` - Type string `json:"type" binding:"Required"` - Access DsAccess `json:"access" binding:"Required"` - Url string `json:"url"` - Password string `json:"password"` - User string `json:"user"` - Database string `json:"database"` - BasicAuth bool `json:"basicAuth"` - BasicAuthUser string `json:"basicAuthUser"` - BasicAuthPassword string `json:"basicAuthPassword"` - WithCredentials bool `json:"withCredentials"` - IsDefault bool `json:"isDefault"` - JsonData map[string]interface{} `json:"jsonData"` + Name string `json:"name" binding:"Required"` + Type string `json:"type" binding:"Required"` + Access DsAccess `json:"access" binding:"Required"` + Url string `json:"url"` + Password string `json:"password"` + User string `json:"user"` + Database string `json:"database"` + BasicAuth bool `json:"basicAuth"` + BasicAuthUser string `json:"basicAuthUser"` + BasicAuthPassword string `json:"basicAuthPassword"` + WithCredentials bool `json:"withCredentials"` + IsDefault bool `json:"isDefault"` + JsonData *simplejson.Json `json:"jsonData"` OrgId int64 `json:"-"` Id int64 `json:"-"` diff --git a/pkg/plugins/dashboard_importer.go b/pkg/plugins/dashboard_importer.go new file mode 100644 index 00000000000..b5dc286f6d1 --- /dev/null +++ b/pkg/plugins/dashboard_importer.go @@ -0,0 +1,171 @@ +package plugins + +import ( + "encoding/json" + "fmt" + "regexp" + + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/log" + m "github.com/grafana/grafana/pkg/models" +) + +type ImportDashboardCommand struct { + Path string `json:"string"` + Inputs []ImportDashboardInput `json:"inputs"` + + OrgId int64 `json:"-"` + UserId int64 `json:"-"` + PluginId string `json:"-"` + Result *PluginDashboardInfoDTO +} + +type ImportDashboardInput struct { + Type string `json:"type"` + PluginId string `json:"pluginId"` + Name string `json:"name"` + Value string `json:"value"` +} + +type DashboardInputMissingError struct { + VariableName string +} + +func (e DashboardInputMissingError) Error() string { + return fmt.Sprintf("Dashbord input variable: %v missing from import command", e.VariableName) +} + +func init() { + bus.AddHandler("plugins", ImportDashboard) +} + +func ImportDashboard(cmd *ImportDashboardCommand) error { + plugin, exists := Plugins[cmd.PluginId] + + if !exists { + return PluginNotFoundError{cmd.PluginId} + } + + var dashboard *m.Dashboard + var err error + + if dashboard, err = loadPluginDashboard(plugin, cmd.Path); err != nil { + return err + } + + evaluator := &DashTemplateEvaluator{ + template: dashboard.Data, + inputs: cmd.Inputs, + } + + generatedDash, err := evaluator.Eval() + if err != nil { + return err + } + + saveCmd := m.SaveDashboardCommand{ + Dashboard: generatedDash, + OrgId: cmd.OrgId, + UserId: cmd.UserId, + } + + if err := bus.Dispatch(&saveCmd); err != nil { + return err + } + + cmd.Result = &PluginDashboardInfoDTO{ + PluginId: cmd.PluginId, + Title: dashboard.Title, + Path: cmd.Path, + Revision: dashboard.GetString("revision", "1.0"), + InstalledUri: "db/" + saveCmd.Result.Slug, + InstalledRevision: dashboard.GetString("revision", "1.0"), + Installed: true, + } + + return nil +} + +type DashTemplateEvaluator struct { + template *simplejson.Json + inputs []ImportDashboardInput + variables map[string]string + result *simplejson.Json + varRegex *regexp.Regexp +} + +func (this *DashTemplateEvaluator) findInput(varName string, varDef *simplejson.Json) *ImportDashboardInput { + inputType := varDef.Get("type").MustString() + + for _, input := range this.inputs { + if inputType == input.Type && (input.Name == varName || input.Name == "*") { + return &input + } + } + + return nil +} + +func (this *DashTemplateEvaluator) Eval() (*simplejson.Json, error) { + this.result = simplejson.New() + this.variables = make(map[string]string) + this.varRegex, _ = regexp.Compile("\\$__(\\w+)") + + // check that we have all inputs we need + if inputDefs := this.template.Get("__inputs"); inputDefs != nil { + for varName, value := range inputDefs.MustMap() { + input := this.findInput(varName, simplejson.NewFromAny(value)) + + if input == nil { + return nil, &DashboardInputMissingError{VariableName: varName} + } + + this.variables["$__"+varName] = input.Value + } + } else { + log.Info("Import: dashboard has no __import section") + } + + return simplejson.NewFromAny(this.evalObject(this.template)), nil +} + +func (this *DashTemplateEvaluator) evalValue(source *simplejson.Json) interface{} { + + sourceValue := source.Interface() + + switch v := sourceValue.(type) { + case string: + interpolated := this.varRegex.ReplaceAllStringFunc(v, func(match string) string { + return this.variables[match] + }) + return interpolated + case bool: + return v + case json.Number: + return v + case map[string]interface{}: + return this.evalObject(source) + case []interface{}: + array := make([]interface{}, 0) + for _, item := range v { + array = append(array, this.evalValue(simplejson.NewFromAny(item))) + } + return array + } + + return nil +} + +func (this *DashTemplateEvaluator) evalObject(source *simplejson.Json) interface{} { + result := make(map[string]interface{}) + + for key, value := range source.MustMap() { + if key == "__inputs" { + continue + } + result[key] = this.evalValue(simplejson.NewFromAny(value)) + } + + return result +} diff --git a/pkg/plugins/dashboard_importer_test.go b/pkg/plugins/dashboard_importer_test.go new file mode 100644 index 00000000000..9b5605277d4 --- /dev/null +++ b/pkg/plugins/dashboard_importer_test.go @@ -0,0 +1,93 @@ +package plugins + +import ( + "io/ioutil" + "testing" + + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/components/simplejson" + m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/setting" + . "github.com/smartystreets/goconvey/convey" + "gopkg.in/ini.v1" +) + +func TestDashboardImport(t *testing.T) { + + Convey("When importing plugin dashboard", t, func() { + setting.Cfg = ini.Empty() + sec, _ := setting.Cfg.NewSection("plugin.test-app") + sec.NewKey("path", "../../tests/test-app") + err := Init() + + So(err, ShouldBeNil) + + var importedDash *m.Dashboard + bus.AddHandler("test", func(cmd *m.SaveDashboardCommand) error { + importedDash = cmd.GetDashboardModel() + cmd.Result = importedDash + return nil + }) + + cmd := ImportDashboardCommand{ + PluginId: "test-app", + Path: "dashboards/connections.json", + OrgId: 1, + UserId: 1, + Inputs: []ImportDashboardInput{ + {Name: "*", Type: "datasource", Value: "graphite"}, + }, + } + + err = ImportDashboard(&cmd) + So(err, ShouldBeNil) + + Convey("should install dashboard", func() { + So(importedDash, ShouldNotBeNil) + + resultStr, _ := importedDash.Data.EncodePretty() + expectedBytes, _ := ioutil.ReadFile("../../tests/test-app/dashboards/connections_result.json") + expectedJson, _ := simplejson.NewJson(expectedBytes) + expectedStr, _ := expectedJson.EncodePretty() + + So(string(resultStr), ShouldEqual, string(expectedStr)) + + panel := importedDash.Data.Get("rows").GetIndex(0).Get("panels").GetIndex(0) + So(panel.Get("datasource").MustString(), ShouldEqual, "graphite") + }) + }) + + Convey("When evaling dashboard template", t, func() { + template, _ := simplejson.NewJson([]byte(`{ + "__inputs": { + "graphite": { + "type": "datasource" + } + }, + "test": { + "prop": "$__graphite" + } + }`)) + + evaluator := &DashTemplateEvaluator{ + template: template, + inputs: []ImportDashboardInput{ + {Name: "*", Type: "datasource", Value: "my-server"}, + }, + } + + res, err := evaluator.Eval() + So(err, ShouldBeNil) + + Convey("should render template", func() { + So(res.GetPath("test", "prop").MustString(), ShouldEqual, "my-server") + }) + + Convey("should not include inputs in output", func() { + inputs := res.Get("__inputs") + So(inputs.Interface(), ShouldBeNil) + }) + + }) + +} diff --git a/pkg/plugins/dashboard_installer.go b/pkg/plugins/dashboard_installer.go deleted file mode 100644 index 279ab209989..00000000000 --- a/pkg/plugins/dashboard_installer.go +++ /dev/null @@ -1,57 +0,0 @@ -package plugins - -import ( - "github.com/grafana/grafana/pkg/bus" - m "github.com/grafana/grafana/pkg/models" -) - -type InstallPluginDashboardCommand struct { - Path string `json:"string"` - Inputs map[string]interface{} `json:"inputs"` - - OrgId int64 `json:"-"` - UserId int64 `json:"-"` - PluginId string `json:"-"` - Result *PluginDashboardInfoDTO -} - -func init() { - bus.AddHandler("plugins", InstallPluginDashboard) -} - -func InstallPluginDashboard(cmd *InstallPluginDashboardCommand) error { - plugin, exists := Plugins[cmd.PluginId] - - if !exists { - return PluginNotFoundError{cmd.PluginId} - } - - var dashboard *m.Dashboard - var err error - - if dashboard, err = loadPluginDashboard(plugin, cmd.Path); err != nil { - return err - } - - saveCmd := m.SaveDashboardCommand{ - Dashboard: dashboard.Data, - OrgId: cmd.OrgId, - UserId: cmd.UserId, - } - - if err := bus.Dispatch(&saveCmd); err != nil { - return err - } - - cmd.Result = &PluginDashboardInfoDTO{ - PluginId: cmd.PluginId, - Title: dashboard.Title, - Path: cmd.Path, - Revision: dashboard.GetString("revision", "1.0"), - InstalledUri: "db/" + saveCmd.Result.Slug, - InstalledRevision: dashboard.GetString("revision", "1.0"), - Installed: true, - } - - return nil -} diff --git a/pkg/plugins/dashboards.go b/pkg/plugins/dashboards.go index 1697ad808f2..932196a42a9 100644 --- a/pkg/plugins/dashboards.go +++ b/pkg/plugins/dashboards.go @@ -1,11 +1,11 @@ package plugins import ( - "encoding/json" "os" "path/filepath" "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/components/simplejson" m "github.com/grafana/grafana/pkg/models" ) @@ -52,10 +52,8 @@ func loadPluginDashboard(plugin *PluginBase, path string) (*m.Dashboard, error) defer reader.Close() - jsonParser := json.NewDecoder(reader) - var data map[string]interface{} - - if err := jsonParser.Decode(&data); err != nil { + data, err := simplejson.NewFromReader(reader) + if err != nil { return nil, err } diff --git a/pkg/plugins/dashboards_test.go b/pkg/plugins/dashboards_test.go index 58cbe2f4920..bdd08ceefd2 100644 --- a/pkg/plugins/dashboards_test.go +++ b/pkg/plugins/dashboards_test.go @@ -23,7 +23,7 @@ func TestPluginDashboards(t *testing.T) { bus.AddHandler("test", func(query *m.GetDashboardQuery) error { if query.Slug == "nginx-connections" { dash := m.NewDashboard("Nginx Connections") - dash.Data["revision"] = "1.1" + dash.Data.Set("revision", "1.1") query.Result = dash return nil } diff --git a/pkg/services/search/json_index.go b/pkg/services/search/json_index.go index e70c662438d..79c238b27f9 100644 --- a/pkg/services/search/json_index.go +++ b/pkg/services/search/json_index.go @@ -1,12 +1,12 @@ package search import ( - "encoding/json" "os" "path/filepath" "strings" "time" + "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" ) @@ -120,10 +120,8 @@ func loadDashboardFromFile(filename string) (*JsonDashIndexItem, error) { } defer reader.Close() - jsonParser := json.NewDecoder(reader) - var data map[string]interface{} - - if err := jsonParser.Decode(&data); err != nil { + data, err := simplejson.NewFromReader(reader) + if err != nil { return nil, err } diff --git a/pkg/services/sqlstore/dashboard.go b/pkg/services/sqlstore/dashboard.go index a33ef10d725..396d507cfd2 100644 --- a/pkg/services/sqlstore/dashboard.go +++ b/pkg/services/sqlstore/dashboard.go @@ -69,7 +69,7 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error { affectedRows, err = sess.Insert(dash) } else { dash.Version += 1 - dash.Data["version"] = dash.Version + dash.Data.Set("version", dash.Version) affectedRows, err = sess.Id(dash.Id).Update(dash) } @@ -108,7 +108,7 @@ func GetDashboard(query *m.GetDashboardQuery) error { return m.ErrDashboardNotFound } - dashboard.Data["id"] = dashboard.Id + dashboard.Data.Set("id", dashboard.Id) query.Result = &dashboard return nil diff --git a/pkg/services/sqlstore/dashboard_snapshot_test.go b/pkg/services/sqlstore/dashboard_snapshot_test.go index 5301f0f1cc9..50375088b4b 100644 --- a/pkg/services/sqlstore/dashboard_snapshot_test.go +++ b/pkg/services/sqlstore/dashboard_snapshot_test.go @@ -5,6 +5,7 @@ import ( . "github.com/smartystreets/goconvey/convey" + "github.com/grafana/grafana/pkg/components/simplejson" m "github.com/grafana/grafana/pkg/models" ) @@ -16,9 +17,9 @@ func TestDashboardSnapshotDBAccess(t *testing.T) { Convey("Given saved snaphot", func() { cmd := m.CreateDashboardSnapshotCommand{ Key: "hej", - Dashboard: map[string]interface{}{ + Dashboard: simplejson.NewFromAny(map[string]interface{}{ "hello": "mupp", - }, + }), } err := CreateDashboardSnapshot(&cmd) So(err, ShouldBeNil) @@ -29,7 +30,7 @@ func TestDashboardSnapshotDBAccess(t *testing.T) { So(err, ShouldBeNil) So(query.Result, ShouldNotBeNil) - So(query.Result.Dashboard["hello"], ShouldEqual, "mupp") + So(query.Result.Dashboard.Get("hello").MustString(), ShouldEqual, "mupp") }) }) diff --git a/pkg/services/sqlstore/dashboard_test.go b/pkg/services/sqlstore/dashboard_test.go index 2d35a0742de..609639f7788 100644 --- a/pkg/services/sqlstore/dashboard_test.go +++ b/pkg/services/sqlstore/dashboard_test.go @@ -5,6 +5,7 @@ import ( . "github.com/smartystreets/goconvey/convey" + "github.com/grafana/grafana/pkg/components/simplejson" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/search" ) @@ -12,11 +13,11 @@ import ( func insertTestDashboard(title string, orgId int64, tags ...interface{}) *m.Dashboard { cmd := m.SaveDashboardCommand{ OrgId: orgId, - Dashboard: map[string]interface{}{ + Dashboard: simplejson.NewFromAny(map[string]interface{}{ "id": nil, "title": title, "tags": tags, - }, + }), } err := SaveDashboard(&cmd) @@ -58,11 +59,11 @@ func TestDashboardDataAccess(t *testing.T) { cmd := m.SaveDashboardCommand{ OrgId: 1, Overwrite: true, - Dashboard: map[string]interface{}{ + Dashboard: simplejson.NewFromAny(map[string]interface{}{ "id": float64(123412321), "title": "Expect error", "tags": []interface{}{}, - }, + }), } err := SaveDashboard(&cmd) @@ -76,11 +77,11 @@ func TestDashboardDataAccess(t *testing.T) { cmd := m.SaveDashboardCommand{ OrgId: 2, Overwrite: true, - Dashboard: map[string]interface{}{ + Dashboard: simplejson.NewFromAny(map[string]interface{}{ "id": float64(query.Result.Id), "title": "Expect error", "tags": []interface{}{}, - }, + }), } err := SaveDashboard(&cmd) @@ -135,11 +136,11 @@ func TestDashboardDataAccess(t *testing.T) { Convey("Should not be able to save dashboard with same name", func() { cmd := m.SaveDashboardCommand{ OrgId: 1, - Dashboard: map[string]interface{}{ + Dashboard: simplejson.NewFromAny(map[string]interface{}{ "id": nil, "title": "test dash 23", "tags": []interface{}{}, - }, + }), } err := SaveDashboard(&cmd) diff --git a/public/app/features/dashboard/dashnav/dashnav.ts b/public/app/features/dashboard/dashnav/dashnav.ts index dcc2f40d0dc..2d5b66cb13c 100644 --- a/public/app/features/dashboard/dashnav/dashnav.ts +++ b/public/app/features/dashboard/dashnav/dashnav.ts @@ -137,8 +137,8 @@ export class DashNavCtrl { $scope.deleteDashboard = function() { $scope.appEvent('confirm-modal', { - title: 'Delete dashboard', - text: 'Do you want to delete dashboard?', + title: 'Delete', + text: 'Do you want to delete this dashboard?', text2: $scope.dashboard.title, icon: 'fa-trash', yesText: 'Delete', diff --git a/public/app/features/plugins/import_list/import_list.html b/public/app/features/plugins/import_list/import_list.html index d6c1bb914ce..f9dc4ff28d0 100644 --- a/public/app/features/plugins/import_list/import_list.html +++ b/public/app/features/plugins/import_list/import_list.html @@ -21,13 +21,13 @@ diff --git a/public/app/features/plugins/import_list/import_list.ts b/public/app/features/plugins/import_list/import_list.ts index 62145623fa2..19f847a58d0 100644 --- a/public/app/features/plugins/import_list/import_list.ts +++ b/public/app/features/plugins/import_list/import_list.ts @@ -7,6 +7,7 @@ import coreModule from 'app/core/core_module'; export class DashImportListCtrl { dashboards: any[]; plugin: any; + datasource: any; constructor(private $http, private backendSrv, private $rootScope) { this.dashboards = []; @@ -21,10 +22,19 @@ export class DashImportListCtrl { pluginId: this.plugin.id, path: dash.path, reinstall: reinstall, - inputs: {} + inputs: [] }; - this.backendSrv.post(`/api/plugins/dashboards/install`, installCmd).then(res => { + if (this.datasource) { + installCmd.inputs.push({ + name: '*', + type: 'datasource', + pluginId: this.datasource.type, + value: this.datasource.name + }); + } + + this.backendSrv.post(`/api/plugins/dashboards/import`, installCmd).then(res => { this.$rootScope.appEvent('alert-success', ['Dashboard Installed', dash.title]); _.extend(dash, res); }); @@ -46,7 +56,8 @@ export function dashboardImportList() { bindToController: true, controllerAs: 'ctrl', scope: { - plugin: "=" + plugin: "=", + datasource: "=" } }; } diff --git a/public/app/features/plugins/partials/ds_edit.html b/public/app/features/plugins/partials/ds_edit.html index 4024b77b2c4..3305bfd67af 100644 --- a/public/app/features/plugins/partials/ds_edit.html +++ b/public/app/features/plugins/partials/ds_edit.html @@ -74,7 +74,7 @@
- +
diff --git a/public/app/plugins/datasource/graphite/dashboards/carbon_metrics.json b/public/app/plugins/datasource/graphite/dashboards/carbon_metrics.json new file mode 100644 index 00000000000..b9f2db18927 --- /dev/null +++ b/public/app/plugins/datasource/graphite/dashboards/carbon_metrics.json @@ -0,0 +1,176 @@ +{ + "__inputs": { + "graphite": { + "type": "datasource", + "pluginId": "graphite", + "description": "Graphite datasource" + } + }, + + "revision": "1.0", + "title": "Graphite Carbon Metrics", + "tags": ["graphite", "carbon"], + "style": "dark", + "timezone": "browser", + "editable": true, + "hideControls": false, + "sharedCrosshair": false, + "rows": [ + { + "collapsable": true, + "collapse": false, + "editable": true, + "height": "350px", + "notice": false, + "panels": [ + { + "aliasColors": {}, + "annotate": { + "enable": false + }, + "bars": false, + "datasource": "$__graphite", + "editable": true, + "fill": 0, + "grid": { + "leftLogBase": 1, + "leftMax": null, + "leftMin": null, + "max": null, + "min": 0, + "rightLogBase": 1, + "rightMax": null, + "rightMin": null, + "threshold1": null, + "threshold1Color": "rgba(216, 200, 27, 0.27)", + "threshold2": null, + "threshold2Color": "rgba(234, 112, 112, 0.22)" + }, + "id": 1, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "loadingEditor": false, + "nullPointMode": "null as zero", + "percentage": false, + "pointradius": 5, + "points": false, + "renderer": "flot", + "resolution": 100, + "scale": 1, + "seriesOverrides": [ + { + "alias": "Points Per Update", + "yaxis": 2 + }, + { + "alias": "CPU", + "yaxis": 2 + } + ], + "span": 12, + "stack": false, + "steppedLine": false, + "targets": [ + { + "refId": "A", + "target": "alias(sumSeries(carbon.agents.*.updateOperations),\"Updates\") " + }, + { + "refId": "B", + "target": "alias(sumSeries(carbon.agents.*.metricsReceived),'Metrics Received')" + }, + { + "refId": "C", + "target": "alias(sumSeries(carbon.agents.*.committedPoints),'Committed Points')" + }, + { + "refId": "D", + "target": "alias(sumSeries(carbon.agents.*.pointsPerUpdate),'Points Per Update')" + }, + { + "refId": "E", + "target": "alias(averageSeries(carbon.agents.*.cpuUsage),'CPU')" + }, + { + "refId": "F", + "target": "alias(sumSeries(carbon.agents.*.creates),'Creates')" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Graphite Carbon Metrics", + "tooltip": { + "query_as_alias": true, + "shared": false, + "value_type": "cumulative" + }, + "type": "graph", + "x-axis": true, + "y-axis": true, + "y_formats": [ + "short", + "short" + ], + "zerofill": true + } + ], + "title": "Row1" + } + ], + "time": { + "from": "now-3h", + "to": "now" + }, + "timepicker": { + "collapse": false, + "enable": true, + "notice": false, + "now": true, + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "status": "Stable", + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ], + "type": "timepicker" + }, + "templating": { + "enable": false, + "list": [] + }, + "annotations": { + "enable": false, + "list": [] + }, + "refresh": false, + "schemaVersion": 8, + "version": 2, + "links": [] +} diff --git a/public/app/plugins/datasource/graphite/dashboards/carbon_stats.json b/public/app/plugins/datasource/graphite/dashboards/carbon_stats.json deleted file mode 100644 index ad2c32be759..00000000000 --- a/public/app/plugins/datasource/graphite/dashboards/carbon_stats.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "__inputs": { - "graphite": { - "type": "datasource", - "description": "Graphite datasource" - } - }, - - "title": "Carbon Cache Stats", - "version": 1, - "rows": [ - { - "panels": [ - { - "type": "graph", - "datasource": "__$graphite" - } - ] - } - ] -} diff --git a/public/app/plugins/datasource/graphite/plugin.json b/public/app/plugins/datasource/graphite/plugin.json index f7715f9c5b9..a1c35094701 100644 --- a/public/app/plugins/datasource/graphite/plugin.json +++ b/public/app/plugins/datasource/graphite/plugin.json @@ -4,7 +4,7 @@ "id": "graphite", "includes": [ - {"type": "dashboard", "name": "Carbon Cache Stats", "path": "dashboards/carbon_stats.json"} + {"type": "dashboard", "name": "Graphite Carbon Metrics", "path": "dashboards/carbon_metrics.json"} ], "metrics": true, diff --git a/tests/test-app/dashboards/connections.json b/tests/test-app/dashboards/connections.json index f1f62826f73..2f5102fba3e 100644 --- a/tests/test-app/dashboards/connections.json +++ b/tests/test-app/dashboards/connections.json @@ -1,5 +1,28 @@ { + "__inputs": { + "graphite": { + "type": "datasource", + "pluginId": "graphite", + "description": "Graphite datasource" + } + }, + "title": "Nginx Connections", "revision": "1.5", - "schemaVersion": 11 + "schemaVersion": 11, + "tags": ["tag1", "tag2"], + "number_array": [1,2,3,10.33], + "boolean_true": true, + "boolean_false": false, + + "rows": [ + { + "panels": [ + { + "type": "graph", + "datasource": "$__graphite" + } + ] + } + ] } diff --git a/tests/test-app/dashboards/connections_result.json b/tests/test-app/dashboards/connections_result.json new file mode 100644 index 00000000000..1c662f6c269 --- /dev/null +++ b/tests/test-app/dashboards/connections_result.json @@ -0,0 +1,20 @@ +{ + "revision": "1.5", + "tags": ["tag1", "tag2"], + "boolean_false": false, + "boolean_true": true, + "number_array": [1,2,3,10.33], + "rows": [ + { + "panels": [ + { + "datasource": "graphite", + "type": "graph" + } + ] + } + ], + "schemaVersion": 11, + "title": "Nginx Connections", + "version": 0 +}