Merge pull request #354 from apelisse/unstructpath

unstructpath: Create json path type package.
This commit is contained in:
k8s-ci-robot 2018-03-16 13:53:24 -07:00 committed by GitHub
commit fffaddd605
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 2196 additions and 0 deletions

View File

@ -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

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)))
}

View File

@ -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}`)
}
}

View File

@ -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
}

View File

@ -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))
}

View File

@ -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")
}
}

View File

@ -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
}

View File

@ -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")
}
}

View File

@ -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
}

View File

@ -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)))
}

View File

@ -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}`)
}
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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"`)
}
}

View File

@ -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
}

View File

@ -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")
}
}

View File

@ -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
}

View File

@ -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...))
}

View File

@ -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}")
}
}

View File

@ -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...)}}
}

View File

@ -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)
}
}