ast: Add helper to convert ref to pointer string

In the past we've written a bunch of ad-hoc code to convert refs to
slash-separated/JSON Pointer-esque strings. These just add a helper to
the ref struct to convert to/from that kind of string.

Signed-off-by: Torin Sandall <torinsandall@gmail.com>
This commit is contained in:
Torin Sandall
2019-02-15 07:17:13 -08:00
parent e13143c737
commit 7df209106f
2 changed files with 80 additions and 0 deletions
+37
View File
@@ -8,6 +8,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"net/url"
"regexp"
"sort"
"strconv"
@@ -805,6 +806,27 @@ func EmptyRef() Ref {
return Ref([]*Term{})
}
// PtrRef returns a new reference against the head for the pointer
// s. Path components in the pointer are unescaped.
func PtrRef(head *Term, s string) (Ref, error) {
s = strings.Trim(s, "/")
if s == "" {
return Ref{head}, nil
}
parts := strings.Split(s, "/")
ref := make(Ref, len(parts)+1)
ref[0] = head
for i := 0; i < len(parts); i++ {
var err error
parts[i], err = url.PathUnescape(parts[i])
if err != nil {
return nil, err
}
ref[i+1] = StringTerm(parts[i])
}
return ref, nil
}
// RefTerm creates a new Term with a Ref value.
func RefTerm(r ...*Term) *Term {
return &Term{Value: Ref(r)}
@@ -967,6 +989,21 @@ func (ref Ref) IsNested() bool {
return false
}
// Ptr returns a slash-separated path string for this ref. If the ref
// contains non-string terms this function returns an error. Path
// components are escaped.
func (ref Ref) Ptr() (string, error) {
parts := make([]string, 0, len(ref)-1)
for _, term := range ref[1:] {
if str, ok := term.Value.(String); ok {
parts = append(parts, url.PathEscape(string(str)))
} else {
return "", fmt.Errorf("invalid path value type")
}
}
return strings.Join(parts, "/"), nil
}
var varRegexp = regexp.MustCompile("^[[:alpha:]_][[:alpha:][:digit:]_]*$")
func (ref Ref) String() string {
+43
View File
@@ -419,6 +419,49 @@ func TestRefConcat(t *testing.T) {
}
}
func TestRefPtr(t *testing.T) {
cases := []string{
"",
"a",
"a/b",
"/a/b",
"/a/b/",
"a%2Fb",
}
for _, tc := range cases {
ref, err := PtrRef(DefaultRootDocument.Copy(), tc)
if err != nil {
t.Fatal("Unexpected error:", err)
}
ptr, err := ref.Ptr()
if err != nil {
t.Fatal("Unexpected error:", err)
}
roundtrip, err := PtrRef(DefaultRootDocument.Copy(), ptr)
if err != nil {
t.Fatal("Unexpected error:", err)
}
if !ref.Equal(roundtrip) {
t.Fatalf("Expected roundtrip of %q to be equal but got %v and %v", tc, ref, roundtrip)
}
}
if _, err := PtrRef(DefaultRootDocument.Copy(), "2%"); err == nil {
t.Fatalf("Expected error from %q", "2%")
}
ref := Ref{VarTerm("x"), IntNumberTerm(1)}
if _, err := ref.Ptr(); err == nil {
t.Fatal("Expected error from x[1]")
}
}
func TestSetEqual(t *testing.T) {
tests := []struct {
a string