1
0
mirror of https://github.com/taigrr/yq synced 2025-01-18 04:53:17 -08:00

Compare commits

...

59 Commits

Author SHA1 Message Date
Mike Farah
2d7be26ad5 wip update docs 2020-01-13 16:58:11 +11:00
Mike Farah
350a8343e9 adv search with prefix! 2020-01-11 19:52:33 +11:00
Mike Farah
a3f8f9df10 more bits 2020-01-11 19:38:16 +11:00
Mike Farah
35fd5b7ae4 Extracted out is path expression checking logic 2020-01-11 19:30:27 +11:00
Mike Farah
2d237e7e8e it works! wip 2020-01-11 19:13:52 +11:00
Mike Farah
74c7a4e027 it works! wip 2020-01-11 18:52:15 +11:00
Mike Farah
96955ffa9c release notes 2020-01-11 09:55:24 +11:00
Mike Farah
9361b8b3e9 Beta 2020-01-11 09:14:32 +11:00
Mike Farah
24dcb56466 Inc version - fix help text 2020-01-11 09:13:42 +11:00
Mike Farah
728cbe991a Print path is more accurate than keys (i think) 2020-01-11 09:07:39 +11:00
Mike Farah
854f5f0fc9 wip json encoding 2020-01-10 22:01:59 +11:00
Mike Farah
feba7b04fa Added path stack to string test 2020-01-09 21:36:05 +11:00
Mike Farah
0621307391 Fixed linting errors 2020-01-09 21:27:52 +11:00
Mike Farah
924eb6c462 Added missing functions to interface 2020-01-09 21:18:24 +11:00
Mike Farah
52eef67e37 more tests, some refactoring 2020-01-09 08:17:56 +11:00
Mike Farah
38d35185bc Can overwrite and append with merge 2020-01-06 16:27:00 +13:00
Mike Farah
d8c29b26c1 Merge can allow empty merges! 2020-01-06 16:22:24 +13:00
Mike Farah
e3f4eedd51 Fixed merge new array 2020-01-06 10:12:38 +13:00
Mike Farah
690da9ee74 Fixed merge new array 2020-01-06 10:12:30 +13:00
Mike Farah
1f7f1b0def Merge arrays! 2020-01-05 17:28:24 +13:00
Mike Farah
1aa5ec1d40 Merge! wip 2020-01-05 17:14:14 +13:00
Mike Farah
a065a47b37 Fixed tests 2020-01-05 16:22:18 +13:00
Mike Farah
625cfdac75 wip; 2019-12-31 15:21:39 +13:00
Mike Farah
4dbdd4a805 Deep splat! 2019-12-30 16:51:07 +13:00
Mike Farah
8a6af1720d Fixed modify array issue! 2019-12-30 11:21:21 +13:00
Mike Farah
0652f67a91 Refactored! 2019-12-28 20:19:37 +13:00
Mike Farah
df52383ffb Delete works! needs refactor 2019-12-28 10:51:54 +13:00
Mike Farah
707ad09ba5 Refactor wip 2019-12-27 19:06:58 +11:00
Mike Farah
cf389bed4a Refactor wip 2019-12-27 19:06:08 +11:00
Mike Farah
ff5b23251b Refactor wip 2019-12-25 12:11:04 +11:00
Mike Farah
9925b26b9d Added Key and Value printing tests 2019-12-24 10:46:21 +11:00
Mike Farah
93dbe80a77 wip 2019-12-24 10:35:57 +11:00
Mike Farah
1e541cd65f wip handle aliases when printing keys 2019-12-23 09:25:44 +11:00
Mike Farah
5204a13685 Show paths 2019-12-23 09:08:00 +11:00
Mike Farah
3d3eaf3034 Return path, smart print 2019-12-22 17:16:03 +11:00
Mike Farah
4fb44dbc47 Return path, smart print 2019-12-22 17:13:11 +11:00
Mike Farah
784513dd18 Merge anchors - refactored 2019-12-22 15:35:16 +11:00
Mike Farah
865a55645c Merge anchors - refactored 2019-12-22 15:33:54 +11:00
Mike Farah
949bf1c1d7 Merge anchors - wip 2019-12-22 15:15:15 +11:00
Mike Farah
19fe718cfb Aliases! 2019-12-16 21:09:23 +11:00
Mike Farah
290579ac7f Handle simple aliases 2019-12-16 20:38:55 +11:00
Mike Farah
d7392f7b58 Refactoring 2019-12-16 16:46:20 +11:00
Mike Farah
a3cebec2fd Added prefix command 2019-12-16 16:17:01 +11:00
Mike Farah
b81fd638d7 wip - new node 2019-12-15 19:34:05 +11:00
Mike Farah
2344638da4 Fixed delete splat 2019-12-15 18:53:49 +11:00
Mike Farah
8be006fba4 Fixed delete splat 2019-12-15 18:52:37 +11:00
Mike Farah
53a4a47ce3 wip - prefix splat 2019-12-15 18:38:40 +11:00
Mike Farah
5988d0cffa Simplified 2019-12-15 18:24:23 +11:00
Mike Farah
b7640946ac Delete! 2019-12-15 17:31:26 +11:00
Mike Farah
d061b2f9f9 Can delete arrays 2019-12-12 20:47:22 +11:00
Mike Farah
8c0046a622 Refactoring 2019-12-09 13:57:38 +11:00
Mike Farah
586ffb833b Refactoring 2019-12-09 13:57:10 +11:00
Mike Farah
9771e7001c splatting 2019-12-09 13:44:53 +11:00
Mike Farah
8da9a81702 visitor! 2019-12-08 15:59:24 +11:00
Mike Farah
d97f1d8be2 recurse 2019-12-08 15:37:30 +11:00
Mike Farah
dad61ec615 remove json conversion for now 2019-12-06 16:52:00 +11:00
Mike Farah
676fc63219 remove json conversion for now 2019-12-06 16:41:21 +11:00
Mike Farah
972e2b9575 wip 2019-12-06 16:36:42 +11:00
Mike Farah
aad15ccc6e better v3 2019-12-06 15:57:46 +11:00
71 changed files with 3329 additions and 3329 deletions

1
.gitignore vendored
View File

@@ -23,6 +23,7 @@ _cgo_export.*
_testmain.go _testmain.go
coverage.out coverage.out
coverage.html
*.exe *.exe
*.test *.test
*.prof *.prof

View File

@@ -43,7 +43,7 @@ sudo apt install yq -y
``` ```
### or, [Download latest binary](https://github.com/mikefarah/yq/releases/latest) or alternatively: ### or, [Download latest binary](https://github.com/mikefarah/yq/releases/latest) or alternatively:
``` ```
GO111MODULE=on go get github.com/mikefarah/yq/v2 GO111MODULE=on go get github.com/mikefarah/yq/v3
``` ```
## Run with Docker ## Run with Docker

74
Upgrade Notes Normal file
View File

@@ -0,0 +1,74 @@
Major release! Upgraded underlying yaml parser, re-written majority of yq. This has brought on a number of features that have been in demand for a while (see below).
This is in beta and needs some community feedback and testing :)
# New Features
- Keeps yaml comments and formatting, can specify yaml tags when updating. https://github.com/mikefarah/yq/issues/19, https://github.com/mikefarah/yq/issues/169, https://github.com/mikefarah/yq/issues/107, https://github.com/mikefarah/yq/issues/171, https://github.com/mikefarah/yq/issues/245, https://github.com/mikefarah/yq/issues/303,https://github.com/mikefarah/yq/issues/308,https://github.com/mikefarah/yq/issues/314
- Handles anchors! https://github.com/mikefarah/yq/issues/310, https://github.com/mikefarah/yq/issues/178
- Can print out matching paths and values when splatting https://github.com/mikefarah/yq/issues/20
- JSON output works for all commands! Yaml files with multiple documents are printed out as one JSON document per line.
- Deep splat (**) to match arbitrary paths
# Breaking changes
## Update scripts file format has changed to be more powerful.
Comments can be added, and delete commands have been introduced.
Before:
```yaml
b.e[+].name: Mike Farah
```
After:
```yaml
- command: update
path: b.e[+].thing
value:
#great
things: frog # wow!
- command: delete
path: b.d
```
https://github.com/mikefarah/yq/issues/305
## Reading and splatting, matching results are printed once per line.
e.g:
```json
parent:
childA:
no: matches here
childB:
there: matches
hi: no match
there2: also matches
```
```bash
yq r sample.yaml 'parent.*.there*'
```
old
```yaml
- null
- - matches
- also matches
```
new
```yaml
matches
also matches
```
and you can print the matching paths:
yq r --printMode pv sample.yaml 'parent.*.there*'
```yaml
parent.childB.there: matches
parent.childB.there2: also matches
```

View File

@@ -7,7 +7,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/mikefarah/yq/v2/test" "github.com/mikefarah/yq/v3/test"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@@ -63,30 +63,6 @@ func TestRootCmd_VerboseShort(t *testing.T) {
} }
} }
func TestRootCmd_TrimLong(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "--trim")
if result.Error != nil {
t.Error(result.Error)
}
if !trimOutput {
t.Error("Expected trimOutput to be true")
}
}
func TestRootCmd_TrimShort(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "-t")
if result.Error != nil {
t.Error(result.Error)
}
if !trimOutput {
t.Error("Expected trimOutput to be true")
}
}
func TestRootCmd_VersionShort(t *testing.T) { func TestRootCmd_VersionShort(t *testing.T) {
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, "-V") result := test.RunCmd(cmd, "-V")
@@ -115,7 +91,161 @@ func TestReadCmd(t *testing.T) {
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
test.AssertResult(t, "2\n", result.Output) test.AssertResult(t, "2", result.Output)
}
func TestReadWithAdvancedFilterCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read -v examples/sample.yaml b.e(name==sam).value")
if result.Error != nil {
t.Error(result.Error)
}
test.AssertResult(t, "4", result.Output)
}
func TestReadWithAdvancedFilterMapCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read -v examples/sample.yaml b.e[name==fr*]")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `name: fred
value: 3
`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestReadWithKeyAndValueCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read -p pv examples/sample.yaml b.c")
if result.Error != nil {
t.Error(result.Error)
}
test.AssertResult(t, "b.c: 2\n", result.Output)
}
func TestReadArrayCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read -p pv examples/sample.yaml b.e.1.name")
if result.Error != nil {
t.Error(result.Error)
}
test.AssertResult(t, "b.e.1.name: sam\n", result.Output)
}
func TestReadDeepSplatCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read -p pv examples/sample.yaml b.**")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b.c: 2
b.d.[0]: 3
b.d.[1]: 4
b.d.[2]: 5
b.e.[0].name: fred
b.e.[0].value: 3
b.e.[1].name: sam
b.e.[1].value: 4
`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestReadDeepSplatWithSuffixCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read -p pv examples/sample.yaml b.**.name")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b.e.[0].name: fred
b.e.[1].name: sam
`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestReadWithKeyCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read -p p examples/sample.yaml b.c")
if result.Error != nil {
t.Error(result.Error)
}
test.AssertResult(t, "b.c", result.Output)
}
func TestReadAnchorsCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read examples/simple-anchor.yaml foobar.a")
if result.Error != nil {
t.Error(result.Error)
}
test.AssertResult(t, "1", result.Output)
}
func TestReadAnchorsWithKeyAndValueCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read -p pv examples/simple-anchor.yaml foobar.a")
if result.Error != nil {
t.Error(result.Error)
}
test.AssertResult(t, "foobar.a: 1\n", result.Output)
}
func TestReadMergeAnchorsOriginalCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobar.a")
if result.Error != nil {
t.Error(result.Error)
}
test.AssertResult(t, "original", result.Output)
}
func TestReadMergeAnchorsOverrideCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobar.thing")
if result.Error != nil {
t.Error(result.Error)
}
test.AssertResult(t, "ice", result.Output)
}
func TestReadMergeAnchorsPrefixMatchCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "r -p pv examples/merge-anchor.yaml foobar.th*")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `foobar.thirty: well beyond
foobar.thing: ice
foobar.thirsty: yep
`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestReadMergeAnchorsListOriginalCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobarList.a")
if result.Error != nil {
t.Error(result.Error)
}
test.AssertResult(t, "original", result.Output)
}
func TestReadMergeAnchorsListOverrideInListCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobarList.thing")
if result.Error != nil {
t.Error(result.Error)
}
test.AssertResult(t, "coconut", result.Output)
}
func TestReadMergeAnchorsListOverrideCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read examples/merge-anchor.yaml foobarList.c")
if result.Error != nil {
t.Error(result.Error)
}
test.AssertResult(t, "newbar", result.Output)
} }
func TestReadInvalidDocumentIndexCmd(t *testing.T) { func TestReadInvalidDocumentIndexCmd(t *testing.T) {
@@ -134,7 +264,7 @@ func TestReadBadDocumentIndexCmd(t *testing.T) {
if result.Error == nil { if result.Error == nil {
t.Error("Expected command to fail due to invalid path") t.Error("Expected command to fail due to invalid path")
} }
expectedOutput := `asked to process document index 1 but there are only 1 document(s)` expectedOutput := `Could not process document index 1 as there are only 1 document(s)`
test.AssertResult(t, expectedOutput, result.Error.Error()) test.AssertResult(t, expectedOutput, result.Error.Error())
} }
@@ -157,7 +287,16 @@ func TestReadMultiCmd(t *testing.T) {
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
test.AssertResult(t, "here\n", result.Output) test.AssertResult(t, "here", result.Output)
}
func TestReadMultiWithKeyAndValueCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read -p vp -d 1 examples/multiple_docs.yaml another.document")
if result.Error != nil {
t.Error(result.Error)
}
test.AssertResult(t, "another.document: here\n", result.Output)
} }
func TestReadMultiAllCmd(t *testing.T) { func TestReadMultiAllCmd(t *testing.T) {
@@ -167,9 +306,21 @@ func TestReadMultiAllCmd(t *testing.T) {
t.Error(result.Error) t.Error(result.Error)
} }
test.AssertResult(t, test.AssertResult(t,
`- first document `first document
- second document second document
- third document third document`, result.Output)
}
func TestReadMultiAllWithKeyAndValueCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read -p pv -d* examples/multiple_docs.yaml commonKey")
if result.Error != nil {
t.Error(result.Error)
}
test.AssertResult(t,
`commonKey: first document
commonKey: second document
commonKey: third document
`, result.Output) `, result.Output)
} }
@@ -179,7 +330,7 @@ func TestReadCmd_ArrayYaml(t *testing.T) {
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
test.AssertResult(t, "false\n", result.Output) test.AssertResult(t, "false", result.Output)
} }
func TestReadCmd_ArrayYaml_NoPath(t *testing.T) { func TestReadCmd_ArrayYaml_NoPath(t *testing.T) {
@@ -191,11 +342,13 @@ func TestReadCmd_ArrayYaml_NoPath(t *testing.T) {
expectedOutput := `- become: true expectedOutput := `- become: true
gather_facts: false gather_facts: false
hosts: lalaland hosts: lalaland
name: Apply smth name: "Apply smth"
roles: roles:
- lala - lala
- land - land
serial: 1 serial: 1
- become: false
gather_facts: true
` `
test.AssertResult(t, expectedOutput, result.Output) test.AssertResult(t, expectedOutput, result.Output)
} }
@@ -209,7 +362,7 @@ func TestReadCmd_ArrayYaml_OneElement(t *testing.T) {
expectedOutput := `become: true expectedOutput := `become: true
gather_facts: false gather_facts: false
hosts: lalaland hosts: lalaland
name: Apply smth name: "Apply smth"
roles: roles:
- lala - lala
- land - land
@@ -218,31 +371,67 @@ serial: 1
test.AssertResult(t, expectedOutput, result.Output) test.AssertResult(t, expectedOutput, result.Output)
} }
func TestReadCmd_ArrayYaml_Splat(t *testing.T) { func TestReadCmd_ArrayYaml_SplatCmd(t *testing.T) {
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, "read examples/array.yaml [*]") result := test.RunCmd(cmd, "read examples/array.yaml [*]")
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
expectedOutput := `- become: true expectedOutput := `become: true
gather_facts: false
hosts: lalaland
name: "Apply smth"
roles:
- lala
- land
serial: 1
become: false
gather_facts: true
`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestReadCmd_ArrayYaml_SplatWithKeyAndValueCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read -p pv examples/array.yaml [*]")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `'[0]':
become: true
gather_facts: false gather_facts: false
hosts: lalaland hosts: lalaland
name: Apply smth name: "Apply smth"
roles: roles:
- lala - lala
- land - land
serial: 1 serial: 1
'[1]':
become: false
gather_facts: true
` `
test.AssertResult(t, expectedOutput, result.Output) test.AssertResult(t, expectedOutput, result.Output)
} }
func TestReadCmd_ArrayYaml_SplatWithKeyCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read -p p examples/array.yaml [*]")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `[0]
[1]`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestReadCmd_ArrayYaml_SplatKey(t *testing.T) { func TestReadCmd_ArrayYaml_SplatKey(t *testing.T) {
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, "read examples/array.yaml [*].gather_facts") result := test.RunCmd(cmd, "read examples/array.yaml [*].gather_facts")
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
expectedOutput := "- false\n" expectedOutput := `false
true`
test.AssertResult(t, expectedOutput, result.Output) test.AssertResult(t, expectedOutput, result.Output)
} }
@@ -250,9 +439,9 @@ func TestReadCmd_ArrayYaml_ErrorBadPath(t *testing.T) {
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, "read examples/array.yaml [x].gather_facts") result := test.RunCmd(cmd, "read examples/array.yaml [x].gather_facts")
if result.Error == nil { if result.Error == nil {
t.Error("Expected command to fail due to invalid path") t.Error("Expected command to fail due to missing arg")
} }
expectedOutput := `Error reading path in document index 0: error accessing array: strconv.ParseInt: parsing "x": invalid syntax` expectedOutput := `Error reading path in document index 0: Error parsing array index 'x' for '': strconv.ParseInt: parsing "x": invalid syntax`
test.AssertResult(t, expectedOutput, result.Error.Error()) test.AssertResult(t, expectedOutput, result.Error.Error())
} }
@@ -260,9 +449,9 @@ func TestReadCmd_ArrayYaml_Splat_ErrorBadPath(t *testing.T) {
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, "read examples/array.yaml [*].roles[x]") result := test.RunCmd(cmd, "read examples/array.yaml [*].roles[x]")
if result.Error == nil { if result.Error == nil {
t.Error("Expected command to fail due to invalid path") t.Error("Expected command to fail due to missing arg")
} }
expectedOutput := `Error reading path in document index 0: error accessing array: strconv.ParseInt: parsing "x": invalid syntax` expectedOutput := `Error reading path in document index 0: Error parsing array index 'x' for '[0].roles': strconv.ParseInt: parsing "x": invalid syntax`
test.AssertResult(t, expectedOutput, result.Error.Error()) test.AssertResult(t, expectedOutput, result.Error.Error())
} }
@@ -316,47 +505,117 @@ func TestReadCmd_ErrorBadPath(t *testing.T) {
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("read %s b.d.*.[x]", filename)) result := test.RunCmd(cmd, fmt.Sprintf("read %s b.d.*.[x]", filename))
if result.Error == nil { expectedOutput := `Error reading path in document index 0: Error parsing array index 'x' for 'b.d.e': strconv.ParseInt: parsing "x": invalid syntax`
t.Fatal("Expected command to fail due to invalid path")
}
expectedOutput := `Error reading path in document index 0: error accessing array: strconv.ParseInt: parsing "x": invalid syntax`
test.AssertResult(t, expectedOutput, result.Error.Error()) test.AssertResult(t, expectedOutput, result.Error.Error())
} }
func TestReadCmd_Verbose(t *testing.T) { func TestReadCmd_Verbose(t *testing.T) {
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, "-v read examples/sample.yaml b.c") result := test.RunCmd(cmd, "read -v examples/sample.yaml b.c")
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
test.AssertResult(t, "2\n", result.Output) test.AssertResult(t, "2", result.Output)
} }
func TestReadCmd_NoTrim(t *testing.T) { // func TestReadCmd_ToJson(t *testing.T) {
// cmd := getRootCommand()
// result := test.RunCmd(cmd, "read -j examples/sample.yaml b.c")
// if result.Error != nil {
// t.Error(result.Error)
// }
// test.AssertResult(t, "2\n", result.Output)
// }
// func TestReadCmd_ToJsonLong(t *testing.T) {
// cmd := getRootCommand()
// result := test.RunCmd(cmd, "read --tojson examples/sample.yaml b.c")
// if result.Error != nil {
// t.Error(result.Error)
// }
// test.AssertResult(t, "2\n", result.Output)
// }
func TestReadSplatPrefixCmd(t *testing.T) {
content := `a: 2
b:
hi:
c: things
d: something else
there:
c: more things
d: more something else
there2:
c: more things also
d: more something else also
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, "--trim=false read examples/sample.yaml b.c") result := test.RunCmd(cmd, fmt.Sprintf("read %s b.there*.c", filename))
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
test.AssertResult(t, "2\n\n", result.Output)
expectedOutput := `more things
more things also`
test.AssertResult(t, expectedOutput, result.Output)
} }
func TestReadCmd_ToJson(t *testing.T) { func TestReadSplatPrefixWithKeyAndValueCmd(t *testing.T) {
content := `a: 2
b:
hi:
c: things
d: something else
there:
c: more things
d: more something else
there2:
c: more things also
d: more something else also
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, "read -j examples/sample.yaml b.c") result := test.RunCmd(cmd, fmt.Sprintf("read -p pv %s b.there*.c", filename))
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
test.AssertResult(t, "2\n", result.Output)
expectedOutput := `b.there.c: more things
b.there2.c: more things also
`
test.AssertResult(t, expectedOutput, result.Output)
} }
func TestReadCmd_ToJsonLong(t *testing.T) { func TestReadSplatPrefixWithKeyCmd(t *testing.T) {
content := `a: 2
b:
hi:
c: things
d: something else
there:
c: more things
d: more something else
there2:
c: more things also
d: more something else also
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, "read --tojson examples/sample.yaml b.c") result := test.RunCmd(cmd, fmt.Sprintf("read -p p %s b.there*.c", filename))
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
test.AssertResult(t, "2\n", result.Output)
expectedOutput := `b.there.c
b.there2.c`
test.AssertResult(t, expectedOutput, result.Output)
} }
func TestPrefixCmd(t *testing.T) { func TestPrefixCmd(t *testing.T) {
@@ -527,7 +786,7 @@ func TestPrefixCmd_Verbose(t *testing.T) {
defer test.RemoveTempYamlFile(filename) defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("-v prefix %s x", filename)) result := test.RunCmd(cmd, fmt.Sprintf("prefix %s x", filename))
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
@@ -569,6 +828,18 @@ func TestNewCmd(t *testing.T) {
test.AssertResult(t, expectedOutput, result.Output) test.AssertResult(t, expectedOutput, result.Output)
} }
func TestNewArrayCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "new b[0] 3")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b:
- 3
`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestNewCmd_Error(t *testing.T) { func TestNewCmd_Error(t *testing.T) {
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, "new b.c") result := test.RunCmd(cmd, "new b.c")
@@ -579,18 +850,6 @@ func TestNewCmd_Error(t *testing.T) {
test.AssertResult(t, expectedOutput, result.Error.Error()) test.AssertResult(t, expectedOutput, result.Error.Error())
} }
func TestNewCmd_Verbose(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "-v new b.c 3")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b:
c: 3
`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestWriteCmd(t *testing.T) { func TestWriteCmd(t *testing.T) {
content := `b: content := `b:
c: 3 c: 3
@@ -609,6 +868,52 @@ func TestWriteCmd(t *testing.T) {
test.AssertResult(t, expectedOutput, result.Output) test.AssertResult(t, expectedOutput, result.Output)
} }
func TestWriteCmdScript(t *testing.T) {
content := `b:
c: 3
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
updateScript := `- command: update
path: b.c
value: 7`
scriptFilename := test.WriteTempYamlFile(updateScript)
defer test.RemoveTempYamlFile(scriptFilename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("write --script %s %s", scriptFilename, filename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b:
c: 7
`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestWriteCmdEmptyScript(t *testing.T) {
content := `b:
c: 3
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
updateScript := ``
scriptFilename := test.WriteTempYamlFile(updateScript)
defer test.RemoveTempYamlFile(scriptFilename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("write --script %s %s", scriptFilename, filename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b:
c: 3
`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestWriteMultiCmd(t *testing.T) { func TestWriteMultiCmd(t *testing.T) {
content := `b: content := `b:
c: 3 c: 3
@@ -724,24 +1029,6 @@ func TestWriteCmd_ErrorUnreadableFile(t *testing.T) {
test.AssertResult(t, expectedOutput, result.Error.Error()) test.AssertResult(t, expectedOutput, result.Error.Error())
} }
func TestWriteCmd_Verbose(t *testing.T) {
content := `b:
c: 3
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("-v write %s b.c 7", filename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b:
c: 7
`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestWriteCmd_Inplace(t *testing.T) { func TestWriteCmd_Inplace(t *testing.T) {
content := `b: content := `b:
c: 3 c: 3
@@ -786,7 +1073,7 @@ func TestWriteCmd_AppendEmptyArray(t *testing.T) {
defer test.RemoveTempYamlFile(filename) defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("write -v %s b[+] v", filename)) result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] v", filename))
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
@@ -806,7 +1093,7 @@ func TestWriteCmd_SplatArray(t *testing.T) {
defer test.RemoveTempYamlFile(filename) defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("write -v %s b[*].c new", filename)) result := test.RunCmd(cmd, fmt.Sprintf("write %s b[*].c new", filename))
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
@@ -826,7 +1113,7 @@ func TestWriteCmd_SplatMap(t *testing.T) {
defer test.RemoveTempYamlFile(filename) defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("write -v %s b.* new", filename)) result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* new", filename))
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
@@ -846,7 +1133,7 @@ func TestWriteCmd_SplatMapEmpty(t *testing.T) {
defer test.RemoveTempYamlFile(filename) defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("write -v %s b.c.* new", filename)) result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c.* new", filename))
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
@@ -857,7 +1144,7 @@ func TestWriteCmd_SplatMapEmpty(t *testing.T) {
test.AssertResult(t, expectedOutput, result.Output) test.AssertResult(t, expectedOutput, result.Output)
} }
func TestDeleteYaml(t *testing.T) { func TestDeleteYamlCmd(t *testing.T) {
content := `a: 2 content := `a: 2
b: b:
c: things c: things
@@ -880,35 +1167,28 @@ b:
} }
func TestDeleteSplatYaml(t *testing.T) { func TestDeleteSplatYaml(t *testing.T) {
content := `a: 2 content := `a: other
b: b: [3, 4]
hi: c:
c: things toast: leave
d: something else test: 1
hello: tell: 1
c: things2 taco: cool
d: something else2
there:
c: more things
d: more something else
` `
filename := test.WriteTempYamlFile(content) filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename) defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("delete -v %s b.*.c", filename)) result := test.RunCmd(cmd, fmt.Sprintf("delete %s c.te*", filename))
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
expectedOutput := `a: 2 expectedOutput := `a: other
b: b: [3, 4]
hi: c:
d: something else toast: leave
hello: taco: cool
d: something else2
there:
d: more something else
` `
test.AssertResult(t, expectedOutput, result.Output) test.AssertResult(t, expectedOutput, result.Output)
} }
@@ -926,7 +1206,7 @@ b:
defer test.RemoveTempYamlFile(filename) defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("delete -v %s b.hi[*].thing", filename)) result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.hi[*].thing", filename))
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
@@ -957,7 +1237,7 @@ b:
defer test.RemoveTempYamlFile(filename) defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("delete -v %s b.there*.c", filename)) result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.there*.c", filename))
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
@@ -1048,10 +1328,25 @@ func TestMergeCmd(t *testing.T) {
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
expectedOutput := `a: simple expectedOutput := `a: simple # just the best
b: b: [1, 2]
- 1 c:
- 2 test: 1
toast: leave
tell: 1
taco: cool
`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestMergeNoAutoCreateCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "merge -c=false examples/data1.yaml examples/data2.yaml")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `a: simple # just the best
b: [1, 2]
c: c:
test: 1 test: 1
` `
@@ -1060,14 +1355,12 @@ c:
func TestMergeOverwriteCmd(t *testing.T) { func TestMergeOverwriteCmd(t *testing.T) {
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, "merge --overwrite examples/data1.yaml examples/data2.yaml") result := test.RunCmd(cmd, "merge -c=false --overwrite examples/data1.yaml examples/data2.yaml")
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
expectedOutput := `a: other expectedOutput := `a: other # better than the original
b: b: [3, 4]
- 3
- 4
c: c:
test: 1 test: 1
` `
@@ -1076,56 +1369,64 @@ c:
func TestMergeAppendCmd(t *testing.T) { func TestMergeAppendCmd(t *testing.T) {
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, "merge --append examples/data1.yaml examples/data2.yaml") result := test.RunCmd(cmd, "merge --autocreate=false --append examples/data1.yaml examples/data2.yaml")
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
expectedOutput := `a: simple expectedOutput := `a: simple # just the best
b: b: [1, 2, 3, 4]
- 1
- 2
- 3
- 4
c: c:
test: 1 test: 1
` `
test.AssertResult(t, expectedOutput, result.Output) test.AssertResult(t, expectedOutput, result.Output)
} }
func TestMergeOverwriteAndAppendCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "merge --autocreate=false --append --overwrite examples/data1.yaml examples/data2.yaml")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `a: other # better than the original
b: [1, 2, 3, 4]
c:
test: 1
`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestMergeArraysCmd(t *testing.T) { func TestMergeArraysCmd(t *testing.T) {
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, "merge --append examples/sample_array.yaml examples/sample_array_2.yaml") result := test.RunCmd(cmd, "merge --append examples/sample_array.yaml examples/sample_array_2.yaml")
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
expectedOutput := `- 1 expectedOutput := `[1, 2, 3, 4, 5]
- 2
- 3
- 4
- 5
` `
test.AssertResult(t, expectedOutput, result.Output) test.AssertResult(t, expectedOutput, result.Output)
} }
func TestMergeCmd_Multi(t *testing.T) { func TestMergeCmd_Multi(t *testing.T) {
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, "merge -d1 examples/multiple_docs_small.yaml examples/data2.yaml") result := test.RunCmd(cmd, "merge -d1 examples/multiple_docs_small.yaml examples/data1.yaml")
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
expectedOutput := `a: Easy! as one two three expectedOutput := `a: Easy! as one two three
--- ---
a: other
another: another:
document: here document: here
a: simple # just the best
b: b:
- 3 - 1
- 4 - 2
c: c:
test: 1 test: 1
--- ---
- 1 - 1
- 2` - 2
test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n ")) `
test.AssertResult(t, expectedOutput, result.Output)
} }
func TestMergeYamlMultiAllCmd(t *testing.T) { func TestMergeYamlMultiAllCmd(t *testing.T) {
@@ -1147,14 +1448,15 @@ something: good`
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
expectedOutput := `apples: green expectedOutput := `b:
b:
c: 3 c: 3
apples: green
something: good something: good
--- ---
something: else
apples: red apples: red
something: else` `
test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n ")) test.AssertResult(t, expectedOutput, result.Output)
} }
func TestMergeYamlMultiAllOverwriteCmd(t *testing.T) { func TestMergeYamlMultiAllOverwriteCmd(t *testing.T) {
@@ -1176,14 +1478,15 @@ something: good`
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
expectedOutput := `apples: red expectedOutput := `b:
b:
c: 3 c: 3
apples: red
something: good something: good
--- ---
something: good
apples: red apples: red
something: good` `
test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n ")) test.AssertResult(t, expectedOutput, result.Output)
} }
func TestMergeCmd_Error(t *testing.T) { func TestMergeCmd_Error(t *testing.T) {
@@ -1204,29 +1507,13 @@ func TestMergeCmd_ErrorUnreadableFile(t *testing.T) {
} }
var expectedOutput string var expectedOutput string
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
expectedOutput = `Error updating document at index 0: open fake-unknown: The system cannot find the file specified.` expectedOutput = `open fake-unknown: The system cannot find the file specified.`
} else { } else {
expectedOutput = `Error updating document at index 0: open fake-unknown: no such file or directory` expectedOutput = `open fake-unknown: no such file or directory`
} }
test.AssertResult(t, expectedOutput, result.Error.Error()) test.AssertResult(t, expectedOutput, result.Error.Error())
} }
func TestMergeCmd_Verbose(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "-v merge examples/data1.yaml examples/data2.yaml")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `a: simple
b:
- 1
- 2
c:
test: 1
`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestMergeCmd_Inplace(t *testing.T) { func TestMergeCmd_Inplace(t *testing.T) {
filename := test.WriteTempYamlFile(test.ReadTempYamlFile("examples/data1.yaml")) filename := test.WriteTempYamlFile(test.ReadTempYamlFile("examples/data1.yaml"))
err := os.Chmod(filename, os.FileMode(int(0666))) err := os.Chmod(filename, os.FileMode(int(0666)))
@@ -1242,13 +1529,15 @@ func TestMergeCmd_Inplace(t *testing.T) {
} }
info, _ := os.Stat(filename) info, _ := os.Stat(filename)
gotOutput := test.ReadTempYamlFile(filename) gotOutput := test.ReadTempYamlFile(filename)
expectedOutput := `a: simple expectedOutput := `a: simple # just the best
b: b: [1, 2]
- 1
- 2
c: c:
test: 1` test: 1
test.AssertResult(t, expectedOutput, strings.Trim(gotOutput, "\n ")) toast: leave
tell: 1
taco: cool
`
test.AssertResult(t, expectedOutput, gotOutput)
test.AssertResult(t, os.FileMode(int(0666)), info.Mode()) test.AssertResult(t, os.FileMode(int(0666)), info.Mode())
} }
@@ -1258,10 +1547,17 @@ func TestMergeAllowEmptyCmd(t *testing.T) {
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
expectedOutput := `a: simple expectedOutput := `a: simple # just the best
b: b: [1, 2]
- 1 c:
- 2 test: 1
` `
test.AssertResult(t, expectedOutput, result.Output) test.AssertResult(t, expectedOutput, result.Output)
} }
func TestMergeDontAllowEmptyCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "merge examples/data1.yaml examples/empty.yaml")
expectedOutput := `Could not process document index 0 as there are only 0 document(s)`
test.AssertResult(t, expectedOutput, result.Error.Error())
}

