mirror of
				https://github.com/taigrr/yq
				synced 2025-01-18 04:53:17 -08:00 
			
		
		
		
	Added append equals, merge append. Fixed creating numeric arrays
This commit is contained in:
		
							parent
							
								
									3f48201a19
								
							
						
					
					
						commit
						3a030651a3
					
				| @ -3,6 +3,31 @@ Add behaves differently according to the type of the LHS: | |||||||
| - number scalars: arithmetic addition (soon) | - number scalars: arithmetic addition (soon) | ||||||
| - string scalars: concatenate (soon) | - string scalars: concatenate (soon) | ||||||
| 
 | 
 | ||||||
|  | Use `+=` as append assign for things like increment. `.a += .x` is equivalent to running `.a |= . + .x`. | ||||||
|  | 
 | ||||||
|  | ## Concatenate and assign arrays | ||||||
|  | Given a sample.yml file of: | ||||||
|  | ```yaml | ||||||
|  | a: | ||||||
|  |   val: thing | ||||||
|  |   b: | ||||||
|  |     - cat | ||||||
|  |     - dog | ||||||
|  | ``` | ||||||
|  | then | ||||||
|  | ```bash | ||||||
|  | yq eval '.a.b += ["cow"]' sample.yml | ||||||
|  | ``` | ||||||
|  | will output | ||||||
|  | ```yaml | ||||||
|  | a: | ||||||
|  |   val: thing | ||||||
|  |   b: | ||||||
|  |     - cat | ||||||
|  |     - dog | ||||||
|  |     - cow | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| ## Concatenate arrays | ## Concatenate arrays | ||||||
| Given a sample.yml file of: | Given a sample.yml file of: | ||||||
| ```yaml | ```yaml | ||||||
|  | |||||||
| @ -148,7 +148,7 @@ Given a sample.yml file of: | |||||||
| ``` | ``` | ||||||
| then | then | ||||||
| ```bash | ```bash | ||||||
| yq eval '.a.b[0] |= "bogs"' sample.yml | yq eval '.a.b.[0] |= "bogs"' sample.yml | ||||||
| ``` | ``` | ||||||
| will output | will output | ||||||
| ```yaml | ```yaml | ||||||
|  | |||||||
| @ -2,6 +2,8 @@ Like the multiple operator in `jq`, depending on the operands, this multiply ope | |||||||
| 
 | 
 | ||||||
| Upcoming versions of `yq` will add support for other types of multiplication (numbers, strings). | Upcoming versions of `yq` will add support for other types of multiplication (numbers, strings). | ||||||
| 
 | 
 | ||||||
|  | To concatenate when merging objects, use the `*+` form (see examples below). This will recursively merge objects, appending arrays when it encounters them. | ||||||
|  | 
 | ||||||
| Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below. | Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below. | ||||||
| 
 | 
 | ||||||
| ## Merging files | ## Merging files | ||||||
| @ -76,6 +78,9 @@ yq eval '. * {"a":.b}' sample.yml | |||||||
| ``` | ``` | ||||||
| will output | will output | ||||||
| ```yaml | ```yaml | ||||||
|  | a: {things: great, also: "me"} | ||||||
|  | b: | ||||||
|  |   also: "me" | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ## Merge arrays | ## Merge arrays | ||||||
|  | |||||||
| @ -138,7 +138,7 @@ Given a sample.yml file of: | |||||||
| ``` | ``` | ||||||
| then | then | ||||||
| ```bash | ```bash | ||||||
| yq eval '[0]' sample.yml | yq eval '.[0]' sample.yml | ||||||
| ``` | ``` | ||||||
| will output | will output | ||||||
| ```yaml | ```yaml | ||||||
| @ -152,7 +152,7 @@ Given a sample.yml file of: | |||||||
| ``` | ``` | ||||||
| then | then | ||||||
| ```bash | ```bash | ||||||
| yq eval '[2]' sample.yml | yq eval '.[2]' sample.yml | ||||||
| ``` | ``` | ||||||
| will output | will output | ||||||
| ```yaml | ```yaml | ||||||
| @ -166,7 +166,7 @@ a: b | |||||||
| ``` | ``` | ||||||
| then | then | ||||||
| ```bash | ```bash | ||||||
| yq eval '[0]' sample.yml | yq eval '.[0]' sample.yml | ||||||
| ``` | ``` | ||||||
| will output | will output | ||||||
| ```yaml | ```yaml | ||||||
|  | |||||||
| @ -2,3 +2,5 @@ Add behaves differently according to the type of the LHS: | |||||||
| - arrays: concatenate | - arrays: concatenate | ||||||
| - number scalars: arithmetic addition (soon) | - number scalars: arithmetic addition (soon) | ||||||
| - string scalars: concatenate (soon) | - string scalars: concatenate (soon) | ||||||
|  | 
 | ||||||
|  | Use `+=` as append assign for things like increment. `.a += .x` is equivalent to running `.a |= . + .x`. | ||||||
|  | |||||||
| @ -2,6 +2,8 @@ Like the multiple operator in `jq`, depending on the operands, this multiply ope | |||||||
| 
 | 
 | ||||||
| Upcoming versions of `yq` will add support for other types of multiplication (numbers, strings). | Upcoming versions of `yq` will add support for other types of multiplication (numbers, strings). | ||||||
| 
 | 
 | ||||||
|  | To concatenate when merging objects, use the `*+` form (see examples below). This will recursively merge objects, appending arrays when it encounters them. | ||||||
|  | 
 | ||||||
| Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below. | Note that when merging objects, this operator returns the merged object (not the parent). This will be clearer in the examples below. | ||||||
| 
 | 
 | ||||||
| ## Merging files | ## Merging files | ||||||
|  | |||||||
| @ -31,6 +31,7 @@ var And = &OperationType{Type: "AND", NumArgs: 2, Precedence: 20, Handler: AndOp | |||||||
| var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: UnionOperator} | var Union = &OperationType{Type: "UNION", NumArgs: 2, Precedence: 10, Handler: UnionOperator} | ||||||
| 
 | 
 | ||||||
| var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignUpdateOperator} | var Assign = &OperationType{Type: "ASSIGN", NumArgs: 2, Precedence: 40, Handler: AssignUpdateOperator} | ||||||
|  | var AddAssign = &OperationType{Type: "ADD_ASSIGN", NumArgs: 2, Precedence: 40, Handler: AddAssignOperator} | ||||||
| 
 | 
 | ||||||
| var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator} | var AssignAttributes = &OperationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: AssignAttributesOperator} | ||||||
| var AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: AssignStyleOperator} | var AssignStyle = &OperationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: AssignStyleOperator} | ||||||
|  | |||||||
| @ -8,8 +8,18 @@ import ( | |||||||
| 	yaml "gopkg.in/yaml.v3" | 	yaml "gopkg.in/yaml.v3" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| type AddPreferences struct { | func createSelfAddOp(rhs *PathTreeNode) *PathTreeNode { | ||||||
| 	InPlace bool | 	return &PathTreeNode{Operation: &Operation{OperationType: Add}, | ||||||
|  | 		Lhs: &PathTreeNode{Operation: &Operation{OperationType: SelfReference}}, | ||||||
|  | 		Rhs: rhs} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func AddAssignOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { | ||||||
|  | 	assignmentOp := &Operation{OperationType: Assign} | ||||||
|  | 	assignmentOp.Preferences = &AssignOpPreferences{true} | ||||||
|  | 
 | ||||||
|  | 	assignmentOpNode := &PathTreeNode{Operation: assignmentOp, Lhs: pathNode.Lhs, Rhs: createSelfAddOp(pathNode.Rhs)} | ||||||
|  | 	return d.GetMatchingNodes(matchingNodes, assignmentOpNode) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func toNodes(candidates *list.List) []*yaml.Node { | func toNodes(candidates *list.List) []*yaml.Node { | ||||||
| @ -45,25 +55,16 @@ func AddOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathT | |||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	preferences := pathNode.Operation.Preferences |  | ||||||
| 	inPlace := preferences != nil && preferences.(*AddPreferences).InPlace |  | ||||||
| 
 |  | ||||||
| 	for el := lhs.Front(); el != nil; el = el.Next() { | 	for el := lhs.Front(); el != nil; el = el.Next() { | ||||||
| 		lhsCandidate := el.Value.(*CandidateNode) | 		lhsCandidate := el.Value.(*CandidateNode) | ||||||
| 		lhsNode := UnwrapDoc(lhsCandidate.Node) | 		lhsNode := UnwrapDoc(lhsCandidate.Node) | ||||||
| 
 | 
 | ||||||
| 		var target *CandidateNode | 		target := &CandidateNode{ | ||||||
| 
 |  | ||||||
| 		if inPlace { |  | ||||||
| 			target = lhsCandidate |  | ||||||
| 		} else { |  | ||||||
| 			target = &CandidateNode{ |  | ||||||
| 			Path:     lhsCandidate.Path, | 			Path:     lhsCandidate.Path, | ||||||
| 			Document: lhsCandidate.Document, | 			Document: lhsCandidate.Document, | ||||||
| 			Filename: lhsCandidate.Filename, | 			Filename: lhsCandidate.Filename, | ||||||
| 			Node:     &yaml.Node{}, | 			Node:     &yaml.Node{}, | ||||||
| 		} | 		} | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		switch lhsNode.Kind { | 		switch lhsNode.Kind { | ||||||
| 		case yaml.MappingNode: | 		case yaml.MappingNode: | ||||||
|  | |||||||
| @ -6,9 +6,12 @@ import ( | |||||||
| 
 | 
 | ||||||
| var addOperatorScenarios = []expressionScenario{ | var addOperatorScenarios = []expressionScenario{ | ||||||
| 	{ | 	{ | ||||||
| 		description: "+= test and doc", | 		description: "Concatenate and assign arrays", | ||||||
| 		expression: ".a.b+= .e.f" | 		document:    `{a: {val: thing, b: [cat,dog]}}`, | ||||||
| 		expected: []string{"add .e.g to be, return top level node"} | 		expression:  ".a.b += [\"cow\"]", | ||||||
|  | 		expected: []string{ | ||||||
|  | 			"D0, P[], (doc)::{a: {val: thing, b: [cat, dog, cow]}}\n", | ||||||
|  | 		}, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		description: "Concatenate arrays", | 		description: "Concatenate arrays", | ||||||
|  | |||||||
| @ -99,7 +99,7 @@ var assignOperatorScenarios = []expressionScenario{ | |||||||
| 		description:           "Update empty object and array", | 		description:           "Update empty object and array", | ||||||
| 		dontFormatInputForDoc: true, | 		dontFormatInputForDoc: true, | ||||||
| 		document:              `{}`, | 		document:              `{}`, | ||||||
| 		expression:            `.a.b[0] |= "bogs"`, | 		expression:            `.a.b.[0] |= "bogs"`, | ||||||
| 		expected: []string{ | 		expected: []string{ | ||||||
| 			"D0, P[], (doc)::{a: {b: [bogs]}}\n", | 			"D0, P[], (doc)::{a: {b: [bogs]}}\n", | ||||||
| 		}, | 		}, | ||||||
| @ -107,7 +107,7 @@ var assignOperatorScenarios = []expressionScenario{ | |||||||
| 	{ | 	{ | ||||||
| 		skipDoc:    true, | 		skipDoc:    true, | ||||||
| 		document:   `{}`, | 		document:   `{}`, | ||||||
| 		expression: `.a.b[1].c |= "bogs"`, | 		expression: `.a.b.[1].c |= "bogs"`, | ||||||
| 		expected: []string{ | 		expected: []string{ | ||||||
| 			"D0, P[], (doc)::{a: {b: [null, {c: bogs}]}}\n", | 			"D0, P[], (doc)::{a: {b: [null, {c: bogs}]}}\n", | ||||||
| 		}, | 		}, | ||||||
|  | |||||||
| @ -13,6 +13,14 @@ var collectOperatorScenarios = []expressionScenario{ | |||||||
| 			"D0, P[], (!!seq)::[]\n", | 			"D0, P[], (!!seq)::[]\n", | ||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
|  | 	{ | ||||||
|  | 		skipDoc:    true, | ||||||
|  | 		document:   ``, | ||||||
|  | 		expression: `[3]`, | ||||||
|  | 		expected: []string{ | ||||||
|  | 			"D0, P[], (!!seq)::- 3\n", | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		description: "Collect single", | 		description: "Collect single", | ||||||
| 		document:    ``, | 		document:    ``, | ||||||
|  | |||||||
| @ -128,9 +128,7 @@ func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *Candid | |||||||
| 		assignmentOp.OperationType = Assign | 		assignmentOp.OperationType = Assign | ||||||
| 		assignmentOp.Preferences = &AssignOpPreferences{false} | 		assignmentOp.Preferences = &AssignOpPreferences{false} | ||||||
| 	} else if shouldAppendArrays && rhs.Node.Kind == yaml.SequenceNode { | 	} else if shouldAppendArrays && rhs.Node.Kind == yaml.SequenceNode { | ||||||
| 		log.Debugf("append! lhs %v, rhs: %v", NodeToString(lhs), NodeToString(rhs)) | 		assignmentOp.OperationType = AddAssign | ||||||
| 		assignmentOp.OperationType = Add |  | ||||||
| 		assignmentOp.Preferences = &AddPreferences{InPlace: true} |  | ||||||
| 	} | 	} | ||||||
| 	rhsOp := &Operation{OperationType: ValueOp, CandidateNode: rhs} | 	rhsOp := &Operation{OperationType: ValueOp, CandidateNode: rhs} | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -5,9 +5,6 @@ import ( | |||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| var multiplyOperatorScenarios = []expressionScenario{ | var multiplyOperatorScenarios = []expressionScenario{ | ||||||
| 	{ |  | ||||||
| 		description: "*+ doc", |  | ||||||
| 	}, |  | ||||||
| 	{ | 	{ | ||||||
| 		skipDoc:    true, | 		skipDoc:    true, | ||||||
| 		document:   `{a: {also: [1]}, b: {also: me}}`, | 		document:   `{a: {also: [1]}, b: {also: me}}`, | ||||||
|  | |||||||
| @ -159,7 +159,7 @@ var traversePathOperatorScenarios = []expressionScenario{ | |||||||
| 	{ | 	{ | ||||||
| 		description: "Traversing arrays by index", | 		description: "Traversing arrays by index", | ||||||
| 		document:    `[1,2,3]`, | 		document:    `[1,2,3]`, | ||||||
| 		expression:  `[0]`, | 		expression:  `.[0]`, | ||||||
| 		expected: []string{ | 		expected: []string{ | ||||||
| 			"D0, P[0], (!!int)::1\n", | 			"D0, P[0], (!!int)::1\n", | ||||||
| 		}, | 		}, | ||||||
| @ -167,7 +167,7 @@ var traversePathOperatorScenarios = []expressionScenario{ | |||||||
| 	{ | 	{ | ||||||
| 		description: "Maps with numeric keys", | 		description: "Maps with numeric keys", | ||||||
| 		document:    `{2: cat}`, | 		document:    `{2: cat}`, | ||||||
| 		expression:  `[2]`, | 		expression:  `.[2]`, | ||||||
| 		expected: []string{ | 		expected: []string{ | ||||||
| 			"D0, P[2], (!!str)::cat\n", | 			"D0, P[2], (!!str)::cat\n", | ||||||
| 		}, | 		}, | ||||||
| @ -175,7 +175,7 @@ var traversePathOperatorScenarios = []expressionScenario{ | |||||||
| 	{ | 	{ | ||||||
| 		description: "Maps with non existing numeric keys", | 		description: "Maps with non existing numeric keys", | ||||||
| 		document:    `{a: b}`, | 		document:    `{a: b}`, | ||||||
| 		expression:  `[0]`, | 		expression:  `.[0]`, | ||||||
| 		expected: []string{ | 		expected: []string{ | ||||||
| 			"D0, P[0], (!!null)::null\n", | 			"D0, P[0], (!!null)::null\n", | ||||||
| 		}, | 		}, | ||||||
|  | |||||||
| @ -37,7 +37,7 @@ func testScenario(t *testing.T, s *expressionScenario) { | |||||||
| 	if s.document != "" { | 	if s.document != "" { | ||||||
| 		inputs, err = readDocuments(strings.NewReader(s.document), "sample.yml", 0) | 		inputs, err = readDocuments(strings.NewReader(s.document), "sample.yml", 0) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			t.Error(err) | 			t.Error(err, s.document) | ||||||
| 			return | 			return | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | |||||||
| @ -49,6 +49,11 @@ var pathTests = []struct { | |||||||
| 		append(make([]interface{}, 0), "[", "]"), | 		append(make([]interface{}, 0), "[", "]"), | ||||||
| 		append(make([]interface{}, 0), "EMPTY", "COLLECT", "PIPE"), | 		append(make([]interface{}, 0), "EMPTY", "COLLECT", "PIPE"), | ||||||
| 	}, | 	}, | ||||||
|  | 	{ | ||||||
|  | 		`[3]`, | ||||||
|  | 		append(make([]interface{}, 0), "[", "3 (int64)", "]"), | ||||||
|  | 		append(make([]interface{}, 0), "3 (int64)", "COLLECT", "PIPE"), | ||||||
|  | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		`d0.a`, | 		`d0.a`, | ||||||
| 		append(make([]interface{}, 0), "d0", "PIPE", "a"), | 		append(make([]interface{}, 0), "d0", "PIPE", "a"), | ||||||
|  | |||||||
| @ -223,7 +223,6 @@ func initLexer() (*lex.Lexer, error) { | |||||||
| 
 | 
 | ||||||
| 	lexer.Add([]byte(`\s*\|=\s*`), opTokenWithPrefs(Assign, nil, &AssignOpPreferences{true})) | 	lexer.Add([]byte(`\s*\|=\s*`), opTokenWithPrefs(Assign, nil, &AssignOpPreferences{true})) | ||||||
| 
 | 
 | ||||||
| 	lexer.Add([]byte(`\[-?[0-9]+\]`), arrayIndextoken(false)) |  | ||||||
| 	lexer.Add([]byte(`\.\[-?[0-9]+\]`), arrayIndextoken(true)) | 	lexer.Add([]byte(`\.\[-?[0-9]+\]`), arrayIndextoken(true)) | ||||||
| 
 | 
 | ||||||
| 	lexer.Add([]byte("( |\t|\n|\r)+"), skip) | 	lexer.Add([]byte("( |\t|\n|\r)+"), skip) | ||||||
| @ -254,7 +253,7 @@ func initLexer() (*lex.Lexer, error) { | |||||||
| 	lexer.Add([]byte(`\*`), opTokenWithPrefs(Multiply, nil, &MultiplyPreferences{AppendArrays: false})) | 	lexer.Add([]byte(`\*`), opTokenWithPrefs(Multiply, nil, &MultiplyPreferences{AppendArrays: false})) | ||||||
| 	lexer.Add([]byte(`\*\+`), opTokenWithPrefs(Multiply, nil, &MultiplyPreferences{AppendArrays: true})) | 	lexer.Add([]byte(`\*\+`), opTokenWithPrefs(Multiply, nil, &MultiplyPreferences{AppendArrays: true})) | ||||||
| 	lexer.Add([]byte(`\+`), opToken(Add)) | 	lexer.Add([]byte(`\+`), opToken(Add)) | ||||||
| 	lexer.Add([]byte(`\+=`), opTokenWithPrefs(Add, nil, &AddPreferences{InPlace: true})) | 	lexer.Add([]byte(`\+=`), opToken(AddAssign)) | ||||||
| 
 | 
 | ||||||
| 	err := lexer.Compile() | 	err := lexer.Compile() | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user