Merge pull request #354 from apelisse/unstructpath
unstructpath: Create json path type package.
This commit is contained in:
commit
fffaddd605
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// This package helps you find specific fields in your unstruct
|
||||
// object. It is similar to what you can do with jsonpath, but reads the
|
||||
// path from strongly typed object, not strings.
|
||||
package unstructpath
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package unstructpath
|
||||
|
||||
import (
|
||||
"github.com/ghodss/yaml"
|
||||
)
|
||||
|
||||
// This example is inspired from http://goessner.net/articles/JsonPath/#e3.
|
||||
func Example() {
|
||||
data := `{ "store": {
|
||||
"book": [
|
||||
{ "category": "reference",
|
||||
"author": "Nigel Rees",
|
||||
"title": "Sayings of the Century",
|
||||
"price": 8.95
|
||||
},
|
||||
{ "category": "fiction",
|
||||
"author": "Evelyn Waugh",
|
||||
"title": "Sword of Honour",
|
||||
"price": 12.99
|
||||
},
|
||||
{ "category": "fiction",
|
||||
"author": "Herman Melville",
|
||||
"title": "Moby Dick",
|
||||
"isbn": "0-553-21311-3",
|
||||
"price": 8.99
|
||||
},
|
||||
{ "category": "fiction",
|
||||
"author": "J. R. R. Tolkien",
|
||||
"title": "The Lord of the Rings",
|
||||
"isbn": "0-395-19395-8",
|
||||
"price": 22.99
|
||||
}
|
||||
],
|
||||
"bicycle": {
|
||||
"color": "red",
|
||||
"price": 19.95
|
||||
}
|
||||
}
|
||||
}`
|
||||
u := map[string]interface{}{}
|
||||
if err := yaml.Unmarshal([]byte(data), &u); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// The authors of all books in the store. Returns a list of strings.
|
||||
Children().Map().Field("book").Children().Map().Field("author").String().SelectFrom(u)
|
||||
|
||||
// All authors. Returns a list of strings.
|
||||
All().Map().Field("author").String().SelectFrom(u)
|
||||
|
||||
// All things in store, which are some books and a red bicycle. Returns a list of interface{}.
|
||||
Map().Field("store").Children().SelectFrom(u)
|
||||
|
||||
// The price of everything in the store. Returns a list of "float64".
|
||||
Map().Field("store").All().Map().Field("price").Number().SelectFrom(u)
|
||||
|
||||
// The third book. Returns a list of 1 interface{}.
|
||||
All().Map().Field("book").Slice().At(2).SelectFrom(u)
|
||||
|
||||
// The last book in order. Return a list of 1 interface{}.
|
||||
All().Map().Field("book").Slice().Last().SelectFrom(u)
|
||||
|
||||
// The first two books. Returns a list of 2 interface{}.
|
||||
All().Map().Field("book").Slice().AtP(NumberLessThan(2)).SelectFrom(u)
|
||||
|
||||
// Filter all books with isbn number. Returns a list of interface{}.
|
||||
All().Map().Field("book").Filter(Map().Field("isbn")).SelectFrom(u)
|
||||
|
||||
// Filter all books cheaper than 10. Returns a list of interface{}.
|
||||
All().Map().Field("book").Children().Filter(Map().Field("price").Number().Filter(NumberLessThan(10))).SelectFrom(u)
|
||||
|
||||
// All elements in structure. Returns a list of interface{}.
|
||||
All().SelectFrom(u)
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package unstructpath
|
||||
|
||||
import "sort"
|
||||
|
||||
// This is a Map-to-Value filter.
|
||||
type mapFilter interface {
|
||||
SelectFrom(...map[string]interface{}) []interface{}
|
||||
}
|
||||
|
||||
func filterMap(ms MapS, mf mapFilter) ValueS {
|
||||
return &valueS{
|
||||
vf: &valueMapFilter{
|
||||
ms: ms,
|
||||
mf: mf,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type mapFieldPFilter struct {
|
||||
sp StringP
|
||||
}
|
||||
|
||||
func (f mapFieldPFilter) SelectFrom(maps ...map[string]interface{}) []interface{} {
|
||||
values := []interface{}{}
|
||||
|
||||
for _, m := range maps {
|
||||
for _, field := range sortedKeys(m) {
|
||||
if !f.sp.Match(field) {
|
||||
continue
|
||||
}
|
||||
values = append(values, m[field])
|
||||
}
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
type mapAllFilter struct{}
|
||||
|
||||
func (mapAllFilter) SelectFrom(maps ...map[string]interface{}) []interface{} {
|
||||
values := []interface{}{}
|
||||
for _, m := range maps {
|
||||
for _, field := range sortedKeys(m) {
|
||||
values = append(values, All().SelectFrom(m[field])...)
|
||||
}
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
func sortedKeys(m map[string]interface{}) []string {
|
||||
keys := []string{}
|
||||
for key := range m {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
return keys
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package unstructpath
|
||||
|
||||
// MapP is a "map predicate". It's a type that decides if a
|
||||
// map matches or not.
|
||||
type MapP interface {
|
||||
Match(map[string]interface{}) bool
|
||||
}
|
||||
|
||||
// MapNot inverses the value of the predicate.
|
||||
func MapNot(predicate MapP) MapP {
|
||||
return mapNot{mp: predicate}
|
||||
}
|
||||
|
||||
type mapNot struct {
|
||||
mp MapP
|
||||
}
|
||||
|
||||
func (p mapNot) Match(m map[string]interface{}) bool {
|
||||
return !p.mp.Match(m)
|
||||
}
|
||||
|
||||
// MapAnd returns true if all the sub-predicates are true. If there are
|
||||
// no sub-predicates, always returns true.
|
||||
func MapAnd(predicates ...MapP) MapP {
|
||||
return mapAnd{mps: predicates}
|
||||
}
|
||||
|
||||
type mapAnd struct {
|
||||
mps []MapP
|
||||
}
|
||||
|
||||
func (p mapAnd) Match(m map[string]interface{}) bool {
|
||||
for _, mp := range p.mps {
|
||||
if !mp.Match(m) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// MapOr returns true if any sub-predicate is true. If there are no
|
||||
// sub-predicates, always returns false.
|
||||
func MapOr(predicates ...MapP) MapP {
|
||||
mps := []MapP{}
|
||||
|
||||
// Implements "De Morgan's law"
|
||||
for _, mp := range predicates {
|
||||
mps = append(mps, MapNot(mp))
|
||||
}
|
||||
return MapNot(MapAnd(mps...))
|
||||
}
|
||||
|
||||
// MapNumFields matches if the number of fields matches the number
|
||||
// predicate.
|
||||
func MapNumFields(predicate NumberP) MapP {
|
||||
return mapNumFields{ip: predicate}
|
||||
}
|
||||
|
||||
type mapNumFields struct {
|
||||
ip NumberP
|
||||
}
|
||||
|
||||
func (p mapNumFields) Match(m map[string]interface{}) bool {
|
||||
return p.ip.Match(float64(len(m)))
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package unstructpath_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "k8s.io/kubectl/pkg/framework/unstructpath"
|
||||
)
|
||||
|
||||
type MapTrue struct{}
|
||||
|
||||
func (MapTrue) Match(m map[string]interface{}) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func TestMapNot(t *testing.T) {
|
||||
if MapNot(MapTrue{}).Match(map[string]interface{}{}) {
|
||||
t.Fatal("MapNot(MapTrue{}) should never match")
|
||||
}
|
||||
if !MapNot(MapNot(MapTrue{})).Match(map[string]interface{}{}) {
|
||||
t.Fatal("MapNot(MapNot(MapTrue{})) should always match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapAnd(t *testing.T) {
|
||||
if !MapAnd().Match(map[string]interface{}{}) {
|
||||
t.Fatal("MapAnd() should always match")
|
||||
}
|
||||
if MapAnd(MapNot(MapTrue{})).Match(map[string]interface{}{}) {
|
||||
t.Fatal("MapAnd(MapNot(MapTrue{})) should never match")
|
||||
}
|
||||
if !MapAnd(MapTrue{}).Match(map[string]interface{}{}) {
|
||||
t.Fatal("MapAnd(MapTrue{}) should always match")
|
||||
}
|
||||
if !MapAnd(MapTrue{}, MapTrue{}).Match(map[string]interface{}{}) {
|
||||
t.Fatal("MapAnd(MapTrue{}, MapTrue{}) should always match")
|
||||
}
|
||||
if MapAnd(MapTrue{}, MapNot(MapTrue{}), MapTrue{}).Match(map[string]interface{}{}) {
|
||||
t.Fatal("MapAnd(MapTrue{}, MapNot(MapTrue{}), MapTrue{}) should never match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapOr(t *testing.T) {
|
||||
if MapOr().Match(map[string]interface{}{}) {
|
||||
t.Fatal("MapOr() should never match")
|
||||
}
|
||||
if MapOr(MapNot(MapTrue{})).Match(map[string]interface{}{}) {
|
||||
t.Fatal("MapOr(MapNot(MapTrue{})) should never match")
|
||||
}
|
||||
if !MapOr(MapTrue{}).Match(map[string]interface{}{}) {
|
||||
t.Fatal("MapOr(MapTrue{}) should always match")
|
||||
}
|
||||
if !MapOr(MapTrue{}, MapTrue{}).Match(map[string]interface{}{}) {
|
||||
t.Fatal("MapOr(MapTrue{}, MapTrue{}) should always match")
|
||||
}
|
||||
if !MapOr(MapTrue{}, MapNot(MapTrue{}), MapTrue{}).Match(map[string]interface{}{}) {
|
||||
t.Fatal("MapOr(MapTrue{}, MapNot(MapTrue{}), MapTrue{}) should always match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMapNumFields(t *testing.T) {
|
||||
m := map[string]interface{}{"First": 1, "Second": 2, "Third": 3}
|
||||
if !MapNumFields(NumberEqual(3)).Match(m) {
|
||||
t.Fatal(`MapNumFields(NumberEqual(3)) should match []interface{}{1, 2, 3}`)
|
||||
}
|
||||
|
||||
if MapNumFields(NumberLessThan(2)).Match(m) {
|
||||
t.Fatal(`MapNumFields(NumberLessThan(2)) should not match []interface{}{1, 2, 3}`)
|
||||
}
|
||||
|
||||
if !MapNumFields(NumberLessThan(5)).Match(m) {
|
||||
t.Fatal(`MapNumFields(NumberLessThan(5)) should match []interface{}{1, 2, 3}`)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package unstructpath
|
||||
|
||||
// MapS is a "map selector". It selects values as maps (if
|
||||
// possible) and filters those maps based on the "filtered"
|
||||
// predicates.
|
||||
type MapS interface {
|
||||
// MapS can be used as a Value predicate. If the selector can't
|
||||
// select any map from the value, then the predicate is
|
||||
// false.
|
||||
ValueP
|
||||
|
||||
// SelectFrom finds maps from values using this selector. The
|
||||
// list can be bigger or smaller than the initial lists,
|
||||
// depending on the select criterias.
|
||||
SelectFrom(...interface{}) []map[string]interface{}
|
||||
|
||||
// Field returns the value pointed by this specific field in the
|
||||
// map. If the field doesn't exist, the value will be filtered
|
||||
// out.
|
||||
Field(string) ValueS
|
||||
// FieldP returns all the values pointed by field that match the
|
||||
// string predicate. This selector can return more values than
|
||||
// it gets (for one map, it can returns multiple sub-values, one
|
||||
// for each field that matches the predicate).
|
||||
FieldP(...StringP) ValueS
|
||||
|
||||
// All returns a selector that selects all direct and indrect
|
||||
// children of the given values.
|
||||
Children() ValueS
|
||||
// All returns a selector that selects all direct and indrect
|
||||
// children of the given values.
|
||||
All() ValueS
|
||||
|
||||
// Filter will create a new MapS that filters only the values
|
||||
// who match the predicate.
|
||||
Filter(...MapP) MapS
|
||||
}
|
||||
|
||||
// Map creates a selector that takes values and filters them into maps
|
||||
// if possible.
|
||||
func Map() MapS {
|
||||
return &mapS{}
|
||||
}
|
||||
|
||||
type mapS struct {
|
||||
vs ValueS
|
||||
mp MapP
|
||||
}
|
||||
|
||||
func (s *mapS) SelectFrom(values ...interface{}) []map[string]interface{} {
|
||||
if s.vs != nil {
|
||||
values = s.vs.SelectFrom(values...)
|
||||
}
|
||||
|
||||
maps := []map[string]interface{}{}
|
||||
for _, value := range values {
|
||||
m, ok := value.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if s.mp != nil && !s.mp.Match(m) {
|
||||
continue
|
||||
}
|
||||
maps = append(maps, m)
|
||||
}
|
||||
|
||||
return maps
|
||||
}
|
||||
|
||||
func (s *mapS) Field(str string) ValueS {
|
||||
return s.FieldP(StringEqual(str))
|
||||
}
|
||||
|
||||
func (s *mapS) FieldP(predicates ...StringP) ValueS {
|
||||
return filterMap(s, mapFieldPFilter{sp: StringAnd(predicates...)})
|
||||
}
|
||||
|
||||
func (s *mapS) Children() ValueS {
|
||||
// No predicate means select all.
|
||||
return s.FieldP()
|
||||
}
|
||||
|
||||
func (s *mapS) All() ValueS {
|
||||
return filterMap(s, mapAllFilter{})
|
||||
}
|
||||
|
||||
func (s *mapS) Filter(predicates ...MapP) MapS {
|
||||
return &mapS{vs: s.vs, mp: MapAnd(append(predicates, s.mp)...)}
|
||||
}
|
||||
|
||||
func (s *mapS) Match(value interface{}) bool {
|
||||
return len(s.SelectFrom(value)) != 0
|
||||
}
|
||||
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package unstructpath
|
||||
|
||||
// NumberP is a "number predicate". It's a type that decides if a
|
||||
// number matches or not.
|
||||
type NumberP interface {
|
||||
Match(float64) bool
|
||||
}
|
||||
|
||||
// NumberNot inverts the result of the sub-predicate.
|
||||
func NumberNot(predicate NumberP) NumberP {
|
||||
return numberNot{ip: predicate}
|
||||
}
|
||||
|
||||
type numberNot struct {
|
||||
ip NumberP
|
||||
}
|
||||
|
||||
func (p numberNot) Match(i float64) bool {
|
||||
return !p.ip.Match(i)
|
||||
}
|
||||
|
||||
// NumberAnd returns true if all the sub-predicates are true. If there are
|
||||
// no sub-predicates, always returns true.
|
||||
func NumberAnd(predicates ...NumberP) NumberP {
|
||||
return numberAnd{ips: predicates}
|
||||
}
|
||||
|
||||
type numberAnd struct {
|
||||
ips []NumberP
|
||||
}
|
||||
|
||||
func (p numberAnd) Match(i float64) bool {
|
||||
for _, ip := range p.ips {
|
||||
if !ip.Match(i) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// NumberOr returns true if any sub-predicate is true. If there are no
|
||||
// sub-predicates, always returns false.
|
||||
func NumberOr(predicates ...NumberP) NumberP {
|
||||
ips := []NumberP{}
|
||||
|
||||
// Implements "De Morgan's law"
|
||||
for _, ip := range predicates {
|
||||
ips = append(ips, NumberNot(ip))
|
||||
}
|
||||
return NumberNot(NumberAnd(ips...))
|
||||
}
|
||||
|
||||
// NumberEqual returns true if the value is exactly i.
|
||||
func NumberEqual(i float64) NumberP {
|
||||
return numberEqual{i: i}
|
||||
}
|
||||
|
||||
type numberEqual struct {
|
||||
i float64
|
||||
}
|
||||
|
||||
func (p numberEqual) Match(i float64) bool {
|
||||
return i == p.i
|
||||
}
|
||||
|
||||
// NumberGreaterThan returns true if the value is strictly greater than i.
|
||||
func NumberGreaterThan(i float64) NumberP {
|
||||
return numberGreaterThan{i: i}
|
||||
}
|
||||
|
||||
type numberGreaterThan struct {
|
||||
i float64
|
||||
}
|
||||
|
||||
func (p numberGreaterThan) Match(i float64) bool {
|
||||
return i > p.i
|
||||
}
|
||||
|
||||
// NumberEqualOrGreaterThan returns true if the value is equal or greater
|
||||
// than i.
|
||||
func NumberEqualOrGreaterThan(i float64) NumberP {
|
||||
return NumberOr(NumberEqual(i), NumberGreaterThan(i))
|
||||
}
|
||||
|
||||
// NumberLessThan returns true if the value is strictly less than i.
|
||||
func NumberLessThan(i float64) NumberP {
|
||||
// It's not equal, and it's not greater than i.
|
||||
return NumberAnd(NumberNot(NumberEqual(i)), NumberNot(NumberGreaterThan(i)))
|
||||
}
|
||||
|
||||
// NumberEqualOrLessThan returns true if the value is equal or less than i.
|
||||
func NumberEqualOrLessThan(i float64) NumberP {
|
||||
return NumberOr(NumberEqual(i), NumberLessThan(i))
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package unstructpath_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
. "k8s.io/kubectl/pkg/framework/unstructpath"
|
||||
)
|
||||
|
||||
// This example shows you how you can create a IntP, and how it's use to
|
||||
// compare with the actual value.
|
||||
//
|
||||
// XXX: This could definitely be improved to add a better example.
|
||||
func ExampleNumberP() {
|
||||
fmt.Println(NumberEqual(5).Match(5))
|
||||
// Output: true
|
||||
}
|
||||
|
||||
func TestNumberEqual(t *testing.T) {
|
||||
if !NumberEqual(5).Match(5) {
|
||||
t.Fatal("NumberEqual(5) should match 5")
|
||||
}
|
||||
if NumberEqual(5).Match(4) {
|
||||
t.Fatal("NumberEqual(5) should not match 4")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumberNot(t *testing.T) {
|
||||
if NumberNot(NumberEqual(5)).Match(5) {
|
||||
t.Fatal("NumberNot(NumberEqual(5)) should not match 5")
|
||||
}
|
||||
if !NumberNot(NumberEqual(5)).Match(4) {
|
||||
t.Fatal("NumberNot(NumberEqual(5)) should match 4")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumberAnd(t *testing.T) {
|
||||
if !NumberAnd().Match(5) {
|
||||
t.Fatal("NumberAnd() should match 5")
|
||||
}
|
||||
if !NumberAnd(NumberEqual(5)).Match(5) {
|
||||
t.Fatal("NumberAnd(NumberEqual(5)) should match 5")
|
||||
}
|
||||
if NumberAnd(NumberEqual(5)).Match(4) {
|
||||
t.Fatal("NumberAnd(NumberEqual(5)) should not match 4")
|
||||
}
|
||||
if NumberAnd(NumberEqual(5), NumberEqual(4)).Match(5) {
|
||||
t.Fatal("NumberAnd(NumberEqual(5), NumberEqual(4)) should not match 5")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumberOr(t *testing.T) {
|
||||
if NumberOr().Match(5) {
|
||||
t.Fatal("NumberOr() should not match 5")
|
||||
}
|
||||
if !NumberOr(NumberEqual(5)).Match(5) {
|
||||
t.Fatal("NumberOr(NumberEqual(5)) should match 5")
|
||||
}
|
||||
if NumberOr(NumberEqual(5)).Match(4) {
|
||||
t.Fatal("NumberOr(NumberEqual(5)) should not match 4")
|
||||
}
|
||||
if !NumberOr(NumberEqual(5), NumberEqual(4)).Match(5) {
|
||||
t.Fatal("NumberOr(NumberEqual(5), NumberEqual(4)) should match 5")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumberLessThan(t *testing.T) {
|
||||
if NumberLessThan(3).Match(5) {
|
||||
t.Fatal("NumberLessThan(3) should not match 5")
|
||||
}
|
||||
if NumberLessThan(3).Match(3) {
|
||||
t.Fatal("NumberLessThan(3) should not match 3")
|
||||
}
|
||||
if !NumberLessThan(3).Match(1) {
|
||||
t.Fatal("NumberLessThan(3) should match 1")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumberEqualOrLessThan(t *testing.T) {
|
||||
if NumberEqualOrLessThan(3).Match(5) {
|
||||
t.Fatal("NumberEqualOrLessThan(3) should not match 5")
|
||||
}
|
||||
if !NumberEqualOrLessThan(3).Match(3) {
|
||||
t.Fatal("NumberEqualOrLessThan(3) should match 3")
|
||||
}
|
||||
if !NumberEqualOrLessThan(3).Match(1) {
|
||||
t.Fatal("NumberEqualOrLessThan(3) should match 1")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumberGreaterThan(t *testing.T) {
|
||||
if !NumberGreaterThan(3).Match(5) {
|
||||
t.Fatal("NumberGreaterThan(3) should match 5")
|
||||
}
|
||||
if NumberGreaterThan(3).Match(3) {
|
||||
t.Fatal("NumberGreaterThan(3) should not match 3")
|
||||
}
|
||||
if NumberGreaterThan(3).Match(1) {
|
||||
t.Fatal("NumberGreaterThan(3) should not match 1")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumberEqualOrGreaterThan(t *testing.T) {
|
||||
if !NumberGreaterThan(3).Match(5) {
|
||||
t.Fatal("NumberGreaterThan(3) should match 5")
|
||||
}
|
||||
if NumberGreaterThan(3).Match(3) {
|
||||
t.Fatal("NumberGreaterThan(3) should not match 3")
|
||||
}
|
||||
if NumberGreaterThan(3).Match(1) {
|
||||
t.Fatal("NumberGreaterThan(3) should not match 1")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package unstructpath
|
||||
|
||||
// NumberS is a "number selector". It selects values as numbers (if
|
||||
// possible) and filters those numbers based on the "filtered"
|
||||
// predicates.
|
||||
type NumberS interface {
|
||||
// NumberS can be used as a Value predicate. If the selector can't
|
||||
// select any number from the value, then the predicate is
|
||||
// false.
|
||||
ValueP
|
||||
|
||||
// SelectFrom finds numbers from values using this selector. The
|
||||
// list can be bigger or smaller than the initial lists,
|
||||
// depending on the select criterias.
|
||||
SelectFrom(...interface{}) []float64
|
||||
|
||||
// Filter will create a new NumberS that filters only the values
|
||||
// who match the predicate.
|
||||
Filter(...NumberP) NumberS
|
||||
}
|
||||
|
||||
// Number returns a NumberS that selects numbers from given values.
|
||||
func Number() NumberS {
|
||||
return &numberS{}
|
||||
}
|
||||
|
||||
type numberS struct {
|
||||
vs ValueS
|
||||
ip NumberP
|
||||
}
|
||||
|
||||
func (s *numberS) SelectFrom(values ...interface{}) []float64 {
|
||||
numbers := []float64{}
|
||||
if s.vs != nil {
|
||||
values = s.vs.SelectFrom(values...)
|
||||
}
|
||||
for _, value := range values {
|
||||
i, ok := value.(float64)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if s.ip != nil && !s.ip.Match(i) {
|
||||
continue
|
||||
}
|
||||
numbers = append(numbers, i)
|
||||
}
|
||||
return numbers
|
||||
}
|
||||
|
||||
func (s *numberS) Filter(predicates ...NumberP) NumberS {
|
||||
if s.ip != nil {
|
||||
predicates = append(predicates, s.ip)
|
||||
}
|
||||
return &numberS{
|
||||
vs: s.vs,
|
||||
ip: NumberAnd(predicates...),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *numberS) Match(values interface{}) bool {
|
||||
return len(s.SelectFrom(values)) != 0
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package unstructpath_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
. "k8s.io/kubectl/pkg/framework/unstructpath"
|
||||
)
|
||||
|
||||
func TestNumberSSelectFrom(t *testing.T) {
|
||||
s := Number().SelectFrom(
|
||||
1.,
|
||||
"string",
|
||||
2.,
|
||||
[]float64{3, 4})
|
||||
|
||||
if !reflect.DeepEqual(s, []float64{1, 2}) {
|
||||
t.Fatal("SelectFrom should select all integers")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumberSFilter(t *testing.T) {
|
||||
s := Number().
|
||||
Filter(NumberGreaterThan(2), NumberEqualOrLessThan(4)).
|
||||
SelectFrom(
|
||||
1.,
|
||||
2.,
|
||||
3.,
|
||||
4.,
|
||||
5.)
|
||||
|
||||
if !reflect.DeepEqual(s, []float64{3, 4}) {
|
||||
t.Fatal("SelectFrom should filter selected numberegers")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumberSPredicate(t *testing.T) {
|
||||
if !Number().Filter(NumberGreaterThan(10)).Match(12.) {
|
||||
t.Fatal("SelectFromor matching element should match")
|
||||
}
|
||||
if Number().Filter(NumberGreaterThan(10)).Match(4.) {
|
||||
t.Fatal("SelectFromor not matching element should not match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNumberSFromValueS(t *testing.T) {
|
||||
if !Children().Number().Filter(NumberGreaterThan(10)).Match([]interface{}{1., 2., 5., 12.}) {
|
||||
t.Fatal("SelectFromor should find element that match")
|
||||
}
|
||||
if Children().Number().Filter(NumberGreaterThan(10)).Match([]interface{}{1., 2., 5.}) {
|
||||
t.Fatal("SelectFromor shouldn't find element that match")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package unstructpath
|
||||
|
||||
func filterSlice(ss SliceS, sf sliceFilter) ValueS {
|
||||
return &valueS{
|
||||
vf: &valueSliceFilter{
|
||||
ss: ss,
|
||||
sf: sf,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// This is a Slice-to-Value filter.
|
||||
type sliceFilter interface {
|
||||
SelectFrom(...[]interface{}) []interface{}
|
||||
}
|
||||
|
||||
type sliceAtPFilter struct {
|
||||
ip NumberP
|
||||
}
|
||||
|
||||
func (f sliceAtPFilter) SelectFrom(slices ...[]interface{}) []interface{} {
|
||||
values := []interface{}{}
|
||||
|
||||
for _, slice := range slices {
|
||||
for i := range slice {
|
||||
if !f.ip.Match(float64(i)) {
|
||||
continue
|
||||
}
|
||||
values = append(values, slice[i])
|
||||
}
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
type sliceLastFilter struct{}
|
||||
|
||||
func (f sliceLastFilter) SelectFrom(slices ...[]interface{}) []interface{} {
|
||||
values := []interface{}{}
|
||||
for _, slice := range slices {
|
||||
if len(slice) == 0 {
|
||||
continue
|
||||
}
|
||||
values = append(values, slice[len(slice)-1])
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
type sliceAllFilter struct{}
|
||||
|
||||
func (sliceAllFilter) SelectFrom(slices ...[]interface{}) []interface{} {
|
||||
values := []interface{}{}
|
||||
for _, slice := range slices {
|
||||
for _, v := range slice {
|
||||
values = append(values, All().SelectFrom(v)...)
|
||||
}
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package unstructpath
|
||||
|
||||
// SliceP is a "slice predicate". It's a type that decides if a
|
||||
// slice matches or not.
|
||||
type SliceP interface {
|
||||
Match([]interface{}) bool
|
||||
}
|
||||
|
||||
// SliceNot inverses the value of the predicate.
|
||||
func SliceNot(predicate SliceP) SliceP {
|
||||
return sliceNot{vp: predicate}
|
||||
}
|
||||
|
||||
type sliceNot struct {
|
||||
vp SliceP
|
||||
}
|
||||
|
||||
func (p sliceNot) Match(slice []interface{}) bool {
|
||||
return !p.vp.Match(slice)
|
||||
}
|
||||
|
||||
// SliceAnd returns true if all the sub-predicates are true. If there are
|
||||
// no sub-predicates, always returns true.
|
||||
func SliceAnd(predicates ...SliceP) SliceP {
|
||||
return sliceAnd{sps: predicates}
|
||||
}
|
||||
|
||||
type sliceAnd struct {
|
||||
sps []SliceP
|
||||
}
|
||||
|
||||
func (p sliceAnd) Match(slice []interface{}) bool {
|
||||
for _, sp := range p.sps {
|
||||
if !sp.Match(slice) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SliceOr returns true if any sub-predicate is true. If there are no
|
||||
// sub-predicates, always returns false.
|
||||
func SliceOr(predicates ...SliceP) SliceP {
|
||||
sps := []SliceP{}
|
||||
|
||||
// Implements "De Morgan's law"
|
||||
for _, sp := range predicates {
|
||||
sps = append(sps, SliceNot(sp))
|
||||
}
|
||||
return SliceNot(SliceAnd(sps...))
|
||||
}
|
||||
|
||||
// SliceLength matches if the length of the list matches the given
|
||||
// integer predicate.
|
||||
func SliceLength(ip NumberP) SliceP {
|
||||
return sliceLength{ip: ip}
|
||||
}
|
||||
|
||||
type sliceLength struct {
|
||||
ip NumberP
|
||||
}
|
||||
|
||||
func (p sliceLength) Match(slice []interface{}) bool {
|
||||
return p.ip.Match(float64(len(slice)))
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package unstructpath_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "k8s.io/kubectl/pkg/framework/unstructpath"
|
||||
)
|
||||
|
||||
type SliceTrue struct{}
|
||||
|
||||
func (SliceTrue) Match(slice []interface{}) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func TestSliceNot(t *testing.T) {
|
||||
if SliceNot(SliceTrue{}).Match([]interface{}{}) {
|
||||
t.Fatal("SliceNot(SliceTrue{}) should never match")
|
||||
}
|
||||
if !SliceNot(SliceNot(SliceTrue{})).Match([]interface{}{}) {
|
||||
t.Fatal("SliceNot(SliceNot(SliceTrue{})) should always match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSliceAnd(t *testing.T) {
|
||||
if !SliceAnd().Match([]interface{}{}) {
|
||||
t.Fatal("SliceAnd() should always match")
|
||||
}
|
||||
if SliceAnd(SliceNot(SliceTrue{})).Match([]interface{}{}) {
|
||||
t.Fatal("SliceAnd(SliceNot(SliceTrue{})) should never match")
|
||||
}
|
||||
if !SliceAnd(SliceTrue{}).Match([]interface{}{}) {
|
||||
t.Fatal("SliceAnd(SliceTrue{}) should always match")
|
||||
}
|
||||
if !SliceAnd(SliceTrue{}, SliceTrue{}).Match([]interface{}{}) {
|
||||
t.Fatal("SliceAnd(SliceTrue{}, SliceTrue{}) should always match")
|
||||
}
|
||||
if SliceAnd(SliceTrue{}, SliceNot(SliceTrue{}), SliceTrue{}).Match([]interface{}{}) {
|
||||
t.Fatal("SliceAnd(SliceTrue{}, SliceNot(SliceTrue{}), SliceTrue{}) should never match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSliceOr(t *testing.T) {
|
||||
if SliceOr().Match([]interface{}{}) {
|
||||
t.Fatal("SliceOr() should never match")
|
||||
}
|
||||
if SliceOr(SliceNot(SliceTrue{})).Match([]interface{}{}) {
|
||||
t.Fatal("SliceOr(SliceNot(SliceTrue{})) should never match")
|
||||
}
|
||||
if !SliceOr(SliceTrue{}).Match([]interface{}{}) {
|
||||
t.Fatal("SliceOr(SliceTrue{}) should always match")
|
||||
}
|
||||
if !SliceOr(SliceTrue{}, SliceTrue{}).Match([]interface{}{}) {
|
||||
t.Fatal("SliceOr(SliceTrue{}, SliceTrue{}) should always match")
|
||||
}
|
||||
if !SliceOr(SliceTrue{}, SliceNot(SliceTrue{}), SliceTrue{}).Match([]interface{}{}) {
|
||||
t.Fatal("SliceOr(SliceTrue{}, SliceNot(SliceTrue{}), SliceTrue{}) should always match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSliceLength(t *testing.T) {
|
||||
slice := []interface{}{1, 2, 3}
|
||||
if !SliceLength(NumberEqual(3)).Match(slice) {
|
||||
t.Fatal(`SliceLength(NumberEqual(3)) should match []interface{}{1, 2, 3}`)
|
||||
}
|
||||
|
||||
if SliceLength(NumberLessThan(2)).Match(slice) {
|
||||
t.Fatal(`SliceLength(NumberLessThan(2)) should not match []interface{}{1, 2, 3}`)
|
||||
}
|
||||
|
||||
if !SliceLength(NumberLessThan(5)).Match(slice) {
|
||||
t.Fatal(`SliceLength(NumberLessThan(5)) should match []interface{}{1, 2, 3}`)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package unstructpath
|
||||
|
||||
// SliceS is a "slice selector". It selects values as slices (if
|
||||
// possible) and filters those slices based on the "filtered"
|
||||
// predicates.
|
||||
type SliceS interface {
|
||||
// SliceS can be used as a Value predicate. If the selector
|
||||
// can't select any slice from the value, then the predicate is
|
||||
// false.
|
||||
ValueP
|
||||
|
||||
// SelectFrom finds slices from values using this selector. The
|
||||
// list can be bigger or smaller than the initial lists,
|
||||
// depending on the select criterias.
|
||||
SelectFrom(...interface{}) [][]interface{}
|
||||
|
||||
// At returns a selector that select the child at the given
|
||||
// index, if the list has such an index. Otherwise, nothing is
|
||||
// returned.
|
||||
At(index int) ValueS
|
||||
// AtP returns a selector that selects all the item whose index
|
||||
// matches the number predicate. More predicates can be given,
|
||||
// they are "and"-ed by this method.
|
||||
AtP(ips ...NumberP) ValueS
|
||||
// Last returns a selector that selects the last value of the
|
||||
// list. If the list is empty, then nothing will be selected.
|
||||
Last() ValueS
|
||||
|
||||
// All returns a selector that selects all direct and indrect
|
||||
// children of the given values.
|
||||
Children() ValueS
|
||||
// All returns a selector that selects all direct and indrect
|
||||
// children of the given values.
|
||||
All() ValueS
|
||||
|
||||
// Filter will create a new SliceS that filters only the values
|
||||
// who match the predicate.
|
||||
Filter(...SliceP) SliceS
|
||||
}
|
||||
|
||||
// Slice creates a selector that takes values and filters them into
|
||||
// slices if possible.
|
||||
func Slice() SliceS {
|
||||
return &sliceS{}
|
||||
}
|
||||
|
||||
type sliceS struct {
|
||||
vs ValueS
|
||||
sp SliceP
|
||||
}
|
||||
|
||||
func (s *sliceS) SelectFrom(values ...interface{}) [][]interface{} {
|
||||
if s.vs != nil {
|
||||
values = s.vs.SelectFrom(values...)
|
||||
}
|
||||
|
||||
slices := [][]interface{}{}
|
||||
for _, value := range values {
|
||||
slice, ok := value.([]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if s.sp != nil && !s.sp.Match(slice) {
|
||||
continue
|
||||
}
|
||||
slices = append(slices, slice)
|
||||
}
|
||||
|
||||
return slices
|
||||
}
|
||||
|
||||
func (s *sliceS) At(index int) ValueS {
|
||||
return s.AtP(NumberEqual(float64(index)))
|
||||
}
|
||||
|
||||
func (s *sliceS) AtP(predicates ...NumberP) ValueS {
|
||||
return filterSlice(s, sliceAtPFilter{ip: NumberAnd(predicates...)})
|
||||
}
|
||||
|
||||
func (s *sliceS) Last() ValueS {
|
||||
return filterSlice(s, sliceLastFilter{})
|
||||
}
|
||||
|
||||
func (s *sliceS) Children() ValueS {
|
||||
// No predicates means select all direct children.
|
||||
return s.AtP()
|
||||
}
|
||||
|
||||
func (s *sliceS) All() ValueS {
|
||||
return filterSlice(s, sliceAllFilter{})
|
||||
}
|
||||
|
||||
func (s *sliceS) Filter(sps ...SliceP) SliceS {
|
||||
return &sliceS{vs: s.vs, sp: SliceAnd(append(sps, s.sp)...)}
|
||||
}
|
||||
|
||||
func (s *sliceS) Match(value interface{}) bool {
|
||||
return len(s.SelectFrom(value)) != 0
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package unstructpath
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// StringP is a "string predicate". It's a type that decides if a
|
||||
// string matches or not.
|
||||
type StringP interface {
|
||||
Match(string) bool
|
||||
}
|
||||
|
||||
// StringNot will inverse the result of the predicate.
|
||||
func StringNot(predicate StringP) StringP {
|
||||
return stringNot{sp: predicate}
|
||||
}
|
||||
|
||||
type stringNot struct {
|
||||
sp StringP
|
||||
}
|
||||
|
||||
func (p stringNot) Match(str string) bool {
|
||||
return !p.sp.Match(str)
|
||||
}
|
||||
|
||||
// StringAnd returns true if all the sub-predicates are true. If there are
|
||||
// no sub-predicates, always returns true.
|
||||
func StringAnd(predicates ...StringP) StringP {
|
||||
return stringAnd{sps: predicates}
|
||||
}
|
||||
|
||||
type stringAnd struct {
|
||||
sps []StringP
|
||||
}
|
||||
|
||||
func (p stringAnd) Match(str string) bool {
|
||||
for _, sp := range p.sps {
|
||||
if !sp.Match(str) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// StringOr returns true if any sub-predicate is true. If there are no
|
||||
// sub-predicates, always returns false.
|
||||
func StringOr(predicates ...StringP) StringP {
|
||||
sps := []StringP{}
|
||||
|
||||
// Implements "De Morgan's law"
|
||||
for _, sp := range predicates {
|
||||
sps = append(sps, StringNot(sp))
|
||||
}
|
||||
return StringNot(StringAnd(sps...))
|
||||
}
|
||||
|
||||
// StringEqual returns a predicate that matches only the exact string.
|
||||
func StringEqual(str string) StringP {
|
||||
return stringEqual{str: str}
|
||||
}
|
||||
|
||||
type stringEqual struct {
|
||||
str string
|
||||
}
|
||||
|
||||
func (p stringEqual) Match(str string) bool {
|
||||
return p.str == str
|
||||
}
|
||||
|
||||
// StringLength matches if the length of the string matches the given
|
||||
// integer predicate.
|
||||
func StringLength(predicate NumberP) StringP {
|
||||
return stringLength{ip: predicate}
|
||||
}
|
||||
|
||||
type stringLength struct {
|
||||
ip NumberP
|
||||
}
|
||||
|
||||
func (p stringLength) Match(str string) bool {
|
||||
return p.ip.Match(float64(len(str)))
|
||||
}
|
||||
|
||||
// StringHasPrefix matches if the string starts with the given prefix.
|
||||
func StringHasPrefix(prefix string) StringP {
|
||||
return stringHasPrefix{prefix: prefix}
|
||||
}
|
||||
|
||||
type stringHasPrefix struct {
|
||||
prefix string
|
||||
}
|
||||
|
||||
func (p stringHasPrefix) Match(str string) bool {
|
||||
return strings.HasPrefix(str, p.prefix)
|
||||
}
|
||||
|
||||
// StringHasSuffix matches if the string ends with the given suffix.
|
||||
func StringHasSuffix(suffix string) StringP {
|
||||
return stringHasSuffix{suffix: suffix}
|
||||
}
|
||||
|
||||
type stringHasSuffix struct {
|
||||
suffix string
|
||||
}
|
||||
|
||||
func (p stringHasSuffix) Match(str string) bool {
|
||||
return strings.HasSuffix(str, p.suffix)
|
||||
}
|
||||
|
||||
// StringRegexp matches if the string matches with the given regexp.
|
||||
func StringRegexp(regex *regexp.Regexp) StringP {
|
||||
return stringRegexp{regex: regex}
|
||||
}
|
||||
|
||||
type stringRegexp struct {
|
||||
regex *regexp.Regexp
|
||||
}
|
||||
|
||||
func (p stringRegexp) Match(str string) bool {
|
||||
return p.regex.MatchString(str)
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package unstructpath_test
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
. "k8s.io/kubectl/pkg/framework/unstructpath"
|
||||
)
|
||||
|
||||
func TestStringEqual(t *testing.T) {
|
||||
if !StringEqual("some string").Match("some string") {
|
||||
t.Fatal(`StringEqual("some string") should match "some string"`)
|
||||
}
|
||||
if StringEqual("some string").Match("another string") {
|
||||
t.Fatal(`StringEqual("some string") should not match "another string"`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringNot(t *testing.T) {
|
||||
if StringNot(StringEqual("some string")).Match("some string") {
|
||||
t.Fatal(`StringNot(StringEqual("some string")) should not match "some string"`)
|
||||
}
|
||||
if !StringNot(StringEqual("some string")).Match("another string") {
|
||||
t.Fatal(`StringNot(StringEqual("some string")) should match "another string"`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringAnd(t *testing.T) {
|
||||
if !StringAnd().Match("some string") {
|
||||
t.Fatal(`StringAnd() should match "some string"`)
|
||||
}
|
||||
if !StringAnd(StringEqual("some string")).Match("some string") {
|
||||
t.Fatal(`StringAnd(StringEqual("some string")) should match "some string"`)
|
||||
}
|
||||
if StringAnd(StringEqual("some string")).Match("another string") {
|
||||
t.Fatal(`StringAnd(StringEqual("some string")) should not match "another string"`)
|
||||
}
|
||||
if StringAnd(StringEqual("some string"), StringEqual("another string")).Match("some string") {
|
||||
t.Fatal(`StringAnd(StringEqual("some string"), StringEqual("another string")) should not match "some string"`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringOr(t *testing.T) {
|
||||
if StringOr().Match("some string") {
|
||||
t.Fatal(`StringOr() should not match "some string"`)
|
||||
}
|
||||
if !StringOr(StringEqual("some string")).Match("some string") {
|
||||
t.Fatal(`StringOr(StringEqual("some string")) should match "some string"`)
|
||||
}
|
||||
if StringOr(StringEqual("some string")).Match("another string") {
|
||||
t.Fatal(`StringOr(StringEqual("some string")) should not match "another string"`)
|
||||
}
|
||||
if !StringOr(StringEqual("some string"), StringEqual("another string")).Match("some string") {
|
||||
t.Fatal(`StringOr(StringEqual("some string"), StringEqual("another string")) should match "some string"`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringLength(t *testing.T) {
|
||||
if !StringLength(NumberEqual(11)).Match("some string") {
|
||||
t.Fatal(`StringLength(NumberEqual(11)) should match "some string"`)
|
||||
}
|
||||
|
||||
if StringLength(NumberLessThan(6)).Match("some string") {
|
||||
t.Fatal(`StringLength(NumberLessThan(6)) should not match "some string"`)
|
||||
}
|
||||
|
||||
if !StringLength(NumberLessThan(16)).Match("some string") {
|
||||
t.Fatal(`StringLength(NumberLessThan(16)) should match "some string"`)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestStringHasPrefix(t *testing.T) {
|
||||
if !StringHasPrefix("some ").Match("some string") {
|
||||
t.Fatal(`StringHasPrefix("some ") should match "some string"`)
|
||||
}
|
||||
if StringHasPrefix("some ").Match("another string") {
|
||||
t.Fatal(`StringHasPrefix("some ") should not match "some string"`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringHasSuffix(t *testing.T) {
|
||||
if !StringHasSuffix("string").Match("some string") {
|
||||
t.Fatal(`StringHasSuffix("string") should match "some string"`)
|
||||
}
|
||||
if StringHasSuffix("integer").Match("some string") {
|
||||
t.Fatal(`StringHasSuffix("integer") should not match "some string"`)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringRegexp(t *testing.T) {
|
||||
if !StringRegexp(regexp.MustCompile(".*")).Match("") {
|
||||
t.Fatal(`StringRegexp(regexp.MustCompile(".*")) should match ""`)
|
||||
}
|
||||
if !StringRegexp(regexp.MustCompile(".*")).Match("Anything") {
|
||||
t.Fatal(`StringRegexp(regexp.MustCompile(".*")) should match "Anything"`)
|
||||
}
|
||||
if !StringRegexp(regexp.MustCompile("word")).Match("word") {
|
||||
t.Fatal(`StringRegexp(regexp.MustCompile("word")) should match "word"`)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package unstructpath
|
||||
|
||||
// StringS is a "string selector". It selects values as strings (if
|
||||
// possible) and filters those strings based on the "filtered"
|
||||
// predicates.
|
||||
type StringS interface {
|
||||
// StringS can be used as a Value predicate. If the selector can't
|
||||
// select any string from the value, then the predicate is
|
||||
// false.
|
||||
ValueP
|
||||
|
||||
// SelectFrom finds strings from values using this selector. The
|
||||
// list can be bigger or smaller than the initial lists,
|
||||
// depending on the select criterias.
|
||||
SelectFrom(...interface{}) []string
|
||||
|
||||
// Filter will create a new StringS that filters only the values
|
||||
// who match the predicate.
|
||||
Filter(...StringP) StringS
|
||||
}
|
||||
|
||||
type stringS struct {
|
||||
vs ValueS
|
||||
sp StringP
|
||||
}
|
||||
|
||||
// String returns a StringS that selects strings from values.
|
||||
func String() StringS {
|
||||
return &stringS{}
|
||||
}
|
||||
|
||||
func (s *stringS) SelectFrom(values ...interface{}) []string {
|
||||
strings := []string{}
|
||||
if s.vs != nil {
|
||||
values = s.vs.SelectFrom(values...)
|
||||
}
|
||||
for _, value := range values {
|
||||
str, ok := value.(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if s.sp != nil && !s.sp.Match(str) {
|
||||
continue
|
||||
}
|
||||
strings = append(strings, str)
|
||||
}
|
||||
return strings
|
||||
}
|
||||
|
||||
func (s *stringS) Filter(predicates ...StringP) StringS {
|
||||
if s.sp != nil {
|
||||
predicates = append(predicates, s.sp)
|
||||
}
|
||||
return &stringS{
|
||||
vs: s.vs,
|
||||
sp: StringAnd(predicates...),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *stringS) Match(values interface{}) bool {
|
||||
return len(s.SelectFrom(values)) != 0
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package unstructpath_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
. "k8s.io/kubectl/pkg/framework/unstructpath"
|
||||
)
|
||||
|
||||
func TestStringSSelectFrom(t *testing.T) {
|
||||
s := String().SelectFrom(
|
||||
"my string",
|
||||
1,
|
||||
"your string",
|
||||
[]int{3, 4})
|
||||
|
||||
if !reflect.DeepEqual(s, []string{"my string", "your string"}) {
|
||||
t.Fatal("SelectFrom should select all integers")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringSFilter(t *testing.T) {
|
||||
s := String().
|
||||
Filter(StringLength(NumberEqual(4))).
|
||||
SelectFrom(
|
||||
"one",
|
||||
"two",
|
||||
"three",
|
||||
"four",
|
||||
"five")
|
||||
|
||||
if !reflect.DeepEqual(s, []string{"four", "five"}) {
|
||||
t.Fatal("SelectFrom should filter selected strings")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringSPredicate(t *testing.T) {
|
||||
if !String().Filter(StringLength(NumberEqual(4))).Match("four") {
|
||||
t.Fatal("SelectFromor matching element should match")
|
||||
}
|
||||
if String().Filter(StringLength(NumberEqual(10))).Match("four") {
|
||||
t.Fatal("SelectFromor not matching element should not match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringSFromValueS(t *testing.T) {
|
||||
if !Children().String().Filter(StringLength(NumberEqual(4))).Match([]interface{}{"four", "five"}) {
|
||||
t.Fatal("SelectFromor should find element that match")
|
||||
}
|
||||
if Children().String().Filter(StringLength(NumberEqual(4))).Match([]interface{}{"one", "two", "three"}) {
|
||||
t.Fatal("SelectFromor shouldn't find element that match")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package unstructpath
|
||||
|
||||
// A valueFilter allows us to chain ValueS to ValueS. None of this is
|
||||
// public. It's implementing the "SelectFrom" part of a ValueS.
|
||||
type valueFilter interface {
|
||||
SelectFrom(...interface{}) []interface{}
|
||||
}
|
||||
|
||||
// valueFilterP filters using a predicate.
|
||||
type valueFilterP struct {
|
||||
vp ValueP
|
||||
}
|
||||
|
||||
func (f *valueFilterP) SelectFrom(values ...interface{}) []interface{} {
|
||||
vs := []interface{}{}
|
||||
for _, value := range values {
|
||||
if f.vp.Match(value) {
|
||||
vs = append(vs, value)
|
||||
}
|
||||
}
|
||||
return vs
|
||||
}
|
||||
|
||||
type valueChildrenFilter struct{}
|
||||
|
||||
func (valueChildrenFilter) SelectFrom(values ...interface{}) []interface{} {
|
||||
children := []interface{}{}
|
||||
// We could process all slices and then all maps, but we want to
|
||||
// keep things in the same order.
|
||||
for _, value := range values {
|
||||
// Only one of the two should do something useful.
|
||||
children = append(children, Slice().Children().SelectFrom(value)...)
|
||||
children = append(children, Map().Children().SelectFrom(value)...)
|
||||
}
|
||||
return children
|
||||
}
|
||||
|
||||
// valueSliceFilter is a Value-to-Slice combined with a Slice-to-Value
|
||||
// to form a Value-to-Value.
|
||||
type valueSliceFilter struct {
|
||||
ss SliceS
|
||||
sf sliceFilter
|
||||
}
|
||||
|
||||
func (s *valueSliceFilter) SelectFrom(values ...interface{}) []interface{} {
|
||||
return s.sf.SelectFrom(s.ss.SelectFrom(values...)...)
|
||||
}
|
||||
|
||||
// valueMapFilter is a Value-to-Map combined with a Map-to-Value to form
|
||||
// a Value-to-Value.
|
||||
type valueMapFilter struct {
|
||||
ms MapS
|
||||
mf mapFilter
|
||||
}
|
||||
|
||||
func (s *valueMapFilter) SelectFrom(values ...interface{}) []interface{} {
|
||||
return s.mf.SelectFrom(s.ms.SelectFrom(values...)...)
|
||||
}
|
||||
|
||||
type valueAllFilter struct{}
|
||||
|
||||
func (valueAllFilter) SelectFrom(values ...interface{}) []interface{} {
|
||||
vs := []interface{}{}
|
||||
for _, value := range values {
|
||||
vs = append(vs, value)
|
||||
// Only one of the follow two statements should return something ...
|
||||
vs = append(vs, Slice().All().SelectFrom(value)...)
|
||||
vs = append(vs, Map().All().SelectFrom(value)...)
|
||||
}
|
||||
return vs
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package unstructpath
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// ValueP is a "value predicate". It's a type that decides if a
|
||||
// value matches or not.
|
||||
type ValueP interface {
|
||||
Match(interface{}) bool
|
||||
}
|
||||
|
||||
// ValueDeepEqual compares the Value data with DeepEqual.
|
||||
func ValueDeepEqual(v interface{}) ValueP {
|
||||
return valueEqual{v: v}
|
||||
}
|
||||
|
||||
type valueEqual struct {
|
||||
v interface{}
|
||||
}
|
||||
|
||||
func (p valueEqual) Match(v interface{}) bool {
|
||||
return reflect.DeepEqual(v, p.v)
|
||||
}
|
||||
|
||||
// ValueNot inverses the value of the predicate.
|
||||
func ValueNot(predicate ValueP) ValueP {
|
||||
return valueNot{vp: predicate}
|
||||
}
|
||||
|
||||
type valueNot struct {
|
||||
vp ValueP
|
||||
}
|
||||
|
||||
func (p valueNot) Match(v interface{}) bool {
|
||||
return !p.vp.Match(v)
|
||||
}
|
||||
|
||||
// ValueAnd returns true if all the sub-predicates are true. If there are
|
||||
// no sub-predicates, always returns true.
|
||||
func ValueAnd(predicates ...ValueP) ValueP {
|
||||
return valueAnd{vps: predicates}
|
||||
}
|
||||
|
||||
type valueAnd struct {
|
||||
vps []ValueP
|
||||
}
|
||||
|
||||
func (p valueAnd) Match(value interface{}) bool {
|
||||
for _, vp := range p.vps {
|
||||
if !vp.Match(value) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ValueOr returns true if any sub-predicate is true. If there are no
|
||||
// sub-predicates, always returns false.
|
||||
func ValueOr(predicates ...ValueP) ValueP {
|
||||
vps := []ValueP{}
|
||||
|
||||
// Implements "De Morgan's law"
|
||||
for _, vp := range predicates {
|
||||
vps = append(vps, ValueNot(vp))
|
||||
}
|
||||
return ValueNot(ValueAnd(vps...))
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package unstructpath_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "k8s.io/kubectl/pkg/framework/unstructpath"
|
||||
)
|
||||
|
||||
type ValueTrue struct{}
|
||||
|
||||
func (ValueTrue) Match(value interface{}) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func TestValueNot(t *testing.T) {
|
||||
if ValueNot(ValueTrue{}).Match(nil) {
|
||||
t.Fatal("ValueNot(ValueTrue{}) should never match")
|
||||
}
|
||||
if !ValueNot(ValueNot(ValueTrue{})).Match(nil) {
|
||||
t.Fatal("ValueNot(ValueNot(ValueTrue{})) should always match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueAnd(t *testing.T) {
|
||||
if !ValueAnd().Match(nil) {
|
||||
t.Fatal("ValueAnd() should always match")
|
||||
}
|
||||
if ValueAnd(ValueNot(ValueTrue{})).Match(nil) {
|
||||
t.Fatal("ValueAnd(ValueNot(ValueTrue{})) should never match")
|
||||
}
|
||||
if !ValueAnd(ValueTrue{}).Match(nil) {
|
||||
t.Fatal("ValueAnd(ValueTrue{}) should always match")
|
||||
}
|
||||
if !ValueAnd(ValueTrue{}, ValueTrue{}).Match(nil) {
|
||||
t.Fatal("ValueAnd(ValueTrue{}, ValueTrue{}) should always match")
|
||||
}
|
||||
if ValueAnd(ValueTrue{}, ValueNot(ValueTrue{}), ValueTrue{}).Match(nil) {
|
||||
t.Fatal("ValueAnd(ValueTrue{}, ValueNot(ValueTrue{}), ValueTrue{}) should never match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueOr(t *testing.T) {
|
||||
if ValueOr().Match(nil) {
|
||||
t.Fatal("ValueOr() should never match")
|
||||
}
|
||||
if ValueOr(ValueNot(ValueTrue{})).Match(nil) {
|
||||
t.Fatal("ValueOr(ValueNot(ValueTrue{})) should never match")
|
||||
}
|
||||
if !ValueOr(ValueTrue{}).Match(nil) {
|
||||
t.Fatal("ValueOr(ValueTrue{}) should always match")
|
||||
}
|
||||
if !ValueOr(ValueTrue{}, ValueTrue{}).Match(nil) {
|
||||
t.Fatal("ValueOr(ValueTrue{}, ValueTrue{}) should always match")
|
||||
}
|
||||
if !ValueOr(ValueTrue{}, ValueNot(ValueTrue{}), ValueTrue{}).Match(nil) {
|
||||
t.Fatal("ValueOr(ValueTrue{}, ValueNot(ValueTrue{}), ValueTrue{}) should always match")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueDeepEqual(t *testing.T) {
|
||||
if !ValueDeepEqual([]int{1, 2, 3}).Match([]int{1, 2, 3}) {
|
||||
t.Fatal("ValueDeepEqual([]int{1, 2, 3}) should match []int{1, 2, 3}")
|
||||
}
|
||||
if ValueDeepEqual([]int{1, 2, 3}).Match([]int{1, 2, 4}) {
|
||||
t.Fatal("ValueDeepEqual([]int{1, 2, 3}) should not match []int{1, 2, 4}")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package unstructpath
|
||||
|
||||
// ValueS is a "value selector". It filters values based on the
|
||||
// "filtered" predicates.
|
||||
type ValueS interface {
|
||||
// ValueS can be used as a Value predicate. If the selector can't
|
||||
// select any value from the value, then the predicate is
|
||||
// false.
|
||||
ValueP
|
||||
|
||||
// SelectFrom finds values from values using this selector. The
|
||||
// list can be bigger or smaller than the initial lists,
|
||||
// depending on the select criterias.
|
||||
SelectFrom(...interface{}) []interface{}
|
||||
|
||||
// Map returns a selector that selects Maps from the given
|
||||
// values.
|
||||
Map() MapS
|
||||
// Slice returns a selector that selects Slices from the given
|
||||
// values.
|
||||
Slice() SliceS
|
||||
// Number returns a selector taht selects Numbers from the given values.
|
||||
Number() NumberS
|
||||
// String returns a selector that selects strings from the given values.
|
||||
String() StringS
|
||||
|
||||
// Children returns a selector that selects the direct children
|
||||
// of the given values.
|
||||
Children() ValueS
|
||||
// All returns a selector that selects all direct and indrect
|
||||
// children of the given values.
|
||||
All() ValueS
|
||||
|
||||
// Filter will create a new StringS that filters only the values
|
||||
// who match the predicate.
|
||||
Filter(...ValueP) ValueS
|
||||
}
|
||||
|
||||
// Children selects all the children of the values.
|
||||
func Children() ValueS {
|
||||
return &valueS{vf: valueChildrenFilter{}}
|
||||
}
|
||||
|
||||
// All selects all the direct and indirect childrens of the values.
|
||||
func All() ValueS {
|
||||
return &valueS{vf: valueAllFilter{}}
|
||||
}
|
||||
|
||||
// Filter will only return the values that match the predicate.
|
||||
func Filter(predicates ...ValueP) ValueS {
|
||||
return &valueS{vf: &valueFilterP{vp: ValueAnd(predicates...)}}
|
||||
}
|
||||
|
||||
// ValueS is a "Value SelectFromor". It selects a list of values, maps,
|
||||
// slices, strings, integer from a list of values.
|
||||
type valueS struct {
|
||||
vs ValueS
|
||||
vf valueFilter
|
||||
}
|
||||
|
||||
// Match returns true if the selector can find items in the given
|
||||
// value. Otherwise, it returns false.
|
||||
func (s *valueS) Match(value interface{}) bool {
|
||||
return len(s.SelectFrom(value)) != 0
|
||||
}
|
||||
|
||||
func (s *valueS) SelectFrom(values ...interface{}) []interface{} {
|
||||
if s.vs != nil {
|
||||
values = s.vs.SelectFrom(values...)
|
||||
}
|
||||
return s.vf.SelectFrom(values...)
|
||||
}
|
||||
|
||||
func (s *valueS) Map() MapS {
|
||||
return &mapS{vs: s}
|
||||
}
|
||||
|
||||
func (s *valueS) Slice() SliceS {
|
||||
return &sliceS{vs: s}
|
||||
}
|
||||
|
||||
func (s *valueS) Number() NumberS {
|
||||
return &numberS{vs: s}
|
||||
}
|
||||
|
||||
func (s *valueS) String() StringS {
|
||||
return &stringS{vs: s}
|
||||
}
|
||||
|
||||
func (s *valueS) Children() ValueS {
|
||||
return &valueS{vs: s, vf: valueChildrenFilter{}}
|
||||
}
|
||||
|
||||
func (s *valueS) All() ValueS {
|
||||
return &valueS{vs: s, vf: valueAllFilter{}}
|
||||
}
|
||||
|
||||
func (s *valueS) Filter(predicates ...ValueP) ValueS {
|
||||
return &valueS{vs: s, vf: &valueFilterP{vp: ValueAnd(predicates...)}}
|
||||
}
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
/*
|
||||
Copyright 2018 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package unstructpath_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubectl/pkg/framework/unstructpath"
|
||||
)
|
||||
|
||||
func TestAll(t *testing.T) {
|
||||
u := map[string]interface{}{
|
||||
"key1": 1.,
|
||||
"key2": []interface{}{2., 3., map[string]interface{}{"key3": 4.}},
|
||||
"key4": map[string]interface{}{"key5": 5.},
|
||||
}
|
||||
|
||||
numbers := unstructpath.All().Number().SelectFrom(u)
|
||||
expected := []float64{1., 2., 3., 4., 5.}
|
||||
if !reflect.DeepEqual(expected, numbers) {
|
||||
t.Fatalf("Expected to find all numbers (%v), got: %v", expected, numbers)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChildren(t *testing.T) {
|
||||
u := map[string]interface{}{
|
||||
"key1": 1.,
|
||||
"key2": []interface{}{2., 3., map[string]interface{}{"key3": 4.}},
|
||||
"key4": 5.,
|
||||
}
|
||||
|
||||
numbers := unstructpath.Children().Number().SelectFrom(u)
|
||||
expected := []float64{1., 5.}
|
||||
if !reflect.DeepEqual(expected, numbers) {
|
||||
t.Fatalf("Expected to find all numbers (%v), got: %v", expected, numbers)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilter(t *testing.T) {
|
||||
us := []interface{}{
|
||||
[]interface{}{1., 2., 3.},
|
||||
[]interface{}{3., 4., 5., 6.},
|
||||
map[string]interface{}{},
|
||||
5.,
|
||||
"string",
|
||||
}
|
||||
expected := []interface{}{us[1]}
|
||||
actual := unstructpath.Filter(unstructpath.Slice().At(3)).SelectFrom(us...)
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Fatalf("Expected to filter (%v), got: %v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueSPredicate(t *testing.T) {
|
||||
if !unstructpath.Slice().Match([]interface{}{}) {
|
||||
t.Fatal("SelectFroming a slice from a slice should match.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueSMap(t *testing.T) {
|
||||
root := map[string]interface{}{
|
||||
"key1": "value",
|
||||
"key2": 1,
|
||||
"key3": []interface{}{
|
||||
"other value",
|
||||
2,
|
||||
},
|
||||
"key4": map[string]interface{}{
|
||||
"subkey": []interface{}{
|
||||
3,
|
||||
"string",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expected := []map[string]interface{}{
|
||||
root,
|
||||
root["key4"].(map[string]interface{}),
|
||||
}
|
||||
actual := unstructpath.All().Map().SelectFrom(root)
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Fatalf("Map should find maps %v, got %v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueSSlice(t *testing.T) {
|
||||
root := map[string]interface{}{
|
||||
"key1": "value",
|
||||
"key2": 1,
|
||||
"key3": []interface{}{
|
||||
"other value",
|
||||
2,
|
||||
},
|
||||
"key4": map[string]interface{}{
|
||||
"subkey": []interface{}{
|
||||
3,
|
||||
"string",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expected := [][]interface{}{
|
||||
root["key3"].([]interface{}),
|
||||
root["key4"].(map[string]interface{})["subkey"].([]interface{}),
|
||||
}
|
||||
actual := unstructpath.All().Slice().SelectFrom(root)
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Fatalf("Slice should find slices %#v, got %#v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueSChildren(t *testing.T) {
|
||||
root := map[string]interface{}{
|
||||
"key1": "value",
|
||||
"key2": 1,
|
||||
"key3": []interface{}{
|
||||
"other value",
|
||||
2,
|
||||
},
|
||||
"key4": map[string]interface{}{
|
||||
"subkey": []interface{}{
|
||||
3,
|
||||
"string",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expected := []interface{}{
|
||||
root["key3"].([]interface{})[0],
|
||||
root["key3"].([]interface{})[1],
|
||||
}
|
||||
actual := unstructpath.Map().Field("key3").Children().SelectFrom(root)
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Fatalf("Expected %v, got %v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueSNumber(t *testing.T) {
|
||||
u := []interface{}{1., 2., "three", 4., 5., []interface{}{}}
|
||||
|
||||
numbers := unstructpath.Children().Number().SelectFrom(u)
|
||||
expected := []float64{1., 2., 4., 5.}
|
||||
if !reflect.DeepEqual(expected, numbers) {
|
||||
t.Fatalf("Children().Number() should select %v, got %v", expected, numbers)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueSString(t *testing.T) {
|
||||
root := map[string]interface{}{
|
||||
"key1": "value",
|
||||
"key2": 1,
|
||||
"key3": []interface{}{
|
||||
"other value",
|
||||
2,
|
||||
},
|
||||
"key4": map[string]interface{}{
|
||||
"subkey": []interface{}{
|
||||
3,
|
||||
"string",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expected := []string{
|
||||
"value",
|
||||
"other value",
|
||||
"string",
|
||||
}
|
||||
actual := unstructpath.All().String().SelectFrom(root)
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Fatalf("Expected %v, got %v", expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueSAll(t *testing.T) {
|
||||
root := map[string]interface{}{
|
||||
"key1": "value",
|
||||
"key2": 1,
|
||||
"key3": []interface{}{
|
||||
"other value",
|
||||
2,
|
||||
},
|
||||
"key4": map[string]interface{}{
|
||||
"subkey": []interface{}{
|
||||
3,
|
||||
"string",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expected := []interface{}{
|
||||
root["key4"],
|
||||
root["key4"].(map[string]interface{})["subkey"],
|
||||
root["key4"].(map[string]interface{})["subkey"].([]interface{})[0],
|
||||
root["key4"].(map[string]interface{})["subkey"].([]interface{})[1],
|
||||
}
|
||||
|
||||
actual := unstructpath.Map().Field("key4").All().SelectFrom(root)
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
t.Fatalf("Expected %v, got %v", expected, actual)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue