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

Compare commits

..

8 Commits

Author SHA1 Message Date
Mike Farah
2f9f8665dd bolden contribute disclaimer 2020-01-13 10:21:28 +11:00
Conor Nosal
3dea1efc03 Allow merge commands to specify overwrite and append simultaneously 2020-01-13 10:17:29 +11:00
Ryan SIU
547787f3ae #314 Fix the big int changing into float 2020-01-13 10:17:08 +11:00
Ryan SIU
3e83ff7ac8 #20 Read the top level keys only 2020-01-13 10:16:53 +11:00
Mike Farah
f18c5161e0 Fixed sponge instructions 2020-01-11 16:01:32 +11:00
Mike Farah
eeeeeffd7b Added note about v3 2020-01-11 11:44:02 +11:00
Mike Farah
27604289f4 Log out original error if removing temporary file failed 2019-12-23 12:09:38 +11:00
Mike Farah
3f36a18791 Add readme notice re v3 2019-12-23 10:01:55 +11:00
72 changed files with 3413 additions and 3300 deletions

1
.gitignore vendored
View File

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

View File

@@ -7,7 +7,21 @@ a lightweight and portable command-line YAML processor
The aim of the project is to be the [jq](https://github.com/stedolan/jq) or sed of yaml files.
## Major upgrade - V3 beta is out!
This addresses a number of features requests and issues that have been raised :)
Currently only available only available as a [binary release here](https://github.com/mikefarah/yq/releases/tag/3.0.0-beta) or via docker mikefarah/yq:3.0.0-beta!
It does have a few breaking changes listed on the [release page](https://github.com/mikefarah/yq/releases/tag/3.0.0-beta)
Looking forward to feedback - once this is out of beta it will be added to the remaining package managers, and be the default version downloaded (and merged into master).
V2 will no longer have any new features added, and will be moved to a branch (v2). It will have limited maintenance for bugs for a few months.
## Install
### On MacOS:
```
brew install yq
@@ -21,16 +35,16 @@ snap install yq
`yq` installs with with [_strict confinement_](https://docs.snapcraft.io/snap-confinement/6233) in snap, this means it doesn't have direct access to root files. To read root files you can:
```
sudo cat /etc/myfile | yq -r - somecommand
sudo cat /etc/myfile | yq r - a.path
```
And to write to a root file you can either use [sponge](https://linux.die.net/man/1/sponge):
```
sudo cat /etc/myfile | yq -r - somecommand | sudo sponge /etc/myfile
sudo cat /etc/myfile | yq w - a.path value | sudo sponge /etc/myfile
```
or write to a temporary file:
```
sudo cat /etc/myfile | yq -r - somecommand | sudo tee /etc/myfile.tmp
sudo cat /etc/myfile | yq w - a.path value | sudo tee /etc/myfile.tmp
sudo mv /etc/myfile.tmp /etc/myfile
rm /etc/myfile.tmp
```
@@ -43,7 +57,7 @@ sudo apt install yq -y
```
### or, [Download latest binary](https://github.com/mikefarah/yq/releases/latest) or alternatively:
```
GO111MODULE=on go get github.com/mikefarah/yq/v3
GO111MODULE=on go get github.com/mikefarah/yq/v2
```
## Run with Docker
@@ -115,6 +129,9 @@ Use "yq [command] --help" for more information about a command.
```
## Contribute
**Note: v3 is currently in progress - for the moment I won't be accepting new feature PRs until v3 is ready :)**
1. `scripts/devtools.sh`
2. `make [local] vendor`
3. add unit tests

View File

@@ -1,74 +0,0 @@
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"
"testing"
"github.com/mikefarah/yq/v3/test"
"github.com/mikefarah/yq/v2/test"
"github.com/spf13/cobra"
)
@@ -63,6 +63,30 @@ 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) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "-V")
@@ -91,161 +115,7 @@ func TestReadCmd(t *testing.T) {
if result.Error != nil {
t.Error(result.Error)
}
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)
test.AssertResult(t, "2\n", result.Output)
}
func TestReadInvalidDocumentIndexCmd(t *testing.T) {
@@ -264,7 +134,7 @@ func TestReadBadDocumentIndexCmd(t *testing.T) {
if result.Error == nil {
t.Error("Expected command to fail due to invalid path")
}
expectedOutput := `Could not process document index 1 as there are only 1 document(s)`
expectedOutput := `asked to process document index 1 but there are only 1 document(s)`
test.AssertResult(t, expectedOutput, result.Error.Error())
}
@@ -287,16 +157,7 @@ func TestReadMultiCmd(t *testing.T) {
if result.Error != nil {
t.Error(result.Error)
}
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)
test.AssertResult(t, "here\n", result.Output)
}
func TestReadMultiAllCmd(t *testing.T) {
@@ -306,21 +167,9 @@ func TestReadMultiAllCmd(t *testing.T) {
t.Error(result.Error)
}
test.AssertResult(t,
`first document
second 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
`- first document
- second document
- third document
`, result.Output)
}
@@ -330,7 +179,7 @@ func TestReadCmd_ArrayYaml(t *testing.T) {
if result.Error != nil {
t.Error(result.Error)
}
test.AssertResult(t, "false", result.Output)
test.AssertResult(t, "false\n", result.Output)
}
func TestReadCmd_ArrayYaml_NoPath(t *testing.T) {
@@ -342,13 +191,11 @@ func TestReadCmd_ArrayYaml_NoPath(t *testing.T) {
expectedOutput := `- become: true
gather_facts: false
hosts: lalaland
name: "Apply smth"
name: Apply smth
roles:
- lala
- land
serial: 1
- become: false
gather_facts: true
`
test.AssertResult(t, expectedOutput, result.Output)
}
@@ -362,7 +209,7 @@ func TestReadCmd_ArrayYaml_OneElement(t *testing.T) {
expectedOutput := `become: true
gather_facts: false
hosts: lalaland
name: "Apply smth"
name: Apply smth
roles:
- lala
- land
@@ -371,67 +218,31 @@ serial: 1
test.AssertResult(t, expectedOutput, result.Output)
}
func TestReadCmd_ArrayYaml_SplatCmd(t *testing.T) {
func TestReadCmd_ArrayYaml_Splat(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read examples/array.yaml [*]")
if result.Error != nil {
t.Error(result.Error)
}
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
expectedOutput := `- become: true
gather_facts: false
hosts: lalaland
name: "Apply smth"
name: Apply smth
roles:
- lala
- land
serial: 1
'[1]':
become: false
gather_facts: true
`
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) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read examples/array.yaml [*].gather_facts")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `false
true`
expectedOutput := "- false\n"
test.AssertResult(t, expectedOutput, result.Output)
}
@@ -439,9 +250,9 @@ func TestReadCmd_ArrayYaml_ErrorBadPath(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read examples/array.yaml [x].gather_facts")
if result.Error == nil {
t.Error("Expected command to fail due to missing arg")
t.Error("Expected command to fail due to invalid path")
}
expectedOutput := `Error reading path in document index 0: Error parsing array index 'x' for '': strconv.ParseInt: parsing "x": invalid syntax`
expectedOutput := `Error reading path in document index 0: error accessing array: strconv.ParseInt: parsing "x": invalid syntax`
test.AssertResult(t, expectedOutput, result.Error.Error())
}
@@ -449,9 +260,9 @@ func TestReadCmd_ArrayYaml_Splat_ErrorBadPath(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read examples/array.yaml [*].roles[x]")
if result.Error == nil {
t.Error("Expected command to fail due to missing arg")
t.Error("Expected command to fail due to invalid path")
}
expectedOutput := `Error reading path in document index 0: Error parsing array index 'x' for '[0].roles': strconv.ParseInt: parsing "x": invalid syntax`
expectedOutput := `Error reading path in document index 0: error accessing array: strconv.ParseInt: parsing "x": invalid syntax`
test.AssertResult(t, expectedOutput, result.Error.Error())
}
@@ -505,117 +316,47 @@ func TestReadCmd_ErrorBadPath(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("read %s b.d.*.[x]", filename))
expectedOutput := `Error reading path in document index 0: Error parsing array index 'x' for 'b.d.e': strconv.ParseInt: parsing "x": invalid syntax`
if result.Error == nil {
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())
}
func TestReadCmd_Verbose(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read -v examples/sample.yaml b.c")
result := test.RunCmd(cmd, "-v read examples/sample.yaml b.c")
if result.Error != nil {
t.Error(result.Error)
}
test.AssertResult(t, "2", result.Output)
test.AssertResult(t, "2\n", result.Output)
}
// 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)
func TestReadCmd_NoTrim(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("read %s b.there*.c", filename))
result := test.RunCmd(cmd, "--trim=false read examples/sample.yaml b.c")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `more things
more things also`
test.AssertResult(t, expectedOutput, result.Output)
test.AssertResult(t, "2\n\n", result.Output)
}
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)
func TestReadCmd_ToJson(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("read -p pv %s b.there*.c", filename))
result := test.RunCmd(cmd, "read -j examples/sample.yaml b.c")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b.there.c: more things
b.there2.c: more things also
`
test.AssertResult(t, expectedOutput, result.Output)
test.AssertResult(t, "2\n", result.Output)
}
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)
func TestReadCmd_ToJsonLong(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("read -p p %s b.there*.c", filename))
result := test.RunCmd(cmd, "read --tojson examples/sample.yaml b.c")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b.there.c
b.there2.c`
test.AssertResult(t, expectedOutput, result.Output)
test.AssertResult(t, "2\n", result.Output)
}
func TestPrefixCmd(t *testing.T) {
@@ -786,7 +527,7 @@ func TestPrefixCmd_Verbose(t *testing.T) {
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("prefix %s x", filename))
result := test.RunCmd(cmd, fmt.Sprintf("-v prefix %s x", filename))
if result.Error != nil {
t.Error(result.Error)
}
@@ -828,18 +569,6 @@ func TestNewCmd(t *testing.T) {
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) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "new b.c")
@@ -850,6 +579,18 @@ func TestNewCmd_Error(t *testing.T) {
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) {
content := `b:
c: 3
@@ -868,52 +609,6 @@ func TestWriteCmd(t *testing.T) {
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) {
content := `b:
c: 3
@@ -1029,6 +724,24 @@ func TestWriteCmd_ErrorUnreadableFile(t *testing.T) {
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) {
content := `b:
c: 3
@@ -1073,7 +786,7 @@ func TestWriteCmd_AppendEmptyArray(t *testing.T) {
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] v", filename))
result := test.RunCmd(cmd, fmt.Sprintf("write -v %s b[+] v", filename))
if result.Error != nil {
t.Error(result.Error)
}
@@ -1093,7 +806,7 @@ func TestWriteCmd_SplatArray(t *testing.T) {
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("write %s b[*].c new", filename))
result := test.RunCmd(cmd, fmt.Sprintf("write -v %s b[*].c new", filename))
if result.Error != nil {
t.Error(result.Error)
}
@@ -1113,7 +826,7 @@ func TestWriteCmd_SplatMap(t *testing.T) {
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* new", filename))
result := test.RunCmd(cmd, fmt.Sprintf("write -v %s b.* new", filename))
if result.Error != nil {
t.Error(result.Error)
}
@@ -1133,7 +846,7 @@ func TestWriteCmd_SplatMapEmpty(t *testing.T) {
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c.* new", filename))
result := test.RunCmd(cmd, fmt.Sprintf("write -v %s b.c.* new", filename))
if result.Error != nil {
t.Error(result.Error)
}
@@ -1144,7 +857,7 @@ func TestWriteCmd_SplatMapEmpty(t *testing.T) {
test.AssertResult(t, expectedOutput, result.Output)
}
func TestDeleteYamlCmd(t *testing.T) {
func TestDeleteYaml(t *testing.T) {
content := `a: 2
b:
c: things
@@ -1167,28 +880,35 @@ b:
}
func TestDeleteSplatYaml(t *testing.T) {
content := `a: other
b: [3, 4]
c:
toast: leave
test: 1
tell: 1
taco: cool
content := `a: 2
b:
hi:
c: things
d: something else
hello:
c: things2
d: something else2
there:
c: more things
d: more something else
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("delete %s c.te*", filename))
result := test.RunCmd(cmd, fmt.Sprintf("delete -v %s b.*.c", filename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `a: other
b: [3, 4]
c:
toast: leave
taco: cool
expectedOutput := `a: 2
b:
hi:
d: something else
hello:
d: something else2
there:
d: more something else
`
test.AssertResult(t, expectedOutput, result.Output)
}
@@ -1206,7 +926,7 @@ b:
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.hi[*].thing", filename))
result := test.RunCmd(cmd, fmt.Sprintf("delete -v %s b.hi[*].thing", filename))
if result.Error != nil {
t.Error(result.Error)
}
@@ -1237,7 +957,7 @@ b:
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.there*.c", filename))
result := test.RunCmd(cmd, fmt.Sprintf("delete -v %s b.there*.c", filename))
if result.Error != nil {
t.Error(result.Error)
}
@@ -1328,25 +1048,10 @@ func TestMergeCmd(t *testing.T) {
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `a: simple # just the best
b: [1, 2]
c:
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]
expectedOutput := `a: simple
b:
- 1
- 2
c:
test: 1
`
@@ -1355,12 +1060,14 @@ c:
func TestMergeOverwriteCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "merge -c=false --overwrite examples/data1.yaml examples/data2.yaml")
result := test.RunCmd(cmd, "merge --overwrite examples/data1.yaml examples/data2.yaml")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `a: other # better than the original
b: [3, 4]
expectedOutput := `a: other
b:
- 3
- 4
c:
test: 1
`
@@ -1369,12 +1076,16 @@ c:
func TestMergeAppendCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "merge --autocreate=false --append examples/data1.yaml examples/data2.yaml")
result := test.RunCmd(cmd, "merge --append examples/data1.yaml examples/data2.yaml")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `a: simple # just the best
b: [1, 2, 3, 4]
expectedOutput := `a: simple
b:
- 1
- 2
- 3
- 4
c:
test: 1
`
@@ -1383,12 +1094,16 @@ c:
func TestMergeOverwriteAndAppendCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "merge --autocreate=false --append --overwrite examples/data1.yaml examples/data2.yaml")
result := test.RunCmd(cmd, "merge --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]
expectedOutput := `a: other
b:
- 1
- 2
- 3
- 4
c:
test: 1
`
@@ -1401,32 +1116,35 @@ func TestMergeArraysCmd(t *testing.T) {
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `[1, 2, 3, 4, 5]
expectedOutput := `- 1
- 2
- 3
- 4
- 5
`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestMergeCmd_Multi(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "merge -d1 examples/multiple_docs_small.yaml examples/data1.yaml")
result := test.RunCmd(cmd, "merge -d1 examples/multiple_docs_small.yaml examples/data2.yaml")
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `a: Easy! as one two three
---
a: other
another:
document: here
a: simple # just the best
b:
- 1
- 2
- 3
- 4
c:
test: 1
---
- 1
- 2
`
test.AssertResult(t, expectedOutput, result.Output)
- 2`
test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
}
func TestMergeYamlMultiAllCmd(t *testing.T) {
@@ -1448,15 +1166,14 @@ something: good`
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b:
expectedOutput := `apples: green
b:
c: 3
apples: green
something: good
---
something: else
apples: red
`
test.AssertResult(t, expectedOutput, result.Output)
something: else`
test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
}
func TestMergeYamlMultiAllOverwriteCmd(t *testing.T) {
@@ -1478,15 +1195,14 @@ something: good`
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `b:
expectedOutput := `apples: red
b:
c: 3
apples: red
something: good
---
something: good
apples: red
`
test.AssertResult(t, expectedOutput, result.Output)
something: good`
test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
}
func TestMergeCmd_Error(t *testing.T) {
@@ -1507,13 +1223,29 @@ func TestMergeCmd_ErrorUnreadableFile(t *testing.T) {
}
var expectedOutput string
if runtime.GOOS == "windows" {
expectedOutput = `open fake-unknown: The system cannot find the file specified.`
expectedOutput = `Error updating document at index 0: open fake-unknown: The system cannot find the file specified.`
} else {
expectedOutput = `open fake-unknown: no such file or directory`
expectedOutput = `Error updating document at index 0: open fake-unknown: no such file or directory`
}
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) {
filename := test.WriteTempYamlFile(test.ReadTempYamlFile("examples/data1.yaml"))
err := os.Chmod(filename, os.FileMode(int(0666)))
@@ -1529,15 +1261,13 @@ func TestMergeCmd_Inplace(t *testing.T) {
}
info, _ := os.Stat(filename)
gotOutput := test.ReadTempYamlFile(filename)
expectedOutput := `a: simple # just the best
b: [1, 2]
expectedOutput := `a: simple
b:
- 1
- 2
c:
test: 1
toast: leave
tell: 1
taco: cool
`
test.AssertResult(t, expectedOutput, gotOutput)
test: 1`
test.AssertResult(t, expectedOutput, strings.Trim(gotOutput, "\n "))
test.AssertResult(t, os.FileMode(int(0666)), info.Mode())
}
@@ -1547,17 +1277,10 @@ func TestMergeAllowEmptyCmd(t *testing.T) {
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `a: simple # just the best
b: [1, 2]
c:
test: 1
expectedOutput := `a: simple
b:
- 1
- 2
`
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())
}

View File

@@ -1,13 +0,0 @@
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,18 +248,6 @@
<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">
<a href="/write/" title="Write/Update" class="md-nav__link">
Write/Update

View File

@@ -252,18 +252,6 @@
<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">
<a href="../write/" title="Write/Update" class="md-nav__link">
Write/Update
@@ -426,37 +414,20 @@
<h1>Convert</h1>
<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 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>To convert output to json, use the --tojson (or -j) flag. This can only be used with the read command.</p>
<p>Given a sample.yaml file of:</p>
<pre><code class="yaml">b:
c: 2
</code></pre>
<p>then</p>
<pre><code class="bash">yq r -j sample.yaml
<pre><code class="bash">yq r -j sample.yaml b.c
</code></pre>
<p>will output</p>
<pre><code class="json">{&quot;b&quot;:{&quot;c&quot;:2}}
</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>
<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>

View File

@@ -252,18 +252,6 @@
<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">
<a href="../write/" title="Write/Update" class="md-nav__link">
Write/Update
@@ -337,6 +325,20 @@
</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>
@@ -404,6 +406,20 @@
</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>
@@ -425,11 +441,10 @@
<h1>Create</h1>
<pre><code>yq n &lt;path_expression&gt; &lt;new value&gt;
<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&gt; &lt;new value&gt;
</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>
<pre><code class="bash">yq n b.c cat
</code></pre>
@@ -442,11 +457,8 @@
<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>Given a script create_instructions.yaml of:</p>
<pre><code class="yaml">- command: update
path: b.c
value:
#great
things: frog # wow!
<pre><code class="yaml">b.c: 3
b.e[+].name: Howdy Partner
</code></pre>
<p>then</p>
@@ -455,14 +467,38 @@
<p>will output:</p>
<pre><code class="yaml">b:
c:
#great
things: frog # wow!
c: 3
e:
- name: Howdy Partner
</code></pre>
<p>You can also pipe the instructions in:</p>
<pre><code class="bash">cat create_instructions.yaml | yq n -s -
</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">
<label class="md-overlay" data-md-component="overlay" for="__drawer"></label>
<a href="#from-stdin" tabindex="1" class="md-skip">
<a href="#to-stdout" tabindex="1" class="md-skip">
Skip to content
</a>
@@ -252,18 +252,6 @@
<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">
<a href="../write/" title="Write/Update" class="md-nav__link">
Write/Update
@@ -311,11 +299,25 @@
<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="#to-stdout" title="To Stdout" class="md-nav__link">
To Stdout
</a>
</li>
<li class="md-nav__item">
<a href="#from-stdin" title="From STDIN" class="md-nav__link">
From STDIN
</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 class="md-nav__item">
@@ -323,6 +325,27 @@
Deleting nodes in-place
</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 class="md-nav__item">
@@ -339,6 +362,20 @@
</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>
@@ -404,11 +441,25 @@
<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="#to-stdout" title="To Stdout" class="md-nav__link">
To Stdout
</a>
</li>
<li class="md-nav__item">
<a href="#from-stdin" title="From STDIN" class="md-nav__link">
From STDIN
</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 class="md-nav__item">
@@ -416,6 +467,27 @@
Deleting nodes in-place
</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 class="md-nav__item">
@@ -432,6 +504,20 @@
</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>
@@ -453,16 +539,49 @@
<h1>Delete</h1>
<pre><code>yq delete &lt;yaml_file|-&gt; &lt;path_expression&gt;
<pre><code>yq d &lt;yaml_file&gt; &lt;path_to_delete&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>
<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>
<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
</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>
<p>Given a sample.yaml file of:</p>
<pre><code class="yaml">b:
@@ -475,6 +594,91 @@
</code></pre>
<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>
<p>Given a sample.yaml file of:</p>
<pre><code class="yaml">something: else
@@ -519,6 +723,29 @@ b:
</code></pre>
<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,18 +290,6 @@
<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">
<a href="write/" title="Write/Update" class="md-nav__link">
Write/Update
@@ -434,7 +422,7 @@ sudo apt install yq -y
</code></pre>
<p>or, <a href="https://github.com/mikefarah/yq/releases/latest">Download latest binary</a> or alternatively:</p>
<pre><code>GO111MODULE=on go get github.com/mikefarah/yq/v3
<pre><code>go get gopkg.in/mikefarah/yq.v2
</code></pre>
<p><a href="https://github.com/mikefarah/yq">View on GitHub</a></p>

View File

@@ -252,18 +252,6 @@
<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">
<a href="../write/" title="Write/Update" class="md-nav__link">
Write/Update
@@ -620,6 +608,7 @@ d: hi
</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>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>
<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>

View File

@@ -1,862 +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>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,18 +252,6 @@
<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">
<a href="../write/" title="Write/Update" class="md-nav__link">
Write/Update
@@ -467,11 +455,11 @@
<h1>Prefix</h1>
<pre><code>yq p &lt;yaml_file&gt; &lt;path&gt;
<p>Paths can be prefixed using the 'prefix' command.
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>
<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>
<p>Given a data1.yaml file of:</p>
<pre><code class="yaml">a: simple

View File

@@ -319,6 +319,20 @@
</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>
@@ -335,18 +349,6 @@
<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">
<a href="../write/" title="Write/Update" class="md-nav__link">
Write/Update
@@ -488,6 +490,20 @@
</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>
@@ -509,11 +525,10 @@
<h1>Read</h1>
<pre><code>yq r &lt;yaml_file|json_file&gt; &lt;path_expression&gt;
<pre><code>yq r &lt;yaml_file|json_file&gt; &lt;path&gt;
</code></pre>
<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>
<p>This command can take a json file as input too, and will output yaml unless specified to export as json (-j)</p>
<h3 id="basic">Basic<a class="headerlink" href="#basic" title="Permanent link">&para;</a></h3>
<p>Given a sample.yaml file of:</p>
<pre><code class="yaml">b:
@@ -647,6 +662,29 @@ e.g.: given a sample file of</p>
</code></pre>
<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>
@@ -682,13 +720,13 @@ e.g.: given a sample file of</p>
</a>
<a href="../path_expressions/" title="Path Expressions" class="md-flex md-footer-nav__link md-footer-nav__link--next" rel="next">
<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>
Path Expressions
Write/Update
</span>
</div>
<div class="md-flex__cell md-flex__cell--shrink">

File diff suppressed because one or more lines are too long

View File

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

Binary file not shown.

View File

@@ -0,0 +1,447 @@
<!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

@@ -0,0 +1,385 @@
<!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,18 +251,6 @@
<li class="md-nav__item">
<a href="../path_expressions/" title="Path Expressions" class="md-nav__link">
Path Expressions
</a>
</li>
@@ -371,6 +359,20 @@
</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>
@@ -544,6 +546,20 @@
</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>
@@ -565,11 +581,9 @@
<h1>Write/Update</h1>
<pre><code>yq w &lt;yaml_file&gt; &lt;path_expression&gt; &lt;new value&gt;
<pre><code>yq w &lt;yaml_file&gt; &lt;path&gt; &lt;new value&gt;
</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>
<p>Given a sample.yaml file of:</p>
<pre><code class="yaml">b:
@@ -794,6 +808,30 @@ b.e[+].name: Howdy Partner
<pre><code class="yaml">my:
path: -3
</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>
@@ -814,7 +852,7 @@ b.e[+].name: Howdy Partner
<div class="md-footer-nav">
<nav class="md-footer-nav__inner md-grid">
<a href="../path_expressions/" title="Path Expressions" class="md-flex md-footer-nav__link md-footer-nav__link--prev" rel="prev">
<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>
@@ -823,7 +861,7 @@ b.e[+].name: Howdy Partner
<span class="md-footer-nav__direction">
Previous
</span>
Path Expressions
Read
</span>
</div>
</a>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

9
go.mod
View File

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

12
go.sum
View File

@@ -10,11 +10,8 @@ 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/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
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/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/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
@@ -35,21 +32,14 @@ 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=
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-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/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-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/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-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-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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/imdario/mergo.v0 v0.3.7 h1:QDotlIZtaO/p+Um0ok18HRTpq5i5/SAk/qprsor+9c8=
@@ -58,5 +48,3 @@ 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/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.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,7 +5,6 @@ theme: 'material'
pages:
- Install: index.md
- Read: read.md
- Path Expressions: path_expressions.md
- Write/Update: write.md
- Prefix: prefix.md
- Delete: delete.md

View File

@@ -1,7 +1,5 @@
## Yaml to Json
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.
### Yaml to Json
To convert output to json, use the --tojson (or -j) flag. This can only be used with the read command.
Given a sample.yaml file of:
```yaml
@@ -10,7 +8,7 @@ b:
```
then
```bash
yq r -j sample.yaml
yq r -j sample.yaml b.c
```
will output
@@ -18,25 +16,7 @@ will output
{"b":{"c":2}}
```
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
### Json to Yaml
To read in json, just pass in a json file instead of yaml, it will just work :)
e.g given a json file

View File

@@ -1,12 +1,10 @@
```
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.
See docs for [path expression](path_expressions.md)
```
yq n <path> <new value>
```
## Creating a simple yaml file
### Creating a simple yaml file
```bash
yq n b.c cat
```
@@ -16,16 +14,13 @@ b:
c: cat
```
## Creating using a create script
### Creating using a create script
Create scripts follow the same format as the update scripts.
Given a script create_instructions.yaml of:
```yaml
- command: update
path: b.c
value:
#great
things: frog # wow!
b.c: 3
b.e[+].name: Howdy Partner
```
then
@@ -35,13 +30,15 @@ yq n -s create_instructions.yaml
will output:
```yaml
b:
c:
#great
things: frog # wow!
c: 3
e:
- name: Howdy Partner
```
You can also pipe the instructions in:
```bash
cat create_instructions.yaml | yq n -s -
```
```
{!snippets/niche.md!}

View File

@@ -1,13 +1,8 @@
```
yq delete <yaml_file|-> <path_expression>
yq d <yaml_file> <path_to_delete>
```
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
### To Stdout
Given a sample.yaml file of:
```yaml
b:
@@ -18,29 +13,141 @@ then
```bash
yq d sample.yaml b.c
```
will output
will output:
```yaml
b:
apples: green
```
## From STDIN
Use "-" (without quotes) in-place of a file name if you wish to pipe in input from STDIN.
### From STDIN
```bash
cat sample.yaml | yq d - b.c
```
## Deleting in-place
### Deleting array elements
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
yq d -i sample.yaml b.c
```
will update the sample.yaml file so that the 'c' node is deleted
## Multiple Documents
### Splat
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
```
### Delete from single document
### Prefix Splat
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:
```yaml
something: else
@@ -63,7 +170,7 @@ b:
c: 2
```
### Delete from all documents
### Multiple Documents - delete from all documents
Given a sample.yaml file of:
```yaml
something: else
@@ -84,3 +191,7 @@ something: else
b:
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:
```
GO111MODULE=on go get github.com/mikefarah/yq/v3
go get gopkg.in/mikefarah/yq.v2
```
[View on GitHub](https://github.com/mikefarah/yq)

View File

@@ -6,7 +6,7 @@ yq m <yaml_file> <path>...
```
## Merge example
### To Stdout
Given a data1.yaml file of:
```yaml
a: simple
@@ -30,13 +30,25 @@ c:
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
yq m -i data1.yaml data2.yaml
```
will update the data1.yaml file with the merged result.
will update the data1.yaml file so that the value of 'c' is 'test: 1'.
## Overwrite values
### Overwrite values
Given a data1.yaml file of:
```yaml
a: simple
@@ -90,7 +102,7 @@ d: false
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:
```yaml
a: simple
@@ -121,8 +133,9 @@ 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).
## Multiple Documents
### Merge into single document
Append cannot be used with overwrite, if both flags are given then append is ignored.
### 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.
Given a data1.yaml file of:
@@ -148,7 +161,7 @@ a: simple
b: dog
```
### Merge into all documents
### Multiple 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.
Given a data1.yaml file of:

View File

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

View File

@@ -1,14 +1,10 @@
```
yq r <yaml_file|json_file> <path_expression>
yq r <yaml_file|json_file> <path>
```
TALK PRINTING ABOUT KEYS AND VALUES
{!snippets/works_with_json.md!}
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
### Basic
Given a sample.yaml file of:
```yaml
b:
@@ -20,16 +16,59 @@ yq r sample.yaml b.c
```
will output the value of '2'.
## From Stdin
### From Stdin
Given a sample.yaml file of:
```bash
cat sample.yaml | yq r - b.c
```
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
```
## Multiple Documents
### Reading from a single document
### Prefix 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.item*.cats
```
will output
```yaml
- bananas
- apples
```
### Multiple Documents - specify a single document
Given a sample.yaml file of:
```yaml
something: else
@@ -43,7 +82,7 @@ yq r -d1 sample.yaml b.c
```
will output the value of '2'.
### Read from all documents
### Multiple Documents - read all documents
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:
@@ -66,4 +105,46 @@ will output:
- Fred
- Stella
- 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!}

35
mkdocs/snippets/niche.md Normal file
View File

@@ -0,0 +1,35 @@
### 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

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

View File

@@ -1,12 +1,8 @@
```
yq w <yaml_file> <path_expression> <new value>
yq w <yaml_file> <path> <new value>
```
Updates all the matching nodes of path expression to the supplied value.
See docs for [path expression](path_expressions.md) for more details.
## Basic
### To Stdout
Given a sample.yaml file of:
```yaml
b:
@@ -22,18 +18,12 @@ b:
c: cat
```
### 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
### From STDIN
```bash
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.
Given a sample.yaml file of:
@@ -53,7 +43,85 @@ b:
- new thing
```
## Appending value to an array field
### 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.*.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:
```yaml
b:
@@ -78,8 +146,7 @@ b:
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:
```yaml
something: else
@@ -99,7 +166,7 @@ b:
c: 5
```
### Update all documents
### Multiple Documents - update all documents
Given a sample.yaml file of:
```yaml
something: else
@@ -121,11 +188,22 @@ b:
c: 5
```
UPDATE THIS
UPDATE THIS
INCLUDE DELETE EXAMPLE
Note that '*' is in quotes to avoid being interpreted by your shell.
## Updating multiple values with a script
### Updating files in-place
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:
```yaml
b:
@@ -155,3 +233,18 @@ And, of course, you can pipe the instructions in using '-':
```bash
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

@@ -0,0 +1,54 @@
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

@@ -0,0 +1,48 @@
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

@@ -0,0 +1,43 @@
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

@@ -0,0 +1,52 @@
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,249 +1,376 @@
package yqlib
import (
"fmt"
"reflect"
"strconv"
"strings"
errors "github.com/pkg/errors"
yaml "gopkg.in/yaml.v3"
yaml "github.com/mikefarah/yaml/v2"
logging "gopkg.in/op/go-logging.v1"
)
type DataNavigator interface {
Traverse(value *yaml.Node, path []string) error
ReadChildValue(child interface{}, remainingPaths []string) (interface{}, error)
UpdatedChildValue(child interface{}, remainingPaths []string, value interface{}) interface{}
DeleteChildValue(child interface{}, remainingPaths []string) (interface{}, error)
}
type navigator struct {
navigationStrategy NavigationStrategy
log *logging.Logger
}
func NewDataNavigator(NavigationStrategy NavigationStrategy) DataNavigator {
func NewDataNavigator(l *logging.Logger) DataNavigator {
return &navigator{
navigationStrategy: NavigationStrategy,
log: l,
}
}
func (n *navigator) Traverse(value *yaml.Node, path []string) error {
realValue := value
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)
func (n *navigator) ReadChildValue(child interface{}, remainingPaths []string) (interface{}, error) {
if len(remainingPaths) == 0 {
return child, nil
}
return n.doTraverse(value, "", path, emptyArray)
return n.recurse(child, remainingPaths[0], remainingPaths[1:])
}
func (n *navigator) doTraverse(value *yaml.Node, head string, tail []string, pathStack []interface{}) error {
log.Debug("head %v", head)
DebugNode(value)
var errorDeepSplatting error
if head == "**" && value.Kind != yaml.ScalarNode {
errorDeepSplatting = n.recurse(value, head, tail, pathStack)
// ignore errors here, we are deep splatting so we may accidently give a string key
// to an array sequence
if len(tail) > 0 {
_ = n.recurse(value, tail[0], tail[1:], pathStack)
func (n *navigator) UpdatedChildValue(child interface{}, remainingPaths []string, value interface{}) interface{} {
if len(remainingPaths) == 0 {
return value
}
n.log.Debugf("UpdatedChildValue for child %v with path %v to set value %v", child, remainingPaths, value)
n.log.Debugf("type of child is %v", reflect.TypeOf(child))
switch child := child.(type) {
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 errorDeepSplatting
}
if len(tail) > 0 {
log.Debugf("diving into %v", tail[0])
DebugNode(value)
return n.recurse(value, tail[0], tail[1:], pathStack)
}
return n.navigationStrategy.Visit(NewNodeContext(value, head, tail, pathStack))
return n.writeMap(child, remainingPaths, value)
}
func (n *navigator) getOrReplace(original *yaml.Node, expectedKind yaml.Kind) *yaml.Node {
if original.Kind != expectedKind {
log.Debug("wanted %v but it was %v, overriding", expectedKind, original.Kind)
return &yaml.Node{Kind: expectedKind}
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
}
return original
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 *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)
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)
}
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)
index, err := strconv.ParseInt(head, 10, 64)
if err != nil {
return nil, fmt.Errorf("error accessing array: %v", err)
}
return nil
return n.readArray(value, index, tail)
case yaml.MapSlice:
return n.readMap(value, head, tail)
default:
return nil
return nil, nil
}
}
func (n *navigator) recurseMap(value *yaml.Node, head string, tail []string, pathStack []interface{}) error {
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
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)
}
if traversedEntry || n.navigationStrategy.GetPathParser().IsPathExpression(head) || !n.navigationStrategy.AutoCreateMap(NewNodeContext(value, head, tail, pathStack)) {
return nil
}
mapEntryKey := yaml.Node{Value: head, Kind: yaml.ScalarNode}
value.Content = append(value.Content, &mapEntryKey)
mapEntryValue := yaml.Node{Kind: guessKind(head, tail, 0)}
value.Content = append(value.Content, &mapEntryValue)
log.Debug("adding new node %v", head)
return n.doTraverse(&mapEntryValue, head, tail, append(pathStack, head))
return actualString == key
}
// 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
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 nil
return matches
}
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
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 n.visitAliases(contents, head, tail, pathStack, visit)
return mapSlice
}
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 {
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
}
if contents[index+1].Kind == yaml.AliasNode {
valueNode := contents[index+1]
log.Debug("found an alias")
DebugNode(contents[index])
DebugNode(valueNode)
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)
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
}
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.
}
return nil
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) visitAliasSequence(possibleAliasArray []*yaml.Node, head string, tail []string, pathStack []interface{}, visit mapVisitorFn) error {
// need to search this backwards too, so that aliases defined last override the preceding.
for aliasIndex := len(possibleAliasArray) - 1; aliasIndex >= 0; aliasIndex = aliasIndex - 1 {
child := possibleAliasArray[aliasIndex]
if child.Kind == yaml.AliasNode {
log.Debug("found an alias")
DebugNode(child)
errorInAlias := n.visitMatchingEntries(child.Alias, head, tail, pathStack, visit)
if errorInAlias != nil {
return errorInAlias
}
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 nil
return values, nil
}
func (n *navigator) splatArray(value *yaml.Node, head string, tail []string, pathStack []interface{}) error {
for index, childValue := range value.Content {
log.Debug("processing")
DebugNode(childValue)
childValue = n.getOrReplace(childValue, guessKind(head, tail, childValue.Kind))
newPathStack := append(pathStack, index)
if n.navigationStrategy.ShouldTraverse(NewNodeContext(childValue, head, tail, newPathStack), childValue.Value) {
var err = n.doTraverse(childValue, head, tail, newPathStack)
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 {
val, err := n.recurse(entry.Value, tail[0], tail[1:])
if err != nil {
return err
return nil, err
}
newArray[i] = val
} else {
newArray[i] = entry.Value
}
i++
}
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 {
return n.recurse(value, tail[0], tail[1:])
}
return value, nil
}
func (n *navigator) deleteMap(context interface{}, paths []string) (yaml.MapSlice, error) {
n.log.Debugf("deleteMap for %v for %v\n", paths, context)
mapSlice := n.getMapSlice(context)
if len(paths) == 0 {
return mapSlice, nil
}
var index int
var child yaml.MapItem
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)
var badDelete error
mapSlice, badDelete = n.deleteEntryInMap(mapSlice, child, index, paths)
if badDelete != nil {
return nil, badDelete
}
}
}
return nil
return mapSlice, nil
}
func (n *navigator) appendArray(value *yaml.Node, head string, tail []string, pathStack []interface{}) error {
var newNode = yaml.Node{Kind: guessKind(head, tail, 0)}
value.Content = append(value.Content, &newNode)
log.Debug("appending a new node, %v", value.Content)
return n.doTraverse(&newNode, head, tail, append(pathStack, len(value.Content)-1))
}
func (n *navigator) deleteEntryInMap(original yaml.MapSlice, child yaml.MapItem, index int, paths []string) (yaml.MapSlice, error) {
remainingPaths := paths[1:]
func (n *navigator) recurseArray(value *yaml.Node, head string, tail []string, pathStack []interface{}) error {
var index, err = strconv.ParseInt(head, 10, 64) // nolint
if err != nil {
return errors.Wrapf(err, "Error parsing array index '%v' for '%v'", head, pathStackToString(pathStack))
var newSlice yaml.MapSlice
if len(remainingPaths) > 0 {
newChild := yaml.MapItem{Key: child.Key}
var errorDeleting error
newChild.Value, errorDeleting = n.DeleteChildValue(child.Value, remainingPaths)
if errorDeleting != nil {
return nil, errorDeleting
}
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)
}
for int64(len(value.Content)) <= index {
value.Content = append(value.Content, &yaml.Node{Kind: guessKind(head, tail, 0)})
n.log.Debugf("\tReturning original %v\n", original)
return newSlice, nil
}
func (n *navigator) deleteArraySplat(array []interface{}, tail []string) (interface{}, error) {
n.log.Debugf("deleteArraySplat for %v for %v\n", tail, array)
var newArray = make([]interface{}, len(array))
for index, value := range array {
val, err := n.DeleteChildValue(value, tail)
if err != nil {
return nil, err
}
newArray[index] = val
}
return newArray, nil
}
func (n *navigator) deleteArray(array []interface{}, paths []string, index int64) (interface{}, error) {
n.log.Debugf("deleteArray for %v for %v\n", paths, array)
if index >= int64(len(array)) {
return array, nil
}
value.Content[index] = n.getOrReplace(value.Content[index], guessKind(head, tail, value.Content[index].Kind))
remainingPaths := paths[1:]
if len(remainingPaths) > 0 {
// recurse into the array element at index
var errorDeleting error
array[index], errorDeleting = n.deleteMap(array[index], remainingPaths)
if errorDeleting != nil {
return nil, errorDeleting
}
return n.doTraverse(value.Content[index], head, tail, append(pathStack, index))
} else {
// Delete the array element at index
array = append(array[:index], array[index+1:]...)
n.log.Debugf("\tDeleted item index %d from array, leaving %v", index, array)
}
n.log.Debugf("\tReturning array: %v\n", array)
return array, nil
}

View File

@@ -1 +1,397 @@
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

@@ -1,67 +0,0 @@
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
}

View File

@@ -1,44 +0,0 @@
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

@@ -1,21 +0,0 @@
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,149 +1,70 @@
package yqlib
import (
"bytes"
"fmt"
"strconv"
"strings"
mergo "gopkg.in/imdario/mergo.v0"
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 {
Get(rootNode *yaml.Node, path string) ([]*NodeContext, error)
Update(rootNode *yaml.Node, updateCommand UpdateCommand, autoCreate bool) error
New(path string) yaml.Node
PathStackToString(pathStack []interface{}) string
MergePathStackToString(pathStack []interface{}, appendArrays bool) string
ReadPath(dataBucket interface{}, path string) (interface{}, error)
WritePath(dataBucket interface{}, path string, value interface{}) interface{}
PrefixPath(dataBucket interface{}, prefix string) interface{}
DeletePath(dataBucket interface{}, path string) (interface{}, error)
Merge(dst interface{}, src interface{}, overwrite bool, append bool) error
}
type lib struct {
parser PathParser
navigator DataNavigator
parser PathParser
}
func NewYqLib() YqLib {
func NewYqLib(l *logging.Logger) YqLib {
return &lib{
parser: NewPathParser(),
navigator: NewDataNavigator(l),
parser: NewPathParser(),
}
}
func (l *lib) Get(rootNode *yaml.Node, path string) ([]*NodeContext, error) {
func (l *lib) ReadPath(dataBucket interface{}, path string) (interface{}, error) {
var paths = l.parser.ParsePath(path)
navigationStrategy := ReadNavigationStrategy()
navigator := NewDataNavigator(navigationStrategy)
error := navigator.Traverse(rootNode, paths)
return navigationStrategy.GetVisitedNodes(), error
return l.navigator.ReadChildValue(dataBucket, paths)
}
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 {
func (l *lib) WritePath(dataBucket interface{}, path string, value interface{}) interface{} {
var paths = l.parser.ParsePath(path)
newNode := yaml.Node{Kind: guessKind("", paths, 0)}
return newNode
return l.navigator.UpdatedChildValue(dataBucket, paths, value)
}
func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand, autoCreate bool) error {
log.Debugf("%v to %v", updateCommand.Command, updateCommand.Path)
switch updateCommand.Command {
case "update":
var paths = l.parser.ParsePath(updateCommand.Path)
navigator := NewDataNavigator(UpdateNavigationStrategy(updateCommand, autoCreate))
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)
func (l *lib) PrefixPath(dataBucket interface{}, prefix string) interface{} {
var paths = l.parser.ParsePath(prefix)
// Inverse order
for i := len(paths)/2 - 1; i >= 0; i-- {
opp := len(paths) - 1 - i
paths[i], paths[opp] = paths[opp], paths[i]
}
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{}, overwriteFlag bool, appendFlag bool) error {
opts := []func(*mergo.Config){}
if overwriteFlag {
opts = append(opts, mergo.WithOverride)
}
if appendFlag {
opts = append(opts, mergo.WithAppendSlice)
}
return mergo.Merge(dst, src, opts...)
}

View File

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

@@ -1,152 +0,0 @@
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,14 +1,7 @@
package yqlib
import (
"strconv"
"strings"
)
type PathParser interface {
ParsePath(path string) []string
MatchesNextPathElement(nodeContext NodeContext, nodeKey string) bool
IsPathExpression(pathElement string) bool
}
type pathParser struct{}
@@ -17,66 +10,7 @@ func NewPathParser() 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 {
if path == "" {
return []string{}
}
return p.parsePathAccum([]string{}, path)
}
@@ -96,12 +30,9 @@ func (p *pathParser) nextYamlPath(path string) (pathElement string, remaining st
case '"':
// e.g "a.b".blah.cat -> we need to return "a.b" and "blah.cat"
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:
// 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,18 +3,14 @@ package yqlib
import (
"testing"
"github.com/mikefarah/yq/v3/test"
"github.com/mikefarah/yq/v2/test"
)
var parser = NewPathParser()
var parsePathsTests = []struct {
path string
expectedPaths []string
}{
{"a.b", []string{"a", "b"}},
{"a.b.**", []string{"a", "b", "**"}},
{"a.b.*", []string{"a", "b", "*"}},
{"a.b[0]", []string{"a", "b", "0"}},
{"a.b.d[+]", []string{"a", "b", "d", "+"}},
{"a", []string{"a"}},
@@ -26,53 +22,8 @@ var parsePathsTests = []struct {
{"[0]", []string{"0"}},
}
func TestPathParserParsePath(t *testing.T) {
func TestParsePath(t *testing.T) {
for _, tt := range parsePathsTests {
test.AssertResultComplex(t, tt.expectedPaths, parser.ParsePath(tt.path))
test.AssertResultComplex(t, tt.expectedPaths, NewPathParser().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

@@ -1,17 +0,0 @@
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

@@ -1,35 +0,0 @@
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,46 +2,41 @@ package yqlib
import (
"strconv"
yaml "gopkg.in/yaml.v3"
)
type ValueParser interface {
Parse(argument string, customTag string) *yaml.Node
ParseValue(argument string) interface{}
}
type valueParser struct {
}
type valueParser struct{}
func NewValueParser() ValueParser {
return &valueParser{}
}
func (v *valueParser) Parse(argument string, customTag string) *yaml.Node {
var err interface{}
var tag = customTag
if tag == "" {
_, err = strconv.ParseBool(argument)
if err == nil {
tag = "!!bool"
func (v *valueParser) ParseValue(argument string) interface{} {
var value, err interface{}
var inQuotes = len(argument) > 0 && argument[0] == '"'
if !inQuotes {
intValue, intErr := strconv.ParseInt(argument, 10, 64)
floatValue, floatErr := strconv.ParseFloat(argument, 64)
if intErr == nil && floatErr == nil {
if int64(floatValue) == intValue {
return intValue
}
return floatValue
} else if floatErr == nil {
// In case cannot parse the int due to large precision
return floatValue
}
_, err = strconv.ParseFloat(argument, 64)
value, err = strconv.ParseBool(argument)
if err == nil {
tag = "!!float"
}
_, err = strconv.ParseInt(argument, 10, 64)
if err == nil {
tag = "!!int"
}
if argument == "null" {
tag = "!!null"
return value
}
if argument == "[]" {
return &yaml.Node{Tag: "!!seq", Kind: yaml.SequenceNode}
return make([]interface{}, 0)
}
return argument
}
log.Debugf("parsed value '%v', tag: '%v'", argument, tag)
return &yaml.Node{Value: argument, Tag: tag, Kind: yaml.ScalarNode}
return argument[1 : len(argument)-1]
}

View File

@@ -3,36 +3,24 @@ package yqlib
import (
"testing"
"github.com/mikefarah/yq/v3/test"
yaml "gopkg.in/yaml.v3"
"github.com/mikefarah/yq/v2/test"
)
var parseValueTests = []struct {
argument string
customTag string
expectedTag string
expectedResult interface{}
testDescription string
}{
{"true", "", "!!bool", "boolean"},
{"true", "!!str", "!!str", "boolean forced as string"},
{"3.4", "", "!!float", "float"},
{"1212121", "", "!!int", "big number"},
{"1212121.1", "", "!!float", "big float number"},
{"3", "", "!!int", "int"},
{"null", "", "!!null", "null"},
{"true", true, "boolean"},
{"\"true\"", "true", "boolean as string"},
{"3.4", 3.4, "number"},
{"\"3.4\"", "3.4", "number as string"},
{"", "", "empty string"},
{"1212121", int64(1212121), "big number"},
}
func TestValueParserParse(t *testing.T) {
func TestParseValue(t *testing.T) {
for _, tt := range parseValueTests {
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)
test.AssertResultWithContext(t, tt.expectedResult, NewValueParser().ParseValue(tt.argument), tt.testDescription)
}
}
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
go test -coverprofile=coverage.out -v $(go list ./... | grep -v -E 'examples' | grep -v -E 'test')
go tool cover -html=coverage.out -o coverage.html
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o cover/coverage.html

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,6 @@
a:
b:
c: 1
d:
e: 2
f:

View File

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

View File

@@ -11,12 +11,12 @@ var (
GitDescribe string
// Version is main version number that is being run at the moment.
Version = "3.0.0"
Version = "2.4.1"
// 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
// such as "dev" (in development), "beta", "rc1", etc.
VersionPrerelease = "beta"
VersionPrerelease = ""
)
// ProductName is the name of the product

548
yq.go
View File

@@ -6,32 +6,35 @@ import (
"io"
"io/ioutil"
"os"
"reflect"
"strconv"
"strings"
"github.com/mikefarah/yq/v3/pkg/yqlib"
"github.com/mikefarah/yq/v2/pkg/marshal"
"github.com/mikefarah/yq/v2/pkg/yqlib"
errors "github.com/pkg/errors"
yaml "github.com/mikefarah/yaml/v2"
"github.com/spf13/cobra"
logging "gopkg.in/op/go-logging.v1"
yaml "gopkg.in/yaml.v3"
)
var customTag = ""
var printMode = "v"
var trimOutput = true
var writeInplace = false
var writeScript = ""
var outputToJSON = false
var overwriteFlag = false
var autoCreateFlag = true
var keyOnlyFlag = false
var allowEmptyFlag = false
var appendFlag = false
var verbose = false
var version = false
var docIndex = "0"
var log = logging.MustGetLogger("yq")
var lib = yqlib.NewYqLib()
var lib = yqlib.NewYqLib(log)
var jsonConverter = marshal.NewJsonConverter()
var yamlConverter = marshal.NewYamlConverter()
var valueParser = yqlib.NewValueParser()
func main() {
@@ -43,6 +46,7 @@ func main() {
}
func newCommandCLI() *cobra.Command {
yaml.DefaultMapType = reflect.TypeOf(yaml.MapSlice{})
var rootCmd = &cobra.Command{
Use: "yq",
Short: "yq is a lightweight and portable command-line YAML processor.",
@@ -73,8 +77,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(&outputToJSON, "tojson", "j", false, "output as json")
rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit")
rootCmd.AddCommand(
@@ -92,110 +96,97 @@ func newCommandCLI() *cobra.Command {
func createReadCmd() *cobra.Command {
var cmdRead = &cobra.Command{
Use: "read [yaml_file] [path_expression]",
Use: "read [yaml_file] [path]",
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: `
yq read things.yaml 'a.b.c'
yq r - 'a.b.c' # reads from stdin
yq r things.yaml 'a.*.c'
yq r things.yaml 'a.**.c' # deep splat
yq r things.yaml 'a.(child.subchild==co*).c'
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'
yq read things.yaml a.b.c
yq r - a.b.c (reads from stdin)
yq r things.yaml a.*.c
yq r -d1 things.yaml a.array[0].blah
yq r things.yaml a.array[*].blah
yq r -- things.yaml --key-starting-with-dashes
`,
Long: "Outputs the value of the given path in the yaml file to STDOUT",
RunE: readProperty,
}
cmdRead.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
cmdRead.PersistentFlags().StringVarP(&printMode, "printMode", "p", "v", "print mode (v (values, default), p (paths), pv (path and value pairs)")
cmdRead.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "output as json")
cmdRead.PersistentFlags().BoolVarP(&keyOnlyFlag, "keyonly", "k", false, "output with top level keys only")
return cmdRead
}
func createWriteCmd() *cobra.Command {
var cmdWrite = &cobra.Command{
Use: "write [yaml_file] [path_expression] [value]",
Use: "write [yaml_file] [path] [value]",
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: `
yq write things.yaml 'a.b.c' true
yq write things.yaml 'a.*.c' true
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 write things.yaml a.b.c cat
yq write --inplace -- things.yaml a.b.c --cat
yq w -i things.yaml a.b.c cat
yq w --script update_script.yaml things.yaml
yq w -i -s update_script.yaml things.yaml
yq w things.yaml 'a.b.d[+]' foo # appends a new node to the 'd' array
yq w --doc 2 things.yaml 'a.b.d[+]' foo # updates the 3rd document of the yaml file
yq w --doc 2 things.yaml a.b.d[+] foo
yq w -d2 things.yaml a.b.d[+] foo
`,
Long: `Updates the matching nodes of path expression to the specified value
Long: `Updates the yaml file w.r.t the given path and value.
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.
Update Scripts:
Note that you can give an update script to perform more sophisticated update. Update script
format is list of update commands (update or delete) like so:
Note that you can give an update script to perform more sophisticated updated. Update script
format is a yaml map where the key is the path and the value is..well the value. e.g.:
---
- command: update
path: b.c
value:
#great
things: frog # wow!
- command: delete
path: b.d
a.b.c: true,
a.b.e:
- name: bob
`,
RunE: writeProperty,
}
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(&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)")
return cmdWrite
}
func createPrefixCmd() *cobra.Command {
var cmdPrefix = &cobra.Command{
var cmdWrite = &cobra.Command{
Use: "prefix [yaml_file] [path]",
Aliases: []string{"p"},
Short: "yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c",
Example: `
yq prefix things.yaml 'a.b.c'
yq prefix --inplace things.yaml 'a.b.c'
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 --doc 2 things.yaml 'a.b.d'
yq p -d2 things.yaml 'a.b.d'
yq prefix things.yaml a.b.c
yq prefix --inplace things.yaml a.b.c
yq prefix --inplace -- things.yaml --key-starting-with-dash
yq p -i things.yaml a.b.c
yq p --doc 2 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.
Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
`,
RunE: prefixProperty,
}
cmdPrefix.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the yaml file inplace")
cmdPrefix.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
return cmdPrefix
cmdWrite.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)")
return cmdWrite
}
func createDeleteCmd() *cobra.Command {
var cmdDelete = &cobra.Command{
Use: "delete [yaml_file] [path_expression]",
Use: "delete [yaml_file] [path]",
Aliases: []string{"d"},
Short: "yq d [--inplace/-i] [--doc/-d index] sample.yaml a.b.c",
Example: `
yq delete things.yaml 'a.b.c'
yq delete things.yaml 'a.*.c'
yq delete things.yaml 'a.(child.subchild==co*).c'
yq delete things.yaml 'a.**'
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'
yq delete things.yaml a.b.c
yq delete --inplace things.yaml a.b.c
yq delete --inplace -- things.yaml --key-starting-with-dash
yq d -i things.yaml a.b.c
yq d things.yaml a.b.c
`,
Long: `Deletes the nodes matching the given path expression from the YAML file.
Long: `Deletes the given path from the YAML file.
Outputs to STDOUT unless the inplace flag is used, in which case the file is updated instead.
`,
RunE: deleteProperty,
@@ -211,10 +202,9 @@ func createNewCmd() *cobra.Command {
Aliases: []string{"n"},
Short: "yq n [--script/-s script_file] a.b.c newValue",
Example: `
yq new '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 'a.b[+]' cat
yq n -- '--key-starting-with-dash' cat # need to use '--' to stop processing arguments as flags
yq new a.b.c cat
yq n a.b.c cat
yq n -- --key-starting-with-dash cat
yq n --script create_script.yaml
`,
Long: `Creates a new yaml w.r.t the given path and value.
@@ -225,8 +215,7 @@ Note that you can give a create script to perform more sophisticated yaml. This
`,
RunE: newProperty,
}
cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for creating yaml")
cmdNew.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)")
cmdNew.PersistentFlags().StringVarP(&writeScript, "script", "s", "", "yaml script for updating yaml")
return cmdNew
}
@@ -242,19 +231,19 @@ yq m -i things.yaml other.yaml
yq m --overwrite things.yaml other.yaml
yq m -i -x 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).
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 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,
}
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(&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(&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)")
@@ -274,119 +263,101 @@ func readProperty(cmd *cobra.Command, args []string) error {
if errorParsingDocIndex != nil {
return errorParsingDocIndex
}
matchingNodes, errorReadingStream := readYamlFile(args[0], path, updateAll, docIndexInt)
var mappedDocs []interface{}
var dataBucket interface{}
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 {
return errorReadingStream
}
return printResults(matchingNodes, cmd)
}
if !updateAll {
dataBucket = mappedDocs[0]
} else {
dataBucket = mappedDocs
}
func readYamlFile(filename string, path string, updateAll bool, docIndexInt int) ([]*yqlib.NodeContext, error) {
var matchingNodes []*yqlib.NodeContext
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
if keyOnlyFlag {
for _, value := range dataBucket.(yaml.MapSlice) {
cmd.Println(value.Key)
}
})
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)
}
return nil
}
func appendDocument(originalMatchingNodes []*yqlib.NodeContext, dataBucket yaml.Node, path string, updateAll bool, docIndexInt int, currentIndex int) ([]*yqlib.NodeContext, error) {
log.Debugf("processing document %v - requested index %v", currentIndex, docIndexInt)
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 {
dataStr, err := toString(dataBucket)
if err != nil {
return err
}
cmd.Println(dataStr)
return nil
}
func printResults(matchingNodes []*yqlib.NodeContext, cmd *cobra.Command) error {
if len(matchingNodes) == 0 {
log.Debug("no matching results, nothing to print")
return nil
func newProperty(cmd *cobra.Command, args []string) error {
updatedData, err := newYaml(args)
if err != nil {
return err
}
for index, mappedDoc := range matchingNodes {
switch printMode {
case "p":
cmd.Print(lib.PathStackToString(mappedDoc.PathStack))
if index < len(matchingNodes)-1 {
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")
}
}
dataStr, err := toString(updatedData)
if err != nil {
return err
}
cmd.Println(dataStr)
return nil
}
func newYaml(args []string) (interface{}, error) {
var writeCommands, writeCommandsError = readWriteCommands(args, 2, "Must provide <path_to_update> <value>")
if writeCommandsError != nil {
return nil, writeCommandsError
}
var dataBucket interface{}
var isArray = strings.HasPrefix(writeCommands[0].Key.(string), "[")
if isArray {
dataBucket = make([]interface{}, 0)
} else {
dataBucket = make(yaml.MapSlice, 0)
}
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
}
func parseDocumentIndex() (bool, int, error) {
if docIndex == "*" {
return true, -1, nil
@@ -398,11 +369,11 @@ func parseDocumentIndex() (bool, int, error) {
return false, int(docIndexInt64), nil
}
type updateDataFn func(dataBucket *yaml.Node, currentIndex int) error
type updateDataFn func(dataBucket interface{}, currentIndex int) (interface{}, error)
func mapYamlDecoder(updateData updateDataFn, encoder yqlib.Encoder) yamlDecoderFn {
func mapYamlDecoder(updateData updateDataFn, encoder *yaml.Encoder) yamlDecoderFn {
return func(decoder *yaml.Decoder) error {
var dataBucket yaml.Node
var dataBucket interface{}
var errorReading error
var errorWriting error
var errorUpdating error
@@ -425,12 +396,12 @@ func mapYamlDecoder(updateData updateDataFn, encoder yqlib.Encoder) yamlDecoderF
} else if errorReading != nil {
return errors.Wrapf(errorReading, "Error reading document at index %v, %v", currentIndex, errorReading)
}
errorUpdating = updateData(&dataBucket, currentIndex)
dataBucket, errorUpdating = updateData(dataBucket, currentIndex)
if errorUpdating != nil {
return errors.Wrapf(errorUpdating, "Error updating document at index %v", currentIndex)
}
errorWriting = encoder.Encode(&dataBucket)
errorWriting = encoder.Encode(dataBucket)
if errorWriting != nil {
return errors.Wrapf(errorWriting, "Error writing document at index %v, %v", currentIndex, errorWriting)
@@ -441,125 +412,51 @@ func mapYamlDecoder(updateData updateDataFn, encoder yqlib.Encoder) yamlDecoderF
}
func writeProperty(cmd *cobra.Command, args []string) error {
var updateCommands, updateCommandsError = readUpdateCommands(args, 3, "Must provide <filename> <path_to_update> <value>")
if updateCommandsError != nil {
return updateCommandsError
var writeCommands, writeCommandsError = readWriteCommands(args, 3, "Must provide <filename> <path_to_update> <value>")
if writeCommandsError != nil {
return writeCommandsError
}
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()
if errorParsingDocIndex != nil {
return errorParsingDocIndex
}
var updateData = func(dataBucket *yaml.Node, currentIndex int) error {
return prefixDocument(updateAll, docIndexInt, currentIndex, dataBucket, updateCommand)
var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) {
if updateAll || currentIndex == docIndexInt {
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)
}
func prefixDocument(updateAll bool, docIndexInt int, currentIndex int, dataBucket *yaml.Node, updateCommand yqlib.UpdateCommand) error {
if updateAll || currentIndex == docIndexInt {
log.Debugf("Prefixing document %v", currentIndex)
yqlib.DebugNode(dataBucket)
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
}
func prefixProperty(cmd *cobra.Command, args []string) error {
if len(args) != 2 {
return errors.New("Must provide <filename> <prefixed_path>")
}
return nil
}
prefixPath := args[1]
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()
if errorParsingDocIndex != nil {
return errorParsingDocIndex
}
var updateData = func(dataBucket *yaml.Node, currentIndex int) error {
var updateData = func(dataBucket interface{}, currentIndex int) (interface{}, error) {
if updateAll || currentIndex == docIndexInt {
log.Debugf("Updating doc %v", currentIndex)
for _, updateCommand := range updateCommands {
log.Debugf("Processing update to Path %v", updateCommand.Path)
errorUpdating := lib.Update(dataBucket, updateCommand, autoCreateFlag)
if errorUpdating != nil {
return errorUpdating
}
}
log.Debugf("Prefixing %v to doc %v", prefixPath, currentIndex)
var mapDataBucket = lib.PrefixPath(dataBucket, prefixPath)
return mapDataBucket, nil
}
return nil
return dataBucket, nil
}
return readAndUpdate(writer, inputFile, updateData)
return readAndUpdate(cmd.OutOrStdout(), args[0], updateData)
}
func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn) error {
@@ -585,64 +482,106 @@ func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn)
safelyRenameFile(tempFile.Name(), inputFile)
}()
} else {
destination = stdOut
var writer = bufio.NewWriter(stdOut)
destination = writer
destinationName = "Stdout"
defer safelyFlush(writer)
}
var encoder = yaml.NewEncoder(destination)
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))
}
type updateCommandParsed struct {
Command string
Path string
Value yaml.Node
func deleteProperty(cmd *cobra.Command, args []string) error {
if len(args) < 2 {
return errors.New("Must provide <filename> <path_to_delete>")
}
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 readUpdateCommands(args []string, expectedArgs int, badArgsMessage string) ([]yqlib.UpdateCommand, error) {
var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0)
func mergeProperties(cmd *cobra.Command, args []string) error {
if len(args) < 2 {
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 != "" {
var parsedCommands = make([]updateCommandParsed, 0)
err := readData(writeScript, 0, &parsedCommands)
if err != nil && err != io.EOF {
if err := readData(writeScript, 0, &writeCommands); err != nil {
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 {
return nil, errors.New(badArgsMessage)
} else {
updateCommands = make([]yqlib.UpdateCommand, 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}
writeCommands = make(yaml.MapSlice, 1)
writeCommands[0] = yaml.MapItem{Key: args[expectedArgs-2], Value: valueParser.ParseValue(args[expectedArgs-1])}
}
return updateCommands, nil
return writeCommands, nil
}
func toString(context interface{}) (string, error) {
if outputToJSON {
return jsonConverter.JsonToString(context)
}
return yamlConverter.YamlToString(context, trimOutput)
}
func safelyRenameFile(from string, to string) {
if renameError := os.Rename(from, to); renameError != nil {
log.Debugf("Error renaming from %v to %v, attempting to copy contents", from, to)
log.Debugf("Error renaming from %v to %v, attemting to copy contents", from, to)
log.Debug(renameError.Error())
// 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.
@@ -653,6 +592,7 @@ func safelyRenameFile(from string, to string) {
removeErr := os.Remove(from)
if removeErr != nil {
log.Errorf("failed removing original file: %s", from)
log.Error(removeErr.Error())
}
}
}

View File

@@ -1,60 +1,94 @@
package main
// import (
// "fmt"
// "runtime"
// "testing"
import (
"bytes"
"fmt"
"runtime"
"testing"
// "github.com/mikefarah/yq/v2/pkg/marshal"
// "github.com/mikefarah/yq/v2/test"
// )
"github.com/mikefarah/yq/v2/pkg/marshal"
"github.com/mikefarah/yq/v2/test"
"github.com/spf13/cobra"
)
// func TestMultilineString(t *testing.T) {
// testString := `
// abcd
// efg`
// formattedResult, _ := marshal.NewYamlConverter().YamlToString(testString, false)
// test.AssertResult(t, testString, formattedResult)
// }
func TestMultilineString(t *testing.T) {
testString := `
abcd
efg`
formattedResult, _ := marshal.NewYamlConverter().YamlToString(testString, false)
test.AssertResult(t, testString, formattedResult)
}
// func TestNewYaml(t *testing.T) {
// result, _ := newYaml([]string{"b.c", "3"})
// formattedResult := fmt.Sprintf("%v", result)
// test.AssertResult(t,
// "[{b [{c 3}]}]",
// formattedResult)
// }
func TestNewYaml(t *testing.T) {
result, _ := newYaml([]string{"b.c", "3"})
formattedResult := fmt.Sprintf("%v", result)
test.AssertResult(t,
"[{b [{c 3}]}]",
formattedResult)
}
// func TestNewYamlArray(t *testing.T) {
// result, _ := newYaml([]string{"[0].cat", "meow"})
// formattedResult := fmt.Sprintf("%v", result)
// test.AssertResult(t,
// "[[{cat meow}]]",
// formattedResult)
// }
func TestNewYamlArray(t *testing.T) {
result, _ := newYaml([]string{"[0].cat", "meow"})
formattedResult := fmt.Sprintf("%v", result)
test.AssertResult(t,
"[[{cat meow}]]",
formattedResult)
}
// func TestNewYaml_WithScript(t *testing.T) {
// writeScript = "examples/instruction_sample.yaml"
// expectedResult := `b:
// c: cat
// e:
// - name: Mike Farah`
// result, _ := newYaml([]string{""})
// actualResult, _ := marshal.NewYamlConverter().YamlToString(result, true)
// test.AssertResult(t, expectedResult, actualResult)
// }
func TestNewYamlBigInt(t *testing.T) {
result, _ := newYaml([]string{"b", "1212121"})
formattedResult := fmt.Sprintf("%v", result)
test.AssertResult(t,
"[{b 1212121}]",
formattedResult)
}
// func TestNewYaml_WithUnknownScript(t *testing.T) {
// writeScript = "fake-unknown"
// _, err := newYaml([]string{""})
// if err == nil {
// t.Error("Expected error due to unknown file")
// }
// var expectedOutput string
// if runtime.GOOS == "windows" {
// expectedOutput = `open fake-unknown: The system cannot find the file specified.`
// } else {
// expectedOutput = `open fake-unknown: no such file or directory`
// }
// test.AssertResult(t, expectedOutput, err.Error())
// }
func TestNewYaml_WithScript(t *testing.T) {
writeScript = "examples/instruction_sample.yaml"
expectedResult := `b:
c: cat
e:
- name: Mike Farah`
result, _ := newYaml([]string{""})
actualResult, _ := marshal.NewYamlConverter().YamlToString(result, true)
test.AssertResult(t, expectedResult, actualResult)
}
func TestNewYaml_WithUnknownScript(t *testing.T) {
writeScript = "fake-unknown"
_, err := newYaml([]string{""})
if err == nil {
t.Error("Expected error due to unknown file")
}
var expectedOutput string
if runtime.GOOS == "windows" {
expectedOutput = `open fake-unknown: The system cannot find the file specified.`
} else {
expectedOutput = `open fake-unknown: no such file or directory`
}
test.AssertResult(t, expectedOutput, err.Error())
}
func TestReadWithKeyOnly(t *testing.T) {
readCmd := createReadCmd()
expectedResult := `b
d
f
`
actualResult, err := executeTestCommand(readCmd, "test/fixture/keyonly.yaml", "a", "-k")
if err != nil {
t.Error(err.Error())
}
test.AssertResult(t, expectedResult, actualResult)
}
func executeTestCommand(command *cobra.Command, args ...string) (output string, err error) {
buf := new(bytes.Buffer)
command.SetOutput(buf)
command.SetArgs(args)
_, err = command.ExecuteC()
return buf.String(), err
}