13
compare.sh Executable file
View File

@@ -0,0 +1,13 @@
GREEN='\033[0;32m'
NC='\033[0m'
echo "${GREEN}---Old---${NC}"
yq $@ > /tmp/yq-old-output
cat /tmp/yq-old-output
echo "${GREEN}---New---${NC}"
./yq $@ > /tmp/yq-new-output
cat /tmp/yq-new-output
echo "${GREEN}---Diff---${NC}"
colordiff /tmp/yq-old-output /tmp/yq-new-output

View File

@@ -248,6 +248,18 @@
<li class="md-nav__item">
<a href="/path_expressions/" title="Path Expressions" class="md-nav__link">
Path Expressions
</a>
</li>
<li class="md-nav__item"> <li class="md-nav__item">
<a href="/write/" title="Write/Update" class="md-nav__link"> <a href="/write/" title="Write/Update" class="md-nav__link">
Write/Update Write/Update

View File

@@ -252,6 +252,18 @@
<li class="md-nav__item">
<a href="../path_expressions/" title="Path Expressions" class="md-nav__link">
Path Expressions
</a>
</li>
<li class="md-nav__item"> <li class="md-nav__item">
<a href="../write/" title="Write/Update" class="md-nav__link"> <a href="../write/" title="Write/Update" class="md-nav__link">
Write/Update Write/Update
@@ -414,20 +426,37 @@
<h1>Convert</h1> <h1>Convert</h1>
<h3 id="yaml-to-json">Yaml to Json<a class="headerlink" href="#yaml-to-json" title="Permanent link">&para;</a></h3> <h3 id="yaml-to-json">Yaml to Json<a class="headerlink" href="#yaml-to-json" title="Permanent link">&para;</a></h3>
<p>To convert output to json, use the --tojson (or -j) flag. This can only be used with the read command.</p> <p>To convert output to json, use the --tojson (or -j) flag. This is supported by all commands.</p>
<p>Each matching yaml node will be converted to json and printed out on a separate line.</p>
<p>Given a sample.yaml file of:</p> <p>Given a sample.yaml file of:</p>
<pre><code class="yaml">b: <pre><code class="yaml">b:
c: 2 c: 2
</code></pre> </code></pre>
<p>then</p> <p>then</p>
<pre><code class="bash">yq r -j sample.yaml b.c <pre><code class="bash">yq r -j sample.yaml
</code></pre> </code></pre>
<p>will output</p> <p>will output</p>
<pre><code class="json">{&quot;b&quot;:{&quot;c&quot;:2}} <pre><code class="json">{&quot;b&quot;:{&quot;c&quot;:2}}
</code></pre> </code></pre>
<p>Given a sample.yaml file of:</p>
<pre><code class="yaml">bob:
c: 2
bab:
c: 5
</code></pre>
<p>then</p>
<pre><code class="bash">yq r -j sample.yaml b*
</code></pre>
<p>will output</p>
<pre><code class="json">{&quot;c&quot;:2}
{&quot;c&quot;:5}
</code></pre>
<h3 id="json-to-yaml">Json to Yaml<a class="headerlink" href="#json-to-yaml" title="Permanent link">&para;</a></h3> <h3 id="json-to-yaml">Json to Yaml<a class="headerlink" href="#json-to-yaml" title="Permanent link">&para;</a></h3>
<p>To read in json, just pass in a json file instead of yaml, it will just work :)</p> <p>To read in json, just pass in a json file instead of yaml, it will just work :)</p>
<p>e.g given a json file</p> <p>e.g given a json file</p>

View File

@@ -252,6 +252,18 @@
<li class="md-nav__item">
<a href="../path_expressions/" title="Path Expressions" class="md-nav__link">
Path Expressions
</a>
</li>
<li class="md-nav__item"> <li class="md-nav__item">
<a href="../write/" title="Write/Update" class="md-nav__link"> <a href="../write/" title="Write/Update" class="md-nav__link">
Write/Update Write/Update
@@ -325,20 +337,6 @@
</li> </li>
<li class="md-nav__item">
<a href="#keys-with-dots" title="Keys with dots" class="md-nav__link">
Keys with dots
</a>
</li>
<li class="md-nav__item">
<a href="#keys-and-values-with-leading-dashes" title="Keys (and values) with leading dashes" class="md-nav__link">
Keys (and values) with leading dashes
</a>
</li>
@@ -406,20 +404,6 @@
</li> </li>
<li class="md-nav__item">
<a href="#keys-with-dots" title="Keys with dots" class="md-nav__link">
Keys with dots
</a>
</li>
<li class="md-nav__item">
<a href="#keys-and-values-with-leading-dashes" title="Keys (and values) with leading dashes" class="md-nav__link">
Keys (and values) with leading dashes
</a>
</li>
@@ -441,10 +425,11 @@
<h1>Create</h1> <h1>Create</h1>
<p>Yaml files can be created using the 'new' command. This works in the same way as the write command, but you don't pass in an existing Yaml file. Currently this does not support creating multiple documents in a single yaml file.</p> <pre><code>yq n &lt;path_expression&gt; &lt;new value&gt;
<pre><code>yq n &lt;path&gt; &lt;new value&gt;
</code></pre> </code></pre>
<p>Yaml files can be created using the 'new' command. This works in the same way as the write command, but you don't pass in an existing Yaml file. Currently this does not support creating multiple documents in a single yaml file.</p>
<p>See docs for <a href="../path_expressions/">path expression</a></p>
<h3 id="creating-a-simple-yaml-file">Creating a simple yaml file<a class="headerlink" href="#creating-a-simple-yaml-file" title="Permanent link">&para;</a></h3> <h3 id="creating-a-simple-yaml-file">Creating a simple yaml file<a class="headerlink" href="#creating-a-simple-yaml-file" title="Permanent link">&para;</a></h3>
<pre><code class="bash">yq n b.c cat <pre><code class="bash">yq n b.c cat
</code></pre> </code></pre>
@@ -457,8 +442,11 @@
<h3 id="creating-using-a-create-script">Creating using a create script<a class="headerlink" href="#creating-using-a-create-script" title="Permanent link">&para;</a></h3> <h3 id="creating-using-a-create-script">Creating using a create script<a class="headerlink" href="#creating-using-a-create-script" title="Permanent link">&para;</a></h3>
<p>Create scripts follow the same format as the update scripts.</p> <p>Create scripts follow the same format as the update scripts.</p>
<p>Given a script create_instructions.yaml of:</p> <p>Given a script create_instructions.yaml of:</p>
<pre><code class="yaml">b.c: 3 <pre><code class="yaml">- command: update
b.e[+].name: Howdy Partner path: b.c
value:
#great
things: frog # wow!
</code></pre> </code></pre>
<p>then</p> <p>then</p>
@@ -467,38 +455,14 @@ b.e[+].name: Howdy Partner
<p>will output:</p> <p>will output:</p>
<pre><code class="yaml">b: <pre><code class="yaml">b:
c: 3 c:
e: #great
- name: Howdy Partner things: frog # wow!
</code></pre> </code></pre>
<p>You can also pipe the instructions in:</p> <p>You can also pipe the instructions in:</p>
<pre><code class="bash">cat create_instructions.yaml | yq n -s - <pre><code class="bash">cat create_instructions.yaml | yq n -s -
</code></pre> </code></pre>
<h3 id="keys-with-dots">Keys with dots<a class="headerlink" href="#keys-with-dots" title="Permanent link">&para;</a></h3>
<p>When specifying a key that has a dot use key lookup indicator.</p>
<pre><code class="yaml">b:
foo.bar: 7
</code></pre>
<pre><code class="bash">yaml r sample.yaml 'b[foo.bar]'
</code></pre>
<pre><code class="bash">yaml w sample.yaml 'b[foo.bar]' 9
</code></pre>
<p>Any valid yaml key can be specified as part of a key lookup.</p>
<p>Note that the path is in quotes to avoid the square brackets being interpreted by your shell.</p>
<h3 id="keys-and-values-with-leading-dashes">Keys (and values) with leading dashes<a class="headerlink" href="#keys-and-values-with-leading-dashes" title="Permanent link">&para;</a></h3>
<p>If a key or value has leading dashes, yq won't know that you are passing a value as opposed to a flag (and you will get a 'bad flag syntax' error).</p>
<p>To fix that, you will need to tell it to stop processing flags by adding '--' after the last flag like so:</p>
<pre><code class="bash">yq n -t -- --key --value
</code></pre>
<p>Will result in</p>
<p><code>`
--key: --value</code></p>

View File

@@ -94,7 +94,7 @@
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off"> <input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" data-md-component="overlay" for="__drawer"></label> <label class="md-overlay" data-md-component="overlay" for="__drawer"></label>
<a href="#to-stdout" tabindex="1" class="md-skip"> <a href="#from-stdin" tabindex="1" class="md-skip">
Skip to content Skip to content
</a> </a>
@@ -252,6 +252,18 @@
<li class="md-nav__item">
<a href="../path_expressions/" title="Path Expressions" class="md-nav__link">
Path Expressions
</a>
</li>
<li class="md-nav__item"> <li class="md-nav__item">
<a href="../write/" title="Write/Update" class="md-nav__link"> <a href="../write/" title="Write/Update" class="md-nav__link">
Write/Update Write/Update
@@ -299,25 +311,11 @@
<label class="md-nav__title" for="__toc">Table of contents</label> <label class="md-nav__title" for="__toc">Table of contents</label>
<ul class="md-nav__list" data-md-scrollfix> <ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="#to-stdout" title="To Stdout" class="md-nav__link">
To Stdout
</a>
</li>
<li class="md-nav__item"> <li class="md-nav__item">
<a href="#from-stdin" title="From STDIN" class="md-nav__link"> <a href="#from-stdin" title="From STDIN" class="md-nav__link">
From STDIN From STDIN
</a> </a>
</li>
<li class="md-nav__item">
<a href="#deleting-array-elements" title="Deleting array elements" class="md-nav__link">
Deleting array elements
</a>
</li> </li>
<li class="md-nav__item"> <li class="md-nav__item">
@@ -325,27 +323,6 @@
Deleting nodes in-place Deleting nodes in-place
</a> </a>
</li>
<li class="md-nav__item">
<a href="#splat" title="Splat" class="md-nav__link">
Splat
</a>
</li>
<li class="md-nav__item">
<a href="#prefix-splat" title="Prefix Splat" class="md-nav__link">
Prefix Splat
</a>
</li>
<li class="md-nav__item">
<a href="#array-splat" title="Array Splat" class="md-nav__link">
Array Splat
</a>
</li> </li>
<li class="md-nav__item"> <li class="md-nav__item">
@@ -362,20 +339,6 @@
</li> </li>
<li class="md-nav__item">
<a href="#keys-with-dots" title="Keys with dots" class="md-nav__link">
Keys with dots
</a>
</li>
<li class="md-nav__item">
<a href="#keys-and-values-with-leading-dashes" title="Keys (and values) with leading dashes" class="md-nav__link">
Keys (and values) with leading dashes
</a>
</li>
@@ -441,25 +404,11 @@
<label class="md-nav__title" for="__toc">Table of contents</label> <label class="md-nav__title" for="__toc">Table of contents</label>
<ul class="md-nav__list" data-md-scrollfix> <ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="#to-stdout" title="To Stdout" class="md-nav__link">
To Stdout
</a>
</li>
<li class="md-nav__item"> <li class="md-nav__item">
<a href="#from-stdin" title="From STDIN" class="md-nav__link"> <a href="#from-stdin" title="From STDIN" class="md-nav__link">
From STDIN From STDIN
</a> </a>
</li>
<li class="md-nav__item">
<a href="#deleting-array-elements" title="Deleting array elements" class="md-nav__link">
Deleting array elements
</a>
</li> </li>
<li class="md-nav__item"> <li class="md-nav__item">
@@ -467,27 +416,6 @@
Deleting nodes in-place Deleting nodes in-place
</a> </a>
</li>
<li class="md-nav__item">
<a href="#splat" title="Splat" class="md-nav__link">
Splat
</a>
</li>
<li class="md-nav__item">
<a href="#prefix-splat" title="Prefix Splat" class="md-nav__link">
Prefix Splat
</a>
</li>
<li class="md-nav__item">
<a href="#array-splat" title="Array Splat" class="md-nav__link">
Array Splat
</a>
</li> </li>
<li class="md-nav__item"> <li class="md-nav__item">
@@ -504,20 +432,6 @@
</li> </li>
<li class="md-nav__item">
<a href="#keys-with-dots" title="Keys with dots" class="md-nav__link">
Keys with dots
</a>
</li>
<li class="md-nav__item">
<a href="#keys-and-values-with-leading-dashes" title="Keys (and values) with leading dashes" class="md-nav__link">
Keys (and values) with leading dashes
</a>
</li>
@@ -539,49 +453,16 @@
<h1>Delete</h1> <h1>Delete</h1>
<pre><code>yq d &lt;yaml_file&gt; &lt;path_to_delete&gt; <pre><code>yq delete &lt;yaml_file|-&gt; &lt;path_expression&gt;
</code></pre>
<h3 id="to-stdout">To Stdout<a class="headerlink" href="#to-stdout" title="Permanent link">&para;</a></h3>
<p>Given a sample.yaml file of:</p>
<pre><code class="yaml">b:
c: 2
apples: green
</code></pre>
<p>then</p>
<pre><code class="bash">yq d sample.yaml b.c
</code></pre>
<p>will output:</p>
<pre><code class="yaml">b:
apples: green
</code></pre> </code></pre>
<p>The delete command will delete all the matching nodes for the path expression in the given yaml input.</p>
<p>See docs for <a href="../path_expressions/">path expression</a> for more details.</p>
<h3 id="from-stdin">From STDIN<a class="headerlink" href="#from-stdin" title="Permanent link">&para;</a></h3> <h3 id="from-stdin">From STDIN<a class="headerlink" href="#from-stdin" title="Permanent link">&para;</a></h3>
<p>Use "-" (without quotes) inplace of a file name if you wish to pipe in input from STDIN.</p>
<pre><code class="bash">cat sample.yaml | yq d - b.c <pre><code class="bash">cat sample.yaml | yq d - b.c
</code></pre> </code></pre>
<h3 id="deleting-array-elements">Deleting array elements<a class="headerlink" href="#deleting-array-elements" title="Permanent link">&para;</a></h3>
<p>Given a sample.yaml file of:</p>
<pre><code class="yaml">b:
c:
- 1
- 2
- 3
</code></pre>
<p>then</p>
<pre><code class="bash">yq d sample.yaml 'b.c[1]'
</code></pre>
<p>will output:</p>
<pre><code class="yaml">b:
c:
- 1
- 3
</code></pre>
<h3 id="deleting-nodes-in-place">Deleting nodes in-place<a class="headerlink" href="#deleting-nodes-in-place" title="Permanent link">&para;</a></h3> <h3 id="deleting-nodes-in-place">Deleting nodes in-place<a class="headerlink" href="#deleting-nodes-in-place" title="Permanent link">&para;</a></h3>
<p>Given a sample.yaml file of:</p> <p>Given a sample.yaml file of:</p>
<pre><code class="yaml">b: <pre><code class="yaml">b:
@@ -594,91 +475,6 @@
</code></pre> </code></pre>
<p>will update the sample.yaml file so that the 'c' node is deleted</p> <p>will update the sample.yaml file so that the 'c' node is deleted</p>
<h3 id="splat">Splat<a class="headerlink" href="#splat" title="Permanent link">&para;</a></h3>
<p>Given a sample.yaml file of:</p>
<pre><code class="yaml">---
bob:
item1:
cats: bananas
dogs: woof
item2:
cats: apples
dogs: woof2
thing:
cats: oranges
dogs: woof3
</code></pre>
<p>then</p>
<pre><code class="bash">yq d sample.yaml bob.*.cats
</code></pre>
<p>will output:</p>
<pre><code class="yaml">---
bob:
item1:
dogs: woof
item2:
dogs: woof2
thing:
dogs: woof3
</code></pre>
<h3 id="prefix-splat">Prefix Splat<a class="headerlink" href="#prefix-splat" title="Permanent link">&para;</a></h3>
<p>Given a sample.yaml file of:</p>
<pre><code class="yaml">---
bob:
item1:
cats: bananas
dogs: woof
item2:
cats: apples
dogs: woof2
thing:
cats: oranges
dogs: woof3
</code></pre>
<p>then</p>
<pre><code class="bash">yq d sample.yaml bob.item*.cats
</code></pre>
<p>will output:</p>
<pre><code class="yaml">---
bob:
item1:
dogs: woof
item2:
dogs: woof2
thing:
cats: oranges
dogs: woof3
</code></pre>
<h3 id="array-splat">Array Splat<a class="headerlink" href="#array-splat" title="Permanent link">&para;</a></h3>
<p>Given a sample.yaml file of:</p>
<pre><code class="yaml">---
bob:
- cats: bananas
dogs: woof
- cats: apples
dogs: woof2
- cats: oranges
dogs: woof3
</code></pre>
<p>then</p>
<pre><code class="bash">yq d sample.yaml bob.[*].cats
</code></pre>
<p>will output:</p>
<pre><code class="yaml">---
bob:
- dogs: woof
- dogs: woof2
- dogs: woof3
</code></pre>
<h3 id="multiple-documents-delete-from-single-document">Multiple Documents - delete from single document<a class="headerlink" href="#multiple-documents-delete-from-single-document" title="Permanent link">&para;</a></h3> <h3 id="multiple-documents-delete-from-single-document">Multiple Documents - delete from single document<a class="headerlink" href="#multiple-documents-delete-from-single-document" title="Permanent link">&para;</a></h3>
<p>Given a sample.yaml file of:</p> <p>Given a sample.yaml file of:</p>
<pre><code class="yaml">something: else <pre><code class="yaml">something: else
@@ -723,29 +519,6 @@ b:
</code></pre> </code></pre>
<p>Note that '*' is in quotes to avoid being interpreted by your shell.</p> <p>Note that '*' is in quotes to avoid being interpreted by your shell.</p>
<h3 id="keys-with-dots">Keys with dots<a class="headerlink" href="#keys-with-dots" title="Permanent link">&para;</a></h3>
<p>When specifying a key that has a dot use key lookup indicator.</p>
<pre><code class="yaml">b:
foo.bar: 7
</code></pre>
<pre><code class="bash">yaml r sample.yaml 'b[foo.bar]'
</code></pre>
<pre><code class="bash">yaml w sample.yaml 'b[foo.bar]' 9
</code></pre>
<p>Any valid yaml key can be specified as part of a key lookup.</p>
<p>Note that the path is in quotes to avoid the square brackets being interpreted by your shell.</p>
<h3 id="keys-and-values-with-leading-dashes">Keys (and values) with leading dashes<a class="headerlink" href="#keys-and-values-with-leading-dashes" title="Permanent link">&para;</a></h3>
<p>If a key or value has leading dashes, yq won't know that you are passing a value as opposed to a flag (and you will get a 'bad flag syntax' error).</p>
<p>To fix that, you will need to tell it to stop processing flags by adding '--' after the last flag like so:</p>
<pre><code class="bash">yq n -t -- --key --value
</code></pre>
<p>Will result in</p>
<p><code>`
--key: --value</code></p>

View File

@@ -290,6 +290,18 @@
<li class="md-nav__item">
<a href="path_expressions/" title="Path Expressions" class="md-nav__link">
Path Expressions
</a>
</li>
<li class="md-nav__item"> <li class="md-nav__item">
<a href="write/" title="Write/Update" class="md-nav__link"> <a href="write/" title="Write/Update" class="md-nav__link">
Write/Update Write/Update
@@ -422,7 +434,7 @@ sudo apt install yq -y
</code></pre> </code></pre>
<p>or, <a href="https://github.com/mikefarah/yq/releases/latest">Download latest binary</a> or alternatively:</p> <p>or, <a href="https://github.com/mikefarah/yq/releases/latest">Download latest binary</a> or alternatively:</p>
<pre><code>go get gopkg.in/mikefarah/yq.v2 <pre><code>GO111MODULE=on go get github.com/mikefarah/yq/v3
</code></pre> </code></pre>
<p><a href="https://github.com/mikefarah/yq">View on GitHub</a></p> <p><a href="https://github.com/mikefarah/yq">View on GitHub</a></p>

View File

@@ -252,6 +252,18 @@
<li class="md-nav__item">
<a href="../path_expressions/" title="Path Expressions" class="md-nav__link">
Path Expressions
</a>
</li>
<li class="md-nav__item"> <li class="md-nav__item">
<a href="../write/" title="Write/Update" class="md-nav__link"> <a href="../write/" title="Write/Update" class="md-nav__link">
Write/Update Write/Update
@@ -608,7 +620,6 @@ d: hi
</code></pre> </code></pre>
<p>Note that the 'b' array has concatenated the values from the second data file. Also note that other map keys are not overridden (field a).</p> <p>Note that the 'b' array has concatenated the values from the second data file. Also note that other map keys are not overridden (field a).</p>
<p>Append cannot be used with overwrite, if both flags are given then append is ignored.</p>
<h3 id="multiple-documents-merge-into-single-document">Multiple Documents - merge into single document<a class="headerlink" href="#multiple-documents-merge-into-single-document" title="Permanent link">&para;</a></h3> <h3 id="multiple-documents-merge-into-single-document">Multiple Documents - merge into single document<a class="headerlink" href="#multiple-documents-merge-into-single-document" title="Permanent link">&para;</a></h3>
<p>Currently yq only has multi-document support for the <em>first</em> document being merged into. The remaining yaml files will have their first document selected.</p> <p>Currently yq only has multi-document support for the <em>first</em> document being merged into. The remaining yaml files will have their first document selected.</p>
<p>Given a data1.yaml file of:</p> <p>Given a data1.yaml file of:</p>

View File

