add elementsetterlist and elementmatcherlist to fns.go

This commit is contained in:
Natasha Sarkar
2020-11-03 15:48:17 -08:00
parent d8d57eae29
commit 1f806b0aa2
2 changed files with 272 additions and 21 deletions

View File

@@ -63,17 +63,48 @@ type ElementSetter struct {
Value string `yaml:"value,omitempty"`
}
func (e ElementSetter) Filter(rn *RNode) (*RNode, error) {
return rn.Pipe(ElementSetterList{
Element: e.Element,
Keys: []string{e.Key},
Values: []string{e.Value},
})
}
// ElementSetterList sets the value for an Element in an associative list.
// It behaves identically to ElementSetter, except that it uses multiple
// key-value pairs (in the form of a list of keys and a corresponding list
// of values) in order to find a matching element, whereas ElementSetter
// uses only one key-value pair.
type ElementSetterList struct {
Kind string `yaml:"kind,omitempty"`
// Element is the new value to set -- remove the existing element if nil
Element *Node
// Key is a list of fields on the elements. It is used to find matching elements to
// update / delete
Keys []string
// Value is a list of field values on the elements corresponding to the keys. It is
// used to find matching elements to update / delete.
Values []string
}
// isMappingNode returns whether node is a mapping node
func (e ElementSetter) isMappingNode(node *RNode) bool {
func (e ElementSetterList) isMappingNode(node *RNode) bool {
return ErrorIfInvalid(node, yaml.MappingNode) == nil
}
// isMappingSetter returns is this setter intended to set a mapping node
func (e ElementSetter) isMappingSetter() bool {
return e.Key != "" && e.Value != ""
func (e ElementSetterList) isMappingSetter() bool {
return len(e.Keys) > 0 && e.Keys[0] != "" &&
len(e.Values) > 0 && e.Values[0] != ""
}
func (e ElementSetter) Filter(rn *RNode) (*RNode, error) {
// the main difference between this Filter and the ElementSetter Filter
// is that here we must iterate through all the key-value pairs
func (e ElementSetterList) Filter(rn *RNode) (*RNode, error) {
if err := ErrorIfInvalid(rn, SequenceNode); err != nil {
return nil, err
}
@@ -96,11 +127,18 @@ func (e ElementSetter) Filter(rn *RNode) (*RNode, error) {
}
// check if this is the element we are matching
val, err := newNode.Pipe(FieldMatcher{Name: e.Key, StringValue: e.Value})
if err != nil {
return nil, err
found := true
for j, key := range e.Keys {
val, err := newNode.Pipe(FieldMatcher{Name: key, StringValue: e.Values[j]})
if err != nil {
return nil, err
}
if val == nil {
found = false
break
}
}
if val == nil {
if !found {
// not the element we are looking for, keep it in the Content
newContent = append(newContent, elem)
continue
@@ -242,18 +280,54 @@ type ElementMatcher struct {
}
func (e ElementMatcher) Filter(rn *RNode) (*RNode, error) {
return rn.Pipe(ElementMatcherList{
Kind: e.Kind,
Keys: []string{e.FieldName},
Values: []string{e.FieldValue},
Create: e.Create,
MatchAnyValue: e.MatchAnyValue,
})
}
func MatchElementList(keys []string, values []string) ElementMatcherList {
return ElementMatcherList{Keys: keys, Values: values}
}
// ElementMatcherList returns the first element from a Sequence matching all
// specified key, value pairs (as opposed to ElementMatcher, which matches
// a single key, value pair). If there's no match, and no configuration error,
// the matcher returns nil, nil.
type ElementMatcherList struct {
Kind string `yaml:"kind,omitempty"`
// Keys are the list of fields upon which to match this element.
Keys []string
// Values are the list of values upon which to match this element.
Values []string
// Create will create the Element if it is not found
Create *RNode `yaml:"create,omitempty"`
// MatchAnyValue indicates that matcher should only consider the key and ignore
// the actual value in the list. FieldValue must be empty when NoValue is
// set to true.
MatchAnyValue bool `yaml:"noValue,omitempty"`
}
func (e ElementMatcherList) Filter(rn *RNode) (*RNode, error) {
if err := ErrorIfInvalid(rn, yaml.SequenceNode); err != nil {
return nil, err
}
if e.MatchAnyValue && e.FieldValue != "" {
if e.MatchAnyValue && len(e.Values) != 0 && e.Values[0] != "" {
return nil, fmt.Errorf("FieldValue must be empty when NoValue is set to true")
}
// SequenceNode Content is a slice of ScalarNodes. Each ScalarNode has a
// YNode containing the primitive data.
if len(e.FieldName) == 0 {
if len(e.Keys) == 0 || len(e.Keys[0]) == 0 {
for i := range rn.Content() {
if rn.Content()[i].Value == e.FieldValue {
if rn.Content()[i].Value == e.Values[0] {
return &RNode{value: rn.Content()[i]}, nil
}
}
@@ -268,20 +342,28 @@ func (e ElementMatcher) Filter(rn *RNode) (*RNode, error) {
for i := range rn.Content() {
// cast the entry to a RNode so we can operate on it
elem := NewRNode(rn.Content()[i])
var field *RNode
var err error
// only check mapping node
if err := ErrorIfInvalid(elem, yaml.MappingNode); err != nil {
if err = ErrorIfInvalid(elem, yaml.MappingNode); err != nil {
continue
}
var field *RNode
var err error
if e.MatchAnyValue {
field, err = elem.Pipe(Get(e.FieldName))
} else {
field, err = elem.Pipe(MatchField(e.FieldName, e.FieldValue))
matchesElement := true
for i, key := range e.Keys {
if e.MatchAnyValue {
field, err = elem.Pipe(Get(key))
} else {
field, err = elem.Pipe(MatchField(key, e.Values[i]))
}
if !IsFoundOrError(field, err) {
// this is not the element we are looking for
matchesElement = false
break
}
}
if IsFoundOrError(field, err) {
if matchesElement {
return elem, err
}
}
@@ -351,7 +433,7 @@ func (f FieldMatcher) Filter(rn *RNode) (*RNode, error) {
return rn, nil
}
return nil, nil
case rn.value.Value == f.Value.YNode().Value:
case f.Value.YNode() != nil && rn.value.Value == f.Value.YNode().Value:
return rn, nil
default:
return nil, nil
@@ -404,7 +486,7 @@ type PathGetter struct {
// See FieldMatcher for more on Fields and Map Keys.
//
// List Entries can be specified as map entry to match [fieldName=fieldValue]
// or a postional index like 0 to get the element. - (unquoted hyphen) is
// or a positional index like 0 to get the element. - (unquoted hyphen) is
// special and means the last element.
//
// See Elem for more on List Entries.

View File

@@ -217,6 +217,133 @@ func TestElementSetter(t *testing.T) {
`, assertNoErrorString(t)(node.String()))
}
func TestElementSetterList(t *testing.T) {
orig := MustParse(`
- a: b
c: d
- scalarValue
- e: f
# null will be removed
- null
`)
// ElementSetter will update node, so make a copy
node := orig.Copy()
// Remove an element using one key-value pair,
// because ElementSetter.Element is left nil.
rn, err := node.Pipe(ElementSetterList{
Keys: []string{"a"},
Values: []string{"b"},
})
assert.NoError(t, err)
assert.Nil(t, rn)
assert.Equal(t, `- scalarValue
- e: f
`, assertNoErrorString(t)(node.String()))
node = orig.Copy()
// Remove an element using multiple key-value pairs,
// because ElementSetter.Element is left nil.
rn, err = node.Pipe(ElementSetterList{
Keys: []string{"a", "c"},
Values: []string{"b", "d"},
})
assert.NoError(t, err)
assert.Nil(t, rn)
assert.Equal(t, `- scalarValue
- e: f
`, assertNoErrorString(t)(node.String()))
node = orig.Copy()
// Should do nothing, because Element is nil
// and there is no element which matches all
// give key-value pairs
rn, err = node.Pipe(ElementSetterList{
Keys: []string{"a", "c"},
Values: []string{"b", "wrong value"},
})
assert.NoError(t, err)
assert.Nil(t, rn)
assert.Equal(t, `- a: b
c: d
- scalarValue
- e: f
`, assertNoErrorString(t)(node.String()))
node = orig.Copy()
// Set an element, with a single key-value pair
// replacing 'a: b, c: d' with 'g: h'
newElement := NewMapRNode(&map[string]string{
"g": "h",
})
rn, err = node.Pipe(ElementSetterList{
Keys: []string{"a"},
Values: []string{"b"},
Element: newElement.YNode(),
})
assert.NoError(t, err)
assert.Equal(t, rn, newElement)
assert.Equal(t, `- g: h
- scalarValue
- e: f
`, assertNoErrorString(t)(node.String()))
node = orig.Copy()
// Set an element, with multiple key-value pairs
// replacing 'a: b, c: d' with 'g: h'
newElement = NewMapRNode(&map[string]string{
"g": "h",
})
rn, err = node.Pipe(ElementSetterList{
Keys: []string{"a", "c"},
Values: []string{"b", "d"},
Element: newElement.YNode(),
})
assert.NoError(t, err)
assert.Equal(t, rn, newElement)
assert.Equal(t, `- g: h
- scalarValue
- e: f
`, assertNoErrorString(t)(node.String()))
node = orig.Copy()
// Set an element scalar,
// {'a: b, c: d'} to "foo"
newElement = NewScalarRNode("foo")
rn, err = node.Pipe(ElementSetterList{
Keys: []string{"a", "c"},
Values: []string{"b", "d"},
Element: newElement.YNode(),
})
assert.NoError(t, err)
assert.Equal(t, rn, newElement)
assert.Equal(t, `- foo
- scalarValue
- e: f
`, assertNoErrorString(t)(node.String()))
node = orig.Copy()
// Append an element
// There is no element which matches all given
// key-value pairs, so the element will be appended.
newElement = NewMapRNode(&map[string]string{
"g": "h",
})
rn, err = node.Pipe(ElementSetterList{
Keys: []string{"a", "c"},
Values: []string{"b", "wrong value"},
Element: newElement.YNode(),
})
assert.NoError(t, err)
assert.Equal(t, rn, newElement)
assert.Equal(t, `- a: b
c: d
- scalarValue
- e: f
- g: h
`, assertNoErrorString(t)(node.String()))
}
func TestElementMatcherWithNoValue(t *testing.T) {
node, err := Parse(`
- a: c
@@ -239,6 +366,48 @@ func TestElementMatcherWithNoValue(t *testing.T) {
_, err = node.Pipe(ElementMatcher{FieldName: "a", FieldValue: "c", MatchAnyValue: true})
assert.Errorf(t, err, "FieldValue must be empty when NoValue is set to true")
}
func TestElementMatcherList(t *testing.T) {
node, err := Parse(`
- a: b
c: d
- e: f
`)
assert.NoError(t, err)
// matches all key-value pairs
rn, err := node.Pipe(MatchElementList(
[]string{"a", "c"}, // keys
[]string{"b", "d"}, // values
))
assert.NoError(t, err)
assert.NotEmpty(t, rn)
// matches one key value pair but not the other
rn, err = node.Pipe(MatchElementList(
[]string{"a", "c"}, // keys
[]string{"b", "f"}, // values
))
assert.NoError(t, err)
assert.Nil(t, rn)
// matches single given key value pair
rn, err = node.Pipe(MatchElementList(
[]string{"e"}, // keys
[]string{"f"}, // values
))
assert.NoError(t, err)
assert.NotEmpty(t, rn)
// matching key, but value doesn't match
rn, err = node.Pipe(MatchElementList(
[]string{"e"}, // keys
[]string{"g"}, // values
))
assert.NoError(t, err)
assert.Nil(t, rn)
}
func TestClearField_Fn(t *testing.T) {
node, err := Parse(NodeSampleData)
assert.NoError(t, err)