Add @type support to paths in Go (#2860)

Add @type support to paths in Go
This commit is contained in:
Aaron Boodman
2016-11-29 18:13:29 -08:00
committed by GitHub
parent 5e4c1c06d8
commit b221caabea
2 changed files with 78 additions and 20 deletions

View File

@@ -17,7 +17,7 @@ import (
"github.com/attic-labs/noms/go/hash"
)
var annotationRe = regexp.MustCompile("^@([a-z]+)")
var annotationRe = regexp.MustCompile("^([a-z]+)")
// A Path is an address to a Noms value - and unlike hashes (i.e. #abcd...) they
// can address inlined values.
@@ -75,32 +75,36 @@ func constructPath(p Path, str string) (Path, error) {
return Path{}, errors.New("[ is missing closing ]")
}
rem = rem[1:]
intoKey := false
if ann, rem2 := getAnnotation(rem); ann != "" {
if ann != "key" {
return Path{}, fmt.Errorf("Unsupported annotation: @%s", ann)
}
intoKey = true
rem = rem2
}
d.Chk.NotEqual(idx == nil, h.IsEmpty())
var part PathPart
switch {
case idx != nil && intoKey:
part = NewIndexIntoKeyPath(idx)
case idx != nil:
if idx != nil {
part = NewIndexPath(idx)
case intoKey:
part = NewHashIndexIntoKeyPath(h)
default:
} else {
part = NewHashIndexPath(h)
}
p = append(p, part)
return constructPath(p, rem)
case '@':
ann, rem := getAnnotation(tail)
switch ann {
case "key":
if len(p) == 0 {
return Path{}, fmt.Errorf("Cannot use @key annotation at beginning of path")
}
lastPart := p[len(p)-1]
if ki, ok := lastPart.(keyIndexable); ok {
p[len(p)-1] = ki.setIntoKey(true).(PathPart)
return constructPath(p, rem)
}
return Path{}, fmt.Errorf("Cannot use @key annotation on: %s", lastPart.String())
case "type":
return constructPath(append(p, TypePart{}), rem)
default:
return Path{}, fmt.Errorf("Unsupported annotation: @%s", ann)
}
case ']':
return Path{}, errors.New("] is missing opening [")
@@ -239,6 +243,11 @@ func (ip IndexPath) String() (str string) {
return fmt.Sprintf("[%s]%s", EncodedIndexValue(ip.Index), ann)
}
func (ip IndexPath) setIntoKey(v bool) keyIndexable {
ip.IntoKey = v
return ip
}
// Indexes into Maps by the hash of a key, or a Set by the hash of a value.
type HashIndexPath struct {
// The hash of the key or value to search for. Maps and Set are ordered, so
@@ -306,6 +315,11 @@ func (hip HashIndexPath) String() string {
return fmt.Sprintf("[#%s]%s", hip.Hash.String(), ann)
}
func (hip HashIndexPath) setIntoKey(v bool) keyIndexable {
hip.IntoKey = v
return hip
}
// Parse a Noms value from the path index syntax.
// 4 -> types.Number
// "4" -> types.String
@@ -369,6 +383,17 @@ Switch:
return
}
type TypePart struct {
}
func (tp TypePart) Resolve(v Value) Value {
return v.Type()
}
func (tp TypePart) String() string {
return "@type"
}
func getAnnotation(str string) (ann, rem string) {
if parts := annotationRe.FindStringSubmatch(str); parts != nil {
ann = parts[1]
@@ -376,3 +401,7 @@ func getAnnotation(str string) (ann, rem string) {
}
return
}
type keyIndexable interface {
setIntoKey(v bool) keyIndexable
}

View File

@@ -213,11 +213,14 @@ func TestPathParseSuccess(t *testing.T) {
h := Number(42).Hash() // arbitrary hash
test(".foo")
test(".foo@type")
test(".Q")
test(".QQ")
test("[true]")
test("[true]@type")
test("[false]")
test("[false]@key")
test("[false]@key@type")
test("[42]")
test("[42]@key")
test("[1e4]")
@@ -289,8 +292,9 @@ func TestPathParseErrors(t *testing.T) {
test(".foo[42]bar", "Invalid operator: b")
test("#foo", "Invalid operator: #")
test("!foo", "Invalid operator: !")
test("@foo", "Invalid operator: @")
test("@key", "Invalid operator: @")
test("@foo", "Unsupported annotation: @foo")
test("@key", "Cannot use @key annotation at beginning of path")
test(".foo@key", "Cannot use @key annotation on: .foo")
test(fmt.Sprintf(".foo[#%s]@soup", hash.FromData([]byte{42}).String()), "Unsupported annotation: @soup")
}
@@ -374,3 +378,28 @@ func TestMustParsePath(t *testing.T) {
assert.Panics(t, func() { MustParsePath(bad) })
}
}
func TestPathType(t *testing.T) {
assert := assert.New(t)
m := NewMap(
String("string"), String("foo"),
String("bool"), Bool(false),
String("number"), Number(42),
String("List<number|string>"), NewList(Number(42), String("foo")),
String("Map<Bool, Bool>"), NewMap(Bool(true), Bool(false)))
m.IterAll(func(k, cv Value) {
ks := k.(String)
assertResolvesTo(assert, cv.Type(), m, fmt.Sprintf("[\"%s\"]@type", ks))
})
assertResolvesTo(assert, StringType, m, `["string"]@key@type`)
assertResolvesTo(assert, m.Type(), m, `@type`)
s := NewStruct("", StructData{
"str": String("foo"),
"num": Number(42),
})
assertResolvesTo(assert, s.Get("str").Type(), s, ".str@type")
assertResolvesTo(assert, s.Get("num").Type(), s, ".num@type")
}