Skip to content
This repository has been archived by the owner on Aug 9, 2018. It is now read-only.

Commit

Permalink
Add NodeReader interface
Browse files Browse the repository at this point in the history
See issue #17
  • Loading branch information
mildred committed Dec 31, 2015
1 parent d68f39d commit 5bff9a5
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 0 deletions.
72 changes: 72 additions & 0 deletions ipld.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ipld
import (
"errors"
"reflect"
"sort"

mh "github.com/jbenet/go-multihash"
)
Expand Down Expand Up @@ -187,3 +188,74 @@ func LinkCast(v interface{}) (l Link, ok bool) {
}
return l, true
}

func (n Node) Read(fun ReadFun) error {
return read(n, fun, []interface{}{})
}

func read(curr interface{}, fun ReadFun, path []interface{}) error {
if nc, ok := curr.(Node); ok { // it's a node!
err := fun(path, TokenNode, nil)
if err == SkipNode {
return nil
} else if err != nil {
return err
}

// Iterate in fixed order (by default, go randomize iteration order)
// Simulate reading from a file where the order is fixed
keys := make([]string, 0, len(nc))
for k := range nc {
keys = append(keys, k)
}
sort.Strings(keys)

for _, k := range keys {
err := fun(path, TokenKey, k)
if err == SkipNode {
return nil
} else if err != nil {
return err
}

subpath := append(path, k)
err = read(nc[k], fun, subpath)
if err != nil {
return err
}
}

} else if sc, ok := curr.([]interface{}); ok { // it's a slice!
err := fun(path, TokenArray, nil)
if err == SkipNode {
return nil
} else if err != nil {
return err
}

for i, v := range sc {
err := fun(path, TokenIndex, i)
if err == SkipNode {
return nil
} else if err != nil {
return err
}

subpath := append(path, i)
err = read(v, fun, subpath)
if err != nil {
return err
}
}

} else {
err := fun(path, TokenValue, curr)
if err == SkipNode {
return nil
} else if err != nil {
return err
}
}
return nil
}

60 changes: 60 additions & 0 deletions reader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package ipld

type ReaderToken int
const (
TokenNode ReaderToken = iota
TokenKey
TokenArray
TokenIndex
TokenValue
)

// Callback function to be called when reading a Node. The path represents the
// location in the hierarchy, tokenType is the type of token being read, and
// value is the corresponding value. The function can return SkipNode to skip
// recursing, or an error to stop the reading at any time.
type ReadFun func(path []interface{}, tokenType ReaderToken, value interface{}) error

// Represents a Node that can be read using the special Read function
type NodeReader interface {

// Read the node from the beginning. Return any errors found during the
// process. The function f is called for every value found in the node stream,
// on the following events:
//
// - Once for each node (TokenNode) with a nil value
// - Once for each key of each node (TokenKey) with the key name as value
// - Once for each array / slice (TokenArray) with a nil value
// - Once for each slice item (TokenIndex) with the index as value
// - Once for each other value (TokenValue) with the actual value given
//
// For example, the node that can be represented using the following JSON
// construct:
//
// {
// "key": "value",
// "items": ["a", "b", "c"],
// "count": 3
// }
//
// Will result in the function called in this way:
//
// f({}, TokenNode, nil)
// f({}, TokenKey, "key")
// f({"key"}, TokenValue, "value")
// f({}, TokenKey, "items")
// f({"items"}, TokenArray, nil)
// f({"items"}, TokenIndex, 0)
// f({"items", 0}, TokenValue, "a")
// f({"items"}, TokenIndex, 1)
// f({"items", 1}, TokenValue, "b")
// f({"items"}, TokenIndex, 2)
// f({"items", 2}, TokenValue, "c")
// f({}, TokenKey, "count")
// f({"count"}, TokenValue, 3)
//
// Iteration order for maps is fixed by the order of the pairs in memory.
//
Read(f ReadFun) error;
}

57 changes: 57 additions & 0 deletions reader_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package ipld

import (
"github.com/mildred/assrt"
"testing"
)

type callback struct {
path []interface{};
tokenType ReaderToken;
value interface{};
}

func TestReader(t *testing.T) {
assert := assrt.NewAssert(t)

var node *Node
var i int = 0

node = &Node{
"key": "value",
"items": []interface{}{"a", "b", "c"},
"count": 3,
}

callbacks := []callback{
callback{[]interface{}{}, TokenNode, nil},
callback{[]interface{}{}, TokenKey, "count"},
callback{[]interface{}{"count"}, TokenValue, 3},
callback{[]interface{}{}, TokenKey, "items"},
callback{[]interface{}{"items"}, TokenArray, nil},
callback{[]interface{}{"items"}, TokenIndex, 0},
callback{[]interface{}{"items", 0}, TokenValue, "a"},
callback{[]interface{}{"items"}, TokenIndex, 1},
callback{[]interface{}{"items", 1}, TokenValue, "b"},
callback{[]interface{}{"items"}, TokenIndex, 2},
callback{[]interface{}{"items", 2}, TokenValue, "c"},
callback{[]interface{}{}, TokenKey, "key"},
callback{[]interface{}{"key"}, TokenValue, "value"},
}

err := node.Read(func(path []interface{}, tokenType ReaderToken, value interface{}) error {
assert.Logf("At callback %d", i)
if i >= len(callbacks) {
assert.Fail()
} else {
cb := callbacks[i]
assert.Equal(cb.path, path)
assert.Equal(cb.tokenType, tokenType)
assert.Equal(cb.value, value)
}
i++
return nil
})
assert.Nil(err)

}

0 comments on commit 5bff9a5

Please sign in to comment.