@@ -0,0 +1,862 @@
<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="lang:clipboard.copy" content="Copy to clipboard">
<meta name="lang:clipboard.copied" content="Copied to clipboard">
<meta name="lang:search.language" content="en">
<meta name="lang:search.pipeline.stopwords" content="True">
<meta name="lang:search.pipeline.trimmer" content="True">
<meta name="lang:search.result.none" content="No matching documents">
<meta name="lang:search.result.one" content="1 matching document">
<meta name="lang:search.result.other" content="# matching documents">
<meta name="lang:search.tokenizer" content="[\s\-]+">
<link rel="shortcut icon" href="../assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.0.4, mkdocs-material-4.2.0">
<title>Path Expressions - Yq</title>
<link rel="stylesheet" href="../assets/stylesheets/application.750b69bd.css">
<script src="../assets/javascripts/modernizr.74668098.js"></script>
<link href="https://fonts.gstatic.com" rel="preconnect" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,400i,700|Roboto+Mono">
<style>body,input{font-family:"Roboto","Helvetica Neue",Helvetica,Arial,sans-serif}code,kbd,pre{font-family:"Roboto Mono","Courier New",Courier,monospace}</style>
<link rel="stylesheet" href="../assets/fonts/material-icons.css">
</head>
<body dir="ltr">
<svg class="md-svg">
<defs>
<svg xmlns="http://www.w3.org/2000/svg" width="416" height="448"
viewBox="0 0 416 448" id="__github">
<path fill="currentColor" d="M160 304q0 10-3.125 20.5t-10.75 19-18.125
8.5-18.125-8.5-10.75-19-3.125-20.5 3.125-20.5 10.75-19 18.125-8.5
18.125 8.5 10.75 19 3.125 20.5zM320 304q0 10-3.125 20.5t-10.75
19-18.125 8.5-18.125-8.5-10.75-19-3.125-20.5 3.125-20.5 10.75-19
18.125-8.5 18.125 8.5 10.75 19 3.125 20.5zM360
304q0-30-17.25-51t-46.75-21q-10.25 0-48.75 5.25-17.75 2.75-39.25
2.75t-39.25-2.75q-38-5.25-48.75-5.25-29.5 0-46.75 21t-17.25 51q0 22 8
38.375t20.25 25.75 30.5 15 35 7.375 37.25 1.75h42q20.5 0
37.25-1.75t35-7.375 30.5-15 20.25-25.75 8-38.375zM416 260q0 51.75-15.25
82.75-9.5 19.25-26.375 33.25t-35.25 21.5-42.5 11.875-42.875 5.5-41.75
1.125q-19.5 0-35.5-0.75t-36.875-3.125-38.125-7.5-34.25-12.875-30.25-20.25-21.5-28.75q-15.5-30.75-15.5-82.75
0-59.25 34-99-6.75-20.5-6.75-42.5 0-29 12.75-54.5 27 0 47.5 9.875t47.25
30.875q36.75-8.75 77.25-8.75 37 0 70 8 26.25-20.5
46.75-30.25t47.25-9.75q12.75 25.5 12.75 54.5 0 21.75-6.75 42 34 40 34
99.5z" />
</svg>
</defs>
</svg>
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" data-md-component="overlay" for="__drawer"></label>
<a href="#simple-expressions" tabindex="1" class="md-skip">
Skip to content
</a>
<header class="md-header" data-md-component="header">
<nav class="md-header-nav md-grid">
<div class="md-flex">
<div class="md-flex__cell md-flex__cell--shrink">
<a href=".." title="Yq" class="md-header-nav__button md-logo">
<i class="md-icon">î Ś</i>
</a>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--menu md-header-nav__button" for="__drawer"></label>
</div>
<div class="md-flex__cell md-flex__cell--stretch">
<div class="md-flex__ellipsis md-header-nav__title" data-md-component="title">
<span class="md-header-nav__topic">
Yq
</span>
<span class="md-header-nav__topic">
Path Expressions
</span>
</div>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--search md-header-nav__button" for="__search"></label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="query" data-md-state="active">
<label class="md-icon md-search__icon" for="__search"></label>
<button type="reset" class="md-icon md-search__icon" data-md-component="reset" tabindex="-1">
&#xE5CD;
</button>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" data-md-scrollfix>
<div class="md-search-result" data-md-component="result">
<div class="md-search-result__meta">
Type to start searching
</div>
<ol class="md-search-result__list"></ol>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<div class="md-header-nav__source">
<a href="https://github.com/mikefarah/yq/" title="Go to repository" class="md-source" data-md-source="github">
<div class="md-source__icon">
<svg viewBox="0 0 24 24" width="24" height="24">
<use xlink:href="#__github" width="24" height="24"></use>
</svg>
</div>
<div class="md-source__repository">
mikefarah/yq
</div>
</a>
</div>
</div>
</div>
</nav>
</header>
<div class="md-container">
<main class="md-main">
<div class="md-main__inner md-grid" data-md-component="container">
<div class="md-sidebar md-sidebar--primary" data-md-component="navigation">
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" data-md-level="0">
<label class="md-nav__title md-nav__title--site" for="__drawer">
<a href=".." title="Yq" class="md-nav__button md-logo">
<i class="md-icon">î Ś</i>
</a>
Yq
</label>
<div class="md-nav__source">
<a href="https://github.com/mikefarah/yq/" title="Go to repository" class="md-source" data-md-source="github">
<div class="md-source__icon">
<svg viewBox="0 0 24 24" width="24" height="24">
<use xlink:href="#__github" width="24" height="24"></use>
</svg>
</div>
<div class="md-source__repository">
mikefarah/yq
</div>
</a>
</div>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href=".." title="Install" class="md-nav__link">
Install
</a>
</li>
<li class="md-nav__item">
<a href="../read/" title="Read" class="md-nav__link">
Read
</a>
</li>
<li class="md-nav__item md-nav__item--active">
<input class="md-toggle md-nav__toggle" data-md-toggle="toc" type="checkbox" id="__toc">
<label class="md-nav__link md-nav__link--active" for="__toc">
Path Expressions
</label>
<a href="./" title="Path Expressions" class="md-nav__link md-nav__link--active">
Path Expressions
</a>
<nav class="md-nav md-nav--secondary">
<label class="md-nav__title" for="__toc">Table of contents</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="#simple-expressions" title="Simple expressions" class="md-nav__link">
Simple expressions
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#maps" title="Maps" class="md-nav__link">
Maps
</a>
</li>
<li class="md-nav__item">
<a href="#arrays" title="Arrays" class="md-nav__link">
Arrays
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#appending-to-arrays" title="Appending to arrays" class="md-nav__link">
Appending to arrays
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#splat" title="Splat" class="md-nav__link">
Splat
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#maps_1" title="Maps" class="md-nav__link">
Maps
</a>
</li>
<li class="md-nav__item">
<a href="#arrays_1" title="Arrays" class="md-nav__link">
Arrays
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#deep-splat" title="Deep Splat" class="md-nav__link">
Deep Splat
</a>
</li>
<li class="md-nav__item">
<a href="#finding-parents-with-particular-children-nodes" title="Finding parents with particular children nodes" class="md-nav__link">
Finding parents with particular children nodes
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#with-prefixes" title="With prefixes" class="md-nav__link">
With prefixes
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#special-characters" title="Special Characters" class="md-nav__link">
Special Characters
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#keys-with-dots" title="Keys with dots" class="md-nav__link">
Keys with dots
</a>
</li>
<li class="md-nav__item">
<a href="#keys-and-values-with-leading-dashes" title="Keys (and values) with leading dashes" class="md-nav__link">
Keys (and values) with leading dashes
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="../write/" title="Write/Update" class="md-nav__link">
Write/Update
</a>
</li>
<li class="md-nav__item">
<a href="../prefix/" title="Prefix" class="md-nav__link">
Prefix
</a>
</li>
<li class="md-nav__item">
<a href="../delete/" title="Delete" class="md-nav__link">
Delete
</a>
</li>
<li class="md-nav__item">
<a href="../create/" title="Create" class="md-nav__link">
Create
</a>
</li>
<li class="md-nav__item">
<a href="../convert/" title="Convert" class="md-nav__link">
Convert
</a>
</li>
<li class="md-nav__item">
<a href="../merge/" title="Merge" class="md-nav__link">
Merge
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="toc">
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary">
<label class="md-nav__title" for="__toc">Table of contents</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="#simple-expressions" title="Simple expressions" class="md-nav__link">
Simple expressions
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#maps" title="Maps" class="md-nav__link">
Maps
</a>
</li>
<li class="md-nav__item">
<a href="#arrays" title="Arrays" class="md-nav__link">
Arrays
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#appending-to-arrays" title="Appending to arrays" class="md-nav__link">
Appending to arrays
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#splat" title="Splat" class="md-nav__link">
Splat
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#maps_1" title="Maps" class="md-nav__link">
Maps
</a>
</li>
<li class="md-nav__item">
<a href="#arrays_1" title="Arrays" class="md-nav__link">
Arrays
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#deep-splat" title="Deep Splat" class="md-nav__link">
Deep Splat
</a>
</li>
<li class="md-nav__item">
<a href="#finding-parents-with-particular-children-nodes" title="Finding parents with particular children nodes" class="md-nav__link">
Finding parents with particular children nodes
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#with-prefixes" title="With prefixes" class="md-nav__link">
With prefixes
</a>
</li>
</ul>
</nav>
</li>
<li class="md-nav__item">
<a href="#special-characters" title="Special Characters" class="md-nav__link">
Special Characters
</a>
<nav class="md-nav">
<ul class="md-nav__list">
<li class="md-nav__item">
<a href="#keys-with-dots" title="Keys with dots" class="md-nav__link">
Keys with dots
</a>
</li>
<li class="md-nav__item">
<a href="#keys-and-values-with-leading-dashes" title="Keys (and values) with leading dashes" class="md-nav__link">
Keys (and values) with leading dashes
</a>
</li>
</ul>
</nav>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content">
<article class="md-content__inner md-typeset">
<a href="https://github.com/mikefarah/yq/edit/master/docs/path_expressions.md" title="Edit this page" class="md-icon md-content__icon">&#xE3C9;</a>
<h1>Path Expressions</h1>
<p>Path expressions are used to deeply navigate and match particular yaml nodes.</p>
<p><em>As a general rule, you should wrap paths in quotes in the CLI to prevent your interpreter from processing '*, []' and other special characters.</em></p>
<h2 id="simple-expressions">Simple expressions<a class="headerlink" href="#simple-expressions" title="Permanent link">&para;</a></h2>
<h3 id="maps">Maps<a class="headerlink" href="#maps" title="Permanent link">&para;</a></h3>
<p>a.b.c</p>
<pre><code class="yaml">a:
b:
c: thing # MATCHES
</code></pre>
<h3 id="arrays">Arrays<a class="headerlink" href="#arrays" title="Permanent link">&para;</a></h3>
<p>a.b[1].c</p>
<pre><code class="yaml">a:
b:
- c: thing0
- c: thing1 # MATCHES
- c: thing2
</code></pre>
<h4 id="appending-to-arrays">Appending to arrays<a class="headerlink" href="#appending-to-arrays" title="Permanent link">&para;</a></h4>
<p>(e.g. when using the write command)</p>
<p>a.b[+].c</p>
<pre><code class="yaml">a:
b:
- c: thing0
</code></pre>
<p>Will add a new entry:</p>
<pre><code class="yaml">a:
b:
- c: thing0
- c: thing1 # NEW entry from [+] on B array.
</code></pre>
<h2 id="splat">Splat<a class="headerlink" href="#splat" title="Permanent link">&para;</a></h2>
<h3 id="maps_1">Maps<a class="headerlink" href="#maps_1" title="Permanent link">&para;</a></h3>
<p>a.*.c</p>
<pre><code class="yaml">a:
b1:
c: thing # MATCHES
b2:
c: thing # MATCHES
</code></pre>
<h3 id="arrays_1">Arrays<a class="headerlink" href="#arrays_1" title="Permanent link">&para;</a></h3>
<p>a.b[*].c</p>
<pre><code class="yaml">a:
b:
- c: thing0 # MATCHES
- c: thing1 # MATCHES
- c: thing2 # MATCHES
</code></pre>
<h2 id="deep-splat">Deep Splat<a class="headerlink" href="#deep-splat" title="Permanent link">&para;</a></h2>
<p>'**' will match arbitrary nodes for both maps and arrays:</p>
<p>a.**.c</p>
<pre><code class="yaml">a:
b1:
c: thing1 # MATCHES
b2:
c: thing2 # MATCHES
b3:
d:
- f:
c: thing3 # MATCHES
- f:
g:
c: thing4 # MATCHES
</code></pre>
<h2 id="finding-parents-with-particular-children-nodes">Finding parents with particular children nodes<a class="headerlink" href="#finding-parents-with-particular-children-nodes" title="Permanent link">&para;</a></h2>
<p>a.(b.d==cat).b.c</p>
<pre><code class="yaml">a:
- b:
c: thing0
d: leopard
ba: fast
- b:
c: thing1 # MATCHES
d: cat
ba: meowy
- b:
c: thing2
d: caterpillar
ba: icky
- b:
c: thing3 # MATCHES
d: cat
ba: also meowy
</code></pre>
<h3 id="with-prefixes">With prefixes<a class="headerlink" href="#with-prefixes" title="Permanent link">&para;</a></h3>
<p>a.(b.d==cat*).c</p>
<pre><code class="yaml">a:
- b:
c: thing0
d: leopard
ba: fast
- b:
c: thing1 # MATCHES
d: cat
ba: meowy
- b:
c: thing2 # MATCHES
d: caterpillar
ba: icky
- b:
c: thing3 # MATCHES
d: cat
ba: also meowy
</code></pre>
<h2 id="special-characters">Special Characters<a class="headerlink" href="#special-characters" title="Permanent link">&para;</a></h2>
<h3 id="keys-with-dots">Keys with dots<a class="headerlink" href="#keys-with-dots" title="Permanent link">&para;</a></h3>
<p>When specifying a key that has a dot use key lookup indicator.</p>
<pre><code class="yaml">b:
foo.bar: 7
</code></pre>
<pre><code class="bash">yaml r sample.yaml 'b[foo.bar]'
</code></pre>
<pre><code class="bash">yaml w sample.yaml 'b[foo.bar]' 9
</code></pre>
<p>Any valid yaml key can be specified as part of a key lookup.</p>
<p>Note that the path is in quotes to avoid the square brackets being interpreted by your shell.</p>
<h3 id="keys-and-values-with-leading-dashes">Keys (and values) with leading dashes<a class="headerlink" href="#keys-and-values-with-leading-dashes" title="Permanent link">&para;</a></h3>
<p>If a key or value has leading dashes, yq won't know that you are passing a value as opposed to a flag (and you will get a 'bad flag syntax' error).</p>
<p>To fix that, you will need to tell it to stop processing flags by adding '--' after the last flag like so:</p>
<pre><code class="bash">yq n -t -- --key --value
</code></pre>
<p>Will result in</p>
<pre><code>--key: --value
</code></pre>
</article>
</div>
</div>
</main>
<footer class="md-footer">
<div class="md-footer-nav">
<nav class="md-footer-nav__inner md-grid">
<a href="../read/" title="Read" class="md-flex md-footer-nav__link md-footer-nav__link--prev" rel="prev">
<div class="md-flex__cell md-flex__cell--shrink">
<i class="md-icon md-icon--arrow-back md-footer-nav__button"></i>
</div>
<div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
<span class="md-flex__ellipsis">
<span class="md-footer-nav__direction">
Previous
</span>
Read
</span>
</div>
</a>
<a href="../write/" title="Write/Update" class="md-flex md-footer-nav__link md-footer-nav__link--next" rel="next">
<div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
<span class="md-flex__ellipsis">
<span class="md-footer-nav__direction">
Next
</span>
Write/Update
</span>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<i class="md-icon md-icon--arrow-forward md-footer-nav__button"></i>
</div>
</a>
</nav>
</div>
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-footer-copyright">
powered by
<a href="https://www.mkdocs.org">MkDocs</a>
and
<a href="https://squidfunk.github.io/mkdocs-material/">
Material for MkDocs</a>
</div>
<div class="md-footer-social">
<link rel="stylesheet" href="../assets/fonts/font-awesome.css">
<a href="https://github.com/mikefarah" class="md-footer-social__link fa fa-github"></a>
<a href="https://www.linkedin.com/in/mike-farah-b5a75b2/" class="md-footer-social__link fa fa-linkedin"></a>
</div>
</div>
</div>
</footer>
</div>
<script src="../assets/javascripts/application.39abc4af.js"></script>
<script>app.initialize({version:"1.0.4",url:{base:".."}})</script>
</body>
</html>

View File

@@ -252,6 +252,18 @@
<li class="md-nav__item">
<a href="../path_expressions/" title="Path Expressions" class="md-nav__link">
Path Expressions
</a>
</li>
<li class="md-nav__item"> <li class="md-nav__item">
<a href="../write/" title="Write/Update" class="md-nav__link"> <a href="../write/" title="Write/Update" class="md-nav__link">
Write/Update Write/Update
@@ -455,11 +467,11 @@
<h1>Prefix</h1> <h1>Prefix</h1>
<p>Paths can be prefixed using the 'prefix' command. <pre><code>yq p &lt;yaml_file&gt; &lt;path&gt;
The complete yaml content will be nested inside the new prefix path.</p>
<pre><code>yq p &lt;yaml_file&gt; &lt;path&gt;
</code></pre> </code></pre>
<p>Prefixes a yaml document with the given path expression. The complete yaml content will be nested inside the new prefix path.</p>
<p>See docs for <a href="../path_expressions/">path expression</a> for more details.</p>
<h3 id="to-stdout">To Stdout<a class="headerlink" href="#to-stdout" title="Permanent link">&para;</a></h3> <h3 id="to-stdout">To Stdout<a class="headerlink" href="#to-stdout" title="Permanent link">&para;</a></h3>
<p>Given a data1.yaml file of:</p> <p>Given a data1.yaml file of:</p>
<pre><code class="yaml">a: simple <pre><code class="yaml">a: simple

View File

@@ -319,20 +319,6 @@
</li> </li>
<li class="md-nav__item">
<a href="#keys-with-dots" title="Keys with dots" class="md-nav__link">
Keys with dots
</a>
</li>
<li class="md-nav__item">
<a href="#keys-and-values-with-leading-dashes" title="Keys (and values) with leading dashes" class="md-nav__link">
Keys (and values) with leading dashes
</a>
</li>
@@ -349,6 +335,18 @@
<li class="md-nav__item">
<a href="../path_expressions/" title="Path Expressions" class="md-nav__link">
Path Expressions
</a>
</li>
<li class="md-nav__item"> <li class="md-nav__item">
<a href="../write/" title="Write/Update" class="md-nav__link"> <a href="../write/" title="Write/Update" class="md-nav__link">
Write/Update Write/Update
@@ -490,20 +488,6 @@
</li> </li>
<li class="md-nav__item">
<a href="#keys-with-dots" title="Keys with dots" class="md-nav__link">
Keys with dots
</a>
</li>
<li class="md-nav__item">
<a href="#keys-and-values-with-leading-dashes" title="Keys (and values) with leading dashes" class="md-nav__link">
Keys (and values) with leading dashes
</a>
</li>
@@ -525,10 +509,11 @@
<h1>Read</h1> <h1>Read</h1>
<pre><code>yq r &lt;yaml_file|json_file&gt; &lt;path&gt; <pre><code>yq r &lt;yaml_file|json_file&gt; &lt;path_expression&gt;
</code></pre> </code></pre>
<p>This command can take a json file as input too, and will output yaml unless specified to export as json (-j)</p> <p>Returns the matching nodes of the path expression for the given yaml file (or STDIN).</p>
<p>See docs for <a href="../path_expressions/">path expression</a> for more details.</p>
<h3 id="basic">Basic<a class="headerlink" href="#basic" title="Permanent link">&para;</a></h3> <h3 id="basic">Basic<a class="headerlink" href="#basic" title="Permanent link">&para;</a></h3>
<p>Given a sample.yaml file of:</p> <p>Given a sample.yaml file of:</p>
<pre><code class="yaml">b: <pre><code class="yaml">b:
@@ -662,29 +647,6 @@ e.g.: given a sample file of</p>
</code></pre> </code></pre>
<p>Note that the path is in quotes to avoid the square brackets being interpreted by your shell.</p> <p>Note that the path is in quotes to avoid the square brackets being interpreted by your shell.</p>
<h3 id="keys-with-dots">Keys with dots<a class="headerlink" href="#keys-with-dots" title="Permanent link">&para;</a></h3>
<p>When specifying a key that has a dot use key lookup indicator.</p>
<pre><code class="yaml">b:
foo.bar: 7
</code></pre>
<pre><code class="bash">yaml r sample.yaml 'b[foo.bar]'
</code></pre>
<pre><code class="bash">yaml w sample.yaml 'b[foo.bar]' 9
</code></pre>
<p>Any valid yaml key can be specified as part of a key lookup.</p>
<p>Note that the path is in quotes to avoid the square brackets being interpreted by your shell.</p>
<h3 id="keys-and-values-with-leading-dashes">Keys (and values) with leading dashes<a class="headerlink" href="#keys-and-values-with-leading-dashes" title="Permanent link">&para;</a></h3>
<p>If a key or value has leading dashes, yq won't know that you are passing a value as opposed to a flag (and you will get a 'bad flag syntax' error).</p>
<p>To fix that, you will need to tell it to stop processing flags by adding '--' after the last flag like so:</p>
<pre><code class="bash">yq n -t -- --key --value
</code></pre>
<p>Will result in</p>
<p><code>`
--key: --value</code></p>
@@ -720,13 +682,13 @@ e.g.: given a sample file of</p>
</a> </a>
<a href="../write/" title="Write/Update" class="md-flex md-footer-nav__link md-footer-nav__link--next" rel="next"> <a href="../path_expressions/" title="Path Expressions" class="md-flex md-footer-nav__link md-footer-nav__link--next" rel="next">
<div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title"> <div class="md-flex__cell md-flex__cell--stretch md-footer-nav__title">
<span class="md-flex__ellipsis"> <span class="md-flex__ellipsis">
<span class="md-footer-nav__direction"> <span class="md-footer-nav__direction">
Next Next
</span> </span>
Write/Update Path Expressions
</span> </span>
</div> </div>
<div class="md-flex__cell md-flex__cell--shrink"> <div class="md-flex__cell md-flex__cell--shrink">

File diff suppressed because one or more lines are too long

View File

@@ -2,42 +2,47 @@
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url> <url>
<loc>None</loc> <loc>None</loc>
<lastmod>2019-05-16</lastmod> <lastmod>2020-01-13</lastmod>
<changefreq>daily</changefreq> <changefreq>daily</changefreq>
</url> </url>
<url> <url>
<loc>None</loc> <loc>None</loc>
<lastmod>2019-05-16</lastmod> <lastmod>2020-01-13</lastmod>
<changefreq>daily</changefreq> <changefreq>daily</changefreq>
</url> </url>
<url> <url>
<loc>None</loc> <loc>None</loc>
<lastmod>2019-05-16</lastmod> <lastmod>2020-01-13</lastmod>
<changefreq>daily</changefreq> <changefreq>daily</changefreq>
</url> </url>
<url> <url>
<loc>None</loc> <loc>None</loc>
<lastmod>2019-05-16</lastmod> <lastmod>2020-01-13</lastmod>
<changefreq>daily</changefreq> <changefreq>daily</changefreq>
</url> </url>
<url> <url>
<loc>None</loc> <loc>None</loc>
<lastmod>2019-05-16</lastmod> <lastmod>2020-01-13</lastmod>
<changefreq>daily</changefreq> <changefreq>daily</changefreq>
</url> </url>
<url> <url>
<loc>None</loc> <loc>None</loc>
<lastmod>2019-05-16</lastmod> <lastmod>2020-01-13</lastmod>
<changefreq>daily</changefreq> <changefreq>daily</changefreq>
</url> </url>
<url> <url>
<loc>None</loc> <loc>None</loc>
<lastmod>2019-05-16</lastmod> <lastmod>2020-01-13</lastmod>
<changefreq>daily</changefreq> <changefreq>daily</changefreq>
</url> </url>
<url> <url>
<loc>None</loc> <loc>None</loc>
<lastmod>2019-05-16</lastmod> <lastmod>2020-01-13</lastmod>
<changefreq>daily</changefreq>
</url>
<url>
<loc>None</loc>
<lastmod>2020-01-13</lastmod>
<changefreq>daily</changefreq> <changefreq>daily</changefreq>
</url> </url>
</urlset> </urlset>

Binary file not shown.

View File

@@ -1,447 +0,0 @@
<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="lang:clipboard.copy" content="Copy to clipboard">
<meta name="lang:clipboard.copied" content="Copied to clipboard">
<meta name="lang:search.language" content="en">
<meta name="lang:search.pipeline.stopwords" content="True">
<meta name="lang:search.pipeline.trimmer" content="True">
<meta name="lang:search.result.none" content="No matching documents">
<meta name="lang:search.result.one" content="1 matching document">
<meta name="lang:search.result.other" content="# matching documents">
<meta name="lang:search.tokenizer" content="[\s\-]+">
<link rel="shortcut icon" href="../../assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.0.4, mkdocs-material-4.2.0">
<title>Niche - Yq</title>
<link rel="stylesheet" href="../../assets/stylesheets/application.750b69bd.css">
<script src="../../assets/javascripts/modernizr.74668098.js"></script>
<link href="https://fonts.gstatic.com" rel="preconnect" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,400i,700|Roboto+Mono">
<style>body,input{font-family:"Roboto","Helvetica Neue",Helvetica,Arial,sans-serif}code,kbd,pre{font-family:"Roboto Mono","Courier New",Courier,monospace}</style>
<link rel="stylesheet" href="../../assets/fonts/material-icons.css">
</head>
<body dir="ltr">
<svg class="md-svg">
<defs>
<svg xmlns="http://www.w3.org/2000/svg" width="416" height="448"
viewBox="0 0 416 448" id="__github">
<path fill="currentColor" d="M160 304q0 10-3.125 20.5t-10.75 19-18.125
8.5-18.125-8.5-10.75-19-3.125-20.5 3.125-20.5 10.75-19 18.125-8.5
18.125 8.5 10.75 19 3.125 20.5zM320 304q0 10-3.125 20.5t-10.75
19-18.125 8.5-18.125-8.5-10.75-19-3.125-20.5 3.125-20.5 10.75-19
18.125-8.5 18.125 8.5 10.75 19 3.125 20.5zM360
304q0-30-17.25-51t-46.75-21q-10.25 0-48.75 5.25-17.75 2.75-39.25
2.75t-39.25-2.75q-38-5.25-48.75-5.25-29.5 0-46.75 21t-17.25 51q0 22 8
38.375t20.25 25.75 30.5 15 35 7.375 37.25 1.75h42q20.5 0
37.25-1.75t35-7.375 30.5-15 20.25-25.75 8-38.375zM416 260q0 51.75-15.25
82.75-9.5 19.25-26.375 33.25t-35.25 21.5-42.5 11.875-42.875 5.5-41.75
1.125q-19.5 0-35.5-0.75t-36.875-3.125-38.125-7.5-34.25-12.875-30.25-20.25-21.5-28.75q-15.5-30.75-15.5-82.75
0-59.25 34-99-6.75-20.5-6.75-42.5 0-29 12.75-54.5 27 0 47.5 9.875t47.25
30.875q36.75-8.75 77.25-8.75 37 0 70 8 26.25-20.5
46.75-30.25t47.25-9.75q12.75 25.5 12.75 54.5 0 21.75-6.75 42 34 40 34
99.5z" />
</svg>
</defs>
</svg>
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" data-md-component="overlay" for="__drawer"></label>
<a href="#keys-with-dots" tabindex="1" class="md-skip">
Skip to content
</a>
<header class="md-header" data-md-component="header">
<nav class="md-header-nav md-grid">
<div class="md-flex">
<div class="md-flex__cell md-flex__cell--shrink">
<a href="../.." title="Yq" class="md-header-nav__button md-logo">
<i class="md-icon">î Ś</i>
</a>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--menu md-header-nav__button" for="__drawer"></label>
</div>
<div class="md-flex__cell md-flex__cell--stretch">
<div class="md-flex__ellipsis md-header-nav__title" data-md-component="title">
<span class="md-header-nav__topic">
Yq
</span>
<span class="md-header-nav__topic">
Niche
</span>
</div>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--search md-header-nav__button" for="__search"></label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="query" data-md-state="active">
<label class="md-icon md-search__icon" for="__search"></label>
<button type="reset" class="md-icon md-search__icon" data-md-component="reset" tabindex="-1">
&#xE5CD;
</button>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" data-md-scrollfix>
<div class="md-search-result" data-md-component="result">
<div class="md-search-result__meta">
Type to start searching
</div>
<ol class="md-search-result__list"></ol>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<div class="md-header-nav__source">
<a href="https://github.com/mikefarah/yq/" title="Go to repository" class="md-source" data-md-source="github">
<div class="md-source__icon">
<svg viewBox="0 0 24 24" width="24" height="24">
<use xlink:href="#__github" width="24" height="24"></use>
</svg>
</div>
<div class="md-source__repository">
mikefarah/yq
</div>
</a>
</div>
</div>
</div>
</nav>
</header>
<div class="md-container">
<main class="md-main">
<div class="md-main__inner md-grid" data-md-component="container">
<div class="md-sidebar md-sidebar--primary" data-md-component="navigation">
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" data-md-level="0">
<label class="md-nav__title md-nav__title--site" for="__drawer">
<a href="../.." title="Yq" class="md-nav__button md-logo">
<i class="md-icon">î Ś</i>
</a>
Yq
</label>
<div class="md-nav__source">
<a href="https://github.com/mikefarah/yq/" title="Go to repository" class="md-source" data-md-source="github">
<div class="md-source__icon">
<svg viewBox="0 0 24 24" width="24" height="24">
<use xlink:href="#__github" width="24" height="24"></use>
</svg>
</div>
<div class="md-source__repository">
mikefarah/yq
</div>
</a>
</div>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../.." title="Install" class="md-nav__link">
Install
</a>
</li>
<li class="md-nav__item">
<a href="../../read/" title="Read" class="md-nav__link">
Read
</a>
</li>
<li class="md-nav__item">
<a href="../../write/" title="Write/Update" class="md-nav__link">
Write/Update
</a>
</li>
<li class="md-nav__item">
<a href="../../prefix/" title="Prefix" class="md-nav__link">
Prefix
</a>
</li>
<li class="md-nav__item">
<a href="../../delete/" title="Delete" class="md-nav__link">
Delete
</a>
</li>
<li class="md-nav__item">
<a href="../../create/" title="Create" class="md-nav__link">
Create
</a>
</li>
<li class="md-nav__item">
<a href="../../convert/" title="Convert" class="md-nav__link">
Convert
</a>
</li>
<li class="md-nav__item">
<a href="../../merge/" title="Merge" class="md-nav__link">
Merge
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-sidebar md-sidebar--secondary" data-md-component="toc">
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--secondary">
<label class="md-nav__title" for="__toc">Table of contents</label>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="#keys-with-dots" title="Keys with dots" class="md-nav__link">
Keys with dots
</a>
</li>
<li class="md-nav__item">
<a href="#keys-and-values-with-leading-dashes" title="Keys (and values) with leading dashes" class="md-nav__link">
Keys (and values) with leading dashes
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content">
<article class="md-content__inner md-typeset">
<a href="https://github.com/mikefarah/yq/edit/master/docs/snippets/niche.md" title="Edit this page" class="md-icon md-content__icon">&#xE3C9;</a>
<h1>Niche</h1>
<h3 id="keys-with-dots">Keys with dots<a class="headerlink" href="#keys-with-dots" title="Permanent link">&para;</a></h3>
<p>When specifying a key that has a dot use key lookup indicator.</p>
<pre><code class="yaml">b:
foo.bar: 7
</code></pre>
<pre><code class="bash">yaml r sample.yaml 'b[foo.bar]'
</code></pre>
<pre><code class="bash">yaml w sample.yaml 'b[foo.bar]' 9
</code></pre>
<p>Any valid yaml key can be specified as part of a key lookup.</p>
<p>Note that the path is in quotes to avoid the square brackets being interpreted by your shell.</p>
<h3 id="keys-and-values-with-leading-dashes">Keys (and values) with leading dashes<a class="headerlink" href="#keys-and-values-with-leading-dashes" title="Permanent link">&para;</a></h3>
<p>If a key or value has leading dashes, yq won't know that you are passing a value as opposed to a flag (and you will get a 'bad flag syntax' error).</p>
<p>To fix that, you will need to tell it to stop processing flags by adding '--' after the last flag like so:</p>
<pre><code class="bash">yq n -t -- --key --value
</code></pre>
<p>Will result in</p>
<pre><code>--key: --value
</code></pre>
</article>
</div>
</div>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-footer-copyright">
powered by
<a href="https://www.mkdocs.org">MkDocs</a>
and
<a href="https://squidfunk.github.io/mkdocs-material/">
Material for MkDocs</a>
</div>
<div class="md-footer-social">
<link rel="stylesheet" href="../../assets/fonts/font-awesome.css">
<a href="https://github.com/mikefarah" class="md-footer-social__link fa fa-github"></a>
<a href="https://www.linkedin.com/in/mike-farah-b5a75b2/" class="md-footer-social__link fa fa-linkedin"></a>
</div>
</div>
</div>
</footer>
</div>
<script src="../../assets/javascripts/application.39abc4af.js"></script>
<script>app.initialize({version:"1.0.4",url:{base:"../.."}})</script>
</body>
</html>

View File

@@ -1,385 +0,0 @@
<!doctype html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="lang:clipboard.copy" content="Copy to clipboard">
<meta name="lang:clipboard.copied" content="Copied to clipboard">
<meta name="lang:search.language" content="en">
<meta name="lang:search.pipeline.stopwords" content="True">
<meta name="lang:search.pipeline.trimmer" content="True">
<meta name="lang:search.result.none" content="No matching documents">
<meta name="lang:search.result.one" content="1 matching document">
<meta name="lang:search.result.other" content="# matching documents">
<meta name="lang:search.tokenizer" content="[\s\-]+">
<link rel="shortcut icon" href="../../assets/images/favicon.png">
<meta name="generator" content="mkdocs-1.0.4, mkdocs-material-4.2.0">
<title>Works with json - Yq</title>
<link rel="stylesheet" href="../../assets/stylesheets/application.750b69bd.css">
<script src="../../assets/javascripts/modernizr.74668098.js"></script>
<link href="https://fonts.gstatic.com" rel="preconnect" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,400i,700|Roboto+Mono">
<style>body,input{font-family:"Roboto","Helvetica Neue",Helvetica,Arial,sans-serif}code,kbd,pre{font-family:"Roboto Mono","Courier New",Courier,monospace}</style>
<link rel="stylesheet" href="../../assets/fonts/material-icons.css">
</head>
<body dir="ltr">
<svg class="md-svg">
<defs>
<svg xmlns="http://www.w3.org/2000/svg" width="416" height="448"
viewBox="0 0 416 448" id="__github">
<path fill="currentColor" d="M160 304q0 10-3.125 20.5t-10.75 19-18.125
8.5-18.125-8.5-10.75-19-3.125-20.5 3.125-20.5 10.75-19 18.125-8.5
18.125 8.5 10.75 19 3.125 20.5zM320 304q0 10-3.125 20.5t-10.75
19-18.125 8.5-18.125-8.5-10.75-19-3.125-20.5 3.125-20.5 10.75-19
18.125-8.5 18.125 8.5 10.75 19 3.125 20.5zM360
304q0-30-17.25-51t-46.75-21q-10.25 0-48.75 5.25-17.75 2.75-39.25
2.75t-39.25-2.75q-38-5.25-48.75-5.25-29.5 0-46.75 21t-17.25 51q0 22 8
38.375t20.25 25.75 30.5 15 35 7.375 37.25 1.75h42q20.5 0
37.25-1.75t35-7.375 30.5-15 20.25-25.75 8-38.375zM416 260q0 51.75-15.25
82.75-9.5 19.25-26.375 33.25t-35.25 21.5-42.5 11.875-42.875 5.5-41.75
1.125q-19.5 0-35.5-0.75t-36.875-3.125-38.125-7.5-34.25-12.875-30.25-20.25-21.5-28.75q-15.5-30.75-15.5-82.75
0-59.25 34-99-6.75-20.5-6.75-42.5 0-29 12.75-54.5 27 0 47.5 9.875t47.25
30.875q36.75-8.75 77.25-8.75 37 0 70 8 26.25-20.5
46.75-30.25t47.25-9.75q12.75 25.5 12.75 54.5 0 21.75-6.75 42 34 40 34
99.5z" />
</svg>
</defs>
</svg>
<input class="md-toggle" data-md-toggle="drawer" type="checkbox" id="__drawer" autocomplete="off">
<input class="md-toggle" data-md-toggle="search" type="checkbox" id="__search" autocomplete="off">
<label class="md-overlay" data-md-component="overlay" for="__drawer"></label>
<header class="md-header" data-md-component="header">
<nav class="md-header-nav md-grid">
<div class="md-flex">
<div class="md-flex__cell md-flex__cell--shrink">
<a href="../.." title="Yq" class="md-header-nav__button md-logo">
<i class="md-icon">î Ś</i>
</a>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--menu md-header-nav__button" for="__drawer"></label>
</div>
<div class="md-flex__cell md-flex__cell--stretch">
<div class="md-flex__ellipsis md-header-nav__title" data-md-component="title">
<span class="md-header-nav__topic">
Yq
</span>
<span class="md-header-nav__topic">
Works with json
</span>
</div>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--search md-header-nav__button" for="__search"></label>
<div class="md-search" data-md-component="search" role="dialog">
<label class="md-search__overlay" for="__search"></label>
<div class="md-search__inner" role="search">
<form class="md-search__form" name="search">
<input type="text" class="md-search__input" name="query" placeholder="Search" autocapitalize="off" autocorrect="off" autocomplete="off" spellcheck="false" data-md-component="query" data-md-state="active">
<label class="md-icon md-search__icon" for="__search"></label>
<button type="reset" class="md-icon md-search__icon" data-md-component="reset" tabindex="-1">
&#xE5CD;
</button>
</form>
<div class="md-search__output">
<div class="md-search__scrollwrap" data-md-scrollfix>
<div class="md-search-result" data-md-component="result">
<div class="md-search-result__meta">
Type to start searching
</div>
<ol class="md-search-result__list"></ol>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<div class="md-header-nav__source">
<a href="https://github.com/mikefarah/yq/" title="Go to repository" class="md-source" data-md-source="github">
<div class="md-source__icon">
<svg viewBox="0 0 24 24" width="24" height="24">
<use xlink:href="#__github" width="24" height="24"></use>
</svg>
</div>
<div class="md-source__repository">
mikefarah/yq
</div>
</a>
</div>
</div>
</div>
</nav>
</header>
<div class="md-container">
<main class="md-main">
<div class="md-main__inner md-grid" data-md-component="container">
<div class="md-sidebar md-sidebar--primary" data-md-component="navigation">
<div class="md-sidebar__scrollwrap">
<div class="md-sidebar__inner">
<nav class="md-nav md-nav--primary" data-md-level="0">
<label class="md-nav__title md-nav__title--site" for="__drawer">
<a href="../.." title="Yq" class="md-nav__button md-logo">
<i class="md-icon">î Ś</i>
</a>
Yq
</label>
<div class="md-nav__source">
<a href="https://github.com/mikefarah/yq/" title="Go to repository" class="md-source" data-md-source="github">
<div class="md-source__icon">
<svg viewBox="0 0 24 24" width="24" height="24">
<use xlink:href="#__github" width="24" height="24"></use>
</svg>
</div>
<div class="md-source__repository">
mikefarah/yq
</div>
</a>
</div>
<ul class="md-nav__list" data-md-scrollfix>
<li class="md-nav__item">
<a href="../.." title="Install" class="md-nav__link">
Install
</a>
</li>
<li class="md-nav__item">
<a href="../../read/" title="Read" class="md-nav__link">
Read
</a>
</li>
<li class="md-nav__item">
<a href="../../write/" title="Write/Update" class="md-nav__link">
Write/Update
</a>
</li>
<li class="md-nav__item">
<a href="../../prefix/" title="Prefix" class="md-nav__link">
Prefix
</a>
</li>
<li class="md-nav__item">
<a href="../../delete/" title="Delete" class="md-nav__link">
Delete
</a>
</li>
<li class="md-nav__item">
<a href="../../create/" title="Create" class="md-nav__link">
Create
</a>
</li>
<li class="md-nav__item">
<a href="../../convert/" title="Convert" class="md-nav__link">
Convert
</a>
</li>
<li class="md-nav__item">
<a href="../../merge/" title="Merge" class="md-nav__link">
Merge
</a>
</li>
</ul>
</nav>
</div>
</div>
</div>
<div class="md-content">
<article class="md-content__inner md-typeset">
<a href="https://github.com/mikefarah/yq/edit/master/docs/snippets/works_with_json.md" title="Edit this page" class="md-icon md-content__icon">&#xE3C9;</a>
<h1>Works with json</h1>
<p>This command can take a json file as input too, and will output yaml unless specified to export as json (-j)</p>
</article>
</div>
</div>
</main>
<footer class="md-footer">
<div class="md-footer-meta md-typeset">
<div class="md-footer-meta__inner md-grid">
<div class="md-footer-copyright">
powered by
<a href="https://www.mkdocs.org">MkDocs</a>
and
<a href="https://squidfunk.github.io/mkdocs-material/">
Material for MkDocs</a>
</div>
<div class="md-footer-social">
<link rel="stylesheet" href="../../assets/fonts/font-awesome.css">
<a href="https://github.com/mikefarah" class="md-footer-social__link fa fa-github"></a>
<a href="https://www.linkedin.com/in/mike-farah-b5a75b2/" class="md-footer-social__link fa fa-linkedin"></a>
</div>
</div>
</div>
</footer>
</div>
<script src="../../assets/javascripts/application.39abc4af.js"></script>
<script>app.initialize({version:"1.0.4",url:{base:"../.."}})</script>
</body>
</html>

View File

@@ -251,6 +251,18 @@
<li class="md-nav__item">
<a href="../path_expressions/" title="Path Expressions" class="md-nav__link">
Path Expressions
</a>
</li>
@@ -359,20 +371,6 @@
</li> </li>
<li class="md-nav__item">
<a href="#keys-with-dots" title="Keys with dots" class="md-nav__link">
Keys with dots
</a>
</li>
<li class="md-nav__item">
<a href="#keys-and-values-with-leading-dashes" title="Keys (and values) with leading dashes" class="md-nav__link">
Keys (and values) with leading dashes
</a>
</li>
@@ -546,20 +544,6 @@
</li> </li>
<li class="md-nav__item">
<a href="#keys-with-dots" title="Keys with dots" class="md-nav__link">
Keys with dots
</a>
</li>
<li class="md-nav__item">
<a href="#keys-and-values-with-leading-dashes" title="Keys (and values) with leading dashes" class="md-nav__link">
Keys (and values) with leading dashes
</a>
</li>
@@ -581,9 +565,11 @@
<h1>Write/Update</h1> <h1>Write/Update</h1>
<pre><code>yq w &lt;yaml_file&gt; &lt;path&gt; &lt;new value&gt; <pre><code>yq w &lt;yaml_file&gt; &lt;path_expression&gt; &lt;new value&gt;
</code></pre> </code></pre>
<p>Updates all the matching nodes of path expression to the supplied value.</p>
<p>See docs for <a href="../path_expressions/">path expression</a> for more details.</p>
<h3 id="to-stdout">To Stdout<a class="headerlink" href="#to-stdout" title="Permanent link">&para;</a></h3> <h3 id="to-stdout">To Stdout<a class="headerlink" href="#to-stdout" title="Permanent link">&para;</a></h3>
<p>Given a sample.yaml file of:</p> <p>Given a sample.yaml file of:</p>
<pre><code class="yaml">b: <pre><code class="yaml">b:
@@ -808,30 +794,6 @@ b.e[+].name: Howdy Partner
<pre><code class="yaml">my: <pre><code class="yaml">my:
path: -3 path: -3
</code></pre> </code></pre>
<h3 id="keys-with-dots">Keys with dots<a class="headerlink" href="#keys-with-dots" title="Permanent link">&para;</a></h3>
<p>When specifying a key that has a dot use key lookup indicator.</p>
<pre><code class="yaml">b:
foo.bar: 7
</code></pre>
<pre><code class="bash">yaml r sample.yaml 'b[foo.bar]'
</code></pre>
<pre><code class="bash">yaml w sample.yaml 'b[foo.bar]' 9
</code></pre>
<p>Any valid yaml key can be specified as part of a key lookup.</p>
<p>Note that the path is in quotes to avoid the square brackets being interpreted by your shell.</p>
<h3 id="keys-and-values-with-leading-dashes">Keys (and values) with leading dashes<a class="headerlink" href="#keys-and-values-with-leading-dashes" title="Permanent link">&para;</a></h3>
<p>If a key or value has leading dashes, yq won't know that you are passing a value as opposed to a flag (and you will get a 'bad flag syntax' error).</p>
<p>To fix that, you will need to tell it to stop processing flags by adding '--' after the last flag like so:</p>
<pre><code class="bash">yq n -t -- --key --value
</code></pre>
<p>Will result in</p>
<p><code>`
--key: --value</code></p>
@@ -852,7 +814,7 @@ b.e[+].name: Howdy Partner
<div class="md-footer-nav"> <div class="md-footer-nav">
<nav class="md-footer-nav__inner md-grid"> <nav class="md-footer-nav__inner md-grid">
<a href="../read/" title="Read" class="md-flex md-footer-nav__link md-footer-nav__link--prev" rel="prev"> <a href="../path_expressions/" title="Path Expressions" class="md-flex md-footer-nav__link md-footer-nav__link--prev" rel="prev">
<div class="md-flex__cell md-flex__cell--shrink"> <div class="md-flex__cell md-flex__cell--shrink">
<i class="md-icon md-icon--arrow-back md-footer-nav__button"></i> <i class="md-icon md-icon--arrow-back md-footer-nav__button"></i>
</div> </div>
@@ -861,7 +823,7 @@ b.e[+].name: Howdy Partner
<span class="md-footer-nav__direction"> <span class="md-footer-nav__direction">
Previous Previous
</span> </span>
Read Path Expressions
</span> </span>
</div> </div>
</a> </a>

