Skip to content

Commit

Permalink
Merge pull request #16 from yassinebenaid/use-smart-pointer-referencing
Browse files Browse the repository at this point in the history
Use smart pointer referencing
  • Loading branch information
yassinebenaid authored Jun 5, 2024
2 parents b3bcd47 + 74ffe98 commit cb915b5
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 109 deletions.
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ type DefinedType struct {
}

type Slice []int
type Array [3]int
type User struct {
Name string
Friend *User
Expand All @@ -81,8 +80,7 @@ func main() {
"signed-float": -1234.5678,
"slice": []int{1, 2, 3},
"typed-slice": Slice{1, 2, 3},
"array": [3]int{1, 2, 3},
"typed-array": Array{1, 2, 3},
"channel": make(chan string, 10),
"map": map[complex64]bool{
0xf4a5c5d: true,
0xa0bff6e: false,
Expand Down
Binary file modified demo/demo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
115 changes: 73 additions & 42 deletions dumper.go
Original file line number Diff line number Diff line change
@@ -1,127 +1,139 @@
package godump

import (
"bytes"
"fmt"
"reflect"
"strings"
)

type pointer struct {
id int
pos int
tagged bool
}
type dumper struct {
buf bytes.Buffer
buf []byte
theme theme
depth int
ptrs map[uintptr]uint
ptrs map[uintptr]*pointer
}

func (d *dumper) dump(v any, ignore_depth ...bool) {
if len(ignore_depth) <= 0 || !ignore_depth[0] {
d.buf.WriteString(strings.Repeat(" ", d.depth))
d.write(strings.Repeat(" ", d.depth))
}

switch reflect.ValueOf(v).Kind() {
case reflect.String:
d.dumpString(fmt.Sprint(v))
case reflect.Bool:
d.buf.WriteString(d.theme.Bool.apply(fmt.Sprintf("%t", v)))
d.write(d.theme.Bool.apply(fmt.Sprintf("%t", v)))
case reflect.Slice, reflect.Array:
d.dumpSlice(v)
case reflect.Map:
d.dumpMap(v)
case reflect.Func:
d.buf.WriteString(d.theme.Func.apply(fmt.Sprintf("%T", v)))
d.write(d.theme.Func.apply(fmt.Sprintf("%T", v)))
case reflect.Chan:
d.buf.WriteString(d.theme.VarType.apply(fmt.Sprintf("%T", v)))
d.write(d.theme.VarType.apply(fmt.Sprintf("%T", v)))
cap := reflect.ValueOf(v).Cap()
if cap > 0 {
d.buf.WriteString(d.theme.VarType.apply(fmt.Sprintf("<%d>", cap)))
d.write(d.theme.VarType.apply(fmt.Sprintf("<%d>", cap)))
}
case reflect.Struct:
d.dumpStruct(v)
case reflect.Pointer:
d.dumpPointer(v)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
v = fmt.Sprint(reflect.ValueOf(v).Int())
d.buf.WriteString(d.theme.Number.apply(v.(string)))
d.write(d.theme.Number.apply(v.(string)))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
v = fmt.Sprint(reflect.ValueOf(v).Uint())
d.buf.WriteString(d.theme.Number.apply(v.(string)))
d.write(d.theme.Number.apply(v.(string)))
case reflect.Float32, reflect.Float64:
v = fmt.Sprint(reflect.ValueOf(v).Float())
d.buf.WriteString(d.theme.Number.apply(v.(string)))
d.write(d.theme.Number.apply(v.(string)))
case reflect.Complex64, reflect.Complex128:
v = fmt.Sprint(reflect.ValueOf(v).Complex())
d.buf.WriteString(d.theme.Number.apply(v.(string)))
d.write(d.theme.Number.apply(v.(string)))
case reflect.Invalid:
d.buf.WriteString(d.theme.Nil.apply("nil"))
d.write(d.theme.Nil.apply("nil"))

}
}

func (d *dumper) dumpString(v string) {
d.buf.WriteString(d.theme.Quotes.apply(`"`))
d.buf.WriteString(d.theme.String.apply(v))
d.buf.WriteString(d.theme.Quotes.apply(`"`))
d.write(d.theme.Quotes.apply(`"`))
d.write(d.theme.String.apply(v))
d.write(d.theme.Quotes.apply(`"`))
}

func (d *dumper) dumpSlice(v any) {
value := reflect.ValueOf(v)
length := value.Len()
capacity := value.Cap()

d.buf.WriteString(d.theme.VarType.apply(fmt.Sprintf("%T:%d:%d {", v, length, capacity)))
d.write(d.theme.VarType.apply(fmt.Sprintf("%T:%d:%d {", v, length, capacity)))

d.depth++
for i := 0; i < length; i++ {
d.buf.WriteByte(0xa)
d.write("\n")
d.dump(value.Index(i).Interface())
d.buf.WriteString((","))
d.write((","))
}
d.depth--
d.buf.WriteString("\n" + strings.Repeat(" ", d.depth) + d.theme.VarType.apply("}"))
d.write("\n" + strings.Repeat(" ", d.depth) + d.theme.VarType.apply("}"))
}

func (d *dumper) dumpMap(v any) {
value := reflect.ValueOf(v)
keys := value.MapKeys()

d.buf.WriteString(d.theme.VarType.apply(fmt.Sprintf("%T:%d {", v, len(keys))))
d.write(d.theme.VarType.apply(fmt.Sprintf("%T:%d {", v, len(keys))))

d.depth++
for _, key := range keys {
d.buf.WriteByte(0xa)
d.write("\n")
d.dump(key.Interface())
d.buf.WriteString((": "))
d.write((": "))
d.dump(value.MapIndex(key).Interface(), true)
d.buf.WriteString((","))
d.write((","))
}
d.depth--

d.buf.WriteString("\n" + strings.Repeat(" ", d.depth) + d.theme.VarType.apply("}"))
d.write("\n" + strings.Repeat(" ", d.depth) + d.theme.VarType.apply("}"))
}

func (d *dumper) dumpPointer(v any) {
if d.ptrs == nil {
d.ptrs = make(map[uintptr]uint)
d.ptrs = make(map[uintptr]*pointer)
}

ptr := uintptr(reflect.ValueOf(v).UnsafePointer())

if ctr, ok := d.ptrs[ptr]; ok {
d.buf.WriteString(d.theme.PointerSign.apply("&"))
d.buf.WriteString(d.theme.PointerCounter.apply(fmt.Sprintf("#%d", ctr)))
if p, ok := d.ptrs[ptr]; ok {
d.write(d.theme.PointerSign.apply("&"))
d.write(d.theme.PointerCounter.apply(fmt.Sprintf("@%d", p.id)))

if !p.tagged {
d.tagPtr(p)
p.tagged = true
}
return
}

d.ptrs[ptr] = uint(len(d.ptrs) + 1)
d.buf.WriteString(d.theme.PointerCounter.apply(fmt.Sprintf("#%d ", d.ptrs[ptr])))
d.buf.WriteString(d.theme.PointerSign.apply("&"))
d.ptrs[ptr] = &pointer{
id: len(d.ptrs) + 1,
pos: len(d.buf),
}

d.write(d.theme.PointerSign.apply("&"))

actual := reflect.ValueOf(v).Elem()
if actual.IsValid() {
d.dump(actual.Interface(), true)
} else {
d.buf.WriteString(d.theme.Nil.apply("nil"))
d.write(d.theme.Nil.apply("nil"))
}

}
Expand All @@ -131,36 +143,55 @@ func (d *dumper) dumpStruct(v any) {
if strings.HasPrefix(typ, "struct") {
typ = "struct"
}
d.buf.WriteString(d.theme.VarType.apply(typ + " {"))
d.write(d.theme.VarType.apply(typ + " {"))

def := reflect.TypeOf(v)
value := reflect.ValueOf(v)

d.depth++
for i := 0; i < def.NumField(); i++ {
d.buf.WriteByte(0xa)
d.write("\n")
k := def.Field(i)
d.dumpStructKey(k)
d.buf.WriteString((": "))
d.write((": "))

if !k.IsExported() {
d.buf.WriteString(d.theme.VarType.apply(fmt.Sprintf("%v", k.Type)))
d.write(d.theme.VarType.apply(fmt.Sprintf("%v", k.Type)))
continue
}

d.dump(value.Field(i).Interface(), true)

d.buf.WriteString((","))
d.write((","))
}
d.depth--

d.buf.WriteString("\n" + strings.Repeat(" ", d.depth) + d.theme.VarType.apply("}"))
d.write("\n" + strings.Repeat(" ", d.depth) + d.theme.VarType.apply("}"))
}

func (d *dumper) dumpStructKey(key reflect.StructField) {
d.buf.WriteString(strings.Repeat(" ", d.depth))
d.write(strings.Repeat(" ", d.depth))
if !key.IsExported() {
d.buf.WriteString(d.theme.StructFieldHash.apply("#"))
d.write(d.theme.StructFieldHash.apply("#"))
}
d.buf.WriteString(d.theme.StructField.apply(key.Name))
d.write(d.theme.StructField.apply(key.Name))
}

func (d *dumper) write(s string) {
d.buf = append(d.buf, []byte(s)...)
}

func (d *dumper) tagPtr(ptr *pointer) {
var shifted int

for _, p := range d.ptrs {
if ptr.pos > p.pos && p.tagged {
shifted += len(d.theme.PointerCounter.apply(fmt.Sprintf("#%d", p.id)))
}
}

nbuf := append([]byte{}, d.buf[:ptr.pos+shifted]...)
nbuf = append(nbuf, []byte(d.theme.PointerCounter.apply(fmt.Sprintf("#%d", ptr.id)))...)
nbuf = append(nbuf, d.buf[ptr.pos+shifted:]...)
d.buf = nbuf
}
12 changes: 6 additions & 6 deletions dumper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,13 +202,13 @@ func TestDumper(t *testing.T) {
person3,
`godump.User {
Name: "test 3",
Friend: #1 &godump.User {
Friend: #1&godump.User {
Name: "test 2",
Friend: #2 &godump.User {
Friend: &godump.User {
Name: "test",
Friend: #3 &godump.User {
Friend: &godump.User {
Name: "test 3",
Friend: &#1,
Friend: &@1,
},
},
},
Expand All @@ -219,7 +219,7 @@ func TestDumper(t *testing.T) {
var d dumper
d.dump(tc.inputVar)

if returned := d.buf.String(); returned != tc.expected {
if returned := string(d.buf); returned != tc.expected {
t.Fatalf(`Case#%d failed, dumper returned unuexpected results : "%s" (%d), expected "%s" (%d)`, i, returned, len(returned), tc.expected,
len(tc.expected))
}
Expand Down Expand Up @@ -565,7 +565,7 @@ func TestDumperWithComplexDataStructure(t *testing.T) {

var d dumper
d.dump(root)
returned := d.buf.Bytes()
returned := d.buf

r_lines := bytes.Split(returned, []byte("\n"))
e_lines := bytes.Split(expectedOutput, []byte("\n"))
Expand Down
17 changes: 8 additions & 9 deletions godump.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package godump

import "os"
import (
"fmt"
"os"
)

// Dump the given variable
func Dump(v any) error {
d := dumper{}
d.theme = defaultTheme
d.dump(v)
d.buf.WriteByte(0xa)
_, err := d.buf.WriteTo(os.Stdout)
_, err := fmt.Fprintln(os.Stdout, string(d.buf))
if err != nil {
return err
}
Expand All @@ -19,8 +21,7 @@ func Dump(v any) error {
func DumpNC(v any) error {
d := dumper{}
d.dump(v)
d.buf.WriteByte(0xa)
_, err := d.buf.WriteTo(os.Stdout)
_, err := fmt.Fprintln(os.Stdout, string(d.buf))
if err != nil {
return err
}
Expand All @@ -32,14 +33,12 @@ func Sdump(v any) string {
d := dumper{}
d.theme = defaultTheme
d.dump(v)
d.buf.WriteByte(0xa)
return d.buf.String()
return string(d.buf)
}

// SdumpNC is just like DumpNC but returns the result instead of printing to STDOUT
func SdumpNC(v any) string {
d := dumper{}
d.dump(v)
d.buf.WriteByte(0xa)
return d.buf.String()
return string(d.buf)
}
Loading

0 comments on commit cb915b5

Please sign in to comment.