import match package

This commit is contained in:
Josh Baker 2016-08-30 07:21:20 -07:00
parent 550c66c276
commit 4d7d1a76a8
2 changed files with 18 additions and 434 deletions

106
gjson.go
View File

@ -1,7 +1,11 @@
// Package gjson provides searching for json strings. // Package gjson provides searching for json strings.
package gjson package gjson
import "strconv" import (
"strconv"
"github.com/tidwall/match"
)
// Type is Result type // Type is Result type
type Type int type Type int
@ -524,16 +528,11 @@ type objectPathResult struct {
part string part string
path string path string
wild bool wild bool
uc bool
more bool more bool
} }
func parseObjectPath(path string) (r objectPathResult) { func parseObjectPath(path string) (r objectPathResult) {
for i := 0; i < len(path); i++ { for i := 0; i < len(path); i++ {
if path[i]&0x60 == 0x60 {
// alpha lowercase
continue
}
if path[i] == '.' { if path[i] == '.' {
r.part = path[:i] r.part = path[:i]
r.path = path[i+1:] r.path = path[i+1:]
@ -544,10 +543,6 @@ func parseObjectPath(path string) (r objectPathResult) {
r.wild = true r.wild = true
continue continue
} }
if path[i] > 0x7f {
r.uc = true
continue
}
if path[i] == '\\' { if path[i] == '\\' {
// go into escape mode. this is a slower path that // go into escape mode. this is a slower path that
// strips off the escape character from the part. // strips off the escape character from the part.
@ -557,10 +552,6 @@ func parseObjectPath(path string) (r objectPathResult) {
epart = append(epart, path[i]) epart = append(epart, path[i])
i++ i++
for ; i < len(path); i++ { for ; i < len(path); i++ {
if path[i] > 0x7f {
r.uc = true
continue
}
if path[i] == '\\' { if path[i] == '\\' {
i++ i++
if i < len(path) { if i < len(path) {
@ -636,7 +627,7 @@ func parseSquash(json string, i int) (int, string) {
} }
func parseObject(c *parseContext, i int, path string) (int, bool) { func parseObject(c *parseContext, i int, path string) (int, bool) {
var match, kesc, vesc, ok, hit bool var pmatch, kesc, vesc, ok, hit bool
var key, val string var key, val string
rp := parseObjectPath(path) rp := parseObjectPath(path)
for i < len(c.json) { for i < len(c.json) {
@ -695,18 +686,18 @@ func parseObject(c *parseContext, i int, path string) (int, bool) {
} }
if rp.wild { if rp.wild {
if kesc { if kesc {
match = wildcardMatch(unescape(key), rp.part, rp.uc) pmatch = match.Match(unescape(key), rp.part)
} else { } else {
match = wildcardMatch(key, rp.part, rp.uc) pmatch = match.Match(key, rp.part)
} }
} else { } else {
if kesc { if kesc {
match = rp.part == unescape(key) pmatch = rp.part == unescape(key)
} else { } else {
match = rp.part == key pmatch = rp.part == key
} }
} }
hit = match && !rp.more hit = pmatch && !rp.more
for ; i < len(c.json); i++ { for ; i < len(c.json); i++ {
switch c.json[i] { switch c.json[i] {
default: default:
@ -728,7 +719,7 @@ func parseObject(c *parseContext, i int, path string) (int, bool) {
return i, true return i, true
} }
case '{': case '{':
if match && !hit { if pmatch && !hit {
i, hit = parseObject(c, i+1, rp.path) i, hit = parseObject(c, i+1, rp.path)
if hit { if hit {
return i, true return i, true
@ -742,7 +733,7 @@ func parseObject(c *parseContext, i int, path string) (int, bool) {
} }
} }
case '[': case '[':
if match && !hit { if pmatch && !hit {
i, hit = parseArray(c, i+1, rp.path) i, hit = parseArray(c, i+1, rp.path)
if hit { if hit {
return i, true return i, true
@ -784,7 +775,7 @@ func parseObject(c *parseContext, i int, path string) (int, bool) {
} }
func parseArray(c *parseContext, i int, path string) (int, bool) { func parseArray(c *parseContext, i int, path string) (int, bool) {
var match, vesc, ok, hit bool var pmatch, vesc, ok, hit bool
var val string var val string
var h int var h int
var alog []int var alog []int
@ -800,8 +791,8 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
} }
for i < len(c.json) { for i < len(c.json) {
if !rp.arrch { if !rp.arrch {
match = partidx == h pmatch = partidx == h
hit = match && !rp.more hit = pmatch && !rp.more
} }
h++ h++
if rp.alogok { if rp.alogok {
@ -831,7 +822,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
return i, true return i, true
} }
case '{': case '{':
if match && !hit { if pmatch && !hit {
i, hit = parseObject(c, i+1, rp.path) i, hit = parseObject(c, i+1, rp.path)
if hit { if hit {
if rp.alogok { if rp.alogok {
@ -851,7 +842,7 @@ func parseArray(c *parseContext, i int, path string) (int, bool) {
} }
} }
case '[': case '[':
if match && !hit { if pmatch && !hit {
i, hit = parseArray(c, i+1, rp.path) i, hit = parseArray(c, i+1, rp.path)
if hit { if hit {
if rp.alogok { if rp.alogok {
@ -1106,64 +1097,3 @@ func stringLessInsensitive(a, b string) bool {
} }
return len(a) < len(b) return len(a) < len(b)
} }
// wilcardMatch returns true if str matches pattern. This is a very
// simple wildcard match where '*' matches on any number characters
// and '?' matches on any one character.
func wildcardMatch(str, pattern string, uc bool) bool {
if pattern == "*" {
return true
}
if !uc {
return deepMatch(str, pattern)
}
rstr := make([]rune, 0, len(str))
rpattern := make([]rune, 0, len(pattern))
for _, r := range str {
rstr = append(rstr, r)
}
for _, r := range pattern {
rpattern = append(rpattern, r)
}
return deepMatchRune(rstr, rpattern)
}
func deepMatch(str, pattern string) bool {
for len(pattern) > 0 {
switch pattern[0] {
default:
if len(str) == 0 || str[0] != pattern[0] {
return false
}
case '?':
if len(str) == 0 {
return false
}
case '*':
return deepMatch(str, pattern[1:]) ||
(len(str) > 0 && deepMatch(str[1:], pattern))
}
str = str[1:]
pattern = pattern[1:]
}
return len(str) == 0 && len(pattern) == 0
}
func deepMatchRune(str, pattern []rune) bool {
for len(pattern) > 0 {
switch pattern[0] {
default:
if len(str) == 0 || str[0] != pattern[0] {
return false
}
case '?':
if len(str) == 0 {
return false
}
case '*':
return deepMatchRune(str, pattern[1:]) ||
(len(str) > 0 && deepMatchRune(str[1:], pattern))
}
str = str[1:]
pattern = pattern[1:]
}
return len(str) == 0 && len(pattern) == 0
}

View File

@ -251,47 +251,6 @@ func TestBasic(t *testing.T) {
t.Fatalf("expecting %v, got %v", "Jason", fn) t.Fatalf("expecting %v, got %v", "Jason", fn)
} }
} }
func TestMatch(t *testing.T) {
if !wildcardMatch("hello world", "hello world", false) {
t.Fatal("fail")
}
if wildcardMatch("hello world", "jello world", false) {
t.Fatal("fail")
}
if !wildcardMatch("hello world", "hello*", false) {
t.Fatal("fail")
}
if wildcardMatch("hello world", "jello*", false) {
t.Fatal("fail")
}
if !wildcardMatch("hello world", "hello?world", false) {
t.Fatal("fail")
}
if wildcardMatch("hello world", "jello?world", false) {
t.Fatal("fail")
}
if !wildcardMatch("hello world", "he*o?world", false) {
t.Fatal("fail")
}
if !wildcardMatch("hello world", "he*o?wor*", false) {
t.Fatal("fail")
}
if !wildcardMatch("hello world", "he*o?*r*", false) {
t.Fatal("fail")
}
if !wildcardMatch("的情况下解析一个", "*", true) {
t.Fatal("fail")
}
if !wildcardMatch("的情况下解析一个", "*况下*", true) {
t.Fatal("fail")
}
if !wildcardMatch("的情况下解析一个", "*况?*", true) {
t.Fatal("fail")
}
if !wildcardMatch("的情况下解析一个", "的情况?解析一个", true) {
t.Fatal("fail")
}
}
func TestUnicode(t *testing.T) { func TestUnicode(t *testing.T) {
var json = `{"key":0,"的情况下解":{"key":1,"的情况":2}}` var json = `{"key":0,"的情况下解":{"key":1,"的情况":2}}`
if Get(json, "的情况下解.key").Num != 1 { if Get(json, "的情况下解.key").Num != 1 {
@ -317,311 +276,6 @@ func TestUnicode(t *testing.T) {
} }
} }
// TestWildcardMatch - Tests validate the logic of wild card matching.
// `WildcardMatch` supports '*' and '?' wildcards.
// Sample usage: In resource matching for folder policy validation.
func TestWildcardMatch(t *testing.T) {
testCases := []struct {
pattern string
text string
matched bool
}{
// Test case - 1.
// Test case with pattern containing key name with a prefix. Should accept the same text without a "*".
{
pattern: "my-folder/oo*",
text: "my-folder/oo",
matched: true,
},
// Test case - 2.
// Test case with "*" at the end of the pattern.
{
pattern: "my-folder/In*",
text: "my-folder/India/Karnataka/",
matched: true,
},
// Test case - 3.
// Test case with prefixes shuffled.
// This should fail.
{
pattern: "my-folder/In*",
text: "my-folder/Karnataka/India/",
matched: false,
},
// Test case - 4.
// Test case with text expanded to the wildcards in the pattern.
{
pattern: "my-folder/In*/Ka*/Ban",
text: "my-folder/India/Karnataka/Ban",
matched: true,
},
// Test case - 5.
// Test case with the keyname part is repeated as prefix several times.
// This is valid.
{
pattern: "my-folder/In*/Ka*/Ban",
text: "my-folder/India/Karnataka/Ban/Ban/Ban/Ban/Ban",
matched: true,
},
// Test case - 6.
// Test case to validate that `*` can be expanded into multiple prefixes.
{
pattern: "my-folder/In*/Ka*/Ban",
text: "my-folder/India/Karnataka/Area1/Area2/Area3/Ban",
matched: true,
},
// Test case - 7.
// Test case to validate that `*` can be expanded into multiple prefixes.
{
pattern: "my-folder/In*/Ka*/Ban",
text: "my-folder/India/State1/State2/Karnataka/Area1/Area2/Area3/Ban",
matched: true,
},
// Test case - 8.
// Test case where the keyname part of the pattern is expanded in the text.
{
pattern: "my-folder/In*/Ka*/Ban",
text: "my-folder/India/Karnataka/Bangalore",
matched: false,
},
// Test case - 9.
// Test case with prefixes and wildcard expanded for all "*".
{
pattern: "my-folder/In*/Ka*/Ban*",
text: "my-folder/India/Karnataka/Bangalore",
matched: true,
},
// Test case - 10.
// Test case with keyname part being a wildcard in the pattern.
{pattern: "my-folder/*",
text: "my-folder/India",
matched: true,
},
// Test case - 11.
{
pattern: "my-folder/oo*",
text: "my-folder/odo",
matched: false,
},
// Test case with pattern containing wildcard '?'.
// Test case - 12.
// "my-folder?/" matches "my-folder1/", "my-folder2/", "my-folder3" etc...
// doesn't match "myfolder/".
{
pattern: "my-folder?/abc*",
text: "myfolder/abc",
matched: false,
},
// Test case - 13.
{
pattern: "my-folder?/abc*",
text: "my-folder1/abc",
matched: true,
},
// Test case - 14.
{
pattern: "my-?-folder/abc*",
text: "my--folder/abc",
matched: false,
},
// Test case - 15.
{
pattern: "my-?-folder/abc*",
text: "my-1-folder/abc",
matched: true,
},
// Test case - 16.
{
pattern: "my-?-folder/abc*",
text: "my-k-folder/abc",
matched: true,
},
// Test case - 17.
{
pattern: "my??folder/abc*",
text: "myfolder/abc",
matched: false,
},
// Test case - 18.
{
pattern: "my??folder/abc*",
text: "my4afolder/abc",
matched: true,
},
// Test case - 19.
{
pattern: "my-folder?abc*",
text: "my-folder/abc",
matched: true,
},
// Test case 20-21.
// '?' matches '/' too. (works with s3).
// This is because the namespace is considered flat.
// "abc?efg" matches both "abcdefg" and "abc/efg".
{
pattern: "my-folder/abc?efg",
text: "my-folder/abcdefg",
matched: true,
},
{
pattern: "my-folder/abc?efg",
text: "my-folder/abc/efg",
matched: true,
},
// Test case - 22.
{
pattern: "my-folder/abc????",
text: "my-folder/abc",
matched: false,
},
// Test case - 23.
{
pattern: "my-folder/abc????",
text: "my-folder/abcde",
matched: false,
},
// Test case - 24.
{
pattern: "my-folder/abc????",
text: "my-folder/abcdefg",
matched: true,
},
// Test case 25-26.
// test case with no '*'.
{
pattern: "my-folder/abc?",
text: "my-folder/abc",
matched: false,
},
{
pattern: "my-folder/abc?",
text: "my-folder/abcd",
matched: true,
},
{
pattern: "my-folder/abc?",
text: "my-folder/abcde",
matched: false,
},
// Test case 27.
{
pattern: "my-folder/mnop*?",
text: "my-folder/mnop",
matched: false,
},
// Test case 28.
{
pattern: "my-folder/mnop*?",
text: "my-folder/mnopqrst/mnopqr",
matched: true,
},
// Test case 29.
{
pattern: "my-folder/mnop*?",
text: "my-folder/mnopqrst/mnopqrs",
matched: true,
},
// Test case 30.
{
pattern: "my-folder/mnop*?",
text: "my-folder/mnop",
matched: false,
},
// Test case 31.
{
pattern: "my-folder/mnop*?",
text: "my-folder/mnopq",
matched: true,
},
// Test case 32.
{
pattern: "my-folder/mnop*?",
text: "my-folder/mnopqr",
matched: true,
},
// Test case 33.
{
pattern: "my-folder/mnop*?and",
text: "my-folder/mnopqand",
matched: true,
},
// Test case 34.
{
pattern: "my-folder/mnop*?and",
text: "my-folder/mnopand",
matched: false,
},
// Test case 35.
{
pattern: "my-folder/mnop*?and",
text: "my-folder/mnopqand",
matched: true,
},
// Test case 36.
{
pattern: "my-folder/mnop*?",
text: "my-folder/mn",
matched: false,
},
// Test case 37.
{
pattern: "my-folder/mnop*?",
text: "my-folder/mnopqrst/mnopqrs",
matched: true,
},
// Test case 38.
{
pattern: "my-folder/mnop*??",
text: "my-folder/mnopqrst",
matched: true,
},
// Test case 39.
{
pattern: "my-folder/mnop*qrst",
text: "my-folder/mnopabcdegqrst",
matched: true,
},
// Test case 40.
{
pattern: "my-folder/mnop*?and",
text: "my-folder/mnopqand",
matched: true,
},
// Test case 41.
{
pattern: "my-folder/mnop*?and",
text: "my-folder/mnopand",
matched: false,
},
// Test case 42.
{
pattern: "my-folder/mnop*?and?",
text: "my-folder/mnopqanda",
matched: true,
},
// Test case 43.
{
pattern: "my-folder/mnop*?and",
text: "my-folder/mnopqanda",
matched: false,
},
// Test case 44.
{
pattern: "my-?-folder/abc*",
text: "my-folder/mnopqanda",
matched: false,
},
}
// Iterating over the test cases, call the function under test and asert the output.
for i, testCase := range testCases {
actualResult := wildcardMatch(testCase.text, testCase.pattern, false)
if testCase.matched != actualResult {
t.Errorf("Test %d: Expected the result to be `%v`, but instead found it to be `%v`", i+1, testCase.matched, actualResult)
}
}
}
func TestUnescape(t *testing.T) { func TestUnescape(t *testing.T) {
unescape(string([]byte{'\\', '\\', 0})) unescape(string([]byte{'\\', '\\', 0}))
unescape(string([]byte{'\\', '/', '\\', 'b', '\\', 'f'})) unescape(string([]byte{'\\', '/', '\\', 'b', '\\', 'f'}))