View File

@@ -7,3 +7,5 @@
- lala - lala
- land - land
serial: 1 serial: 1
- become: false
gather_facts: true

View File

@@ -1,2 +1,4 @@
a: simple a: simple # just the best
b: [1, 2] b: [1, 2]
c:
test: 1

View File

@@ -1,4 +1,7 @@
a: other a: other # better than the original
b: [3, 4] b: [3, 4]
c: c:
toast: leave
test: 1 test: 1
tell: 1
taco: cool

View File

@@ -1,2 +1,7 @@
b.c: cat - command: update
b.e[+].name: Mike Farah path: b.c
value:
#great
things: frog # wow!
- command: delete
path: b.d

View File

@@ -0,0 +1,19 @@
foo: &foo
a: original
thing: coolasdf
thirsty: yep
bar: &bar
b: 2
thing: coconut
c: oldbar
foobarList:
<<: [*foo,*bar]
c: newbar
foobar:
<<: *foo
thirty: well beyond
thing: ice
c: 3

View File

@@ -1,9 +1,13 @@
a: Easy! as one two three bob:
b: item:
c: 2 cats: bananas
d: [3, 4] something:
e: cats: lemons
- name: fred itemThing:
value: 3 cats: bananas
- name: sam item2:
value: 4 cats: apples
thing:
cats: oranges
my:
path: -3

View File

@@ -1,9 +1,2 @@
a: Easy! as one two three
b: b:
c: things c: things
d: whatever
things:
thing1:
cat: 'fred'
thing2:
cat: 'sam'

View File

@@ -1 +1,2 @@
[4,5] - 4
- 5

View File

@@ -0,0 +1,4 @@
foo: &foo
a: 1
foobar: *foo

9
go.mod
View File

@@ -1,12 +1,13 @@
module github.com/mikefarah/yq/v2 module github.com/mikefarah/yq/v3
require ( require (
github.com/mikefarah/yaml/v2 v2.4.0 github.com/mikefarah/yaml/v2 v2.4.0 // indirect
github.com/pkg/errors v0.8.1 github.com/pkg/errors v0.8.1
github.com/spf13/cobra v0.0.5 github.com/spf13/cobra v0.0.5
golang.org/x/tools v0.0.0-20191030203535-5e247c9ad0a0 // indirect golang.org/x/tools v0.0.0-20191213221258-04c2e8eff935 // indirect
gopkg.in/imdario/mergo.v0 v0.3.7 gopkg.in/imdario/mergo.v0 v0.3.7 // indirect
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2
) )
go 1.13 go 1.13

12
go.sum
View File

@@ -10,8 +10,11 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mikefarah/yaml v2.1.0+incompatible h1:nu2cqmzk4WlWJNgnevY88faMcdrDzYGcsUjYFxEpB7Y=
github.com/mikefarah/yaml/v2 v2.4.0 h1:eYqfooY0BnvKTJxr7+ABJs13n3dg9n347GScDaU2Lww= github.com/mikefarah/yaml/v2 v2.4.0 h1:eYqfooY0BnvKTJxr7+ABJs13n3dg9n347GScDaU2Lww=
github.com/mikefarah/yaml/v2 v2.4.0/go.mod h1:ahVqZF4n1W4NqwvVnZzC4es67xsW9uR/RRf2RRxieJU= github.com/mikefarah/yaml/v2 v2.4.0/go.mod h1:ahVqZF4n1W4NqwvVnZzC4es67xsW9uR/RRf2RRxieJU=
github.com/mikefarah/yq v2.4.0+incompatible h1:oBxbWy8R9hI3BIUUxEf0CzikWa2AgnGrGhvGQt5jgjk=
github.com/mikefarah/yq/v2 v2.4.1 h1:tajDonaFK6WqitSZExB6fKlWQy/yCkptqxh2AXEe3N4=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
@@ -32,14 +35,21 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20191030203535-5e247c9ad0a0 h1:s5lp4ug7qHzUccgyFdjsX7OZDzHXRaePrF3B3vmUiuM= golang.org/x/tools v0.0.0-20191030203535-5e247c9ad0a0 h1:s5lp4ug7qHzUccgyFdjsX7OZDzHXRaePrF3B3vmUiuM=
golang.org/x/tools v0.0.0-20191030203535-5e247c9ad0a0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191030203535-5e247c9ad0a0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191213221258-04c2e8eff935 h1:kJQZhwFzSwJS2BxboKjdZzWczQOZx8VuH7Y8hhuGUtM=
golang.org/x/tools v0.0.0-20191213221258-04c2e8eff935/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/imdario/mergo.v0 v0.3.7 h1:QDotlIZtaO/p+Um0ok18HRTpq5i5/SAk/qprsor+9c8= gopkg.in/imdario/mergo.v0 v0.3.7 h1:QDotlIZtaO/p+Um0ok18HRTpq5i5/SAk/qprsor+9c8=
@@ -48,3 +58,5 @@ gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 h1:6D+BvnJ/j6e222UW
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog= gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2 h1:XZx7nhd5GMaZpmDaEHFVafUZC7ya0fuo7cSJ3UCKYmM=
gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -5,6 +5,7 @@ theme: 'material'
pages: pages:
- Install: index.md - Install: index.md
- Read: read.md - Read: read.md
- Path Expressions: path_expressions.md
- Write/Update: write.md - Write/Update: write.md
- Prefix: prefix.md - Prefix: prefix.md
- Delete: delete.md - Delete: delete.md

View File

@@ -1,5 +1,7 @@
### Yaml to Json ## Yaml to Json
To convert output to json, use the --tojson (or -j) flag. This can only be used with the read command. To convert output to json, use the --tojson (or -j) flag. This is supported by all commands.
Each matching yaml node will be converted to json and printed out on a separate line.
Given a sample.yaml file of: Given a sample.yaml file of:
```yaml ```yaml
@@ -8,7 +10,7 @@ b:
``` ```
then then
```bash ```bash
yq r -j sample.yaml b.c yq r -j sample.yaml
``` ```
will output will output
@@ -16,7 +18,25 @@ will output
{"b":{"c":2}} {"b":{"c":2}}
``` ```
### Json to Yaml Given a sample.yaml file of:
```yaml
bob:
c: 2
bab:
c: 5
```
then
```bash
yq r -j sample.yaml b*
```
will output
```json
{"c":2}
{"c":5}
```
## Json to Yaml
To read in json, just pass in a json file instead of yaml, it will just work :) To read in json, just pass in a json file instead of yaml, it will just work :)
e.g given a json file e.g given a json file

View File

@@ -1,10 +1,12 @@
```
yq n <path_expression> <new value>
```
Yaml files can be created using the 'new' command. This works in the same way as the write command, but you don't pass in an existing Yaml file. Currently this does not support creating multiple documents in a single yaml file. Yaml files can be created using the 'new' command. This works in the same way as the write command, but you don't pass in an existing Yaml file. Currently this does not support creating multiple documents in a single yaml file.
``` See docs for [path expression](path_expressions.md)
yq n <path> <new value>
```
### Creating a simple yaml file ## Creating a simple yaml file
```bash ```bash
yq n b.c cat yq n b.c cat
``` ```
@@ -14,13 +16,16 @@ b:
c: cat c: cat
``` ```
### Creating using a create script ## Creating using a create script
Create scripts follow the same format as the update scripts. Create scripts follow the same format as the update scripts.
Given a script create_instructions.yaml of: Given a script create_instructions.yaml of:
```yaml ```yaml
b.c: 3 - command: update
b.e[+].name: Howdy Partner path: b.c
value:
#great
things: frog # wow!
``` ```
then then
@@ -30,15 +35,13 @@ yq n -s create_instructions.yaml
will output: will output:
```yaml ```yaml
b: b:
c: 3 c:
e: #great
- name: Howdy Partner things: frog # wow!
``` ```
You can also pipe the instructions in: You can also pipe the instructions in:
```bash ```bash
cat create_instructions.yaml | yq n -s - cat create_instructions.yaml | yq n -s -
``` ```
{!snippets/niche.md!}

View File

@@ -1,8 +1,13 @@
``` ```
yq d <yaml_file> <path_to_delete> yq delete <yaml_file|-> <path_expression>
``` ```
### To Stdout The delete command will delete all the matching nodes for the path expression in the given yaml input.
See docs for [path expression](path_expressions.md) for more details.
## Deleting from a simple document
Given a sample.yaml file of: Given a sample.yaml file of:
```yaml ```yaml
b: b:
@@ -13,141 +18,29 @@ then
```bash ```bash
yq d sample.yaml b.c yq d sample.yaml b.c
``` ```
will output: will output
```yaml ```yaml
b: b:
apples: green apples: green
``` ```
### From STDIN ## From STDIN
Use "-" (without quotes) in-place of a file name if you wish to pipe in input from STDIN.
```bash ```bash
cat sample.yaml | yq d - b.c cat sample.yaml | yq d - b.c
``` ```
### Deleting array elements ## Deleting in-place
Given a sample.yaml file of:
```yaml
b:
c:
- 1
- 2
- 3
```
then
```bash
yq d sample.yaml 'b.c[1]'
```
will output:
```yaml
b:
c:
- 1
- 3
```
### Deleting nodes in-place
Given a sample.yaml file of:
```yaml
b:
c: 2
apples: green
```
then
```bash ```bash
yq d -i sample.yaml b.c yq d -i sample.yaml b.c
``` ```
will update the sample.yaml file so that the 'c' node is deleted will update the sample.yaml file so that the 'c' node is deleted
### Splat ## Multiple Documents
Given a sample.yaml file of:
```yaml
---
bob:
item1:
cats: bananas
dogs: woof
item2:
cats: apples
dogs: woof2
thing:
cats: oranges
dogs: woof3
```
then
```bash
yq d sample.yaml bob.*.cats
```
will output:
```yaml
---
bob:
item1:
dogs: woof
item2:
dogs: woof2
thing:
dogs: woof3
```
### Prefix Splat ### Delete from single document
Given a sample.yaml file of:
```yaml
---
bob:
item1:
cats: bananas
dogs: woof
item2:
cats: apples
dogs: woof2
thing:
cats: oranges
dogs: woof3
```
then
```bash
yq d sample.yaml bob.item*.cats
```
will output:
```yaml
---
bob:
item1:
dogs: woof
item2:
dogs: woof2
thing:
cats: oranges
dogs: woof3
```
### Array Splat
Given a sample.yaml file of:
```yaml
---
bob:
- cats: bananas
dogs: woof
- cats: apples
dogs: woof2
- cats: oranges
dogs: woof3
```
then
```bash
yq d sample.yaml bob.[*].cats
```
will output:
```yaml
---
bob:
- dogs: woof
- dogs: woof2
- dogs: woof3
```
### Multiple Documents - delete from single document
Given a sample.yaml file of: Given a sample.yaml file of:
```yaml ```yaml
something: else something: else
@@ -170,7 +63,7 @@ b:
c: 2 c: 2
``` ```
### Multiple Documents - delete from all documents ### Delete from all documents
Given a sample.yaml file of: Given a sample.yaml file of:
```yaml ```yaml
something: else something: else
@@ -191,7 +84,3 @@ something: else
b: b:
c: 2 c: 2
``` ```
Note that '*' is in quotes to avoid being interpreted by your shell.
{!snippets/niche.md!}

View File

