This provides somewhat reasonable string representations of GitHub structs. This is specifically designed for this library and takes a number of shortcuts where it can, so is not suitable as a general purpose solution. That said, I am exporting this function because it is useful for things like printing out slices of GitHub structs, as can be seen in examples/example.go. I am certainly open to suggestions for what exactly the stringified output should look like. Currently, I think I've found a reasonable compromise between fmt's "%v" and "%s" output.
| @ -0,0 +1,93 @@ | |||
| // Copyright 2013 The go-github AUTHORS. All rights reserved. | |||
| // | |||
| // Use of this source code is governed by a BSD-style | |||
| // license that can be found in the LICENSE file. | |||
| package github | |||
| import ( | |||
| "bytes" | |||
| "fmt" | |||
| "io" | |||
| "reflect" | |||
| ) | |||
| var timestampType = reflect.TypeOf(Timestamp{}) | |||
| // Stringify attempts to create a reasonable string representation of types in | |||
| // the GitHub library. It does things like resolve pointers to their values | |||
| // and omits struct fields with nil values. | |||
| func Stringify(message interface{}) string { | |||
| var buf bytes.Buffer | |||
| v := reflect.ValueOf(message) | |||
| stringifyValue(&buf, v) | |||
| return buf.String() | |||
| } | |||
| // stringifyValue was heavily inspired by the goprotobuf library. | |||
| func stringifyValue(w io.Writer, val reflect.Value) { | |||
| if val.Kind() == reflect.Ptr && val.IsNil() { | |||
| w.Write([]byte("<nil>")) | |||
| return | |||
| } | |||
| v := reflect.Indirect(val) | |||
| switch v.Kind() { | |||
| case reflect.String: | |||
| fmt.Fprintf(w, `"%s"`, v) | |||
| case reflect.Slice: | |||
| w.Write([]byte{'['}) | |||
| for i := 0; i < v.Len(); i++ { | |||
| if i > 0 { | |||
| w.Write([]byte{' '}) | |||
| } | |||
| stringifyValue(w, v.Index(i)) | |||
| } | |||
| w.Write([]byte{']'}) | |||
| return | |||
| case reflect.Struct: | |||
| if v.Type().Name() != "" { | |||
| w.Write([]byte(v.Type().String())) | |||
| } | |||
| // special handling of Timestamp values | |||
| if v.Type() == timestampType { | |||
| fmt.Fprint(w, v.Interface()) | |||
| return | |||
| } | |||
| w.Write([]byte{'{'}) | |||
| var sep bool | |||
| for i := 0; i < v.NumField(); i++ { | |||
| fv := v.Field(i) | |||
| if fv.Kind() == reflect.Ptr && fv.IsNil() { | |||
| continue | |||
| } | |||
| if fv.Kind() == reflect.Slice && fv.IsNil() { | |||
| continue | |||
| } | |||
| if sep { | |||
| w.Write([]byte(", ")) | |||
| } else { | |||
| sep = true | |||
| } | |||
| w.Write([]byte(v.Type().Field(i).Name)) | |||
| w.Write([]byte{':'}) | |||
| stringifyValue(w, fv) | |||
| } | |||
| w.Write([]byte{'}'}) | |||
| default: | |||
| if v.CanInterface() { | |||
| fmt.Fprint(w, v.Interface()) | |||
| } | |||
| } | |||
| } | |||
| @ -0,0 +1,75 @@ | |||
| // Copyright 2013 The go-github AUTHORS. All rights reserved. | |||
| // | |||
| // Use of this source code is governed by a BSD-style | |||
| // license that can be found in the LICENSE file. | |||
| package github | |||
| import ( | |||
| "testing" | |||
| "time" | |||
| ) | |||
| func TestStringify(t *testing.T) { | |||
| var nilPointer *string | |||
| var tests = []struct { | |||
| in interface{} | |||
| out string | |||
| }{ | |||
| // basic types | |||
| {"foo", `"foo"`}, | |||
| {123, `123`}, | |||
| {1.5, `1.5`}, | |||
| {false, `false`}, | |||
| { | |||
| []string{"a", "b"}, | |||
| `["a" "b"]`, | |||
| }, | |||
| { | |||
| struct { | |||
| A []string | |||
| }{nil}, | |||
| // nil slice is skipped | |||
| `{}`, | |||
| }, | |||
| { | |||
| struct { | |||
| A string | |||
| }{"foo"}, | |||
| // structs not of a named type get no prefix | |||
| `{A:"foo"}`, | |||
| }, | |||
| // pointers | |||
| {nilPointer, `<nil>`}, | |||
| {String("foo"), `"foo"`}, | |||
| {Int(123), `123`}, | |||
| {Bool(false), `false`}, | |||
| { | |||
| []*string{String("a"), String("b")}, | |||
| `["a" "b"]`, | |||
| }, | |||
| // actual GitHub structs | |||
| { | |||
| Timestamp{time.Date(2006, 01, 02, 15, 04, 05, 0, time.UTC)}, | |||
| `github.Timestamp{2006-01-02 15:04:05 +0000 UTC}`, | |||
| }, | |||
| { | |||
| User{ID: Int(123), Name: String("n")}, | |||
| `github.User{ID:123, Name:"n"}`, | |||
| }, | |||
| { | |||
| Repository{Owner: &User{ID: Int(123)}}, | |||
| `github.Repository{Owner:github.User{ID:123}}`, | |||
| }, | |||
| } | |||
| for i, tt := range tests { | |||
| s := Stringify(tt.in) | |||
| if s != tt.out { | |||
| t.Errorf("%d. Stringify(%q) => %q, want %q", i, tt.in, s, tt.out) | |||
| } | |||
| } | |||
| } | |||