commit 79047a09dd67c241b38c77110f05731f8a155972 Author: Arpad Ryszka Date: Tue Oct 20 02:43:52 2020 +0200 import repo, format without wrapping diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..223cec9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.coverprofile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8bb8f75 --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +SOURCES = $(shell find . -name "*.go") + +default: build + +build: + go build ./... + +check: + go test -count 1 ./... + +imports: + @goimports -w $(SOURCES) + +fmt: + @gofmt -w -s $(SOURCES) + +.coverprofile: $(SOURCES) + go test -count 1 -coverprofile .coverprofile + +cover: .coverprofile + go tool cover -func .coverprofile + +showcover: .coverprofile + go tool cover -html .coverprofile diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..79d7ec8 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/aryszka/notation + +go 1.14 diff --git a/notation.go b/notation.go new file mode 100644 index 0000000..4069f92 --- /dev/null +++ b/notation.go @@ -0,0 +1,60 @@ +package notation + +import ( + "reflect" + "strings" +) + +type opts int + +const none opts = 0 + +const ( + wrap opts = 1 << iota + types + skipTypes + allTypes +) + +func sprintValues(o opts, v []interface{}) string { + s := make([]string, len(v)) + for i := range v { + if v[i] == nil { + s[i] = "nil" + continue + } + + s[i] = sprint(o, reflect.ValueOf(v[i])) + } + + sep := " " + if o&wrap != 0 { + sep = "\n" + } + + return strings.Join(s, sep) +} + +func Sprint(v ...interface{}) string { + return sprintValues(none, v) +} + +func Sprintw(v ...interface{}) string { + return sprintValues(wrap, v) +} + +func Sprintt(v ...interface{}) string { + return sprintValues(types, v) +} + +func Sprintwt(v ...interface{}) string { + return sprintValues(wrap|types, v) +} + +func Sprintv(v ...interface{}) string { + return sprintValues(allTypes, v) +} + +func Sprintwv(v ...interface{}) string { + return sprintValues(wrap|allTypes, v) +} diff --git a/sprint.go b/sprint.go new file mode 100644 index 0000000..543ff1e --- /dev/null +++ b/sprint.go @@ -0,0 +1,271 @@ +package notation + +import ( + "fmt" + "reflect" + "sort" + "strings" +) + +func withType(o opts) (opts, bool, bool) { + if o&types == 0 && o&allTypes == 0 { + return o, false, false + } + + if o&skipTypes != 0 && o&allTypes == 0 { + return o &^ skipTypes, false, false + } + + return o, true, o&allTypes != 0 +} + +func sprintNil(o opts, r reflect.Value) string { + if _, _, a := withType(o); !a { + return "nil" + } + + return fmt.Sprintf("%s(nil)", sprintType(r.Type())) +} + +func sprintPrimitive(o opts, r reflect.Value, v interface{}, suppressType ...string) string { + s := fmt.Sprint(v) + if s[0] == '(' && s[len(s)-1] == ')' { + s = s[1 : len(s)-1] + } + + _, w, a := withType(o) + if !w { + return s + } + + t := sprintType(r.Type()) + if !a { + for _, suppress := range suppressType { + if t == suppress { + return s + } + } + } + + return fmt.Sprintf("%s(%s)", t, s) +} + +func sprintItems(o opts, prefix string, r reflect.Value) string { + o, w, _ := withType(o) + itemOpts := o | skipTypes + s := make([]string, r.Len()) + for i := 0; i < r.Len(); i++ { + s[i] = sprint(itemOpts, r.Index(i)) + } + + if !w { + return fmt.Sprintf("%s{%s}", prefix, strings.Join(s, ", ")) + } + + return fmt.Sprintf("%s{%s}", sprintType(r.Type()), strings.Join(s, ", ")) +} + +func sprintHidden(o opts, r reflect.Value, hidden string) string { + if r.IsNil() { + return sprintNil(o, r) + } + + _, w, _ := withType(o) + if !w { + return hidden + } + + return sprintType(r.Type()) +} + +func sprintArray(o opts, r reflect.Value) string { + return sprintItems(o, fmt.Sprintf("[%d]", r.Len()), r) +} + +func sprintChan(o opts, r reflect.Value) string { + return sprintHidden(o, r, "chan") +} + +func sprintFunc(o opts, r reflect.Value) string { + return sprintHidden(o, r, "func()") +} + +func sprinterface(o opts, r reflect.Value) string { + if r.IsNil() { + return sprintNil(o, r) + } + + o, w, _ := withType(o) + if !w { + return sprint(o, r.Elem()) + } + + return fmt.Sprintf("%s(%s)", sprintType(r.Type()), sprint(o, r.Elem())) +} + +func sprintMap(o opts, r reflect.Value) string { + if r.IsNil() { + return sprintNil(o, r) + } + + o, w, _ := withType(o) + itemOpts := o | skipTypes + var items []string + for _, key := range r.MapKeys() { + items = append( + items, + fmt.Sprintf( + "%s: %s", + sprint(itemOpts, key), + sprint(itemOpts, r.MapIndex(key)), + ), + ) + } + + sort.Strings(items) + sitems := strings.Join(items, ", ") + if !w { + return fmt.Sprintf("map{%s}", sitems) + } + + return fmt.Sprintf("%s{%s}", sprintType(r.Type()), sitems) +} + +func sprintPointer(o opts, r reflect.Value) string { + if r.IsNil() { + return sprintNil(o, r) + } + + s := sprint(o, r.Elem()) + if _, w, _ := withType(o); !w { + return s + } + + return fmt.Sprintf("*%s", s) +} + +func sprintList(o opts, r reflect.Value) string { + if r.IsNil() { + return sprintNil(o, r) + } + + return sprintItems(o, "[]", r) +} + +func sprintString(o opts, r reflect.Value) string { + b := []byte(r.String()) + e := make([]byte, 0, len(b)) + for _, c := range b { + switch c { + case '\\': + e = append(e, '\\', '\\') + case '"': + e = append(e, '\\', '"') + case '\b': + e = append(e, '\\', 'b') + case '\f': + e = append(e, '\\', 'f') + case '\n': + e = append(e, '\\', 'n') + case '\r': + e = append(e, '\\', 'r') + case '\t': + e = append(e, '\\', 't') + case '\v': + e = append(e, '\\', 'v') + default: + e = append(e, c) + } + } + + s := fmt.Sprintf("\"%s\"", string(e)) + _, w, a := withType(o) + if !w { + return s + } + + t := sprintType(r.Type()) + if !a && t == "string" { + return s + } + + return fmt.Sprintf("%s(%s)", t, s) +} + +func sprintStruct(o opts, r reflect.Value) string { + o, w, _ := withType(o) + fieldOpts := o | skipTypes + rt := r.Type() + f := make([]string, r.NumField()) + for i := 0; i < r.NumField(); i++ { + name := rt.Field(i).Name + f[i] = fmt.Sprintf( + "%s: %s", + name, + sprint(fieldOpts, r.FieldByName(name)), + ) + } + + fs := strings.Join(f, ", ") + if !w { + return fmt.Sprintf("{%s}", fs) + } + + return fmt.Sprintf("%s{%s}", sprintType(rt), fs) +} + +func sprintUnsafePointer(o opts, r reflect.Value) string { + if r.IsNil() { + return sprintNil(o, r) + } + + return "pointer" +} + +func sprint(o opts, r reflect.Value) string { + switch r.Kind() { + case reflect.Bool: + return sprintPrimitive(o, r, r.Bool(), "bool") + case + reflect.Int, + reflect.Int8, + reflect.Int16, + reflect.Int32, + reflect.Int64: + return sprintPrimitive(o, r, r.Int(), "int") + case + reflect.Uint, + reflect.Uint8, + reflect.Uint16, + reflect.Uint32, + reflect.Uint64, + reflect.Uintptr: + return sprintPrimitive(o, r, r.Uint()) + case reflect.Float32, reflect.Float64: + return sprintPrimitive(o, r, r.Float()) + case reflect.Complex64, reflect.Complex128: + return sprintPrimitive(o, r, r.Complex()) + case reflect.Array: + return sprintArray(o, r) + case reflect.Chan: + return sprintChan(o, r) + case reflect.Func: + return sprintFunc(o, r) + case reflect.Interface: + return sprinterface(o, r) + case reflect.Map: + return sprintMap(o, r) + case reflect.Ptr: + return sprintPointer(o, r) + case reflect.Slice: + return sprintList(o, r) + case reflect.String: + return sprintString(o, r) + case reflect.Struct: + return sprintStruct(o, r) + case reflect.UnsafePointer: + return sprintUnsafePointer(o, r) + default: + return "" + } +} diff --git a/sprint_test.go b/sprint_test.go new file mode 100644 index 0000000..389b3b3 --- /dev/null +++ b/sprint_test.go @@ -0,0 +1,264 @@ +package notation + +import ( + "reflect" + "testing" + "unsafe" +) + +func TestSprint(t *testing.T) { + type ( + myBool bool + myInt int + myFloat float64 + myComplex complex64 + myArray [3]int + myChannel chan int + myFunction func(int, int) int + myMap map[int]int + myPointer *int + myList []int + myString string + myStruct struct{ field interface{} } + myUnsafePointer unsafe.Pointer + ) + + for _, test := range []struct { + title string + value interface{} + expect string + }{ + {"nil", nil, "nil"}, + {"false", false, "false"}, + {"true", true, "true"}, + {"custom false", myBool(false), "false"}, + {"custom true", myBool(true), "true"}, + {"int", 42, "42"}, + {"negative int", -42, "-42"}, + {"custom int", myInt(42), "42"}, + {"uint", uint(42), "42"}, + {"byte", byte(42), "42"}, + {"round float", float64(42), "42"}, + {"custom round float", myFloat(42), "42"}, + {"float with fraction", 1.8, "1.8"}, + {"custom float with fraction", myFloat(1.8), "1.8"}, + {"complex", 2 + 3i, "2+3i"}, + {"custom complex", myComplex(2 + 3i), "2+3i"}, + {"imaginary", 3i, "0+3i"}, + {"custom imaginary", myComplex(3i), "0+3i"}, + {"array", [...]int{1, 2, 3}, "[3]{1, 2, 3}"}, + {"custom array", myArray{1, 2, 3}, "[3]{1, 2, 3}"}, + {"channel", make(chan int), "chan"}, + {"custom channel", make(myChannel), "chan"}, + {"nil channel", struct{ c chan int }{}, "{c: nil}"}, + {"nil custom channel", struct{ c myChannel }{}, "{c: nil}"}, + {"receive channel", struct{ c <-chan int }{make(chan int)}, "{c: chan}"}, + {"send channel", struct{ c chan<- int }{make(chan int)}, "{c: chan}"}, + {"function", func() {}, "func()"}, + {"function with args", func(int, int) int { return 0 }, "func()"}, + {"custom function with args", myFunction(func(int, int) int { return 0 }), "func()"}, + {"function with multiple return args", func(int, int) (int, int) { return 0, 0 }, "func()"}, + {"nil function", struct{ f func(int) int }{}, "{f: nil}"}, + {"interface type", struct{ i interface{ foo() } }{}, "{i: nil}"}, + {"map", map[int]int{24: 42}, "map{24: 42}"}, + {"custom map", myMap{24: 42}, "map{24: 42}"}, + {"nil map", struct{ m map[int]int }{}, "{m: nil}"}, + {"nil custom map", struct{ m myMap }{}, "{m: nil}"}, + {"pointer", &struct{}{}, "{}"}, + {"custom pointer", &myStruct{}, "{field: nil}"}, + {"nil pointer", struct{ p *int }{}, "{p: nil}"}, + {"nil custom pointer", struct{ p myPointer }{}, "{p: nil}"}, + {"list", []int{1, 2, 3}, "[]{1, 2, 3}"}, + {"custom list", myList{1, 2, 3}, "[]{1, 2, 3}"}, + {"nil list", struct{ l []int }{}, "{l: nil}"}, + {"nil custom list", struct{ l myList }{}, "{l: nil}"}, + {"string", "\\\"\b\f\n\r\t\vfoo", "\"\\\\\\\"\\b\\f\\n\\r\\t\\vfoo\""}, + {"custom string", myString("\\\"\b\f\n\r\t\vfoo"), "\"\\\\\\\"\\b\\f\\n\\r\\t\\vfoo\""}, + {"structure", struct{ foo int }{42}, "{foo: 42}"}, + {"custom structure", myStruct{42}, "{field: 42}"}, + {"unsafe pointer", unsafe.Pointer(&struct{}{}), "pointer"}, + {"custom unsafe pointer", myUnsafePointer(&struct{}{}), "pointer"}, + {"unsafe pointer type", struct{ p unsafe.Pointer }{}, "{p: nil}"}, + } { + t.Run(test.title, func(t *testing.T) { + s := Sprint(test.value) + if s != test.expect { + t.Fatalf("expected: %s, got: %s", test.expect, s) + } + }) + } +} + +func TestSprintt(t *testing.T) { + type ( + myBool bool + myInt int + myFloat float64 + myComplex complex64 + myArray [3]int + myChannel chan int + myFunction func(int, int) int + myMap map[int]int + myPointer *int + myList []int + myString string + myStruct struct{ field interface{} } + myUnsafePointer unsafe.Pointer + ) + + for _, test := range []struct { + title string + value interface{} + expect string + }{ + {"nil", nil, "nil"}, + {"false", false, "false"}, + {"true", true, "true"}, + {"custom false", myBool(false), "myBool(false)"}, + {"custom true", myBool(true), "myBool(true)"}, + {"int", 42, "42"}, + {"negative int", -42, "-42"}, + {"custom int", myInt(42), "myInt(42)"}, + {"uint", uint(42), "uint(42)"}, + {"byte", byte(42), "uint8(42)"}, + {"round float", float64(42), "float64(42)"}, + {"custom round float", myFloat(42), "myFloat(42)"}, + {"float with fraction", 1.8, "float64(1.8)"}, + {"custom float with fraction", myFloat(1.8), "myFloat(1.8)"}, + {"complex", 2 + 3i, "complex128(2+3i)"}, + {"custom complex", myComplex(2 + 3i), "myComplex(2+3i)"}, + {"imaginary", 3i, "complex128(0+3i)"}, + {"custom imaginary", myComplex(3i), "myComplex(0+3i)"}, + {"array", [...]int{1, 2, 3}, "[3]int{1, 2, 3}"}, + {"custom array", myArray{1, 2, 3}, "myArray{1, 2, 3}"}, + {"channel", make(chan int), "chan int"}, + {"custom channel", make(myChannel), "myChannel"}, + {"nil channel", struct{ c chan int }{}, "struct{c chan int}{c: nil}"}, + {"nil custom channel", struct{ c myChannel }{}, "struct{c myChannel}{c: nil}"}, + {"receive channel", struct{ c <-chan int }{make(chan int)}, "struct{c <-chan int}{c: chan}"}, + {"send channel", struct{ c chan<- int }{make(chan int)}, "struct{c chan<- int}{c: chan}"}, + {"function", func() {}, "func()"}, + {"function with args", func(int, int) int { return 0 }, "func(int, int) int"}, + {"custom function with args", myFunction(func(int, int) int { return 0 }), "myFunction"}, + {"function with multiple return args", func(int, int) (int, int) { return 0, 0 }, "func(int, int) (int, int)"}, + {"nil function", struct{ f func(int) int }{}, "struct{f func(int) int}{f: nil}"}, + {"interface type", struct{ i interface{ foo() } }{}, "struct{i interface{foo()}}{i: nil}"}, + {"map", map[int]int{24: 42}, "map[int]int{24: 42}"}, + {"custom map", myMap{24: 42}, "myMap{24: 42}"}, + {"nil map", struct{ m map[int]int }{}, "struct{m map[int]int}{m: nil}"}, + {"nil custom map", struct{ m myMap }{}, "struct{m myMap}{m: nil}"}, + {"pointer", &struct{}{}, "*struct{}{}"}, + {"custom pointer", &myStruct{}, "*myStruct{field: nil}"}, + {"nil pointer", struct{ p *int }{}, "struct{p *int}{p: nil}"}, + {"nil custom pointer", struct{ p myPointer }{}, "struct{p myPointer}{p: nil}"}, + {"list", []int{1, 2, 3}, "[]int{1, 2, 3}"}, + {"custom list", myList{1, 2, 3}, "myList{1, 2, 3}"}, + {"nil list", struct{ l []int }{}, "struct{l []int}{l: nil}"}, + {"nil custom list", struct{ l myList }{}, "struct{l myList}{l: nil}"}, + {"string", "\\\"\b\f\n\r\t\vfoo", "\"\\\\\\\"\\b\\f\\n\\r\\t\\vfoo\""}, + {"custom string", myString("\\\"\b\f\n\r\t\vfoo"), "myString(\"\\\\\\\"\\b\\f\\n\\r\\t\\vfoo\")"}, + {"structure", struct{ foo int }{42}, "struct{foo int}{foo: 42}"}, + {"custom structure", myStruct{42}, "myStruct{field: 42}"}, + {"unsafe pointer", unsafe.Pointer(&struct{}{}), "pointer"}, + {"custom unsafe pointer", myUnsafePointer(&struct{}{}), "pointer"}, + {"unsafe pointer type", struct{ p unsafe.Pointer }{}, "struct{p Pointer}{p: nil}"}, + } { + t.Run(test.title, func(t *testing.T) { + s := Sprintt(test.value) + if s != test.expect { + t.Fatalf("expected: %s, got: %s", test.expect, s) + } + }) + } +} + +func TestSprintv(t *testing.T) { + type ( + myBool bool + myInt int + myFloat float64 + myComplex complex64 + myArray [3]int + myChannel chan int + myFunction func(int, int) int + myMap map[int]int + myPointer *int + myList []int + myString string + myStruct struct{ field interface{} } + myUnsafePointer unsafe.Pointer + ) + + for _, test := range []struct { + title string + value interface{} + expect string + }{ + {"nil", nil, "nil"}, + {"false", false, "bool(false)"}, + {"true", true, "bool(true)"}, + {"custom false", myBool(false), "myBool(false)"}, + {"custom true", myBool(true), "myBool(true)"}, + {"int", 42, "int(42)"}, + {"negative int", -42, "int(-42)"}, + {"custom int", myInt(42), "myInt(42)"}, + {"uint", uint(42), "uint(42)"}, + {"byte", byte(42), "uint8(42)"}, + {"round float", float64(42), "float64(42)"}, + {"custom round float", myFloat(42), "myFloat(42)"}, + {"float with fraction", 1.8, "float64(1.8)"}, + {"custom float with fraction", myFloat(1.8), "myFloat(1.8)"}, + {"complex", 2 + 3i, "complex128(2+3i)"}, + {"custom complex", myComplex(2 + 3i), "myComplex(2+3i)"}, + {"imaginary", 3i, "complex128(0+3i)"}, + {"custom imaginary", myComplex(3i), "myComplex(0+3i)"}, + {"array", [...]int{1, 2, 3}, "[3]int{int(1), int(2), int(3)}"}, + {"custom array", myArray{1, 2, 3}, "myArray{int(1), int(2), int(3)}"}, + {"channel", make(chan int), "chan int"}, + {"custom channel", make(myChannel), "myChannel"}, + {"nil channel", struct{ c chan int }{}, "struct{c chan int}{c: chan int(nil)}"}, + {"nil custom channel", struct{ c myChannel }{}, "struct{c myChannel}{c: myChannel(nil)}"}, + {"receive channel", struct{ c <-chan int }{make(chan int)}, "struct{c <-chan int}{c: <-chan int}"}, + {"send channel", struct{ c chan<- int }{make(chan int)}, "struct{c chan<- int}{c: chan<- int}"}, + {"function", func() {}, "func()"}, + {"function with args", func(int, int) int { return 0 }, "func(int, int) int"}, + {"custom function with args", myFunction(func(int, int) int { return 0 }), "myFunction"}, + {"function with multiple return args", func(int, int) (int, int) { return 0, 0 }, "func(int, int) (int, int)"}, + {"nil function", struct{ f func(int) int }{}, "struct{f func(int) int}{f: func(int) int(nil)}"}, + {"interface type", struct{ i interface{ foo() } }{}, "struct{i interface{foo()}}{i: interface{foo()}(nil)}"}, + {"map", map[int]int{24: 42}, "map[int]int{int(24): int(42)}"}, + {"custom map", myMap{24: 42}, "myMap{int(24): int(42)}"}, + {"nil map", struct{ m map[int]int }{}, "struct{m map[int]int}{m: map[int]int(nil)}"}, + {"nil custom map", struct{ m myMap }{}, "struct{m myMap}{m: myMap(nil)}"}, + {"pointer", &struct{}{}, "*struct{}{}"}, + {"custom pointer", &myStruct{}, "*myStruct{field: interface{}(nil)}"}, + {"nil pointer", struct{ p *int }{}, "struct{p *int}{p: *int(nil)}"}, + {"nil custom pointer", struct{ p myPointer }{}, "struct{p myPointer}{p: myPointer(nil)}"}, + {"list", []int{1, 2, 3}, "[]int{int(1), int(2), int(3)}"}, + {"custom list", myList{1, 2, 3}, "myList{int(1), int(2), int(3)}"}, + {"nil list", struct{ l []int }{}, "struct{l []int}{l: []int(nil)}"}, + {"nil custom list", struct{ l myList }{}, "struct{l myList}{l: myList(nil)}"}, + {"string", "\\\"\b\f\n\r\t\vfoo", "string(\"\\\\\\\"\\b\\f\\n\\r\\t\\vfoo\")"}, + {"custom string", myString("\\\"\b\f\n\r\t\vfoo"), "myString(\"\\\\\\\"\\b\\f\\n\\r\\t\\vfoo\")"}, + {"structure", struct{ foo int }{42}, "struct{foo int}{foo: int(42)}"}, + {"custom structure", myStruct{42}, "myStruct{field: interface{}(int(42))}"}, + {"custom structure, nil field", myStruct{}, "myStruct{field: interface{}(nil)}"}, + {"unsafe pointer", unsafe.Pointer(&struct{}{}), "pointer"}, + {"custom unsafe pointer", myUnsafePointer(&struct{}{}), "pointer"}, + {"unsafe pointer type", struct{ p unsafe.Pointer }{}, "struct{p Pointer}{p: Pointer(nil)}"}, + } { + t.Run(test.title, func(t *testing.T) { + s := Sprintv(test.value) + if s != test.expect { + t.Fatalf("expected: %s, got: %s", test.expect, s) + } + }) + } +} + +func TestSprintInvalid(t *testing.T) { + s := sprint(none, reflect.Value{}) + if s != "" { + t.Fatalf("expected: , got: %s", s) + } +} diff --git a/type.go b/type.go new file mode 100644 index 0000000..4967135 --- /dev/null +++ b/type.go @@ -0,0 +1,118 @@ +package notation + +import ( + "fmt" + "reflect" + "strings" +) + +func funcBase(t reflect.Type) string { + args := func(num func() int, typ func(int) reflect.Type) []string { + t := make([]string, num()) + for i := 0; i < num(); i++ { + t[i] = sprintType(typ(i)) + } + + return t + } + + in := args(t.NumIn, t.In) + out := args(t.NumOut, t.Out) + + var outs string + if len(out) == 1 { + outs = out[0] + } else if len(out) > 1 { + outs = fmt.Sprintf("(%s)", strings.Join(out, ", ")) + } + + var s string + if outs == "" { + s = fmt.Sprintf("(%s)", strings.Join(in, ", ")) + } else { + s = fmt.Sprintf("(%s) %s", strings.Join(in, ", "), outs) + } + + return s +} + +func arrayType(t reflect.Type) string { + return fmt.Sprintf("[%d]%s", t.Len(), sprintType(t.Elem())) +} + +func chanType(t reflect.Type) string { + var prefix string + switch t.ChanDir() { + case reflect.RecvDir: + prefix = "<-chan" + case reflect.SendDir: + prefix = "chan<-" + default: + prefix = "chan" + } + + return fmt.Sprintf("%s %s", prefix, sprintType(t.Elem())) +} + +func funcType(t reflect.Type) string { + return fmt.Sprintf("func%s", funcBase(t)) +} + +func interfaceType(t reflect.Type) string { + var m []string + for i := 0; i < t.NumMethod(); i++ { + method := t.Method(i) + m = append(m, fmt.Sprintf("%s%s", method.Name, funcBase(method.Type))) + } + + return fmt.Sprintf("interface{%s}", strings.Join(m, "; ")) +} + +func mapType(t reflect.Type) string { + return fmt.Sprintf("map[%s]%s", sprintType(t.Key()), sprintType(t.Elem())) +} + +func pointerType(t reflect.Type) string { + return fmt.Sprintf("*%s", sprintType(t.Elem())) +} + +func listType(t reflect.Type) string { + return fmt.Sprintf("[]%s", sprintType(t.Elem())) +} + +func structType(t reflect.Type) string { + f := make([]string, t.NumField()) + for i := 0; i < t.NumField(); i++ { + fi := t.Field(i) + f[i] = fmt.Sprintf("%s %s", fi.Name, sprintType(fi.Type)) + } + + return fmt.Sprintf("struct{%s}", strings.Join(f, "; ")) +} + +func sprintType(t reflect.Type) string { + if t.Name() != "" { + return t.Name() + } + + switch t.Kind() { + case reflect.Array: + return arrayType(t) + case reflect.Chan: + return chanType(t) + case reflect.Func: + return funcType(t) + case reflect.Interface: + return interfaceType(t) + case reflect.Map: + return mapType(t) + case reflect.Ptr: + return pointerType(t) + case reflect.Slice: + return listType(t) + case reflect.Struct: + return structType(t) + default: + return "" + } +}