@@ -20,7 +20,7 @@ sudo apt install yq -y
``` ```
or, [Download latest binary](https://github.com/mikefarah/yq/releases/latest) or alternatively: or, [Download latest binary](https://github.com/mikefarah/yq/releases/latest) or alternatively:
``` ```
go get gopkg.in/mikefarah/yq.v2 GO111MODULE=on go get github.com/mikefarah/yq/v3
``` ```
[View on GitHub](https://github.com/mikefarah/yq) [View on GitHub](https://github.com/mikefarah/yq)

View File

@@ -6,7 +6,7 @@ yq m <yaml_file> <path>...
``` ```
### To Stdout ## Merge example
Given a data1.yaml file of: Given a data1.yaml file of:
```yaml ```yaml
a: simple a: simple
@@ -30,25 +30,13 @@ c:
test: 1 test: 1
``` ```
### Updating files in-place ## Updating files in-place
Given a data1.yaml file of:
```yaml
a: simple
b: [1, 2]
```
and data2.yaml file of:
```yaml
a: other
c:
test: 1
```
then
```bash ```bash
yq m -i data1.yaml data2.yaml yq m -i data1.yaml data2.yaml
``` ```
will update the data1.yaml file so that the value of 'c' is 'test: 1'. will update the data1.yaml file with the merged result.
### Overwrite values ## Overwrite values
Given a data1.yaml file of: Given a data1.yaml file of:
```yaml ```yaml
a: simple a: simple
@@ -102,7 +90,7 @@ d: false
Notice that 'b' does not result in the merging of the values within an array. Notice that 'b' does not result in the merging of the values within an array.
### Append values with arrays ## Append values with arrays
Given a data1.yaml file of: Given a data1.yaml file of:
```yaml ```yaml
a: simple a: simple
@@ -133,9 +121,8 @@ d: hi
Note that the 'b' array has concatenated the values from the second data file. Also note that other map keys are not overridden (field a). Note that the 'b' array has concatenated the values from the second data file. Also note that other map keys are not overridden (field a).
Append cannot be used with overwrite, if both flags are given then append is ignored. ## Multiple Documents
### Merge into single document
### Multiple Documents - merge into single document
Currently yq only has multi-document support for the _first_ document being merged into. The remaining yaml files will have their first document selected. Currently yq only has multi-document support for the _first_ document being merged into. The remaining yaml files will have their first document selected.
Given a data1.yaml file of: Given a data1.yaml file of:
@@ -161,7 +148,7 @@ a: simple
b: dog b: dog
``` ```
### Multiple Documents - merge into all documents ### Merge into all documents
Currently yq only has multi-document support for the _first_ document being merged into. The remaining yaml files will have their first document selected. Currently yq only has multi-document support for the _first_ document being merged into. The remaining yaml files will have their first document selected.
Given a data1.yaml file of: Given a data1.yaml file of:

208
mkdocs/path_expressions.md Normal file
View File

@@ -0,0 +1,208 @@
Path expressions are used to deeply navigate and match particular yaml nodes.
_As a general rule, you should wrap paths in quotes to prevent your CLI from processing '*, []' and other special characters._
## Simple expressions
### Maps
a.b.c
```yaml
a:
b:
c: thing # MATCHES
```
### Arrays
a.b[1].c
```yaml
a:
b:
- c: thing0
- c: thing1 # MATCHES
- c: thing2
```
#### Appending to arrays
(e.g. when using the write command)
a.b[+].c
```yaml
a:
b:
- c: thing0
```
Will add a new entry:
```yaml
a:
b:
- c: thing0
- c: thing1 # NEW entry from [+] on B array.
```
## Splat
### Maps
a.*.c
```yaml
a:
b1:
c: thing # MATCHES
d: whatever
b2:
c: thing # MATCHES
f: something irrelevant
```
#### Prefix splat
bob.item*.cats
```yaml
bob:
item:
cats: bananas # MATCHES
something:
cats: lemons
itemThing:
cats: more bananas # MATCHES
item2:
cats: apples # MATCHES
thing:
cats: oranges
```
### Arrays
a.b[*].c
```yaml
a:
b:
- c: thing0 # MATCHES
d: what..ever
- c: thing1 # MATCHES
d: blarh
- c: thing2 # MATCHES
f: thingamabob
```
## Deep Splat
'**' will match arbitrary nodes for both maps and arrays:
a.**.c
```yaml
a:
b1:
c: thing1 # MATCHES
d: cat cat
b2:
c: thing2 # MATCHES
d: dog dog
b3:
d:
- f:
c: thing3 # MATCHES
d: beep
- f:
g:
c: thing4 # MATCHES
d: boop
- d: mooo
```
## Search by children nodes
a.(b.d==cat).b.c
```yaml
a:
- b:
c: thing0
d: leopard
ba: fast
- b:
c: thing1 # MATCHES
d: cat
ba: meowy
- b:
c: thing2
d: caterpillar
ba: icky
- b:
c: thing3 # MATCHES
d: cat
ba: also meowy
```
### With prefixes
a.(b.d==cat*).c
```yaml
a:
- b:
c: thing0
d: leopard
ba: fast
- b:
c: thing1 # MATCHES
d: cat
ba: meowy
- b:
c: thing2 # MATCHES
d: caterpillar
ba: icky
- b:
c: thing3 # MATCHES
d: cat
ba: also meowy
```
## Special Characters
### Keys with dots
When specifying a key that has a dot use key lookup indicator.
```yaml
b:
foo.bar: 7
```
```bash
yaml r sample.yaml 'b[foo.bar]'
```
```bash
yaml w sample.yaml 'b[foo.bar]' 9
```
Any valid yaml key can be specified as part of a key lookup.
Note that the path is in quotes to avoid the square brackets being interpreted by your shell.
### Keys (and values) with leading dashes
The flag terminator needs to be used to stop the app from attempting to parse the subsequent arguments as flags, if they start if a dash.
```bash
yq n -j -- --key --value
```
Will result in
```
--key: --value
```

View File

@@ -1,28 +1,12 @@
Paths can be prefixed using the 'prefix' command.
The complete yaml content will be nested inside the new prefix path.
``` ```
yq p <yaml_file> <path> yq p <yaml_file> <path>
``` ```
### To Stdout Prefixes a yaml document with the given path expression. The complete yaml content will be nested inside the new prefix path.
Given a data1.yaml file of:
```yaml
a: simple
b: [1, 2]
```
then
```bash
yq p data1.yaml c
```
will output:
```yaml
c:
a: simple
b: [1, 2]
```
### Arbitrary depth See docs for [path expression](path_expressions.md) for more details.
## Prefix a document
Given a data1.yaml file of: Given a data1.yaml file of:
```yaml ```yaml
a: a:
@@ -40,19 +24,14 @@ c:
b: [1, 2] b: [1, 2]
``` ```
### Updating files in-place ## Updating files in-place
Given a data1.yaml file of:
```yaml
a: simple
b: [1, 2]
```
then
```bash ```bash
yq p -i data1.yaml c yq p -i data1.yaml c
``` ```
will update the data1.yaml file so that the path 'c' is prefixed to all other paths. will update the data1.yaml file so that the path 'c' prefixes the document.
### Multiple Documents - prefix a single document ## Multiple Documents
### Prefix a single document
Given a data1.yaml file of: Given a data1.yaml file of:
```yaml ```yaml
something: else something: else
@@ -73,7 +52,7 @@ c:
b: cat b: cat
``` ```
### Multiple Documents - prefix all documents ### Prefix all documents
Given a data1.yaml file of: Given a data1.yaml file of:
```yaml ```yaml
something: else something: else

View File

@@ -1,10 +1,14 @@
``` ```
yq r <yaml_file|json_file> <path> yq r <yaml_file|json_file> <path_expression>
``` ```
{!snippets/works_with_json.md!} TALK PRINTING ABOUT KEYS AND VALUES
### Basic Returns the matching nodes of the path expression for the given yaml file (or STDIN).
See docs for [path expression](path_expressions.md) for more details.
## Basic
Given a sample.yaml file of: Given a sample.yaml file of:
```yaml ```yaml
b: b:
@@ -16,59 +20,16 @@ yq r sample.yaml b.c
``` ```
will output the value of '2'. will output the value of '2'.
### From Stdin ## From Stdin
Given a sample.yaml file of: Given a sample.yaml file of:
```bash ```bash
cat sample.yaml | yq r - b.c cat sample.yaml | yq r - b.c
``` ```
will output the value of '2'. will output the value of '2'.
### Splat
Given a sample.yaml file of:
```yaml
---
bob:
item1:
cats: bananas
item2:
cats: apples
thing:
cats: oranges
```
then
```bash
yq r sample.yaml bob.*.cats
```
will output
```yaml
- bananas
- apples
- oranges
```
### Prefix Splat ## Multiple Documents
Given a sample.yaml file of: ### Reading from a single document
```yaml
---
bob:
item1:
cats: bananas
item2:
cats: apples
thing:
cats: oranges
```
then
```bash
yq r sample.yaml bob.item*.cats
```
will output
```yaml
- bananas
- apples
```
### Multiple Documents - specify a single document
Given a sample.yaml file of: Given a sample.yaml file of:
```yaml ```yaml
something: else something: else
@@ -82,7 +43,7 @@ yq r -d1 sample.yaml b.c
``` ```
will output the value of '2'. will output the value of '2'.
### Multiple Documents - read all documents ### Read from all documents
Reading all documents will return the result as an array. This can be converted to json using the '-j' flag if desired. Reading all documents will return the result as an array. This can be converted to json using the '-j' flag if desired.
Given a sample.yaml file of: Given a sample.yaml file of:
@@ -105,46 +66,4 @@ will output:
- Fred - Fred
- Stella - Stella
- Android - Android
``` ```
### Arrays
You can give an index to access a specific element:
e.g.: given a sample file of
```yaml
b:
e:
- name: fred
value: 3
- name: sam
value: 4
```
then
```
yq r sample.yaml 'b.e[1].name'
```
will output 'sam'
Note that the path is in quotes to avoid the square brackets being interpreted by your shell.
### Array Splat
e.g.: given a sample file of
```yaml
b:
e:
- name: fred
value: 3
- name: sam
value: 4
```
then
```
yq r sample.yaml 'b.e[*].name'
```
will output:
```
- fred
- sam
```
Note that the path is in quotes to avoid the square brackets being interpreted by your shell.
{!snippets/niche.md!}

View File

@@ -1,35 +0,0 @@
### Keys with dots
When specifying a key that has a dot use key lookup indicator.
```yaml
b:
foo.bar: 7
```
```bash
yaml r sample.yaml 'b[foo.bar]'
```
```bash
yaml w sample.yaml 'b[foo.bar]' 9
```
Any valid yaml key can be specified as part of a key lookup.
Note that the path is in quotes to avoid the square brackets being interpreted by your shell.
### Keys (and values) with leading dashes
If a key or value has leading dashes, yq won't know that you are passing a value as opposed to a flag (and you will get a 'bad flag syntax' error).
To fix that, you will need to tell it to stop processing flags by adding '--' after the last flag like so:
```bash
yq n -t -- --key --value
```
Will result in
```
--key: --value
```

View File

@@ -1 +0,0 @@
This command can take a json file as input too, and will output yaml unless specified to export as json (-j)

View File

@@ -1,8 +1,12 @@
``` ```
yq w <yaml_file> <path> <new value> yq w <yaml_file> <path_expression> <new value>
``` ```
### To Stdout Updates all the matching nodes of path expression to the supplied value.
See docs for [path expression](path_expressions.md) for more details.
## Basic
Given a sample.yaml file of: Given a sample.yaml file of:
```yaml ```yaml
b: b:
@@ -18,12 +22,18 @@ b:
c: cat c: cat
``` ```
### From STDIN ### Updating files in-place
```bash
yq w -i sample.yaml b.c cat
```
will update the sample.yaml file so that the value of 'c' is cat.
## From STDIN
```bash ```bash
cat sample.yaml | yq w - b.c blah cat sample.yaml | yq w - b.c blah
``` ```
### Adding new fields ## Adding new fields
Any missing fields in the path will be created on the fly. Any missing fields in the path will be created on the fly.
Given a sample.yaml file of: Given a sample.yaml file of:
@@ -43,85 +53,7 @@ b:
- new thing - new thing
``` ```
### Splat ## Appending value to an array field
Given a sample.yaml file of:
```yaml
---
bob:
item1:
cats: bananas
item2:
cats: apples
thing:
cats: oranges
```
then
```bash
yq w sample.yaml bob.*.cats meow
```
will output:
```yaml
---
bob:
item1:
cats: meow
item2:
cats: meow
thing:
cats: meow
```
### Prefix Splat
Given a sample.yaml file of:
```yaml
---
bob:
item1:
cats: bananas
item2:
cats: apples
thing:
cats: oranges
```
then
```bash
yq w sample.yaml bob.item*.cats meow
```
will output:
```yaml
---
bob:
item1:
cats: meow
item2:
cats: meow
thing:
cats: oranges
```
### Array Splat
Given a sample.yaml file of:
```yaml
---
bob:
- cats: bananas
- cats: apples
- cats: oranges
```
then
```bash
yq w sample.yaml bob[*].cats meow
```
will output:
```yaml
---
bob:
- cats: meow
- cats: meow
- cats: meow
```
### Appending value to an array field
Given a sample.yaml file of: Given a sample.yaml file of:
```yaml ```yaml
b: b:
@@ -146,7 +78,8 @@ b:
Note that the path is in quotes to avoid the square brackets being interpreted by your shell. Note that the path is in quotes to avoid the square brackets being interpreted by your shell.
### Multiple Documents - update a single document ## Multiple Documents
### Update a single document
Given a sample.yaml file of: Given a sample.yaml file of:
```yaml ```yaml
something: else something: else
@@ -166,7 +99,7 @@ b:
c: 5 c: 5
``` ```
### Multiple Documents - update all documents ### Update all documents
Given a sample.yaml file of: Given a sample.yaml file of:
```yaml ```yaml
something: else something: else
@@ -188,22 +121,11 @@ b:
c: 5 c: 5
``` ```
Note that '*' is in quotes to avoid being interpreted by your shell. UPDATE THIS
UPDATE THIS
INCLUDE DELETE EXAMPLE
### Updating files in-place ## Updating multiple values with a script
Given a sample.yaml file of:
```yaml
b:
c: 2
```
then
```bash
yq w -i sample.yaml b.c cat
```
will update the sample.yaml file so that the value of 'c' is cat.
### Updating multiple values with a script
Given a sample.yaml file of: Given a sample.yaml file of:
```yaml ```yaml
b: b:
@@ -233,18 +155,3 @@ And, of course, you can pipe the instructions in using '-':
```bash ```bash
cat update_instructions.yaml | yq w -s - sample.yaml cat update_instructions.yaml | yq w -s - sample.yaml
``` ```
### Values starting with a hyphen (or dash)
The flag terminator needs to be used to stop the app from attempting to parse the subsequent arguments as flags:
```
yq w -- my.path -3
```
will output
```yaml
my:
path: -3
```
{!snippets/niche.md!}

View File

@@ -1,54 +0,0 @@
package marshal
import (
"encoding/json"
"fmt"
"strconv"
yaml "github.com/mikefarah/yaml/v2"
)
type JsonConverter interface {
JsonToString(context interface{}) (string, error)
}
type jsonConverter struct{}
func NewJsonConverter() JsonConverter {
return &jsonConverter{}
}
func (j *jsonConverter) JsonToString(context interface{}) (string, error) {
out, err := json.Marshal(j.toJSON(context))
if err != nil {
return "", fmt.Errorf("error printing yaml as json: %v", err)
}
return string(out), nil
}
func (j *jsonConverter) toJSON(context interface{}) interface{} {
switch context := context.(type) {
case []interface{}:
oldArray := context
newArray := make([]interface{}, len(oldArray))
for index, value := range oldArray {
newArray[index] = j.toJSON(value)
}
return newArray
case yaml.MapSlice:
oldMap := context
newMap := make(map[string]interface{})
for _, entry := range oldMap {
if str, ok := entry.Key.(string); ok {
newMap[str] = j.toJSON(entry.Value)
} else if i, ok := entry.Key.(int); ok {
newMap[strconv.Itoa(i)] = j.toJSON(entry.Value)
} else if b, ok := entry.Key.(bool); ok {
newMap[strconv.FormatBool(b)] = j.toJSON(entry.Value)
}
}
return newMap
default:
return context
}
}

View File

@@ -1,48 +0,0 @@
package marshal
import (
"testing"
"github.com/mikefarah/yq/v2/test"
)
func TestJsonToString(t *testing.T) {
var data = test.ParseData(`
---
b:
c: 2
`)
got, _ := NewJsonConverter().JsonToString(data)
test.AssertResult(t, "{\"b\":{\"c\":2}}", got)
}
func TestJsonToString_withIntKey(t *testing.T) {
var data = test.ParseData(`
---
b:
2: c
`)
got, _ := NewJsonConverter().JsonToString(data)
test.AssertResult(t, `{"b":{"2":"c"}}`, got)
}
func TestJsonToString_withBoolKey(t *testing.T) {
var data = test.ParseData(`
---
b:
false: c
`)
got, _ := NewJsonConverter().JsonToString(data)
test.AssertResult(t, `{"b":{"false":"c"}}`, got)
}
func TestJsonToString_withArray(t *testing.T) {
var data = test.ParseData(`
---
b:
- item: one
- item: two
`)
got, _ := NewJsonConverter().JsonToString(data)
test.AssertResult(t, "{\"b\":[{\"item\":\"one\"},{\"item\":\"two\"}]}", got)
}

View File

@@ -1,43 +0,0 @@
package marshal
import (
"strings"
yaml "github.com/mikefarah/yaml/v2"
errors "github.com/pkg/errors"
)
type YamlConverter interface {
YamlToString(context interface{}, trimOutput bool) (string, error)
}
type yamlConverter struct{}
func NewYamlConverter() YamlConverter {
return &yamlConverter{}
}
func (y *yamlConverter) YamlToString(context interface{}, trimOutput bool) (string, error) {
switch context := context.(type) {
case string:
return context, nil
default:
return y.marshalContext(context, trimOutput)
}
}
func (y *yamlConverter) marshalContext(context interface{}, trimOutput bool) (string, error) {
out, err := yaml.Marshal(context)
if err != nil {
return "", errors.Wrap(err, "error printing yaml")
}
outStr := string(out)
// trim the trailing new line as it's easier for a script to add
// it in if required than to remove it
if trimOutput {
return strings.Trim(outStr, "\n "), nil
}
return outStr, nil
}

View File

@@ -1,52 +0,0 @@
package marshal
import (
"testing"
"github.com/mikefarah/yq/v2/test"
)
func TestYamlToString(t *testing.T) {
var raw = `b:
c: 2
`
var data = test.ParseData(raw)
got, _ := NewYamlConverter().YamlToString(data, false)
test.AssertResult(t, raw, got)
}
func TestYamlToString_withTrim(t *testing.T) {
var raw = `b:
c: 2`
var data = test.ParseData(raw)
got, _ := NewYamlConverter().YamlToString(data, true)
test.AssertResult(t, raw, got)
}
func TestYamlToString_withIntKey(t *testing.T) {
var raw = `b:
2: c
`
var data = test.ParseData(raw)
got, _ := NewYamlConverter().YamlToString(data, false)
test.AssertResult(t, raw, got)
}
func TestYamlToString_withBoolKey(t *testing.T) {
var raw = `b:
false: c
`
var data = test.ParseData(raw)
got, _ := NewYamlConverter().YamlToString(data, false)
test.AssertResult(t, raw, got)
}
func TestYamlToString_withArray(t *testing.T) {
var raw = `b:
- item: one
- item: two
`
var data = test.ParseData(raw)
got, _ := NewYamlConverter().YamlToString(data, false)
test.AssertResult(t, raw, got)
}

View File

@@ -1,376 +1,249 @@
package yqlib package yqlib
import ( import (
"fmt"
"reflect"
"strconv" "strconv"
"strings"
yaml "github.com/mikefarah/yaml/v2" errors "github.com/pkg/errors"
logging "gopkg.in/op/go-logging.v1" yaml "gopkg.in/yaml.v3"
) )
type DataNavigator interface { type DataNavigator interface {
ReadChildValue(child interface{}, remainingPaths []string) (interface{}, error) Traverse(value *yaml.Node, path []string) error
UpdatedChildValue(child interface{}, remainingPaths []string, value interface{}) interface{}
DeleteChildValue(child interface{}, remainingPaths []string) (interface{}, error)
} }
type navigator struct { type navigator struct {
log *logging.Logger navigationStrategy NavigationStrategy
} }
func NewDataNavigator(l *logging.Logger) DataNavigator { func NewDataNavigator(NavigationStrategy NavigationStrategy) DataNavigator {
return &navigator{ return &navigator{
log: l, navigationStrategy: NavigationStrategy,
} }
} }
func (n *navigator) ReadChildValue(child interface{}, remainingPaths []string) (interface{}, error) { func (n *navigator) Traverse(value *yaml.Node, path []string) error {
if len(remainingPaths) == 0 { realValue := value
return child, nil emptyArray := make([]interface{}, 0)
if realValue.Kind == yaml.DocumentNode {
log.Debugf("its a document! returning the first child")
return n.doTraverse(value.Content[0], "", path, emptyArray)
} }
return n.recurse(child, remainingPaths[0], remainingPaths[1:]) return n.doTraverse(value, "", path, emptyArray)
} }
func (n *navigator) UpdatedChildValue(child interface{}, remainingPaths []string, value interface{}) interface{} { func (n *navigator) doTraverse(value *yaml.Node, head string, tail []string, pathStack []interface{}) error {
if len(remainingPaths) == 0 { log.Debug("head %v", head)
return value DebugNode(value)
} var errorDeepSplatting error
n.log.Debugf("UpdatedChildValue for child %v with path %v to set value %v", child, remainingPaths, value) if head == "**" && value.Kind != yaml.ScalarNode {
n.log.Debugf("type of child is %v", reflect.TypeOf(child)) errorDeepSplatting = n.recurse(value, head, tail, pathStack)
// ignore errors here, we are deep splatting so we may accidently give a string key
switch child := child.(type) { // to an array sequence
case nil:
if remainingPaths[0] == "+" || remainingPaths[0] == "*" {
return n.writeArray(child, remainingPaths, value)
}
case []interface{}:
_, nextIndexErr := strconv.ParseInt(remainingPaths[0], 10, 64)
arrayCommand := nextIndexErr == nil || remainingPaths[0] == "+" || remainingPaths[0] == "*"
if arrayCommand {
return n.writeArray(child, remainingPaths, value)
}
}
return n.writeMap(child, remainingPaths, value)
}
func (n *navigator) DeleteChildValue(child interface{}, remainingPaths []string) (interface{}, error) {
n.log.Debugf("DeleteChildValue for %v for %v\n", remainingPaths, child)
if len(remainingPaths) == 0 {
return child, nil
}
var head = remainingPaths[0]
var tail = remainingPaths[1:]
switch child := child.(type) {
case yaml.MapSlice:
return n.deleteMap(child, remainingPaths)
case []interface{}:
if head == "*" {
return n.deleteArraySplat(child, tail)
}
index, err := strconv.ParseInt(head, 10, 64)
if err != nil {
return nil, fmt.Errorf("error accessing array: %v", err)
}
return n.deleteArray(child, remainingPaths, index)
}
return child, nil
}
func (n *navigator) recurse(value interface{}, head string, tail []string) (interface{}, error) {
switch value := value.(type) {
case []interface{}:
if head == "*" {
return n.readArraySplat(value, tail)
}
index, err := strconv.ParseInt(head, 10, 64)
if err != nil {
return nil, fmt.Errorf("error accessing array: %v", err)
}
return n.readArray(value, index, tail)
case yaml.MapSlice:
return n.readMap(value, head, tail)
default:
return nil, nil
}
}
func (n *navigator) matchesKey(key string, actual interface{}) bool {
var actualString = fmt.Sprintf("%v", actual)
var prefixMatch = strings.TrimSuffix(key, "*")
if prefixMatch != key {
return strings.HasPrefix(actualString, prefixMatch)
}
return actualString == key
}
func (n *navigator) entriesInSlice(context yaml.MapSlice, key string) []*yaml.MapItem {
var matches = make([]*yaml.MapItem, 0)
for idx := range context {
var entry = &context[idx]
if n.matchesKey(key, entry.Key) {
matches = append(matches, entry)
}
}
return matches
}
func (n *navigator) getMapSlice(context interface{}) yaml.MapSlice {
var mapSlice yaml.MapSlice
switch context := context.(type) {
case yaml.MapSlice:
mapSlice = context
default:
mapSlice = make(yaml.MapSlice, 0)
}
return mapSlice
}
func (n *navigator) getArray(context interface{}) (array []interface{}, ok bool) {
switch context := context.(type) {
case []interface{}:
array = context
ok = true
default:
array = make([]interface{}, 0)
ok = false
}
return
}
func (n *navigator) writeMap(context interface{}, paths []string, value interface{}) interface{} {
n.log.Debugf("writeMap with path %v for %v to set value %v\n", paths, context, value)
mapSlice := n.getMapSlice(context)
if len(paths) == 0 {
return context
}
children := n.entriesInSlice(mapSlice, paths[0])
if len(children) == 0 && paths[0] == "*" {
n.log.Debugf("\tNo matches, return map as is")
return context
}
if len(children) == 0 {
newChild := yaml.MapItem{Key: paths[0]}
mapSlice = append(mapSlice, newChild)
children = n.entriesInSlice(mapSlice, paths[0])
n.log.Debugf("\tAppended child at %v for mapSlice %v\n", paths[0], mapSlice)
}
remainingPaths := paths[1:]
for _, child := range children {
child.Value = n.UpdatedChildValue(child.Value, remainingPaths, value)
}
n.log.Debugf("\tReturning mapSlice %v\n", mapSlice)
return mapSlice
}
func (n *navigator) writeArray(context interface{}, paths []string, value interface{}) []interface{} {
n.log.Debugf("writeArray with path %v for %v to set value %v\n", paths, context, value)
array, _ := n.getArray(context)
if len(paths) == 0 {
return array
}
n.log.Debugf("\tarray %v\n", array)
rawIndex := paths[0]
remainingPaths := paths[1:]
var index int64
// the append array indicator
if rawIndex == "+" {
index = int64(len(array))
} else if rawIndex == "*" {
for index, oldChild := range array {
array[index] = n.UpdatedChildValue(oldChild, remainingPaths, value)
}
return array
} else {
index, _ = strconv.ParseInt(rawIndex, 10, 64) // nolint
// writeArray is only called by UpdatedChildValue which handles parsing the
// index, as such this renders this dead code.
}
for index >= int64(len(array)) {
array = append(array, nil)
}
currentChild := array[index]
n.log.Debugf("\tcurrentChild %v\n", currentChild)
array[index] = n.UpdatedChildValue(currentChild, remainingPaths, value)
n.log.Debugf("\tReturning array %v\n", array)
return array
}
func (n *navigator) readMap(context yaml.MapSlice, head string, tail []string) (interface{}, error) {
n.log.Debugf("readingMap %v with key %v\n", context, head)
if head == "*" {
return n.readMapSplat(context, tail)
}
entries := n.entriesInSlice(context, head)
if len(entries) == 1 {
return n.calculateValue(entries[0].Value, tail)
} else if len(entries) == 0 {
return nil, nil
}
var errInIdx error
values := make([]interface{}, len(entries))
for idx, entry := range entries {
values[idx], errInIdx = n.calculateValue(entry.Value, tail)
if errInIdx != nil {
n.log.Errorf("Error updating index %v in %v", idx, context)
return nil, errInIdx
}
}
return values, nil
}
func (n *navigator) readMapSplat(context yaml.MapSlice, tail []string) (interface{}, error) {
var newArray = make([]interface{}, len(context))
var i = 0
for _, entry := range context {
if len(tail) > 0 { if len(tail) > 0 {
val, err := n.recurse(entry.Value, tail[0], tail[1:]) _ = n.recurse(value, tail[0], tail[1:], pathStack)
if err != nil {
return nil, err
}
newArray[i] = val
} else {
newArray[i] = entry.Value
} }
i++ return errorDeepSplatting
}
return newArray, nil
}
func (n *navigator) readArray(array []interface{}, head int64, tail []string) (interface{}, error) {
if head >= int64(len(array)) {
return nil, nil
} }
value := array[head]
return n.calculateValue(value, tail)
}
func (n *navigator) readArraySplat(array []interface{}, tail []string) (interface{}, error) {
var newArray = make([]interface{}, len(array))
for index, value := range array {
val, err := n.calculateValue(value, tail)
if err != nil {
return nil, err
}
newArray[index] = val
}
return newArray, nil
}
func (n *navigator) calculateValue(value interface{}, tail []string) (interface{}, error) {
if len(tail) > 0 { if len(tail) > 0 {
return n.recurse(value, tail[0], tail[1:]) log.Debugf("diving into %v", tail[0])
DebugNode(value)
return n.recurse(value, tail[0], tail[1:], pathStack)
} }
return value, nil return n.navigationStrategy.Visit(NewNodeContext(value, head, tail, pathStack))
} }
func (n *navigator) deleteMap(context interface{}, paths []string) (yaml.MapSlice, error) { func (n *navigator) getOrReplace(original *yaml.Node, expectedKind yaml.Kind) *yaml.Node {
n.log.Debugf("deleteMap for %v for %v\n", paths, context) if original.Kind != expectedKind {
log.Debug("wanted %v but it was %v, overriding", expectedKind, original.Kind)
return &yaml.Node{Kind: expectedKind}
}
return original
}
mapSlice := n.getMapSlice(context) func (n *navigator) recurse(value *yaml.Node, head string, tail []string, pathStack []interface{}) error {
log.Debug("recursing, processing %v", head)
switch value.Kind {
case yaml.MappingNode:
log.Debug("its a map with %v entries", len(value.Content)/2)
return n.recurseMap(value, head, tail, pathStack)
case yaml.SequenceNode:
log.Debug("its a sequence of %v things!", len(value.Content))
if n.navigationStrategy.GetPathParser().IsPathExpression(head) {
return n.splatArray(value, head, tail, pathStack)
} else if head == "+" {
return n.appendArray(value, head, tail, pathStack)
}
return n.recurseArray(value, head, tail, pathStack)
case yaml.AliasNode:
log.Debug("its an alias!")
DebugNode(value.Alias)
if n.navigationStrategy.FollowAlias(NewNodeContext(value, head, tail, pathStack)) {
log.Debug("following the alias")
return n.recurse(value.Alias, head, tail, pathStack)
}
return nil
default:
return nil
}
}
if len(paths) == 0 { func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pathStack []interface{}) error {
return mapSlice, nil traversedEntry := false
errorVisiting := n.visitMatchingEntries(value, head, tail, pathStack, func(contents []*yaml.Node, indexInMap int) error {
log.Debug("recurseMap: visitMatchingEntries")
n.navigationStrategy.DebugVisitedNodes()
newPathStack := append(pathStack, contents[indexInMap].Value)
log.Debug("appended %v", contents[indexInMap].Value)
n.navigationStrategy.DebugVisitedNodes()
log.Debug("should I traverse? head: %v, path: %v", head, pathStackToString(newPathStack))
DebugNode(value)
if n.navigationStrategy.ShouldTraverse(NewNodeContext(contents[indexInMap+1], head, tail, newPathStack), contents[indexInMap].Value) {
log.Debug("recurseMap: Going to traverse")
traversedEntry = true
// contents[indexInMap+1] = n.getOrReplace(contents[indexInMap+1], guessKind(head, tail, contents[indexInMap+1].Kind))
errorTraversing := n.doTraverse(contents[indexInMap+1], head, tail, newPathStack)
log.Debug("recurseMap: Finished traversing")
n.navigationStrategy.DebugVisitedNodes()
return errorTraversing
} else {
log.Debug("nope not traversing")
}
return nil
})
if errorVisiting != nil {
return errorVisiting
} }
var index int if traversedEntry || n.navigationStrategy.GetPathParser().IsPathExpression(head) || !n.navigationStrategy.AutoCreateMap(NewNodeContext(value, head, tail, pathStack)) {
var child yaml.MapItem return nil
for index, child = range mapSlice { }
if n.matchesKey(paths[0], child.Key) {
n.log.Debugf("\tMatched [%v] with [%v] at index %v", paths[0], child.Key, index) mapEntryKey := yaml.Node{Value: head, Kind: yaml.ScalarNode}
var badDelete error value.Content = append(value.Content, &mapEntryKey)
mapSlice, badDelete = n.deleteEntryInMap(mapSlice, child, index, paths) mapEntryValue := yaml.Node{Kind: guessKind(head, tail, 0)}
if badDelete != nil { value.Content = append(value.Content, &mapEntryValue)
return nil, badDelete log.Debug("adding new node %v", head)
return n.doTraverse(&mapEntryValue, head, tail, append(pathStack, head))
}
// need to pass the node in, as it may be aliased
type mapVisitorFn func(contents []*yaml.Node, index int) error
func (n *navigator) visitDirectMatchingEntries(node *yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) error {
var contents = node.Content
for index := 0; index < len(contents); index = index + 2 {
content := contents[index]
log.Debug("index %v, checking %v, %v", index, content.Value, content.Tag)
n.navigationStrategy.DebugVisitedNodes()
errorVisiting := visit(contents, index)
if errorVisiting != nil {
return errorVisiting
}
}
return nil
}
func (n *navigator) visitMatchingEntries(node *yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) error {
var contents = node.Content
log.Debug("visitMatchingEntries %v", head)
DebugNode(node)
// value.Content is a concatenated array of key, value,
// so keys are in the even indexes, values in odd.
// merge aliases are defined first, but we only want to traverse them
// if we don't find a match directly on this node first.
errorVisitedDirectEntries := n.visitDirectMatchingEntries(node, head, tail, pathStack, visit)
if errorVisitedDirectEntries != nil || !n.navigationStrategy.FollowAlias(NewNodeContext(node, head, tail, pathStack)) {
return errorVisitedDirectEntries
}
return n.visitAliases(contents, head, tail, pathStack, visit)
}
func (n *navigator) visitAliases(contents []*yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) error {
// merge aliases are defined first, but we only want to traverse them
// if we don't find a match on this node first.
// traverse them backwards so that the last alias overrides the preceding.
// a node can either be
// an alias to one other node (e.g. <<: *blah)
// or a sequence of aliases (e.g. <<: [*blah, *foo])
log.Debug("checking for aliases")
for index := len(contents) - 2; index >= 0; index = index - 2 {
if contents[index+1].Kind == yaml.AliasNode {
valueNode := contents[index+1]
log.Debug("found an alias")
DebugNode(contents[index])
DebugNode(valueNode)
errorInAlias := n.visitMatchingEntries(valueNode.Alias, head, tail, pathStack, visit)
if errorInAlias != nil {
return errorInAlias
}
} else if contents[index+1].Kind == yaml.SequenceNode {
// could be an array of aliases...
errorVisitingAliasSeq := n.visitAliasSequence(contents[index+1].Content, head, tail, pathStack, visit)
if errorVisitingAliasSeq != nil {
return errorVisitingAliasSeq
} }
} }
} }
return nil
return mapSlice, nil
} }
func (n *navigator) deleteEntryInMap(original yaml.MapSlice, child yaml.MapItem, index int, paths []string) (yaml.MapSlice, error) { func (n *navigator) visitAliasSequence(possibleAliasArray []*yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) error {
remainingPaths := paths[1:] // need to search this backwards too, so that aliases defined last override the preceding.
for aliasIndex := len(possibleAliasArray) - 1; aliasIndex >= 0; aliasIndex = aliasIndex - 1 {
var newSlice yaml.MapSlice child := possibleAliasArray[aliasIndex]
if len(remainingPaths) > 0 { if child.Kind == yaml.AliasNode {
newChild := yaml.MapItem{Key: child.Key} log.Debug("found an alias")
var errorDeleting error DebugNode(child)
newChild.Value, errorDeleting = n.DeleteChildValue(child.Value, remainingPaths) errorInAlias := n.visitMatchingEntries(child.Alias, head, tail, pathStack, visit)
if errorDeleting != nil { if errorInAlias != nil {
return nil, errorDeleting return errorInAlias
}
newSlice = make(yaml.MapSlice, len(original))
for i := range original {
item := original[i]
if i == index {
item = newChild
} }
newSlice[i] = item
} }
} else {
// Delete item from slice at index
newSlice = append(original[:index], original[index+1:]...)
n.log.Debugf("\tDeleted item index %d from original", index)
} }
return nil
n.log.Debugf("\tReturning original %v\n", original)
return newSlice, nil
} }
func (n *navigator) deleteArraySplat(array []interface{}, tail []string) (interface{}, error) { func (n *navigator) splatArray(value *yaml.Node, head string, tail []string, pathStack []interface{}) error {
n.log.Debugf("deleteArraySplat for %v for %v\n", tail, array) for index, childValue := range value.Content {
var newArray = make([]interface{}, len(array)) log.Debug("processing")
for index, value := range array { DebugNode(childValue)
val, err := n.DeleteChildValue(value, tail) childValue = n.getOrReplace(childValue, guessKind(head, tail, childValue.Kind))
if err != nil {
return nil, err newPathStack := append(pathStack, index)
if n.navigationStrategy.ShouldTraverse(NewNodeContext(childValue, head, tail, newPathStack), childValue.Value) {
var err = n.doTraverse(childValue, head, tail, newPathStack)
if err != nil {
return err
}
} }
newArray[index] = val
} }
return newArray, nil return nil
} }
func (n *navigator) deleteArray(array []interface{}, paths []string, index int64) (interface{}, error) { func (n *navigator) appendArray(value *yaml.Node, head string, tail []string, pathStack []interface{}) error {
n.log.Debugf("deleteArray for %v for %v\n", paths, array) var newNode = yaml.Node{Kind: guessKind(head, tail, 0)}
value.Content = append(value.Content, &newNode)
if index >= int64(len(array)) { log.Debug("appending a new node, %v", value.Content)
return array, nil return n.doTraverse(&newNode, head, tail, append(pathStack, len(value.Content)-1))
} }
remainingPaths := paths[1:] func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, pathStack []interface{}) error {
if len(remainingPaths) > 0 { var index, err = strconv.ParseInt(head, 10, 64) // nolint
// recurse into the array element at index if err != nil {
var errorDeleting error return errors.Wrapf(err, "Error parsing array index '%v' for '%v'", head, pathStackToString(pathStack))
array[index], errorDeleting = n.deleteMap(array[index], remainingPaths) }
if errorDeleting != nil {
return nil, errorDeleting for int64(len(value.Content)) <= index {
} value.Content = append(value.Content, &yaml.Node{Kind: guessKind(head, tail, 0)})
}
} else {
// Delete the array element at index value.Content[index] = n.getOrReplace(value.Content[index], guessKind(head, tail, value.Content[index].Kind))
array = append(array[:index], array[index+1:]...)
n.log.Debugf("\tDeleted item index %d from array, leaving %v", index, array) return n.doTraverse(value.Content[index], head, tail, append(pathStack, index))
}
n.log.Debugf("\tReturning array: %v\n", array)
return array, nil
} }

