mirror of
				https://github.com/taigrr/yq
				synced 2025-01-18 04:53:17 -08:00 
			
		
		
		
	Added merge if empty
This commit is contained in:
		
							parent
							
								
									09ec740d45
								
							
						
					
					
						commit
						91c72d2d9e
					
				| @ -111,6 +111,26 @@ b: | |||||||
|   - 5 |   - 5 | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | ## Merge, only existing fields | ||||||
|  | Given a sample.yml file of: | ||||||
|  | ```yaml | ||||||
|  | a: | ||||||
|  |   thing: one | ||||||
|  |   cat: frog | ||||||
|  | b: | ||||||
|  |   missing: two | ||||||
|  |   thing: two | ||||||
|  | ``` | ||||||
|  | then | ||||||
|  | ```bash | ||||||
|  | yq eval '.a *? .b' sample.yml | ||||||
|  | ``` | ||||||
|  | will output | ||||||
|  | ```yaml | ||||||
|  | thing: two | ||||||
|  | cat: frog | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| ## Merge, appending arrays | ## Merge, appending arrays | ||||||
| Given a sample.yml file of: | Given a sample.yml file of: | ||||||
| ```yaml | ```yaml | ||||||
| @ -143,6 +163,33 @@ array: | |||||||
| value: banana | value: banana | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | ## Merge, only existing fields, appending arrays | ||||||
|  | Given a sample.yml file of: | ||||||
|  | ```yaml | ||||||
|  | a: | ||||||
|  |   thing: | ||||||
|  |     - 1 | ||||||
|  |     - 2 | ||||||
|  | b: | ||||||
|  |   thing: | ||||||
|  |     - 3 | ||||||
|  |     - 4 | ||||||
|  |   another: | ||||||
|  |     - 1 | ||||||
|  | ``` | ||||||
|  | then | ||||||
|  | ```bash | ||||||
|  | yq eval '.a *?+ .b' sample.yml | ||||||
|  | ``` | ||||||
|  | will output | ||||||
|  | ```yaml | ||||||
|  | thing: | ||||||
|  |   - 1 | ||||||
|  |   - 2 | ||||||
|  |   - 3 | ||||||
|  |   - 4 | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| ## Merge to prefix an element | ## Merge to prefix an element | ||||||
| Given a sample.yml file of: | Given a sample.yml file of: | ||||||
| ```yaml | ```yaml | ||||||
|  | |||||||
| @ -52,11 +52,6 @@ var pathTests = []struct { | |||||||
| 		append(make([]interface{}, 0), "[", "3 (int64)", "]"), | 		append(make([]interface{}, 0), "[", "3 (int64)", "]"), | ||||||
| 		append(make([]interface{}, 0), "3 (int64)", "COLLECT", "SHORT_PIPE"), | 		append(make([]interface{}, 0), "3 (int64)", "COLLECT", "SHORT_PIPE"), | ||||||
| 	}, | 	}, | ||||||
| 	{ |  | ||||||
| 		`d0.a`, |  | ||||||
| 		append(make([]interface{}, 0), "d0", "SHORT_PIPE", "a"), |  | ||||||
| 		append(make([]interface{}, 0), "d0", "a", "SHORT_PIPE"), |  | ||||||
| 	}, |  | ||||||
| 	{ | 	{ | ||||||
| 		`.a | .[].b == "apple"`, | 		`.a | .[].b == "apple"`, | ||||||
| 		append(make([]interface{}, 0), "a", "PIPE", "TRAVERSE_ARRAY", "[", "]", "SHORT_PIPE", "b", "EQUALS", "apple (string)"), | 		append(make([]interface{}, 0), "a", "PIPE", "TRAVERSE_ARRAY", "[", "]", "SHORT_PIPE", "b", "EQUALS", "apple (string)"), | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ package yqlib | |||||||
| import ( | import ( | ||||||
| 	"fmt" | 	"fmt" | ||||||
| 	"strconv" | 	"strconv" | ||||||
|  | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	lex "github.com/timtadh/lexmachine" | 	lex "github.com/timtadh/lexmachine" | ||||||
| 	"github.com/timtadh/lexmachine/machines" | 	"github.com/timtadh/lexmachine/machines" | ||||||
| @ -65,21 +66,7 @@ func pathToken(wrapped bool) lex.Action { | |||||||
| 			value = unwrap(value) | 			value = unwrap(value) | ||||||
| 		} | 		} | ||||||
| 		log.Debug("PathToken %v", value) | 		log.Debug("PathToken %v", value) | ||||||
| 		op := &Operation{OperationType: traversePathOpType, Value: value, StringValue: value} | 		op := &Operation{OperationType: traversePathOpType, Value: value, StringValue: value, Preferences: traversePreferences{}} | ||||||
| 		return &token{TokenType: operationToken, Operation: op, CheckForPostTraverse: true}, nil |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| func documentToken() lex.Action { |  | ||||||
| 	return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { |  | ||||||
| 		var numberString = string(m.Bytes) |  | ||||||
| 		numberString = numberString[1:] |  | ||||||
| 		var number, errParsingInt = strconv.ParseInt(numberString, 10, 64) // nolint |  | ||||||
| 		if errParsingInt != nil { |  | ||||||
| 			return nil, errParsingInt |  | ||||||
| 		} |  | ||||||
| 		log.Debug("documentToken %v", string(m.Bytes)) |  | ||||||
| 		op := &Operation{OperationType: documentFilterOpType, Value: number, StringValue: numberString} |  | ||||||
| 		return &token{TokenType: operationToken, Operation: op, CheckForPostTraverse: true}, nil | 		return &token{TokenType: operationToken, Operation: op, CheckForPostTraverse: true}, nil | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| @ -101,6 +88,21 @@ func assignOpToken(updateAssign bool) lex.Action { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func multiplyWithPrefs() lex.Action { | ||||||
|  | 	return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { | ||||||
|  | 		prefs := multiplyPreferences{} | ||||||
|  | 		options := string(m.Bytes) | ||||||
|  | 		if strings.Contains(options, "+") { | ||||||
|  | 			prefs.AppendArrays = true | ||||||
|  | 		} | ||||||
|  | 		if strings.Contains(options, "?") { | ||||||
|  | 			prefs.TraversePrefs = traversePreferences{DontAutoCreate: true} | ||||||
|  | 		} | ||||||
|  | 		op := &Operation{OperationType: multiplyOpType, Value: multiplyOpType.Type, StringValue: options, Preferences: &prefs} | ||||||
|  | 		return &token{TokenType: operationToken, Operation: op}, nil | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func opTokenWithPrefs(op *operationType, assignOpType *operationType, preferences interface{}) lex.Action { | func opTokenWithPrefs(op *operationType, assignOpType *operationType, preferences interface{}) lex.Action { | ||||||
| 	return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { | 	return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { | ||||||
| 		log.Debug("opTokenWithPrefs %v", string(m.Bytes)) | 		log.Debug("opTokenWithPrefs %v", string(m.Bytes)) | ||||||
| @ -220,10 +222,10 @@ func initLexer() (*lex.Lexer, error) { | |||||||
| 
 | 
 | ||||||
| 	lexer.Add([]byte(`\.\[`), literalToken(traverseArrayCollect, false)) | 	lexer.Add([]byte(`\.\[`), literalToken(traverseArrayCollect, false)) | ||||||
| 	lexer.Add([]byte(`\.\.`), opTokenWithPrefs(recursiveDescentOpType, nil, &recursiveDescentPreferences{RecurseArray: true, | 	lexer.Add([]byte(`\.\.`), opTokenWithPrefs(recursiveDescentOpType, nil, &recursiveDescentPreferences{RecurseArray: true, | ||||||
| 		TraversePreferences: &traversePreferences{FollowAlias: false, IncludeMapKeys: false}})) | 		TraversePreferences: traversePreferences{DontFollowAlias: true, IncludeMapKeys: false}})) | ||||||
| 
 | 
 | ||||||
| 	lexer.Add([]byte(`\.\.\.`), opTokenWithPrefs(recursiveDescentOpType, nil, &recursiveDescentPreferences{RecurseArray: true, | 	lexer.Add([]byte(`\.\.\.`), opTokenWithPrefs(recursiveDescentOpType, nil, &recursiveDescentPreferences{RecurseArray: true, | ||||||
| 		TraversePreferences: &traversePreferences{FollowAlias: false, IncludeMapKeys: true}})) | 		TraversePreferences: traversePreferences{DontFollowAlias: true, IncludeMapKeys: true}})) | ||||||
| 
 | 
 | ||||||
| 	lexer.Add([]byte(`,`), opToken(unionOpType)) | 	lexer.Add([]byte(`,`), opToken(unionOpType)) | ||||||
| 	lexer.Add([]byte(`:\s*`), opToken(createMapOpType)) | 	lexer.Add([]byte(`:\s*`), opToken(createMapOpType)) | ||||||
| @ -270,7 +272,6 @@ func initLexer() (*lex.Lexer, error) { | |||||||
| 
 | 
 | ||||||
| 	lexer.Add([]byte("( |\t|\n|\r)+"), skip) | 	lexer.Add([]byte("( |\t|\n|\r)+"), skip) | ||||||
| 
 | 
 | ||||||
| 	lexer.Add([]byte(`d[0-9]+`), documentToken()) |  | ||||||
| 	lexer.Add([]byte(`\."[^ "]+"`), pathToken(true)) | 	lexer.Add([]byte(`\."[^ "]+"`), pathToken(true)) | ||||||
| 	lexer.Add([]byte(`\.[^ \}\{\:\[\],\|\.\[\(\)=]+`), pathToken(false)) | 	lexer.Add([]byte(`\.[^ \}\{\:\[\],\|\.\[\(\)=]+`), pathToken(false)) | ||||||
| 	lexer.Add([]byte(`\.`), selfToken()) | 	lexer.Add([]byte(`\.`), selfToken()) | ||||||
| @ -296,7 +297,7 @@ func initLexer() (*lex.Lexer, error) { | |||||||
| 	lexer.Add([]byte(`\{`), literalToken(openCollectObject, false)) | 	lexer.Add([]byte(`\{`), literalToken(openCollectObject, false)) | ||||||
| 	lexer.Add([]byte(`\}`), literalToken(closeCollectObject, true)) | 	lexer.Add([]byte(`\}`), literalToken(closeCollectObject, true)) | ||||||
| 	lexer.Add([]byte(`\*`), opTokenWithPrefs(multiplyOpType, nil, &multiplyPreferences{AppendArrays: false})) | 	lexer.Add([]byte(`\*`), opTokenWithPrefs(multiplyOpType, nil, &multiplyPreferences{AppendArrays: false})) | ||||||
| 	lexer.Add([]byte(`\*\+`), opTokenWithPrefs(multiplyOpType, nil, &multiplyPreferences{AppendArrays: true})) | 	lexer.Add([]byte(`\*[\+|\?]*`), multiplyWithPrefs()) | ||||||
| 	lexer.Add([]byte(`\+`), opToken(addOpType)) | 	lexer.Add([]byte(`\+`), opToken(addOpType)) | ||||||
| 	lexer.Add([]byte(`\+=`), opToken(addAssignOpType)) | 	lexer.Add([]byte(`\+=`), opToken(addAssignOpType)) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -68,7 +68,6 @@ var collectObjectOpType = &operationType{Type: "COLLECT_OBJECT", NumArgs: 0, Pre | |||||||
| var traversePathOpType = &operationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: traversePathOperator} | var traversePathOpType = &operationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: traversePathOperator} | ||||||
| var traverseArrayOpType = &operationType{Type: "TRAVERSE_ARRAY", NumArgs: 1, Precedence: 50, Handler: traverseArrayOperator} | var traverseArrayOpType = &operationType{Type: "TRAVERSE_ARRAY", NumArgs: 1, Precedence: 50, Handler: traverseArrayOperator} | ||||||
| 
 | 
 | ||||||
| var documentFilterOpType = &operationType{Type: "DOCUMENT_FILTER", NumArgs: 0, Precedence: 50, Handler: traversePathOperator} |  | ||||||
| var selfReferenceOpType = &operationType{Type: "SELF", NumArgs: 0, Precedence: 50, Handler: selfOperator} | var selfReferenceOpType = &operationType{Type: "SELF", NumArgs: 0, Precedence: 50, Handler: selfOperator} | ||||||
| var valueOpType = &operationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: valueOperator} | var valueOpType = &operationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: valueOperator} | ||||||
| var envOpType = &operationType{Type: "ENV", NumArgs: 0, Precedence: 50, Handler: envOperator} | var envOpType = &operationType{Type: "ENV", NumArgs: 0, Precedence: 50, Handler: envOperator} | ||||||
| @ -120,8 +119,6 @@ func createValueOperation(value interface{}, stringValue string) *Operation { | |||||||
| func (p *Operation) toString() string { | func (p *Operation) toString() string { | ||||||
| 	if p.OperationType == traversePathOpType { | 	if p.OperationType == traversePathOpType { | ||||||
| 		return fmt.Sprintf("%v", p.Value) | 		return fmt.Sprintf("%v", p.Value) | ||||||
| 	} else if p.OperationType == documentFilterOpType { |  | ||||||
| 		return fmt.Sprintf("d%v", p.Value) |  | ||||||
| 	} else if p.OperationType == selfReferenceOpType { | 	} else if p.OperationType == selfReferenceOpType { | ||||||
| 		return "SELF" | 		return "SELF" | ||||||
| 	} else if p.OperationType == valueOpType { | 	} else if p.OperationType == valueOpType { | ||||||
|  | |||||||
| @ -60,7 +60,7 @@ func collect(d *dataTreeNavigator, aggregate *list.List, remainingMatches *list. | |||||||
| 	candidate := remainingMatches.Remove(remainingMatches.Front()).(*CandidateNode) | 	candidate := remainingMatches.Remove(remainingMatches.Front()).(*CandidateNode) | ||||||
| 
 | 
 | ||||||
| 	splatted, err := splat(d, nodeToMap(candidate), | 	splatted, err := splat(d, nodeToMap(candidate), | ||||||
| 		&traversePreferences{FollowAlias: false, IncludeMapKeys: false}) | 		traversePreferences{DontFollowAlias: true, IncludeMapKeys: false}) | ||||||
| 
 | 
 | ||||||
| 	for splatEl := splatted.Front(); splatEl != nil; splatEl = splatEl.Next() { | 	for splatEl := splatted.Front(); splatEl != nil; splatEl = splatEl.Next() { | ||||||
| 		splatEl.Value.(*CandidateNode).Path = nil | 		splatEl.Value.(*CandidateNode).Path = nil | ||||||
|  | |||||||
| @ -25,7 +25,7 @@ func deleteChildOperator(d *dataTreeNavigator, matchingNodes *list.List, express | |||||||
| 
 | 
 | ||||||
| 		deleteImmediateChildOpNode := &ExpressionNode{ | 		deleteImmediateChildOpNode := &ExpressionNode{ | ||||||
| 			Operation: deleteImmediateChildOp, | 			Operation: deleteImmediateChildOp, | ||||||
| 			Rhs:       createTraversalTree(candidate.Path[0 : len(candidate.Path)-1]), | 			Rhs:       createTraversalTree(candidate.Path[0:len(candidate.Path)-1], traversePreferences{}), | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		_, err := d.GetMatchingNodes(matchingNodes, deleteImmediateChildOpNode) | 		_, err := d.GetMatchingNodes(matchingNodes, deleteImmediateChildOpNode) | ||||||
|  | |||||||
| @ -45,6 +45,7 @@ func crossFunction(d *dataTreeNavigator, matchingNodes *list.List, expressionNod | |||||||
| 
 | 
 | ||||||
| type multiplyPreferences struct { | type multiplyPreferences struct { | ||||||
| 	AppendArrays  bool | 	AppendArrays  bool | ||||||
|  | 	TraversePrefs traversePreferences | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func multiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) { | func multiplyOperator(d *dataTreeNavigator, matchingNodes *list.List, expressionNode *ExpressionNode) (*list.List, error) { | ||||||
| @ -59,29 +60,28 @@ func multiply(preferences *multiplyPreferences) func(d *dataTreeNavigator, lhs * | |||||||
| 		log.Debugf("Multipling LHS: %v", lhs.Node.Tag) | 		log.Debugf("Multipling LHS: %v", lhs.Node.Tag) | ||||||
| 		log.Debugf("-          RHS: %v", rhs.Node.Tag) | 		log.Debugf("-          RHS: %v", rhs.Node.Tag) | ||||||
| 
 | 
 | ||||||
| 		shouldAppendArrays := preferences.AppendArrays |  | ||||||
| 
 |  | ||||||
| 		if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode || | 		if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode || | ||||||
| 			(lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) { | 			(lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) { | ||||||
| 
 | 
 | ||||||
| 			var newBlank = lhs.CreateChild(nil, &yaml.Node{}) | 			var newBlank = lhs.CreateChild(nil, &yaml.Node{}) | ||||||
| 			var newThing, err = mergeObjects(d, newBlank, lhs, false) | 			var newThing, err = mergeObjects(d, newBlank, lhs, &multiplyPreferences{}) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| 				return nil, err | 				return nil, err | ||||||
| 			} | 			} | ||||||
| 			return mergeObjects(d, newThing, rhs, shouldAppendArrays) | 			return mergeObjects(d, newThing, rhs, preferences) | ||||||
| 
 | 
 | ||||||
| 		} | 		} | ||||||
| 		return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag) | 		return nil, fmt.Errorf("Cannot multiply %v with %v", lhs.Node.Tag, rhs.Node.Tag) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode, shouldAppendArrays bool) (*CandidateNode, error) { | func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode, preferences *multiplyPreferences) (*CandidateNode, error) { | ||||||
|  | 	shouldAppendArrays := preferences.AppendArrays | ||||||
| 	var results = list.New() | 	var results = list.New() | ||||||
| 
 | 
 | ||||||
| 	// shouldn't recurse arrays if appending | 	// shouldn't recurse arrays if appending | ||||||
| 	prefs := &recursiveDescentPreferences{RecurseArray: !shouldAppendArrays, | 	prefs := &recursiveDescentPreferences{RecurseArray: !shouldAppendArrays, | ||||||
| 		TraversePreferences: &traversePreferences{FollowAlias: false}} | 		TraversePreferences: traversePreferences{DontFollowAlias: true}} | ||||||
| 	err := recursiveDecent(d, results, nodeToMap(rhs), prefs) | 	err := recursiveDecent(d, results, nodeToMap(rhs), prefs) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| @ -93,7 +93,7 @@ func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode, | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for el := results.Front(); el != nil; el = el.Next() { | 	for el := results.Front(); el != nil; el = el.Next() { | ||||||
| 		err := applyAssignment(d, pathIndexToStartFrom, lhs, el.Value.(*CandidateNode), shouldAppendArrays) | 		err := applyAssignment(d, pathIndexToStartFrom, lhs, el.Value.(*CandidateNode), preferences) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return nil, err | 			return nil, err | ||||||
| 		} | 		} | ||||||
| @ -101,8 +101,8 @@ func mergeObjects(d *dataTreeNavigator, lhs *CandidateNode, rhs *CandidateNode, | |||||||
| 	return lhs, nil | 	return lhs, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode, shouldAppendArrays bool) error { | func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *CandidateNode, rhs *CandidateNode, preferences *multiplyPreferences) error { | ||||||
| 
 | 	shouldAppendArrays := preferences.AppendArrays | ||||||
| 	log.Debugf("merge - applyAssignment lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs)) | 	log.Debugf("merge - applyAssignment lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs)) | ||||||
| 
 | 
 | ||||||
| 	lhsPath := rhs.Path[pathIndexToStartFrom:] | 	lhsPath := rhs.Path[pathIndexToStartFrom:] | ||||||
| @ -116,7 +116,7 @@ func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *Candid | |||||||
| 	} | 	} | ||||||
| 	rhsOp := &Operation{OperationType: valueOpType, CandidateNode: rhs} | 	rhsOp := &Operation{OperationType: valueOpType, CandidateNode: rhs} | ||||||
| 
 | 
 | ||||||
| 	assignmentOpNode := &ExpressionNode{Operation: assignmentOp, Lhs: createTraversalTree(lhsPath), Rhs: &ExpressionNode{Operation: rhsOp}} | 	assignmentOpNode := &ExpressionNode{Operation: assignmentOp, Lhs: createTraversalTree(lhsPath, preferences.TraversePrefs), Rhs: &ExpressionNode{Operation: rhsOp}} | ||||||
| 
 | 
 | ||||||
| 	_, err := d.GetMatchingNodes(nodeToMap(lhs), assignmentOpNode) | 	_, err := d.GetMatchingNodes(nodeToMap(lhs), assignmentOpNode) | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -107,6 +107,22 @@ b: | |||||||
| 			"D0, P[a], (!!seq)::[1, 2]\n", | 			"D0, P[a], (!!seq)::[1, 2]\n", | ||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
|  | 	{ | ||||||
|  | 		description: "Merge, only existing fields", | ||||||
|  | 		document:    `{a: {thing: one, cat: frog}, b: {missing: two, thing: two}}`, | ||||||
|  | 		expression:  `.a *? .b`, | ||||||
|  | 		expected: []string{ | ||||||
|  | 			"D0, P[a], (!!map)::{thing: two, cat: frog}\n", | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		skipDoc:    true, | ||||||
|  | 		document:   `{a: [{thing: one}], b: [{missing: two, thing: two}]}`, | ||||||
|  | 		expression: `.a *? .b`, | ||||||
|  | 		expected: []string{ | ||||||
|  | 			"D0, P[a], (!!seq)::[{thing: two}]\n", | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		description: "Merge, appending arrays", | 		description: "Merge, appending arrays", | ||||||
| 		document:    `{a: {array: [1, 2, animal: dog], value: coconut}, b: {array: [3, 4, animal: cat], value: banana}}`, | 		document:    `{a: {array: [1, 2, animal: dog], value: coconut}, b: {array: [3, 4, animal: cat], value: banana}}`, | ||||||
| @ -115,6 +131,14 @@ b: | |||||||
| 			"D0, P[a], (!!map)::{array: [1, 2, {animal: dog}, 3, 4, {animal: cat}], value: banana}\n", | 			"D0, P[a], (!!map)::{array: [1, 2, {animal: dog}, 3, 4, {animal: cat}], value: banana}\n", | ||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
|  | 	{ | ||||||
|  | 		description: "Merge, only existing fields, appending arrays", | ||||||
|  | 		document:    `{a: {thing: [1,2]}, b: {thing: [3,4], another: [1]}}`, | ||||||
|  | 		expression:  `.a *?+ .b`, | ||||||
|  | 		expected: []string{ | ||||||
|  | 			"D0, P[a], (!!map)::{thing: [1, 2, 3, 4]}\n", | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		description: "Merge to prefix an element", | 		description: "Merge to prefix an element", | ||||||
| 		document:    `{a: cat, b: dog}`, | 		document:    `{a: cat, b: dog}`, | ||||||
|  | |||||||
| @ -7,7 +7,7 @@ import ( | |||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type recursiveDescentPreferences struct { | type recursiveDescentPreferences struct { | ||||||
| 	TraversePreferences *traversePreferences | 	TraversePreferences traversePreferences | ||||||
| 	RecurseArray        bool | 	RecurseArray        bool | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -10,11 +10,12 @@ import ( | |||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type traversePreferences struct { | type traversePreferences struct { | ||||||
| 	FollowAlias    bool | 	DontFollowAlias bool | ||||||
| 	IncludeMapKeys  bool | 	IncludeMapKeys  bool | ||||||
|  | 	DontAutoCreate  bool // by default, we automatically create entries on the fly. | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func splat(d *dataTreeNavigator, matches *list.List, prefs *traversePreferences) (*list.List, error) { | func splat(d *dataTreeNavigator, matches *list.List, prefs traversePreferences) (*list.List, error) { | ||||||
| 	return traverseNodesWithArrayIndices(matches, make([]*yaml.Node, 0), prefs) | 	return traverseNodesWithArrayIndices(matches, make([]*yaml.Node, 0), prefs) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -54,8 +55,7 @@ func traverse(d *dataTreeNavigator, matchingNode *CandidateNode, operation *Oper | |||||||
| 	switch value.Kind { | 	switch value.Kind { | ||||||
| 	case yaml.MappingNode: | 	case yaml.MappingNode: | ||||||
| 		log.Debug("its a map with %v entries", len(value.Content)/2) | 		log.Debug("its a map with %v entries", len(value.Content)/2) | ||||||
| 		prefs := &traversePreferences{FollowAlias: true} | 		return traverseMap(matchingNode, operation.StringValue, operation.Preferences.(traversePreferences), false) | ||||||
| 		return traverseMap(matchingNode, operation.StringValue, prefs, false) |  | ||||||
| 
 | 
 | ||||||
| 	case yaml.SequenceNode: | 	case yaml.SequenceNode: | ||||||
| 		log.Debug("its a sequence of %v things!", len(value.Content)) | 		log.Debug("its a sequence of %v things!", len(value.Content)) | ||||||
| @ -83,11 +83,10 @@ func traverseArrayOperator(d *dataTreeNavigator, matchingNodes *list.List, expre | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var indicesToTraverse = rhs.Front().Value.(*CandidateNode).Node.Content | 	var indicesToTraverse = rhs.Front().Value.(*CandidateNode).Node.Content | ||||||
| 	prefs := &traversePreferences{FollowAlias: true} | 	return traverseNodesWithArrayIndices(matchingNodes, indicesToTraverse, traversePreferences{}) | ||||||
| 	return traverseNodesWithArrayIndices(matchingNodes, indicesToTraverse, prefs) |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func traverseNodesWithArrayIndices(matchingNodes *list.List, indicesToTraverse []*yaml.Node, prefs *traversePreferences) (*list.List, error) { | func traverseNodesWithArrayIndices(matchingNodes *list.List, indicesToTraverse []*yaml.Node, prefs traversePreferences) (*list.List, error) { | ||||||
| 	var matchingNodeMap = list.New() | 	var matchingNodeMap = list.New() | ||||||
| 	for el := matchingNodes.Front(); el != nil; el = el.Next() { | 	for el := matchingNodes.Front(); el != nil; el = el.Next() { | ||||||
| 		candidate := el.Value.(*CandidateNode) | 		candidate := el.Value.(*CandidateNode) | ||||||
| @ -101,7 +100,7 @@ func traverseNodesWithArrayIndices(matchingNodes *list.List, indicesToTraverse [ | |||||||
| 	return matchingNodeMap, nil | 	return matchingNodeMap, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func traverseArrayIndices(matchingNode *CandidateNode, indicesToTraverse []*yaml.Node, prefs *traversePreferences) (*list.List, error) { // call this if doc / alias like the other traverse | func traverseArrayIndices(matchingNode *CandidateNode, indicesToTraverse []*yaml.Node, prefs traversePreferences) (*list.List, error) { // call this if doc / alias like the other traverse | ||||||
| 	node := matchingNode.Node | 	node := matchingNode.Node | ||||||
| 	if node.Tag == "!!null" { | 	if node.Tag == "!!null" { | ||||||
| 		log.Debugf("OperatorArrayTraverse got a null - turning it into an empty array") | 		log.Debugf("OperatorArrayTraverse got a null - turning it into an empty array") | ||||||
| @ -124,7 +123,7 @@ func traverseArrayIndices(matchingNode *CandidateNode, indicesToTraverse []*yaml | |||||||
| 	return list.New(), nil | 	return list.New(), nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func traverseMapWithIndices(candidate *CandidateNode, indices []*yaml.Node, prefs *traversePreferences) (*list.List, error) { | func traverseMapWithIndices(candidate *CandidateNode, indices []*yaml.Node, prefs traversePreferences) (*list.List, error) { | ||||||
| 	if len(indices) == 0 { | 	if len(indices) == 0 { | ||||||
| 		return traverseMap(candidate, "", prefs, true) | 		return traverseMap(candidate, "", prefs, true) | ||||||
| 	} | 	} | ||||||
| @ -188,7 +187,7 @@ func keyMatches(key *yaml.Node, wantedKey string) bool { | |||||||
| 	return matchKey(key.Value, wantedKey) | 	return matchKey(key.Value, wantedKey) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func traverseMap(matchingNode *CandidateNode, key string, prefs *traversePreferences, splat bool) (*list.List, error) { | func traverseMap(matchingNode *CandidateNode, key string, prefs traversePreferences, splat bool) (*list.List, error) { | ||||||
| 	var newMatches = orderedmap.NewOrderedMap() | 	var newMatches = orderedmap.NewOrderedMap() | ||||||
| 	err := doTraverseMap(newMatches, matchingNode, key, prefs, splat) | 	err := doTraverseMap(newMatches, matchingNode, key, prefs, splat) | ||||||
| 
 | 
 | ||||||
| @ -196,7 +195,7 @@ func traverseMap(matchingNode *CandidateNode, key string, prefs *traversePrefere | |||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if newMatches.Len() == 0 { | 	if !prefs.DontAutoCreate && newMatches.Len() == 0 { | ||||||
| 		//no matches, create one automagically | 		//no matches, create one automagically | ||||||
| 		valueNode := &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"} | 		valueNode := &yaml.Node{Tag: "!!null", Kind: yaml.ScalarNode, Value: "null"} | ||||||
| 		node := matchingNode.Node | 		node := matchingNode.Node | ||||||
| @ -214,7 +213,7 @@ func traverseMap(matchingNode *CandidateNode, key string, prefs *traversePrefere | |||||||
| 	return results, nil | 	return results, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode, wantedKey string, prefs *traversePreferences, splat bool) error { | func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode, wantedKey string, prefs traversePreferences, splat bool) error { | ||||||
| 	// value.Content is a concatenated array of key, value, | 	// value.Content is a concatenated array of key, value, | ||||||
| 	// so keys are in the even indexes, values in odd. | 	// so keys are in the even indexes, values in odd. | ||||||
| 	// merge aliases are defined first, but we only want to traverse them | 	// merge aliases are defined first, but we only want to traverse them | ||||||
| @ -229,7 +228,7 @@ func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode, | |||||||
| 
 | 
 | ||||||
| 		log.Debug("checking %v (%v)", key.Value, key.Tag) | 		log.Debug("checking %v (%v)", key.Value, key.Tag) | ||||||
| 		//skip the 'merge' tag, find a direct match first | 		//skip the 'merge' tag, find a direct match first | ||||||
| 		if key.Tag == "!!merge" && prefs.FollowAlias { | 		if key.Tag == "!!merge" && !prefs.DontFollowAlias { | ||||||
| 			log.Debug("Merge anchor") | 			log.Debug("Merge anchor") | ||||||
| 			err := traverseMergeAnchor(newMatches, candidate, value, wantedKey, prefs, splat) | 			err := traverseMergeAnchor(newMatches, candidate, value, wantedKey, prefs, splat) | ||||||
| 			if err != nil { | 			if err != nil { | ||||||
| @ -249,7 +248,7 @@ func doTraverseMap(newMatches *orderedmap.OrderedMap, candidate *CandidateNode, | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *CandidateNode, value *yaml.Node, wantedKey string, prefs *traversePreferences, splat bool) error { | func traverseMergeAnchor(newMatches *orderedmap.OrderedMap, originalCandidate *CandidateNode, value *yaml.Node, wantedKey string, prefs traversePreferences, splat bool) error { | ||||||
| 	switch value.Kind { | 	switch value.Kind { | ||||||
| 	case yaml.AliasNode: | 	case yaml.AliasNode: | ||||||
| 		candidateNode := originalCandidate.CreateChild(nil, value.Alias) | 		candidateNode := originalCandidate.CreateChild(nil, value.Alias) | ||||||
|  | |||||||
| @ -35,14 +35,16 @@ func nodeToMap(candidate *CandidateNode) *list.List { | |||||||
| 	return elMap | 	return elMap | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func createTraversalTree(path []interface{}) *ExpressionNode { | func createTraversalTree(path []interface{}, traversePrefs traversePreferences) *ExpressionNode { | ||||||
| 	if len(path) == 0 { | 	if len(path) == 0 { | ||||||
| 		return &ExpressionNode{Operation: &Operation{OperationType: selfReferenceOpType}} | 		return &ExpressionNode{Operation: &Operation{OperationType: selfReferenceOpType}} | ||||||
| 	} else if len(path) == 1 { | 	} else if len(path) == 1 { | ||||||
| 		return &ExpressionNode{Operation: &Operation{OperationType: traversePathOpType, Value: path[0], StringValue: fmt.Sprintf("%v", path[0])}} | 		return &ExpressionNode{Operation: &Operation{OperationType: traversePathOpType, Preferences: traversePrefs, Value: path[0], StringValue: fmt.Sprintf("%v", path[0])}} | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
| 	return &ExpressionNode{ | 	return &ExpressionNode{ | ||||||
| 		Operation: &Operation{OperationType: shortPipeOpType}, | 		Operation: &Operation{OperationType: shortPipeOpType}, | ||||||
| 		Lhs:       createTraversalTree(path[0:1]), | 		Lhs:       createTraversalTree(path[0:1], traversePrefs), | ||||||
| 		Rhs:       createTraversalTree(path[1:])} | 		Rhs:       createTraversalTree(path[1:], traversePrefs), | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user