View File

@@ -1,397 +1 @@
package yqlib package yqlib
import (
"fmt"
"sort"
"testing"
"github.com/mikefarah/yq/v2/test"
logging "gopkg.in/op/go-logging.v1"
)
func TestDataNavigator(t *testing.T) {
var log = logging.MustGetLogger("yq")
subject := NewDataNavigator(log)
t.Run("TestReadMap_simple", func(t *testing.T) {
var data = test.ParseData(`
---
b:
c: 2
`)
got, _ := subject.ReadChildValue(data, []string{"b", "c"})
test.AssertResult(t, 2, got)
})
t.Run("TestReadMap_numberKey", func(t *testing.T) {
var data = test.ParseData(`
---
200: things
`)
got, _ := subject.ReadChildValue(data, []string{"200"})
test.AssertResult(t, "things", got)
})
t.Run("TestReadMap_splat", func(t *testing.T) {
var data = test.ParseData(`
---
mapSplat:
item1: things
item2: whatever
otherThing: cat
`)
res, _ := subject.ReadChildValue(data, []string{"mapSplat", "*"})
test.AssertResult(t, "[things whatever cat]", fmt.Sprintf("%v", res))
})
t.Run("TestReadMap_prefixSplat", func(t *testing.T) {
var data = test.ParseData(`
---
mapSplat:
item1: things
item2: whatever
otherThing: cat
`)
res, _ := subject.ReadChildValue(data, []string{"mapSplat", "item*"})
test.AssertResult(t, "[things whatever]", fmt.Sprintf("%v", res))
})
t.Run("TestReadMap_deep_splat", func(t *testing.T) {
var data = test.ParseData(`
---
mapSplatDeep:
item1:
cats: bananas
item2:
cats: apples
`)
res, _ := subject.ReadChildValue(data, []string{"mapSplatDeep", "*", "cats"})
result := res.([]interface{})
var actual = []string{result[0].(string), result[1].(string)}
sort.Strings(actual)
test.AssertResult(t, "[apples bananas]", fmt.Sprintf("%v", actual))
})
t.Run("TestReadMap_key_doesnt_exist", func(t *testing.T) {
var data = test.ParseData(`
---
b:
c: 2
`)
got, _ := subject.ReadChildValue(data, []string{"b", "x", "f", "c"})
test.AssertResult(t, nil, got)
})
t.Run("TestReadMap_recurse_against_string", func(t *testing.T) {
var data = test.ParseData(`
---
a: cat
`)
got, _ := subject.ReadChildValue(data, []string{"a", "b"})
test.AssertResult(t, nil, got)
})
t.Run("TestReadMap_with_array", func(t *testing.T) {
var data = test.ParseData(`
---
b:
d:
- 3
- 4
`)
got, _ := subject.ReadChildValue(data, []string{"b", "d", "1"})
test.AssertResult(t, 4, got)
})
t.Run("TestReadMap_with_array_and_bad_index", func(t *testing.T) {
var data = test.ParseData(`
---
b:
d:
- 3
- 4
`)
_, err := subject.ReadChildValue(data, []string{"b", "d", "x"})
if err == nil {
t.Fatal("Expected error due to invalid path")
}
expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax`
test.AssertResult(t, expectedOutput, err.Error())
})
t.Run("TestReadMap_with_mapsplat_array_and_bad_index", func(t *testing.T) {
var data = test.ParseData(`
---
b:
d:
e:
- 3
- 4
f:
- 1
- 2
`)
_, err := subject.ReadChildValue(data, []string{"b", "d", "*", "x"})
if err == nil {
t.Fatal("Expected error due to invalid path")
}
expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax`
test.AssertResult(t, expectedOutput, err.Error())
})
t.Run("TestReadMap_with_arraysplat_map_array_and_bad_index", func(t *testing.T) {
var data = test.ParseData(`
---
b:
d:
- names:
- fred
- smith
- names:
- sam
- bo
`)
_, err := subject.ReadChildValue(data, []string{"b", "d", "*", "names", "x"})
if err == nil {
t.Fatal("Expected error due to invalid path")
}
expectedOutput := `error accessing array: strconv.ParseInt: parsing "x": invalid syntax`
test.AssertResult(t, expectedOutput, err.Error())
})
t.Run("TestReadMap_with_array_out_of_bounds", func(t *testing.T) {
var data = test.ParseData(`
---
b:
d:
- 3
- 4
`)
got, _ := subject.ReadChildValue(data, []string{"b", "d", "3"})
test.AssertResult(t, nil, got)
})
t.Run("TestReadMap_with_array_out_of_bounds_by_1", func(t *testing.T) {
var data = test.ParseData(`
---
b:
d:
- 3
- 4
`)
got, _ := subject.ReadChildValue(data, []string{"b", "d", "2"})
test.AssertResult(t, nil, got)
})
t.Run("TestReadMap_with_array_splat", func(t *testing.T) {
var data = test.ParseData(`
e:
-
name: Fred
thing: cat
-
name: Sam
thing: dog
`)
got, _ := subject.ReadChildValue(data, []string{"e", "*", "name"})
test.AssertResult(t, "[Fred Sam]", fmt.Sprintf("%v", got))
})
t.Run("TestWrite_really_simple", func(t *testing.T) {
var data = test.ParseData(`
b: 2
`)
updated := subject.UpdatedChildValue(data, []string{"b"}, "4")
test.AssertResult(t, "[{b 4}]", fmt.Sprintf("%v", updated))
})
t.Run("TestWrite_simple", func(t *testing.T) {
var data = test.ParseData(`
b:
c: 2
`)
updated := subject.UpdatedChildValue(data, []string{"b", "c"}, "4")
test.AssertResult(t, "[{b [{c 4}]}]", fmt.Sprintf("%v", updated))
})
t.Run("TestWrite_new", func(t *testing.T) {
var data = test.ParseData(`
b:
c: 2
`)
updated := subject.UpdatedChildValue(data, []string{"b", "d"}, "4")
test.AssertResult(t, "[{b [{c 2} {d 4}]}]", fmt.Sprintf("%v", updated))
})
t.Run("TestWrite_new_deep", func(t *testing.T) {
var data = test.ParseData(`
b:
c: 2
`)
updated := subject.UpdatedChildValue(data, []string{"b", "d", "f"}, "4")
test.AssertResult(t, "[{b [{c 2} {d [{f 4}]}]}]", fmt.Sprintf("%v", updated))
})
t.Run("TestWrite_array", func(t *testing.T) {
var data = test.ParseData(`
b:
- aa
`)
updated := subject.UpdatedChildValue(data, []string{"b", "0"}, "bb")
test.AssertResult(t, "[{b [bb]}]", fmt.Sprintf("%v", updated))
})
t.Run("TestWrite_new_array", func(t *testing.T) {
var data = test.ParseData(`
b:
c: 2
`)
updated := subject.UpdatedChildValue(data, []string{"b", "0"}, "4")
test.AssertResult(t, "[{b [{c 2} {0 4}]}]", fmt.Sprintf("%v", updated))
})
t.Run("TestWrite_new_array_deep", func(t *testing.T) {
var data = test.ParseData(`
a: apple
`)
updated := subject.UpdatedChildValue(data, []string{"b", "+", "c"}, "4")
test.AssertResult(t, "[{a apple} {b [[{c 4}]]}]", fmt.Sprintf("%v", updated))
})
t.Run("TestWrite_new_map_array_deep", func(t *testing.T) {
var data = test.ParseData(`
b:
c: 2
`)
updated := subject.UpdatedChildValue(data, []string{"b", "d", "+"}, "4")
test.AssertResult(t, "[{b [{c 2} {d [4]}]}]", fmt.Sprintf("%v", updated))
})
t.Run("TestWrite_add_to_array", func(t *testing.T) {
var data = test.ParseData(`
b:
- aa
`)
updated := subject.UpdatedChildValue(data, []string{"b", "1"}, "bb")
test.AssertResult(t, "[{b [aa bb]}]", fmt.Sprintf("%v", updated))
})
t.Run("TestWrite_with_no_tail", func(t *testing.T) {
var data = test.ParseData(`
b:
c: 2
`)
updated := subject.UpdatedChildValue(data, []string{"b"}, "4")
test.AssertResult(t, "[{b 4}]", fmt.Sprintf("%v", updated))
})
t.Run("TestWriteMap_no_paths", func(t *testing.T) {
var data = test.ParseData(`
b: 5
`)
var new = test.ParseData(`
c: 4
`)
result := subject.UpdatedChildValue(data, []string{}, new)
test.AssertResult(t, fmt.Sprintf("%v", new), fmt.Sprintf("%v", result))
})
t.Run("TestWriteArray_no_paths", func(t *testing.T) {
var data = make([]interface{}, 1)
data[0] = "mike"
var new = test.ParseData(`
c: 4
`)
result := subject.UpdatedChildValue(data, []string{}, new)
test.AssertResult(t, fmt.Sprintf("%v", new), fmt.Sprintf("%v", result))
})
t.Run("TestDelete_MapItem", func(t *testing.T) {
var data = test.ParseData(`
a: 123
b: 456
`)
var expected = test.ParseData(`
b: 456
`)
result, _ := subject.DeleteChildValue(data, []string{"a"})
test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result))
})
// Ensure deleting an index into a string does nothing
t.Run("TestDelete_index_to_string", func(t *testing.T) {
var data = test.ParseData(`
a: mystring
`)
result, _ := subject.DeleteChildValue(data, []string{"a", "0"})
test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result))
})
t.Run("TestDelete_list_index", func(t *testing.T) {
var data = test.ParseData(`
a: [3, 4]
`)
var expected = test.ParseData(`
a: [3]
`)
result, _ := subject.DeleteChildValue(data, []string{"a", "1"})
test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result))
})
t.Run("TestDelete_list_index_beyond_bounds", func(t *testing.T) {
var data = test.ParseData(`
a: [3, 4]
`)
result, _ := subject.DeleteChildValue(data, []string{"a", "5"})
test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result))
})
t.Run("TestDelete_list_index_out_of_bounds_by_1", func(t *testing.T) {
var data = test.ParseData(`
a: [3, 4]
`)
result, _ := subject.DeleteChildValue(data, []string{"a", "2"})
test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result))
})
t.Run("TestDelete_no_paths", func(t *testing.T) {
var data = test.ParseData(`
a: [3, 4]
b:
- name: test
`)
result, _ := subject.DeleteChildValue(data, []string{})
test.AssertResult(t, fmt.Sprintf("%v", data), fmt.Sprintf("%v", result))
})
t.Run("TestDelete_array_map_item", func(t *testing.T) {
var data = test.ParseData(`
b:
- name: fred
value: blah
- name: john
value: test
`)
var expected = test.ParseData(`
b:
- value: blah
- name: john
value: test
`)
result, _ := subject.DeleteChildValue(data, []string{"b", "0", "name"})
test.AssertResult(t, fmt.Sprintf("%v", expected), fmt.Sprintf("%v", result))
})
}

View File

@@ -0,0 +1,67 @@
package yqlib
import (
"strconv"
yaml "gopkg.in/yaml.v3"
)
func DeleteNavigationStrategy(pathElementToDelete string) NavigationStrategy {
parser := NewPathParser()
return &NavigationStrategyImpl{
visitedNodes: []*NodeContext{},
pathParser: parser,
followAlias: func(nodeContext NodeContext) bool {
return false
},
autoCreateMap: func(nodeContext NodeContext) bool {
return true
},
visit: func(nodeContext NodeContext) error {
node := nodeContext.Node
log.Debug("need to find and delete %v in here", pathElementToDelete)
DebugNode(node)
if node.Kind == yaml.SequenceNode {
newContent, errorDeleting := deleteFromArray(node.Content, pathElementToDelete)
if errorDeleting != nil {
return errorDeleting
}
node.Content = newContent
} else if node.Kind == yaml.MappingNode {
node.Content = deleteFromMap(parser, node.Content, nodeContext.PathStack, pathElementToDelete)
}
return nil
},
}
}
func deleteFromMap(pathParser PathParser, contents []*yaml.Node, pathStack []interface{}, pathElementToDelete string) []*yaml.Node {
newContents := make([]*yaml.Node, 0)
for index := 0; index < len(contents); index = index + 2 {
keyNode := contents[index]
valueNode := contents[index+1]
if !pathParser.MatchesNextPathElement(NewNodeContext(keyNode, pathElementToDelete, []string{}, pathStack), keyNode.Value) {
log.Debug("adding node %v", keyNode.Value)
newContents = append(newContents, keyNode, valueNode)
} else {
log.Debug("skipping node %v", keyNode.Value)
}
}
return newContents
}
func deleteFromArray(content []*yaml.Node, pathElementToDelete string) ([]*yaml.Node, error) {
if pathElementToDelete == "*" {
return make([]*yaml.Node, 0), nil
}
var index, err = strconv.ParseInt(pathElementToDelete, 10, 64) // nolint
if err != nil {
return content, err
}
if index >= int64(len(content)) {
log.Debug("index %v is greater than content length %v", index, len(content))
return content, nil
}
return append(content[:index], content[index+1:]...), nil
}

44
pkg/yqlib/encoder.go Normal file
View File

@@ -0,0 +1,44 @@
package yqlib
import (
"encoding/json"
"io"
yaml "gopkg.in/yaml.v3"
)
type Encoder interface {
Encode(node *yaml.Node) error
}
type yamlEncoder struct {
encoder *yaml.Encoder
}
func NewYamlEncoder(destination io.Writer) Encoder {
var encoder = yaml.NewEncoder(destination)
encoder.SetIndent(2)
return &yamlEncoder{encoder}
}
func (ye *yamlEncoder) Encode(node *yaml.Node) error {
return ye.encoder.Encode(node)
}
type jsonEncoder struct {
encoder *json.Encoder
}
func NewJsonEncoder(destination io.Writer) Encoder {
var encoder = json.NewEncoder(destination)
return &jsonEncoder{encoder}
}
func (je *jsonEncoder) Encode(node *yaml.Node) error {
var dataBucket interface{}
errorDecoding := node.Decode(&dataBucket)
if errorDecoding != nil {
return errorDecoding
}
return je.encoder.Encode(dataBucket)
}

View File

@@ -0,0 +1,21 @@
package yqlib
func FilterMatchingNodesNavigationStrategy(value string) NavigationStrategy {
return &NavigationStrategyImpl{
visitedNodes: []*NodeContext{},
pathParser: NewPathParser(),
followAlias: func(nodeContext NodeContext) bool {
return true
},
autoCreateMap: func(nodeContext NodeContext) bool {
return false
},
visit: func(nodeContext NodeContext) error {
return nil
},
shouldVisitExtraFn: func(nodeContext NodeContext) bool {
log.Debug("does %v match %v ? %v", nodeContext.Node.Value, value, nodeContext.Node.Value == value)
return matchesString(value, nodeContext.Node.Value)
},
}
}

View File

@@ -1,68 +1,149 @@
package yqlib package yqlib
import ( import (
mergo "gopkg.in/imdario/mergo.v0" "bytes"
"fmt"
"strconv"
"strings"
logging "gopkg.in/op/go-logging.v1" logging "gopkg.in/op/go-logging.v1"
yaml "gopkg.in/yaml.v3"
) )
var log = logging.MustGetLogger("yq")
type UpdateCommand struct {
Command string
Path string
Value *yaml.Node
Overwrite bool
}
func DebugNode(value *yaml.Node) {
if value == nil {
log.Debug("-- node is nil --")
} else if log.IsEnabledFor(logging.DEBUG) {
buf := new(bytes.Buffer)
encoder := yaml.NewEncoder(buf)
errorEncoding := encoder.Encode(value)
if errorEncoding != nil {
log.Error("Error debugging node, %v", errorEncoding.Error())
}
encoder.Close()
log.Debug("Tag: %v", value.Tag)
log.Debug("%v", buf.String())
}
}
func pathStackToString(pathStack []interface{}) string {
return mergePathStackToString(pathStack, false)
}
func mergePathStackToString(pathStack []interface{}, appendArrays bool) string {
var sb strings.Builder
for index, path := range pathStack {
switch path.(type) {
case int:
if appendArrays {
sb.WriteString("[+]")
} else {
sb.WriteString(fmt.Sprintf("[%v]", path))
}
default:
sb.WriteString(fmt.Sprintf("%v", path))
}
if index < len(pathStack)-1 {
sb.WriteString(".")
}
}
return sb.String()
}
func guessKind(head string, tail []string, guess yaml.Kind) yaml.Kind {
log.Debug("tail %v", tail)
if len(tail) == 0 && guess == 0 {
log.Debug("end of path, must be a scalar")
return yaml.ScalarNode
} else if len(tail) == 0 {
return guess
}
var _, errorParsingInt = strconv.ParseInt(tail[0], 10, 64)
if tail[0] == "+" || errorParsingInt == nil {
return yaml.SequenceNode
}
pathParser := NewPathParser()
if (pathParser.IsPathExpression(tail[0]) || head == "**") && (guess == yaml.SequenceNode || guess == yaml.MappingNode) {
return guess
}
if guess == yaml.AliasNode {
log.Debug("guess was an alias, okey doke.")
return guess
}
log.Debug("forcing a mapping node")
log.Debug("yaml.SequenceNode %v", guess == yaml.SequenceNode)
log.Debug("yaml.ScalarNode %v", guess == yaml.ScalarNode)
return yaml.MappingNode
}
type YqLib interface { type YqLib interface {
ReadPath(dataBucket interface{}, path string) (interface{}, error) Get(rootNode *yaml.Node, path string) ([]*NodeContext, error)
WritePath(dataBucket interface{}, path string, value interface{}) interface{} Update(rootNode *yaml.Node, updateCommand UpdateCommand, autoCreate bool) error
PrefixPath(dataBucket interface{}, prefix string) interface{} New(path string) yaml.Node
DeletePath(dataBucket interface{}, path string) (interface{}, error)
Merge(dst interface{}, src interface{}, overwrite bool, append bool) error PathStackToString(pathStack []interface{}) string
MergePathStackToString(pathStack []interface{}, appendArrays bool) string
} }
type lib struct { type lib struct {
navigator DataNavigator parser PathParser
parser PathParser
} }
func NewYqLib(l *logging.Logger) YqLib { func NewYqLib() YqLib {
return &lib{ return &lib{
navigator: NewDataNavigator(l), parser: NewPathParser(),
parser: NewPathParser(),
} }
} }
func (l *lib) ReadPath(dataBucket interface{}, path string) (interface{}, error) { func (l *lib) Get(rootNode *yaml.Node, path string) ([]*NodeContext, error) {
var paths = l.parser.ParsePath(path) var paths = l.parser.ParsePath(path)
return l.navigator.ReadChildValue(dataBucket, paths) navigationStrategy := ReadNavigationStrategy()
navigator := NewDataNavigator(navigationStrategy)
error := navigator.Traverse(rootNode, paths)
return navigationStrategy.GetVisitedNodes(), error
} }
func (l *lib) WritePath(dataBucket interface{}, path string, value interface{}) interface{} { func (l *lib) PathStackToString(pathStack []interface{}) string {
return pathStackToString(pathStack)
}
func (l *lib) MergePathStackToString(pathStack []interface{}, appendArrays bool) string {
return mergePathStackToString(pathStack, appendArrays)
}
func (l *lib) New(path string) yaml.Node {
var paths = l.parser.ParsePath(path) var paths = l.parser.ParsePath(path)
return l.navigator.UpdatedChildValue(dataBucket, paths, value) newNode := yaml.Node{Kind: guessKind("", paths, 0)}
return newNode
} }
func (l *lib) PrefixPath(dataBucket interface{}, prefix string) interface{} { func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand, autoCreate bool) error {
var paths = l.parser.ParsePath(prefix) log.Debugf("%v to %v", updateCommand.Command, updateCommand.Path)
switch updateCommand.Command {
// Inverse order case "update":
for i := len(paths)/2 - 1; i >= 0; i-- { var paths = l.parser.ParsePath(updateCommand.Path)
opp := len(paths) - 1 - i navigator := NewDataNavigator(UpdateNavigationStrategy(updateCommand, autoCreate))
paths[i], paths[opp] = paths[opp], paths[i] return navigator.Traverse(rootNode, paths)
case "delete":
var paths = l.parser.ParsePath(updateCommand.Path)
lastBit, newTail := paths[len(paths)-1], paths[:len(paths)-1]
navigator := NewDataNavigator(DeleteNavigationStrategy(lastBit))
return navigator.Traverse(rootNode, newTail)
default:
return fmt.Errorf("Unknown command %v", updateCommand.Command)
} }
var mapDataBucket = dataBucket
for _, key := range paths {
singlePath := []string{key}
mapDataBucket = l.navigator.UpdatedChildValue(nil, singlePath, mapDataBucket)
}
return mapDataBucket
}
func (l *lib) DeletePath(dataBucket interface{}, path string) (interface{}, error) {
var paths = l.parser.ParsePath(path)
return l.navigator.DeleteChildValue(dataBucket, paths)
}
func (l *lib) Merge(dst interface{}, src interface{}, overwrite bool, append bool) error {
if overwrite {
return mergo.Merge(dst, src, mergo.WithOverride)
} else if append {
return mergo.Merge(dst, src, mergo.WithAppendSlice)
}
return mergo.Merge(dst, src)
} }

View File

@@ -1,166 +1,176 @@
package yqlib package yqlib
import ( import (
"fmt"
"testing" "testing"
"github.com/mikefarah/yq/v2/test" "github.com/mikefarah/yq/v3/test"
logging "gopkg.in/op/go-logging.v1"
) )
func TestLib(t *testing.T) { func TestLib(t *testing.T) {
var log = logging.MustGetLogger("yq") subject := NewYqLib()
subject := NewYqLib(log)
t.Run("TestReadPath", func(t *testing.T) { t.Run("PathStackToString_Empty", func(t *testing.T) {
var data = test.ParseData(` emptyArray := make([]interface{}, 0)
--- got := subject.PathStackToString(emptyArray)
b: test.AssertResult(t, ``, got)
2: c
`)
got, _ := subject.ReadPath(data, "b.2")
test.AssertResult(t, `c`, got)
}) })
t.Run("TestReadPath_WithError", func(t *testing.T) { t.Run("PathStackToString", func(t *testing.T) {
var data = test.ParseData(` array := make([]interface{}, 3)
--- array[0] = "a"
b: array[1] = 0
- c array[2] = "b"
`) got := subject.PathStackToString(array)
test.AssertResult(t, `a.[0].b`, got)
_, err := subject.ReadPath(data, "b.[a]")
if err == nil {
t.Fatal("Expected error due to invalid path")
}
}) })
t.Run("TestWritePath", func(t *testing.T) { t.Run("MergePathStackToString", func(t *testing.T) {
var data = test.ParseData(` array := make([]interface{}, 3)
--- array[0] = "a"
b: array[1] = 0
2: c array[2] = "b"
`) got := subject.MergePathStackToString(array, true)
test.AssertResult(t, `a.[+].b`, got)
got := subject.WritePath(data, "b.3", "a")
test.AssertResult(t, `[{b [{2 c} {3 a}]}]`, fmt.Sprintf("%v", got))
}) })
t.Run("TestPrefixPath", func(t *testing.T) { // t.Run("TestReadPath_WithError", func(t *testing.T) {
var data = test.ParseData(` // var data = test.ParseData(`
--- // ---
b: // b:
2: c // - c
`) // `)
got := subject.PrefixPath(data, "a.d") // _, err := subject.ReadPath(data, "b.[a]")
test.AssertResult(t, `[{a [{d [{b [{2 c}]}]}]}]`, fmt.Sprintf("%v", got)) // if err == nil {
}) // t.Fatal("Expected error due to invalid path")
// }
// })
t.Run("TestDeletePath", func(t *testing.T) { // t.Run("TestWritePath", func(t *testing.T) {
var data = test.ParseData(` // var data = test.ParseData(`
--- // ---
b: // b:
2: c // 2: c
3: a // `)
`)
got, _ := subject.DeletePath(data, "b.2") // got := subject.WritePath(data, "b.3", "a")
test.AssertResult(t, `[{b [{3 a}]}]`, fmt.Sprintf("%v", got)) // test.AssertResult(t, `[{b [{2 c} {3 a}]}]`, fmt.Sprintf("%v", got))
}) // })
t.Run("TestDeletePath_WithError", func(t *testing.T) { // t.Run("TestPrefixPath", func(t *testing.T) {
var data = test.ParseData(` // var data = test.ParseData(`
--- // ---
b: // b:
- c // 2: c
`) // `)
_, err := subject.DeletePath(data, "b.[a]") // got := subject.PrefixPath(data, "a.d")
if err == nil { // test.AssertResult(t, `[{a [{d [{b [{2 c}]}]}]}]`, fmt.Sprintf("%v", got))
t.Fatal("Expected error due to invalid path") // })
}
})
t.Run("TestMerge", func(t *testing.T) { // t.Run("TestDeletePath", func(t *testing.T) {
var dst = test.ParseData(` // var data = test.ParseData(`
--- // ---
a: b // b:
c: d // 2: c
`) // 3: a
var src = test.ParseData(` // `)
---
a: 1
b: 2
`)
var mergedData = make(map[interface{}]interface{}) // got, _ := subject.DeletePath(data, "b.2")
mergedData["root"] = dst // test.AssertResult(t, `[{b [{3 a}]}]`, fmt.Sprintf("%v", got))
var mapDataBucket = make(map[interface{}]interface{}) // })
mapDataBucket["root"] = src
err := subject.Merge(&mergedData, mapDataBucket, false, false) // t.Run("TestDeletePath_WithError", func(t *testing.T) {
if err != nil { // var data = test.ParseData(`
t.Fatal("Unexpected error") // ---
} // b:
test.AssertResult(t, `[{a b} {c d}]`, fmt.Sprintf("%v", mergedData["root"])) // - c
}) // `)
t.Run("TestMerge_WithOverwrite", func(t *testing.T) { // _, err := subject.DeletePath(data, "b.[a]")
var dst = test.ParseData(` // if err == nil {
--- // t.Fatal("Expected error due to invalid path")
a: b // }
c: d // })
`)
var src = test.ParseData(`
---
a: 1
b: 2
`)
var mergedData = make(map[interface{}]interface{}) // t.Run("TestMerge", func(t *testing.T) {
mergedData["root"] = dst // var dst = test.ParseData(`
var mapDataBucket = make(map[interface{}]interface{}) // ---
mapDataBucket["root"] = src // a: b
// c: d
// `)
// var src = test.ParseData(`
// ---
// a: 1
// b: 2
// `)
err := subject.Merge(&mergedData, mapDataBucket, true, false) // var mergedData = make(map[interface{}]interface{})
if err != nil { // mergedData["root"] = dst
t.Fatal("Unexpected error") // var mapDataBucket = make(map[interface{}]interface{})
} // mapDataBucket["root"] = src
test.AssertResult(t, `[{a 1} {b 2}]`, fmt.Sprintf("%v", mergedData["root"]))
})
t.Run("TestMerge_WithAppend", func(t *testing.T) { // err := subject.Merge(&mergedData, mapDataBucket, false, false)
var dst = test.ParseData(` // if err != nil {
--- // t.Fatal("Unexpected error")
a: b // }
c: d // test.AssertResult(t, `[{a b} {c d}]`, fmt.Sprintf("%v", mergedData["root"]))
`) // })
var src = test.ParseData(`
---
a: 1
b: 2
`)
var mergedData = make(map[interface{}]interface{}) // t.Run("TestMerge_WithOverwrite", func(t *testing.T) {
mergedData["root"] = dst // var dst = test.ParseData(`
var mapDataBucket = make(map[interface{}]interface{}) // ---
mapDataBucket["root"] = src // a: b
// c: d
// `)
// var src = test.ParseData(`
// ---
// a: 1
// b: 2
// `)
err := subject.Merge(&mergedData, mapDataBucket, false, true) // var mergedData = make(map[interface{}]interface{})
if err != nil { // mergedData["root"] = dst
t.Fatal("Unexpected error") // var mapDataBucket = make(map[interface{}]interface{})
} // mapDataBucket["root"] = src
test.AssertResult(t, `[{a b} {c d} {a 1} {b 2}]`, fmt.Sprintf("%v", mergedData["root"]))
})
t.Run("TestMerge_WithError", func(t *testing.T) { // err := subject.Merge(&mergedData, mapDataBucket, true, false)
err := subject.Merge(nil, nil, false, false) // if err != nil {
if err == nil { // t.Fatal("Unexpected error")
t.Fatal("Expected error due to nil") // }
} // test.AssertResult(t, `[{a 1} {b 2}]`, fmt.Sprintf("%v", mergedData["root"]))
}) // })
// t.Run("TestMerge_WithAppend", func(t *testing.T) {
// var dst = test.ParseData(`
// ---
// a: b
// c: d
// `)
// var src = test.ParseData(`
// ---
// a: 1
// b: 2
// `)
// var mergedData = make(map[interface{}]interface{})
// mergedData["root"] = dst
// var mapDataBucket = make(map[interface{}]interface{})
// mapDataBucket["root"] = src
// err := subject.Merge(&mergedData, mapDataBucket, false, true)
// if err != nil {
// t.Fatal("Unexpected error")
// }
// test.AssertResult(t, `[{a b} {c d} {a 1} {b 2}]`, fmt.Sprintf("%v", mergedData["root"]))
// })
// t.Run("TestMerge_WithError", func(t *testing.T) {
// err := subject.Merge(nil, nil, false, false)
// if err == nil {
// t.Fatal("Expected error due to nil")
// }
// })
} }

View File

@@ -0,0 +1,152 @@
package yqlib
import (
"fmt"
yaml "gopkg.in/yaml.v3"
)
type NodeContext struct {
Node *yaml.Node
Head string
Tail []string
PathStack []interface{}
}
func NewNodeContext(node *yaml.Node, head string, tail []string, pathStack []interface{}) NodeContext {
newTail := make([]string, len(tail))
copy(newTail, tail)
newPathStack := make([]interface{}, len(pathStack))
copy(newPathStack, pathStack)
return NodeContext{
Node: node,
Head: head,
Tail: newTail,
PathStack: newPathStack,
}
}
type NavigationStrategy interface {
FollowAlias(nodeContext NodeContext) bool
AutoCreateMap(nodeContext NodeContext) bool
Visit(nodeContext NodeContext) error
// node key is the string value of the last element in the path stack
// we use it to match against the pathExpression in head.
ShouldTraverse(nodeContext NodeContext, nodeKey string) bool
GetVisitedNodes() []*NodeContext
DebugVisitedNodes()
GetPathParser() PathParser
}
type NavigationStrategyImpl struct {
followAlias func(nodeContext NodeContext) bool
autoCreateMap func(nodeContext NodeContext) bool
visit func(nodeContext NodeContext) error
shouldVisitExtraFn func(nodeContext NodeContext) bool
visitedNodes []*NodeContext
pathParser PathParser
}
func (ns *NavigationStrategyImpl) GetPathParser() PathParser {
return ns.pathParser
}
func (ns *NavigationStrategyImpl) GetVisitedNodes() []*NodeContext {
return ns.visitedNodes
}
func (ns *NavigationStrategyImpl) FollowAlias(nodeContext NodeContext) bool {
return ns.followAlias(nodeContext)
}
func (ns *NavigationStrategyImpl) AutoCreateMap(nodeContext NodeContext) bool {
return ns.autoCreateMap(nodeContext)
}
func (ns *NavigationStrategyImpl) ShouldTraverse(nodeContext NodeContext, nodeKey string) bool {
// we should traverse aliases (if enabled), but not visit them :/
if len(nodeContext.PathStack) == 0 {
return true
}
if ns.alreadyVisited(nodeContext.PathStack) {
return false
}
return (nodeKey == "<<" && ns.FollowAlias(nodeContext)) || (nodeKey != "<<" &&
ns.pathParser.MatchesNextPathElement(nodeContext, nodeKey))
}
func (ns *NavigationStrategyImpl) shouldVisit(nodeContext NodeContext) bool {
pathStack := nodeContext.PathStack
if len(pathStack) == 0 {
return true
}
log.Debug("tail len %v", len(nodeContext.Tail))
// SOMETHING HERE!
if ns.alreadyVisited(pathStack) || len(nodeContext.Tail) != 0 {
return false
}
nodeKey := fmt.Sprintf("%v", pathStack[len(pathStack)-1])
log.Debug("nodeKey: %v, nodeContext.Head: %v", nodeKey, nodeContext.Head)
// only visit aliases if its an exact match
return ((nodeKey == "<<" && nodeContext.Head == "<<") || (nodeKey != "<<" &&
ns.pathParser.MatchesNextPathElement(nodeContext, nodeKey))) && (ns.shouldVisitExtraFn == nil || ns.shouldVisitExtraFn(nodeContext))
}
func (ns *NavigationStrategyImpl) Visit(nodeContext NodeContext) error {
log.Debug("Visit?, %v, %v", nodeContext.Head, pathStackToString(nodeContext.PathStack))
DebugNode(nodeContext.Node)
if ns.shouldVisit(nodeContext) {
log.Debug("yep, visiting")
// pathStack array must be
// copied, as append() may sometimes reuse and modify the array
ns.visitedNodes = append(ns.visitedNodes, &nodeContext)
ns.DebugVisitedNodes()
return ns.visit(nodeContext)
}
log.Debug("nope, skip it")
return nil
}
func (ns *NavigationStrategyImpl) DebugVisitedNodes() {
log.Debug("Visited Nodes:")
for _, candidate := range ns.visitedNodes {
log.Debug(" - %v", pathStackToString(candidate.PathStack))
}
}
func (ns *NavigationStrategyImpl) alreadyVisited(pathStack []interface{}) bool {
log.Debug("checking already visited pathStack: %v", pathStackToString(pathStack))
for _, candidate := range ns.visitedNodes {
candidatePathStack := candidate.PathStack
if patchStacksMatch(candidatePathStack, pathStack) {
log.Debug("paths match, already seen it")
return true
}
}
log.Debug("never seen it before!")
return false
}
func patchStacksMatch(path1 []interface{}, path2 []interface{}) bool {
log.Debug("checking against path: %v", pathStackToString(path1))
if len(path1) != len(path2) {
return false
}
for index, p1Value := range path1 {
p2Value := path2[index]
if p1Value != p2Value {
return false
}
}
return true
}

View File

@@ -1,7 +1,14 @@
package yqlib package yqlib
import (
"strconv"
"strings"
)
type PathParser interface { type PathParser interface {
ParsePath(path string) []string ParsePath(path string) []string
MatchesNextPathElement(nodeContext NodeContext, nodeKey string) bool
IsPathExpression(pathElement string) bool
} }
type pathParser struct{} type pathParser struct{}
@@ -10,7 +17,66 @@ func NewPathParser() PathParser {
return &pathParser{} return &pathParser{}
} }
func matchesString(expression string, value string) bool {
var prefixMatch = strings.TrimSuffix(expression, "*")
if prefixMatch != expression {
log.Debug("prefix match, %v", strings.HasPrefix(value, prefixMatch))
return strings.HasPrefix(value, prefixMatch)
}
return value == expression
}
func (p *pathParser) IsPathExpression(pathElement string) bool {
return pathElement == "*" || pathElement == "**" || strings.Contains(pathElement, "==")
}
/**
* node: node that we may traverse/visit
* head: path element expression to match against
* tail: remaining path element expressions
* pathStack: stack of actual paths we've matched to get to node
* nodeKey: actual value of this nodes 'key' or index.
*/
func (p *pathParser) MatchesNextPathElement(nodeContext NodeContext, nodeKey string) bool {
head := nodeContext.Head
if head == "**" || head == "*" {
return true
}
if strings.Contains(head, "==") {
log.Debug("ooh deep recursion time")
result := strings.SplitN(head, "==", 2)
path := strings.TrimSpace(result[0])
value := strings.TrimSpace(result[1])
log.Debug("path %v", path)
log.Debug("value %v", value)
DebugNode(nodeContext.Node)
navigationStrategy := FilterMatchingNodesNavigationStrategy(value)
navigator := NewDataNavigator(navigationStrategy)
err := navigator.Traverse(nodeContext.Node, p.ParsePath(path))
if err != nil {
log.Error(err.Error())
}
//crap handle error
log.Debug("done deep recursing, found %v matches", len(navigationStrategy.GetVisitedNodes()))
return len(navigationStrategy.GetVisitedNodes()) > 0
}
if head == "+" {
log.Debug("head is +, nodeKey is %v", nodeKey)
var _, err = strconv.ParseInt(nodeKey, 10, 64) // nolint
if err == nil {
return true
}
}
return matchesString(head, nodeKey)
}
func (p *pathParser) ParsePath(path string) []string { func (p *pathParser) ParsePath(path string) []string {
if path == "" {
return []string{}
}
return p.parsePathAccum([]string{}, path) return p.parsePathAccum([]string{}, path)
} }
@@ -30,9 +96,12 @@ func (p *pathParser) nextYamlPath(path string) (pathElement string, remaining st
case '"': case '"':
// e.g "a.b".blah.cat -> we need to return "a.b" and "blah.cat" // e.g "a.b".blah.cat -> we need to return "a.b" and "blah.cat"
return p.search(path[1:], []uint8{'"'}, true) return p.search(path[1:], []uint8{'"'}, true)
case '(':
// e.g "a.b".blah.cat -> we need to return "a.b" and "blah.cat"
return p.search(path[1:], []uint8{')'}, true)
default: default:
// e.g "a.blah.cat" -> return "a" and "blah.cat" // e.g "a.blah.cat" -> return "a" and "blah.cat"
return p.search(path[0:], []uint8{'.', '['}, false) return p.search(path[0:], []uint8{'.', '[', '"', '('}, false)
} }
} }

View File

@@ -3,14 +3,18 @@ package yqlib
import ( import (
"testing" "testing"
"github.com/mikefarah/yq/v2/test" "github.com/mikefarah/yq/v3/test"
) )
var parser = NewPathParser()
var parsePathsTests = []struct { var parsePathsTests = []struct {
path string path string
expectedPaths []string expectedPaths []string
}{ }{
{"a.b", []string{"a", "b"}}, {"a.b", []string{"a", "b"}},
{"a.b.**", []string{"a", "b", "**"}},
{"a.b.*", []string{"a", "b", "*"}},
{"a.b[0]", []string{"a", "b", "0"}}, {"a.b[0]", []string{"a", "b", "0"}},
{"a.b.d[+]", []string{"a", "b", "d", "+"}}, {"a.b.d[+]", []string{"a", "b", "d", "+"}},
{"a", []string{"a"}}, {"a", []string{"a"}},
@@ -22,8 +26,53 @@ var parsePathsTests = []struct {
{"[0]", []string{"0"}}, {"[0]", []string{"0"}},
} }
func TestParsePath(t *testing.T) { func TestPathParserParsePath(t *testing.T) {
for _, tt := range parsePathsTests { for _, tt := range parsePathsTests {
test.AssertResultComplex(t, tt.expectedPaths, NewPathParser().ParsePath(tt.path)) test.AssertResultComplex(t, tt.expectedPaths, parser.ParsePath(tt.path))
} }
} }
func TestPathParserMatchesNextPathElementSplat(t *testing.T) {
var node = NodeContext{Head: "*"}
test.AssertResult(t, true, parser.MatchesNextPathElement(node, ""))
}
func TestPathParserMatchesNextPathElementDeepSplat(t *testing.T) {
var node = NodeContext{Head: "**"}
test.AssertResult(t, true, parser.MatchesNextPathElement(node, ""))
}
func TestPathParserMatchesNextPathElementAppendArrayValid(t *testing.T) {
var node = NodeContext{Head: "+"}
test.AssertResult(t, true, parser.MatchesNextPathElement(node, "3"))
}
func TestPathParserMatchesNextPathElementAppendArrayInvalid(t *testing.T) {
var node = NodeContext{Head: "+"}
test.AssertResult(t, false, parser.MatchesNextPathElement(node, "cat"))
}
func TestPathParserMatchesNextPathElementPrefixMatchesWhole(t *testing.T) {
var node = NodeContext{Head: "cat*"}
test.AssertResult(t, true, parser.MatchesNextPathElement(node, "cat"))
}
func TestPathParserMatchesNextPathElementPrefixMatchesStart(t *testing.T) {
var node = NodeContext{Head: "cat*"}
test.AssertResult(t, true, parser.MatchesNextPathElement(node, "caterpillar"))
}
func TestPathParserMatchesNextPathElementPrefixMismatch(t *testing.T) {
var node = NodeContext{Head: "cat*"}
test.AssertResult(t, false, parser.MatchesNextPathElement(node, "dog"))
}
func TestPathParserMatchesNextPathElementExactMatch(t *testing.T) {
var node = NodeContext{Head: "farahtek"}
test.AssertResult(t, true, parser.MatchesNextPathElement(node, "farahtek"))
}
func TestPathParserMatchesNextPathElementExactMismatch(t *testing.T) {
var node = NodeContext{Head: "farahtek"}
test.AssertResult(t, false, parser.MatchesNextPathElement(node, "othertek"))
}

View File

@@ -0,0 +1,17 @@
package yqlib
func ReadNavigationStrategy() NavigationStrategy {
return &NavigationStrategyImpl{
visitedNodes: []*NodeContext{},
pathParser: NewPathParser(),
followAlias: func(nodeContext NodeContext) bool {
return true
},
autoCreateMap: func(nodeContext NodeContext) bool {
return false
},
visit: func(nodeContext NodeContext) error {
return nil
},
}
}

View File

@@ -0,0 +1,35 @@
package yqlib
func UpdateNavigationStrategy(updateCommand UpdateCommand, autoCreate bool) NavigationStrategy {
return &NavigationStrategyImpl{
visitedNodes: []*NodeContext{},
pathParser: NewPathParser(),
followAlias: func(nodeContext NodeContext) bool {
return false
},
autoCreateMap: func(nodeContext NodeContext) bool {
return autoCreate
},
visit: func(nodeContext NodeContext) error {
node := nodeContext.Node
changesToApply := updateCommand.Value
if updateCommand.Overwrite || node.Value == "" {
log.Debug("going to update")
DebugNode(node)
log.Debug("with")
DebugNode(changesToApply)
node.Value = changesToApply.Value
node.Tag = changesToApply.Tag
node.Kind = changesToApply.Kind
node.Style = changesToApply.Style
node.Content = changesToApply.Content
node.HeadComment = changesToApply.HeadComment
node.LineComment = changesToApply.LineComment
node.FootComment = changesToApply.FootComment
} else {
log.Debug("skipping update as node already has value %v and overwriteFlag is ", node.Value, updateCommand.Overwrite)
}
return nil
},
}
}

View File

@@ -2,34 +2,46 @@ package yqlib
import ( import (
"strconv" "strconv"
yaml "gopkg.in/yaml.v3"
) )
type ValueParser interface { type ValueParser interface {
ParseValue(argument string) interface{} Parse(argument string, customTag string) *yaml.Node
} }
type valueParser struct{} type valueParser struct {
}
func NewValueParser() ValueParser { func NewValueParser() ValueParser {
return &valueParser{} return &valueParser{}
} }
func (v *valueParser) ParseValue(argument string) interface{} { func (v *valueParser) Parse(argument string, customTag string) *yaml.Node {
var value, err interface{} var err interface{}
var inQuotes = len(argument) > 0 && argument[0] == '"' var tag = customTag
if !inQuotes {
value, err = strconv.ParseFloat(argument, 64) if tag == "" {
_, err = strconv.ParseBool(argument)
if err == nil { if err == nil {
return value tag = "!!bool"
} }
value, err = strconv.ParseBool(argument) _, err = strconv.ParseFloat(argument, 64)
if err == nil { if err == nil {
return value tag = "!!float"
}
_, err = strconv.ParseInt(argument, 10, 64)
if err == nil {
tag = "!!int"
}
if argument == "null" {
tag = "!!null"
} }
if argument == "[]" { if argument == "[]" {
return make([]interface{}, 0) return &yaml.Node{Tag: "!!seq", Kind: yaml.SequenceNode}
} }
return argument
} }
return argument[1 : len(argument)-1] log.Debugf("parsed value '%v', tag: '%v'", argument, tag)
return &yaml.Node{Value: argument, Tag: tag, Kind: yaml.ScalarNode}
} }

View File

@@ -3,23 +3,36 @@ package yqlib
import ( import (
"testing" "testing"
"github.com/mikefarah/yq/v2/test" "github.com/mikefarah/yq/v3/test"
yaml "gopkg.in/yaml.v3"
) )
var parseValueTests = []struct { var parseValueTests = []struct {
argument string argument string
expectedResult interface{} customTag string
expectedTag string
testDescription string testDescription string
}{ }{
{"true", true, "boolean"}, {"true", "", "!!bool", "boolean"},
{"\"true\"", "true", "boolean as string"}, {"true", "!!str", "!!str", "boolean forced as string"},
{"3.4", 3.4, "number"}, {"3.4", "", "!!float", "float"},
{"\"3.4\"", "3.4", "number as string"}, {"1212121", "", "!!int", "big number"},
{"", "", "empty string"}, {"1212121.1", "", "!!float", "big float number"},
{"3", "", "!!int", "int"},
{"null", "", "!!null", "null"},
} }
func TestParseValue(t *testing.T) { func TestValueParserParse(t *testing.T) {
for _, tt := range parseValueTests { for _, tt := range parseValueTests {
test.AssertResultWithContext(t, tt.expectedResult, NewValueParser().ParseValue(tt.argument), tt.testDescription) actual := NewValueParser().Parse(tt.argument, tt.customTag)
test.AssertResultWithContext(t, tt.argument, actual.Value, tt.testDescription)
test.AssertResultWithContext(t, tt.expectedTag, actual.Tag, tt.testDescription)
test.AssertResult(t, yaml.ScalarNode, actual.Kind)
} }
} }
func TestValueParserParseEmptyArray(t *testing.T) {
actual := NewValueParser().Parse("[]", "")
test.AssertResult(t, "!!seq", actual.Tag)
test.AssertResult(t, yaml.SequenceNode, actual.Kind)
}

View File

@@ -2,5 +2,5 @@
set -e set -e
go test -coverprofile=coverage.out ./... go test -coverprofile=coverage.out -v $(go list ./... | grep -v -E 'examples' | grep -v -E 'test')
go tool cover -html=coverage.out -o cover/coverage.html go tool cover -html=coverage.out -o coverage.html

View File

@@ -32,5 +32,5 @@ upload() {
done < <(find ./build -mindepth 1 -maxdepth 1 -print0) done < <(find ./build -mindepth 1 -maxdepth 1 -print0)
} }
release # release
upload upload

View File

@@ -1,3 +1,3 @@
#!/bin/bash #!/bin/bash
go test -v $(go list ./... | grep -v -E 'examples') go test -v $(go list ./... | grep -v -E 'examples' | grep -v -E 'test')

View File

@@ -3,7 +3,7 @@
# This assumes that gonative and gox is installed as per the 'one time setup' instructions # This assumes that gonative and gox is installed as per the 'one time setup' instructions
# at https://github.com/inconshreveable/gonative # at https://github.com/inconshreveable/gonative
gox -ldflags "${LDFLAGS}" -output="build/{{.Dir}}_{{.OS}}_{{.Arch}}" gox -ldflags "${LDFLAGS}" -output="build/yq_{{.OS}}_{{.Arch}}"
# include non-default linux builds too # include non-default linux builds too
gox -ldflags "${LDFLAGS}" -os=linux -output="build/{{.Dir}}_{{.OS}}_{{.Arch}}" gox -ldflags "${LDFLAGS}" -os=linux -output="build/yq_{{.OS}}_{{.Arch}}"

View File

@@ -1,5 +1,5 @@
name: yq name: yq
version: '2.4.1' version: '3.0.0-beta'
summary: A lightweight and portable command-line YAML processor summary: A lightweight and portable command-line YAML processor
description: | description: |
The aim of the project is to be the jq or sed of yaml files. The aim of the project is to be the jq or sed of yaml files.

View File

@@ -1,2 +1,3 @@
a: apple b:
c: cat c: thing
d: another thing

View File

@@ -9,8 +9,8 @@ import (
"strings" "strings"
"testing" "testing"
yaml "github.com/mikefarah/yaml/v2"
"github.com/spf13/cobra" "github.com/spf13/cobra"
yaml "gopkg.in/yaml.v3"
) )
type resulter struct { type resulter struct {
@@ -30,8 +30,8 @@ func RunCmd(c *cobra.Command, input string) resulter {
return resulter{err, output, c} return resulter{err, output, c}
} }
func ParseData(rawData string) yaml.MapSlice { func ParseData(rawData string) yaml.Node {
var parsedData yaml.MapSlice var parsedData yaml.Node
err := yaml.Unmarshal([]byte(rawData), &parsedData) err := yaml.Unmarshal([]byte(rawData), &parsedData)
if err != nil { if err != nil {
fmt.Printf("Error parsing yaml: %v\n", err) fmt.Printf("Error parsing yaml: %v\n", err)

View File

@@ -11,12 +11,12 @@ var (
GitDescribe string GitDescribe string
// Version is main version number that is being run at the moment. // Version is main version number that is being run at the moment.
Version = "2.4.1" Version = "3.0.0"
// VersionPrerelease is a pre-release marker for the version. If this is "" (empty string) // VersionPrerelease is a pre-release marker for the version. If this is "" (empty string)
// then it means that it is a final release. Otherwise, this is a pre-release // then it means that it is a final release. Otherwise, this is a pre-release
// such as "dev" (in development), "beta", "rc1", etc. // such as "dev" (in development), "beta", "rc1", etc.
VersionPrerelease = "" VersionPrerelease = "beta"
) )
// ProductName is the name of the product // ProductName is the name of the product

538
yq.go
View File

@@ -6,34 +6,32 @@ import (
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
"reflect"
"strconv" "strconv"
"strings" "strings"
"github.com/mikefarah/yq/v2/pkg/marshal" "github.com/mikefarah/yq/v3/pkg/yqlib"
"github.com/mikefarah/yq/v2/pkg/yqlib"
errors "github.com/pkg/errors" errors "github.com/pkg/errors"
yaml "github.com/mikefarah/yaml/v2"
"github.com/spf13/cobra" "github.com/spf13/cobra"
logging "gopkg.in/op/go-logging.v1" logging "gopkg.in/op/go-logging.v1"
yaml "gopkg.in/yaml.v3"
) )
var trimOutput = true var customTag = ""
var printMode = "v"
var writeInplace = false var writeInplace = false
var writeScript = "" var writeScript = ""
var outputToJSON = false var outputToJSON = false
var overwriteFlag = false var overwriteFlag = false
var autoCreateFlag = true
var allowEmptyFlag = false var allowEmptyFlag = false
var appendFlag = false var appendFlag = false
var verbose = false var verbose = false
var version = false var version = false
var docIndex = "0" var docIndex = "0"
var log = logging.MustGetLogger("yq") var log = logging.MustGetLogger("yq")
var lib = yqlib.NewYqLib(log) var lib = yqlib.NewYqLib()
var jsonConverter = marshal.NewJsonConverter()
var yamlConverter = marshal.NewYamlConverter()
var valueParser = yqlib.NewValueParser() var valueParser = yqlib.NewValueParser()
func main() { func main() {
@@ -45,7 +43,6 @@ func main() {
} }
func newCommandCLI() *cobra.Command { func newCommandCLI() *cobra.Command {
yaml.DefaultMapType = reflect.TypeOf(yaml.MapSlice{})
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "yq", Use: "yq",
Short: "yq is a lightweight and portable command-line YAML processor.", Short: "yq is a lightweight and portable command-line YAML processor.",
@@ -76,8 +73,8 @@ func newCommandCLI() *cobra.Command {
}, },
} }
rootCmd.PersistentFlags().BoolVarP(&trimOutput, "trim", "t", true, "trim yaml output")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose mode") rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose mode")
rootCmd.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "output as json")
rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit") rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit")
rootCmd.AddCommand( rootCmd.AddCommand(
@@ -95,96 +92,110 @@ func newCommandCLI() *cobra.Command {
func createReadCmd() *cobra.Command { func createReadCmd() *cobra.Command {
var cmdRead = &cobra.Command{ var cmdRead = &cobra.Command{
Use: "read [yaml_file] [path]", Use: "read [yaml_file] [path_expression]",
Aliases: []string{"r"}, Aliases: []string{"r"},
Short: "yq r [--doc/-d index] sample.yaml a.b.c", Short: "yq r [--doc/-d index] sample.yaml 'a.b.c'",
Example: ` Example: `
yq read things.yaml a.b.c yq read things.yaml 'a.b.c'
yq r - a.b.c (reads from stdin) yq r - 'a.b.c' # reads from stdin
yq r things.yaml a.*.c yq r things.yaml 'a.*.c'
yq r -d1 things.yaml a.array[0].blah yq r things.yaml 'a.**.c' # deep splat
yq r things.yaml a.array[*].blah yq r things.yaml 'a.(child.subchild==co*).c'
yq r -- things.yaml --key-starting-with-dashes yq r -d1 things.yaml 'a.array[0].blah'
yq r things.yaml 'a.array[*].blah'
yq r -- things.yaml '--key-starting-with-dashes.blah'
`, `,
Long: "Outputs the value of the given path in the yaml file to STDOUT", Long: "Outputs the value of the given path in the yaml file to STDOUT",
RunE: readProperty, RunE: readProperty,
} }
cmdRead.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") cmdRead.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
cmdRead.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "output as json") cmdRead.PersistentFlags().StringVarP(&printMode, "printMode", "p", "v", "print mode (v (values, default), p (paths), pv (path and value pairs)")
return cmdRead return cmdRead
} }
func createWriteCmd() *cobra.Command { func createWriteCmd() *cobra.Command {
var cmdWrite = &cobra.Command{ var cmdWrite = &cobra.Command{
Use: "write [yaml_file] [path] [value]", Use: "write [yaml_file] [path_expression] [value]",
Aliases: []string{"w"}, Aliases: []string{"w"},
Short: "yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml a.b.c newValue", Short: "yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml 'a.b.c' newValue",
Example: ` Example: `
yq write things.yaml a.b.c cat yq write things.yaml 'a.b.c' true
yq write --inplace -- things.yaml a.b.c --cat yq write things.yaml 'a.*.c' true
yq w -i things.yaml a.b.c cat yq write things.yaml 'a.**' true
yq write things.yaml 'a.(child.subchild==co*).c' true
yq write things.yaml 'a.b.c' --tag '!!str' true # force 'true' to be interpreted as a string instead of bool
yq write things.yaml 'a.b.c' --tag '!!float' 3
yq write --inplace -- things.yaml 'a.b.c' '--cat' # need to use '--' to stop processing arguments as flags
yq w -i things.yaml 'a.b.c' cat
yq w --script update_script.yaml things.yaml yq w --script update_script.yaml things.yaml
yq w -i -s update_script.yaml things.yaml yq w -i -s update_script.yaml things.yaml
yq w --doc 2 things.yaml a.b.d[+] foo yq w things.yaml 'a.b.d[+]' foo # appends a new node to the 'd' array
yq w -d2 things.yaml a.b.d[+] foo yq w --doc 2 things.yaml 'a.b.d[+]' foo # updates the 3rd document of the yaml file
`, `,
Long: `Updates the yaml file w.r.t the given path and value. Long: `Updates the matching nodes of path expression to the specified value
Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
Append value to array adds the value to the end of array. Append value to array adds the value to the end of array.
Update Scripts: Update Scripts:
Note that you can give an update script to perform more sophisticated updated. Update script Note that you can give an update script to perform more sophisticated update. Update script
format is a yaml map where the key is the path and the value is..well the value. e.g.: format is list of update commands (update or delete) like so:
--- ---
a.b.c: true, - command: update
a.b.e: path: b.c
- name: bob value:
#great
things: frog # wow!
- command: delete
path: b.d
`, `,
RunE: writeProperty, RunE: writeProperty,
} }
cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
cmdWrite.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml") cmdWrite.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml")
cmdWrite.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)")
cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
return cmdWrite return cmdWrite
} }
func createPrefixCmd() *cobra.Command { func createPrefixCmd() *cobra.Command {
var cmdWrite = &cobra.Command{ var cmdPrefix = &cobra.Command{
Use: "prefix [yaml_file] [path]", Use: "prefix [yaml_file] [path]",
Aliases: []string{"p"}, Aliases: []string{"p"},
Short: "yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c", Short: "yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c",
Example: ` Example: `
yq prefix things.yaml a.b.c yq prefix things.yaml 'a.b.c'
yq prefix --inplace things.yaml a.b.c yq prefix --inplace things.yaml 'a.b.c'
yq prefix --inplace -- things.yaml --key-starting-with-dash yq prefix --inplace -- things.yaml '--key-starting-with-dash' # need to use '--' to stop processing arguments as flags
yq p -i things.yaml a.b.c yq p -i things.yaml 'a.b.c'
yq p --doc 2 things.yaml a.b.d yq p --doc 2 things.yaml 'a.b.d'
yq p -d2 things.yaml a.b.d yq p -d2 things.yaml 'a.b.d'
`, `,
Long: `Prefixes w.r.t to the yaml file at the given path. Long: `Prefixes w.r.t to the yaml file at the given path.
Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
`, `,
RunE: prefixProperty, RunE: prefixProperty,
} }
cmdWrite.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") cmdPrefix.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") cmdPrefix.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
return cmdWrite return cmdPrefix
} }
func createDeleteCmd() *cobra.Command { func createDeleteCmd() *cobra.Command {
var cmdDelete = &cobra.Command{ var cmdDelete = &cobra.Command{
Use: "delete [yaml_file] [path]", Use: "delete [yaml_file] [path_expression]",
Aliases: []string{"d"}, Aliases: []string{"d"},
Short: "yq d [--inplace/-i] [--doc/-d index] sample.yaml a.b.c", Short: "yq d [--inplace/-i] [--doc/-d index] sample.yaml a.b.c",
Example: ` Example: `
yq delete things.yaml a.b.c yq delete things.yaml 'a.b.c'
yq delete --inplace things.yaml a.b.c yq delete things.yaml 'a.*.c'
yq delete --inplace -- things.yaml --key-starting-with-dash yq delete things.yaml 'a.(child.subchild==co*).c'
yq d -i things.yaml a.b.c yq delete things.yaml 'a.**'
yq d things.yaml a.b.c yq delete --inplace things.yaml 'a.b.c'
yq delete --inplace -- things.yaml '--key-starting-with-dash' # need to use '--' to stop processing arguments as flags
yq d -i things.yaml 'a.b.c'
`, `,
Long: `Deletes the given path from the YAML file. Long: `Deletes the nodes matching the given path expression from the YAML file.
Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
`, `,
RunE: deleteProperty, RunE: deleteProperty,
@@ -200,9 +211,10 @@ func createNewCmd() *cobra.Command {
Aliases: []string{"n"}, Aliases: []string{"n"},
Short: "yq n [--script/-s script_file] a.b.c newValue", Short: "yq n [--script/-s script_file] a.b.c newValue",
Example: ` Example: `
yq new a.b.c cat yq new 'a.b.c' cat
yq n a.b.c cat yq n 'a.b.c' --tag '!!str' true # force 'true' to be interpreted as a string instead of bool
yq n -- --key-starting-with-dash cat yq n 'a.b[+]' cat
yq n -- '--key-starting-with-dash' cat # need to use '--' to stop processing arguments as flags
yq n --script create_script.yaml yq n --script create_script.yaml
`, `,
Long: `Creates a new yaml w.r.t the given path and value. Long: `Creates a new yaml w.r.t the given path and value.
@@ -213,7 +225,8 @@ Note that you can give a create script to perform more sophisticated yaml. This
`, `,
RunE: newProperty, RunE: newProperty,
} }
cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml") cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for creating yaml")
cmdNew.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)")
return cmdNew return cmdNew
} }
@@ -229,19 +242,19 @@ yq m -i things.yaml other.yaml
yq m --overwrite things.yaml other.yaml yq m --overwrite things.yaml other.yaml
yq m -i -x things.yaml other.yaml yq m -i -x things.yaml other.yaml
yq m -i -a things.yaml other.yaml yq m -i -a things.yaml other.yaml
yq m -i --autocreate=false things.yaml other.yaml
`, `,
Long: `Updates the yaml file by adding/updating the path(s) and value(s) from additional yaml file(s). Long: `Updates the yaml file by adding/updating the path(s) and value(s) from additional yaml file(s).
Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead. Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
If overwrite flag is set then existing values will be overwritten using the values from each additional yaml file. If overwrite flag is set then existing values will be overwritten using the values from each additional yaml file.
If append flag is set then existing arrays will be merged with the arrays from each additional yaml file. If append flag is set then existing arrays will be merged with the arrays from each additional yaml file.
Note that if you set both flags only overwrite will take effect.
`, `,
RunE: mergeProperties, RunE: mergeProperties,
} }
cmdMerge.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace") cmdMerge.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values") cmdMerge.PersistentFlags().BoolVarP(&overwriteFlag, "overwrite", "x", false, "update the yaml file by overwriting existing values")
cmdMerge.PersistentFlags().BoolVarP(&autoCreateFlag, "autocreate", "c", true, "automatically create any missing entries")
cmdMerge.PersistentFlags().BoolVarP(&appendFlag, "append", "a", false, "update the yaml file by appending array values") cmdMerge.PersistentFlags().BoolVarP(&appendFlag, "append", "a", false, "update the yaml file by appending array values")
cmdMerge.PersistentFlags().BoolVarP(&allowEmptyFlag, "allow-empty", "e", false, "allow empty yaml files") cmdMerge.PersistentFlags().BoolVarP(&allowEmptyFlag, "allow-empty", "e", false, "allow empty yaml files")
cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
@@ -261,92 +274,117 @@ func readProperty(cmd *cobra.Command, args []string) error {
if errorParsingDocIndex != nil { if errorParsingDocIndex != nil {
return errorParsingDocIndex return errorParsingDocIndex
} }
var mappedDocs []interface{}
var dataBucket interface{} matchingNodes, errorReadingStream := readYamlFile(args[0], path, updateAll, docIndexInt)
var currentIndex = 0
var errorReadingStream = readStream(args[0], func(decoder *yaml.Decoder) error {
for {
errorReading := decoder.Decode(&dataBucket)
if errorReading == io.EOF {
log.Debugf("done %v / %v", currentIndex, docIndexInt)
if !updateAll && currentIndex <= docIndexInt {
return fmt.Errorf("asked to process document index %v but there are only %v document(s)", docIndex, currentIndex)
}
return nil
}
log.Debugf("processing %v - requested index %v", currentIndex, docIndexInt)
if updateAll || currentIndex == docIndexInt {
log.Debugf("reading %v in index %v", path, currentIndex)
if path == "" {
log.Debug("no path")
log.Debugf("%v", dataBucket)
mappedDocs = append(mappedDocs, dataBucket)
} else {
mappedDoc, errorParsing := lib.ReadPath(dataBucket, path)
log.Debugf("%v", mappedDoc)
if errorParsing != nil {
return errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex)
}
mappedDocs = append(mappedDocs, mappedDoc)
}
}
currentIndex = currentIndex + 1
}
})
if errorReadingStream != nil { if errorReadingStream != nil {
return errorReadingStream return errorReadingStream
} }
if !updateAll { return printResults(matchingNodes, cmd)
dataBucket = mappedDocs[0] }
} else {
dataBucket = mappedDocs
}
dataStr, err := toString(dataBucket) func readYamlFile(filename string, path string, updateAll bool, docIndexInt int) ([]*yqlib.NodeContext, error) {
if err != nil { var matchingNodes []*yqlib.NodeContext
return err
var currentIndex = 0
var errorReadingStream = readStream(filename, func(decoder *yaml.Decoder) error {
for {
var dataBucket yaml.Node
errorReading := decoder.Decode(&dataBucket)
if errorReading == io.EOF {
return handleEOF(updateAll, docIndexInt, currentIndex)
}
var errorParsing error
matchingNodes, errorParsing = appendDocument(matchingNodes, dataBucket, path, updateAll, docIndexInt, currentIndex)
if errorParsing != nil {
return errorParsing
}
currentIndex = currentIndex + 1
}
})
return matchingNodes, errorReadingStream
}
func handleEOF(updateAll bool, docIndexInt int, currentIndex int) error {
log.Debugf("done %v / %v", currentIndex, docIndexInt)
if !updateAll && currentIndex <= docIndexInt {
return fmt.Errorf("Could not process document index %v as there are only %v document(s)", docIndex, currentIndex)
} }
cmd.Println(dataStr)
return nil return nil
} }
func newProperty(cmd *cobra.Command, args []string) error { func appendDocument(originalMatchingNodes []*yqlib.NodeContext, dataBucket yaml.Node, path string, updateAll bool, docIndexInt int, currentIndex int) ([]*yqlib.NodeContext, error) {
updatedData, err := newYaml(args) log.Debugf("processing document %v - requested index %v", currentIndex, docIndexInt)
if err != nil { yqlib.DebugNode(&dataBucket)
if !updateAll && currentIndex != docIndexInt {
return originalMatchingNodes, nil
}
log.Debugf("reading %v in document %v", path, currentIndex)
matchingNodes, errorParsing := lib.Get(&dataBucket, path)
if errorParsing != nil {
return nil, errors.Wrapf(errorParsing, "Error reading path in document index %v", currentIndex)
}
return append(originalMatchingNodes, matchingNodes...), nil
}
func printValue(node *yaml.Node, cmd *cobra.Command) error {
if node.Kind == yaml.ScalarNode {
cmd.Print(node.Value)
return nil
}
bufferedWriter := bufio.NewWriter(cmd.OutOrStdout())
defer safelyFlush(bufferedWriter)
var encoder yqlib.Encoder
if outputToJSON {
encoder = yqlib.NewJsonEncoder(bufferedWriter)
} else {
encoder = yqlib.NewYamlEncoder(bufferedWriter)
}
if err := encoder.Encode(node); err != nil {
return err return err
} }
dataStr, err := toString(updatedData)
if err != nil {
return err
}
cmd.Println(dataStr)
return nil return nil
} }
func newYaml(args []string) (interface{}, error) { func printResults(matchingNodes []*yqlib.NodeContext, cmd *cobra.Command) error {
var writeCommands, writeCommandsError = readWriteCommands(args, 2, "Must provide <path_to_update> <value>") if len(matchingNodes) == 0 {
if writeCommandsError != nil { log.Debug("no matching results, nothing to print")
return nil, writeCommandsError return nil
} }
var dataBucket interface{} for index, mappedDoc := range matchingNodes {
var isArray = strings.HasPrefix(writeCommands[0].Key.(string), "[") switch printMode {
if isArray { case "p":
dataBucket = make([]interface{}, 0) cmd.Print(lib.PathStackToString(mappedDoc.PathStack))
} else { if index < len(matchingNodes)-1 {
dataBucket = make(yaml.MapSlice, 0) cmd.Print("\n")
}
case "pv", "vp":
// put it into a node and print that.
var parentNode = yaml.Node{Kind: yaml.MappingNode}
parentNode.Content = make([]*yaml.Node, 2)
parentNode.Content[0] = &yaml.Node{Kind: yaml.ScalarNode, Value: lib.PathStackToString(mappedDoc.PathStack)}
parentNode.Content[1] = mappedDoc.Node
if err := printValue(&parentNode, cmd); err != nil {
return err
}
default:
if err := printValue(mappedDoc.Node, cmd); err != nil {
return err
}
// Printing our Scalars does not print a new line at the end
// we only want to do that if there are more values (so users can easily script extraction of values in the yaml)
if index < len(matchingNodes)-1 && mappedDoc.Node.Kind == yaml.ScalarNode {
cmd.Print("\n")
}
}
} }
for _, entry := range writeCommands { return nil
path := entry.Key.(string)
value := entry.Value
log.Debugf("setting %v to %v", path, value)
dataBucket = lib.WritePath(dataBucket, path, value)
}
return dataBucket, nil
} }
func parseDocumentIndex() (bool, int, error) { func parseDocumentIndex() (bool, int, error) {
@@ -360,11 +398,11 @@ func parseDocumentIndex() (bool, int, error) {
return false, int(docIndexInt64), nil return false, int(docIndexInt64), nil
} }
type updateDataFn func(dataBucket interface{}, currentIndex int) (interface{}, error) type updateDataFn func(dataBucket *yaml.Node, currentIndex int) error
func mapYamlDecoder(updateData updateDataFn, encoder *yaml.Encoder) yamlDecoderFn { func mapYamlDecoder(updateData updateDataFn, encoder yqlib.Encoder) yamlDecoderFn {
return func(decoder *yaml.Decoder) error { return func(decoder *yaml.Decoder) error {
var dataBucket interface{} var dataBucket yaml.Node
var errorReading error var errorReading error
var errorWriting error var errorWriting error
var errorUpdating error var errorUpdating error
@@ -387,12 +425,12 @@ func mapYamlDecoder(updateData updateDataFn, encoder *yaml.Encoder) yamlDecoderF
} else if errorReading != nil { } else if errorReading != nil {
return errors.Wrapf(errorReading, "Error reading document at index %v, %v", currentIndex, errorReading) return errors.Wrapf(errorReading, "Error reading document at index %v, %v", currentIndex, errorReading)
} }
dataBucket, errorUpdating = updateData(dataBucket, currentIndex) errorUpdating = updateData(&dataBucket, currentIndex)
if errorUpdating != nil { if errorUpdating != nil {
return errors.Wrapf(errorUpdating, "Error updating document at index %v", currentIndex) return errors.Wrapf(errorUpdating, "Error updating document at index %v", currentIndex)
} }
errorWriting = encoder.Encode(dataBucket) errorWriting = encoder.Encode(&dataBucket)
if errorWriting != nil { if errorWriting != nil {
return errors.Wrapf(errorWriting, "Error writing document at index %v, %v", currentIndex, errorWriting) return errors.Wrapf(errorWriting, "Error writing document at index %v, %v", currentIndex, errorWriting)
@@ -403,51 +441,125 @@ func mapYamlDecoder(updateData updateDataFn, encoder *yaml.Encoder) yamlDecoderF
} }
func writeProperty(cmd *cobra.Command, args []string) error { func writeProperty(cmd *cobra.Command, args []string) error {
var writeCommands, writeCommandsError = readWriteCommands(args, 3, "Must provide <filename> <path_to_update> <value>") var updateCommands, updateCommandsError = readUpdateCommands(args, 3, "Must provide <filename> <path_to_update> <value>")
if writeCommandsError != nil { if updateCommandsError != nil {
return writeCommandsError return updateCommandsError
} }
return updateDoc(args[0], updateCommands, cmd.OutOrStdout())
}
func mergeProperties(cmd *cobra.Command, args []string) error {
if len(args) < 2 {
return errors.New("Must provide at least 2 yaml files")
}
// first generate update commands from the file
var filesToMerge = args[1:]
var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0)
for _, fileToMerge := range filesToMerge {
matchingNodes, errorProcessingFile := readYamlFile(fileToMerge, "**", false, 0)
if errorProcessingFile != nil && (!allowEmptyFlag || !strings.HasPrefix(errorProcessingFile.Error(), "Could not process document index")) {
return errorProcessingFile
}
for _, matchingNode := range matchingNodes {
mergePath := lib.MergePathStackToString(matchingNode.PathStack, appendFlag)
updateCommands = append(updateCommands, yqlib.UpdateCommand{Command: "update", Path: mergePath, Value: matchingNode.Node, Overwrite: overwriteFlag})
}
}
return updateDoc(args[0], updateCommands, cmd.OutOrStdout())
}
func newProperty(cmd *cobra.Command, args []string) error {
var updateCommands, updateCommandsError = readUpdateCommands(args, 2, "Must provide <path_to_update> <value>")
if updateCommandsError != nil {
return updateCommandsError
}
newNode := lib.New(updateCommands[0].Path)
for _, updateCommand := range updateCommands {
errorUpdating := lib.Update(&newNode, updateCommand, true)
if errorUpdating != nil {
return errorUpdating
}
}
var encoder = yaml.NewEncoder(cmd.OutOrStdout())
encoder.SetIndent(2)
errorEncoding := encoder.Encode(&newNode)
encoder.Close()
return errorEncoding
}
func prefixProperty(cmd *cobra.Command, args []string) error {
if len(args) < 2 {
return errors.New("Must provide <filename> <prefixed_path>")
}
updateCommand := yqlib.UpdateCommand{Command: "update", Path: args[1]}
log.Debugf("args %v", args)
var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
if errorParsingDocIndex != nil { if errorParsingDocIndex != nil {
return errorParsingDocIndex return errorParsingDocIndex
} }
var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) { var updateData = func(dataBucket *yaml.Node, currentIndex int) error {
if updateAll || currentIndex == docIndexInt { return prefixDocument(updateAll, docIndexInt, currentIndex, dataBucket, updateCommand)
log.Debugf("Updating doc %v", currentIndex)
for _, entry := range writeCommands {
path := entry.Key.(string)
value := entry.Value
log.Debugf("setting %v to %v", path, value)
dataBucket = lib.WritePath(dataBucket, path, value)
}
}
return dataBucket, nil
} }
return readAndUpdate(cmd.OutOrStdout(), args[0], updateData) return readAndUpdate(cmd.OutOrStdout(), args[0], updateData)
} }
func prefixProperty(cmd *cobra.Command, args []string) error { func prefixDocument(updateAll bool, docIndexInt int, currentIndex int, dataBucket *yaml.Node, updateCommand yqlib.UpdateCommand) error {
if len(args) != 2 { if updateAll || currentIndex == docIndexInt {
return errors.New("Must provide <filename> <prefixed_path>") log.Debugf("Prefixing document %v", currentIndex)
} yqlib.DebugNode(dataBucket)
prefixPath := args[1] updateCommand.Value = dataBucket.Content[0]
dataBucket.Content = make([]*yaml.Node, 1)
newNode := lib.New(updateCommand.Path)
dataBucket.Content[0] = &newNode
errorUpdating := lib.Update(dataBucket, updateCommand, true)
if errorUpdating != nil {
return errorUpdating
}
}
return nil
}
func deleteProperty(cmd *cobra.Command, args []string) error {
if len(args) < 2 {
return errors.New("Must provide <filename> <path_to_delete>")
}
var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 1)
updateCommands[0] = yqlib.UpdateCommand{Command: "delete", Path: args[1]}
return updateDoc(args[0], updateCommands, cmd.OutOrStdout())
}
func updateDoc(inputFile string, updateCommands []yqlib.UpdateCommand, writer io.Writer) error {
var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex() var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
if errorParsingDocIndex != nil { if errorParsingDocIndex != nil {
return errorParsingDocIndex return errorParsingDocIndex
} }
var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) { var updateData = func(dataBucket *yaml.Node, currentIndex int) error {
if updateAll || currentIndex == docIndexInt { if updateAll || currentIndex == docIndexInt {
log.Debugf("Prefixing %v to doc %v", prefixPath, currentIndex) log.Debugf("Updating doc %v", currentIndex)
var mapDataBucket = lib.PrefixPath(dataBucket, prefixPath) for _, updateCommand := range updateCommands {
return mapDataBucket, nil log.Debugf("Processing update to Path %v", updateCommand.Path)
errorUpdating := lib.Update(dataBucket, updateCommand, autoCreateFlag)
if errorUpdating != nil {
return errorUpdating
}
}
} }
return dataBucket, nil return nil
} }
return readAndUpdate(cmd.OutOrStdout(), args[0], updateData) return readAndUpdate(writer, inputFile, updateData)
} }
func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn) error { func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn) error {
@@ -473,106 +585,64 @@ func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn)
safelyRenameFile(tempFile.Name(), inputFile) safelyRenameFile(tempFile.Name(), inputFile)
}() }()
} else { } else {
var writer = bufio.NewWriter(stdOut) destination = stdOut
destination = writer
destinationName = "Stdout" destinationName = "Stdout"
defer safelyFlush(writer)
} }
var encoder = yaml.NewEncoder(destination)
log.Debugf("Writing to %v from %v", destinationName, inputFile) log.Debugf("Writing to %v from %v", destinationName, inputFile)
bufferedWriter := bufio.NewWriter(destination)
defer safelyFlush(bufferedWriter)
var encoder yqlib.Encoder
if outputToJSON {
encoder = yqlib.NewJsonEncoder(bufferedWriter)
} else {
encoder = yqlib.NewYamlEncoder(bufferedWriter)
}
return readStream(inputFile, mapYamlDecoder(updateData, encoder)) return readStream(inputFile, mapYamlDecoder(updateData, encoder))
} }
func deleteProperty(cmd *cobra.Command, args []string) error { type updateCommandParsed struct {
if len(args) < 2 { Command string
return errors.New("Must provide <filename> <path_to_delete>") Path string
} Value yaml.Node
var deletePath = args[1]
var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
if errorParsingDocIndex != nil {
return errorParsingDocIndex
}
var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) {
if updateAll || currentIndex == docIndexInt {
log.Debugf("Deleting path in doc %v", currentIndex)
return lib.DeletePath(dataBucket, deletePath)
}
return dataBucket, nil
}
return readAndUpdate(cmd.OutOrStdout(), args[0], updateData)
} }
func mergeProperties(cmd *cobra.Command, args []string) error { func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string) ([]yqlib.UpdateCommand, error) {
if len(args) < 2 { var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0)
return errors.New("Must provide at least 2 yaml files")
}
var input = args[0]
var filesToMerge = args[1:]
var updateAll, docIndexInt, errorParsingDocIndex = parseDocumentIndex()
if errorParsingDocIndex != nil {
return errorParsingDocIndex
}
var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) {
if updateAll || currentIndex == docIndexInt {
log.Debugf("Merging doc %v", currentIndex)
var mergedData map[interface{}]interface{}
// merge only works for maps, so put everything in a temporary
// map
var mapDataBucket = make(map[interface{}]interface{})
mapDataBucket["root"] = dataBucket
if err := lib.Merge(&mergedData, mapDataBucket, overwriteFlag, appendFlag); err != nil {
return nil, err
}
for _, f := range filesToMerge {
var fileToMerge interface{}
if err := readData(f, 0, &fileToMerge); err != nil {
if allowEmptyFlag && err == io.EOF {
continue
}
return nil, err
}
mapDataBucket["root"] = fileToMerge
if err := lib.Merge(&mergedData, mapDataBucket, overwriteFlag, appendFlag); err != nil {
return nil, err
}
}
return mergedData["root"], nil
}
return dataBucket, nil
}
yaml.DefaultMapType = reflect.TypeOf(map[interface{}]interface{}{})
defer func() { yaml.DefaultMapType = reflect.TypeOf(yaml.MapSlice{}) }()
return readAndUpdate(cmd.OutOrStdout(), input, updateData)
}
func readWriteCommands(args []string, expectedArgs int, badArgsMessage string) (yaml.MapSlice, error) {
var writeCommands yaml.MapSlice
if writeScript != "" { if writeScript != "" {
if err := readData(writeScript, 0, &writeCommands); err != nil { var parsedCommands = make([]updateCommandParsed, 0)
err := readData(writeScript, 0, &parsedCommands)
if err != nil && err != io.EOF {
return nil, err return nil, err
} }
log.Debugf("Read write commands file '%v'", parsedCommands)
for index := range parsedCommands {
parsedCommand := parsedCommands[index]
updateCommand := yqlib.UpdateCommand{Command: parsedCommand.Command, Path: parsedCommand.Path, Value: &parsedCommand.Value, Overwrite: true}
updateCommands = append(updateCommands, updateCommand)
}
log.Debugf("Read write commands file '%v'", updateCommands)
} else if len(args) < expectedArgs { } else if len(args) < expectedArgs {
return nil, errors.New(badArgsMessage) return nil, errors.New(badArgsMessage)
} else { } else {
writeCommands = make(yaml.MapSlice, 1) updateCommands = make([]yqlib.UpdateCommand, 1)
writeCommands[0] = yaml.MapItem{Key: args[expectedArgs-2], Value: valueParser.ParseValue(args[expectedArgs-1])} log.Debug("args %v", args)
log.Debug("path %v", args[expectedArgs-2])
log.Debug("Value %v", args[expectedArgs-1])
updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: valueParser.Parse(args[expectedArgs-1], customTag), Overwrite: true}
} }
return writeCommands, nil return updateCommands, nil
}
func toString(context interface{}) (string, error) {
if outputToJSON {
return jsonConverter.JsonToString(context)
}
return yamlConverter.YamlToString(context, trimOutput)
} }
func safelyRenameFile(from string, to string) { func safelyRenameFile(from string, to string) {
if renameError := os.Rename(from, to); renameError != nil { if renameError := os.Rename(from, to); renameError != nil {
log.Debugf("Error renaming from %v to %v, attemting to copy contents", from, to) log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to)
log.Debug(renameError.Error()) log.Debug(renameError.Error())
// can't do this rename when running in docker to a file targeted in a mounted volume, // can't do this rename when running in docker to a file targeted in a mounted volume,
// so gracefully degrade to copying the entire contents. // so gracefully degrade to copying the entire contents.

View File

@@ -1,60 +1,60 @@
package main package main
import ( // import (
"fmt" // "fmt"
"runtime" // "runtime"
"testing" // "testing"
"github.com/mikefarah/yq/v2/pkg/marshal" // "github.com/mikefarah/yq/v2/pkg/marshal"
"github.com/mikefarah/yq/v2/test" // "github.com/mikefarah/yq/v2/test"
) // )
func TestMultilineString(t *testing.T) { // func TestMultilineString(t *testing.T) {
testString := ` // testString := `
abcd // abcd
efg` // efg`
formattedResult, _ := marshal.NewYamlConverter().YamlToString(testString, false) // formattedResult, _ := marshal.NewYamlConverter().YamlToString(testString, false)
test.AssertResult(t, testString, formattedResult) // test.AssertResult(t, testString, formattedResult)
} // }
func TestNewYaml(t *testing.T) { // func TestNewYaml(t *testing.T) {
result, _ := newYaml([]string{"b.c", "3"}) // result, _ := newYaml([]string{"b.c", "3"})
formattedResult := fmt.Sprintf("%v", result) // formattedResult := fmt.Sprintf("%v", result)
test.AssertResult(t, // test.AssertResult(t,
"[{b [{c 3}]}]", // "[{b [{c 3}]}]",
formattedResult) // formattedResult)
} // }
func TestNewYamlArray(t *testing.T) { // func TestNewYamlArray(t *testing.T) {
result, _ := newYaml([]string{"[0].cat", "meow"}) // result, _ := newYaml([]string{"[0].cat", "meow"})
formattedResult := fmt.Sprintf("%v", result) // formattedResult := fmt.Sprintf("%v", result)
test.AssertResult(t, // test.AssertResult(t,
"[[{cat meow}]]", // "[[{cat meow}]]",
formattedResult) // formattedResult)
} // }
func TestNewYaml_WithScript(t *testing.T) { // func TestNewYaml_WithScript(t *testing.T) {
writeScript = "examples/instruction_sample.yaml" // writeScript = "examples/instruction_sample.yaml"
expectedResult := `b: // expectedResult := `b:
c: cat // c: cat
e: // e:
- name: Mike Farah` // - name: Mike Farah`
result, _ := newYaml([]string{""}) // result, _ := newYaml([]string{""})
actualResult, _ := marshal.NewYamlConverter().YamlToString(result, true) // actualResult, _ := marshal.NewYamlConverter().YamlToString(result, true)
test.AssertResult(t, expectedResult, actualResult) // test.AssertResult(t, expectedResult, actualResult)
} // }
func TestNewYaml_WithUnknownScript(t *testing.T) { // func TestNewYaml_WithUnknownScript(t *testing.T) {
writeScript = "fake-unknown" // writeScript = "fake-unknown"
_, err := newYaml([]string{""}) // _, err := newYaml([]string{""})
if err == nil { // if err == nil {
t.Error("Expected error due to unknown file") // t.Error("Expected error due to unknown file")
} // }
var expectedOutput string // var expectedOutput string
if runtime.GOOS == "windows" { // if runtime.GOOS == "windows" {
expectedOutput = `open fake-unknown: The system cannot find the file specified.` // expectedOutput = `open fake-unknown: The system cannot find the file specified.`
} else { // } else {
expectedOutput = `open fake-unknown: no such file or directory` // expectedOutput = `open fake-unknown: no such file or directory`
} // }
test.AssertResult(t, expectedOutput, err.Error()) // test.AssertResult(t, expectedOutput, err.Error())
} // }