mirror of
https://github.com/taigrr/yq
synced 2025-01-18 04:53:17 -08:00
Compare commits
68 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6afc2e9189 | ||
|
|
996ee0b433 | ||
|
|
bb9cb0c60e | ||
|
|
a125495eec | ||
|
|
7fa2835e13 | ||
|
|
e0f5cb3c59 | ||
|
|
87550b7fe5 | ||
|
|
5554301c29 | ||
|
|
3b0aaac626 | ||
|
|
65cb472604 | ||
|
|
fbba38c9b7 | ||
|
|
e5948c4f16 | ||
|
|
4eaadf98d0 | ||
|
|
eedbb0a99f | ||
|
|
7dabc57b65 | ||
|
|
fcd3a90f67 | ||
|
|
88e99e5336 | ||
|
|
a8cfccd3af | ||
|
|
3355e80d85 | ||
|
|
f528b28938 | ||
|
|
5b7b390a33 | ||
|
|
4f12e09e78 | ||
|
|
ee732fbf0b | ||
|
|
1507f929a2 | ||
|
|
c11c3df84f | ||
|
|
06bb3ac826 | ||
|
|
778f8c6916 | ||
|
|
9f43a4a265 | ||
|
|
bb6f07d147 | ||
|
|
759456e375 | ||
|
|
5e59803037 | ||
|
|
a0cb691601 | ||
|
|
fea8510061 | ||
|
|
b380ea2892 | ||
|
|
d66a709213 | ||
|
|
2fc39b3865 | ||
|
|
ee07edbd88 | ||
|
|
b11661a1be | ||
|
|
eac218980e | ||
|
|
80e7f46538 | ||
|
|
086f0ec6b9 | ||
|
|
89cbe63343 | ||
|
|
07cd3d4b8b | ||
|
|
b7b6988e76 | ||
|
|
9de2039c31 | ||
|
|
767709fef5 | ||
|
|
d9ae8e1e5a | ||
|
|
b0fa0e5b86 | ||
|
|
6777d639c0 | ||
|
|
de8dcff803 | ||
|
|
765ada4dc6 | ||
|
|
1405584892 | ||
|
|
8c9c326342 | ||
|
|
e90b00957b | ||
|
|
71f5f76213 | ||
|
|
b9e304e7a4 | ||
|
|
d473c39a44 | ||
|
|
9624410add | ||
|
|
b55fe48bd8 | ||
|
|
721dd57ed4 | ||
|
|
6fc3566acd | ||
|
|
4b63d92a3c | ||
|
|
3ccd32a47e | ||
|
|
3f913afbb9 | ||
|
|
ff598e1933 | ||
|
|
23de61a8d7 | ||
|
|
64135a16e1 | ||
|
|
06d8715cbe |
3
.github/ISSUE_TEMPLATE/bug_report.md
vendored
3
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -10,6 +10,9 @@ assignees: ''
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
version of yq:
|
||||
operating system:
|
||||
|
||||
**Input Yaml**
|
||||
Concise yaml document(s) (as simple as possible to show the bug)
|
||||
data1.yml:
|
||||
|
||||
4
.github/workflows/go.yml
vendored
4
.github/workflows/go.yml
vendored
@@ -7,10 +7,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Set up Go 1.14
|
||||
- name: Set up Go 1.15
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: 1.14
|
||||
go-version: 1.15
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.14 as builder
|
||||
FROM golang:1.15 as builder
|
||||
|
||||
WORKDIR /go/src/mikefarah/yq
|
||||
|
||||
@@ -12,7 +12,7 @@ RUN CGO_ENABLED=0 make local build
|
||||
|
||||
# Choose alpine as a base image to make this useful for CI, as many
|
||||
# CI tools expect an interactive shell inside the container
|
||||
FROM alpine:3.8 as production
|
||||
FROM alpine:3.12 as production
|
||||
|
||||
COPY --from=builder /go/src/mikefarah/yq/yq /usr/bin/yq
|
||||
RUN chmod +x /usr/bin/yq
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.14
|
||||
FROM golang:1.15
|
||||
|
||||
COPY scripts/devtools.sh /opt/devtools.sh
|
||||
|
||||
|
||||
122
README.md
122
README.md
@@ -7,9 +7,6 @@ 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.
|
||||
|
||||
## New version!
|
||||
V3 is officially out - if you've been using v2 and want/need to upgrade, checkout the [upgrade guide](https://mikefarah.gitbook.io/yq/upgrading-from-v2).
|
||||
|
||||
## Install
|
||||
|
||||
### [Download the latest binary](https://github.com/mikefarah/yq/releases/latest)
|
||||
@@ -18,13 +15,14 @@ V3 is officially out - if you've been using v2 and want/need to upgrade, checkou
|
||||
```
|
||||
brew install yq
|
||||
```
|
||||
|
||||
### Ubuntu and other Linux distros supporting `snap` packages:
|
||||
```
|
||||
snap install yq
|
||||
```
|
||||
|
||||
#### Snap notes
|
||||
`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:
|
||||
`yq` installs 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 - a.path
|
||||
@@ -41,6 +39,64 @@ sudo mv /etc/myfile.tmp /etc/myfile
|
||||
rm /etc/myfile.tmp
|
||||
```
|
||||
|
||||
### wget
|
||||
|
||||
Use wget to download the pre-compiled binaries:
|
||||
|
||||
```bash
|
||||
wget https://github.com/mikefarah/yq/releases/download/{VERSION}/{BINARY} -O /usr/bin/yq &&\
|
||||
chmod +x /usr/bin/yq
|
||||
```
|
||||
|
||||
For instance, VERSION=3.4.0 and BINARY=yq_linux_amd64
|
||||
|
||||
|
||||
### Run with Docker
|
||||
|
||||
#### Oneshot use:
|
||||
|
||||
```bash
|
||||
docker run --rm -v "${PWD}":/workdir mikefarah/yq yq [flags] <command> FILE...
|
||||
```
|
||||
|
||||
#### Run commands interactively:
|
||||
|
||||
```bash
|
||||
docker run --rm -it -v "${PWD}":/workdir mikefarah/yq sh
|
||||
```
|
||||
|
||||
It can be useful to have a bash function to avoid typing the whole docker command:
|
||||
|
||||
```bash
|
||||
yq() {
|
||||
docker run --rm -i -v "${PWD}":/workdir mikefarah/yq yq "$@"
|
||||
}
|
||||
```
|
||||
|
||||
### Go Get:
|
||||
```
|
||||
GO111MODULE=on go get github.com/mikefarah/yq/v3
|
||||
```
|
||||
|
||||
## Community Supported Installation methods
|
||||
As these are supported by the community :heart: - however, they may be out of date with the officially supported releases.
|
||||
|
||||
|
||||
### Windows:
|
||||
```
|
||||
choco install yq
|
||||
```
|
||||
Supported by @chillum (https://chocolatey.org/packages/yq)
|
||||
|
||||
### Alpine Linux
|
||||
- Enable edge/community repo by adding ```$MIRROR/alpine/edge/community``` to ```/etc/apk/repositories```
|
||||
- Update database index with ```apk update```
|
||||
- Install yq with ```apk add yq```
|
||||
|
||||
Supported by Tuan Hoang
|
||||
https://pkgs.alpinelinux.org/package/edge/community/x86/yq
|
||||
|
||||
|
||||
### On Ubuntu 16.04 or higher from Debian package:
|
||||
```sh
|
||||
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys CC86BB64
|
||||
@@ -48,34 +104,7 @@ sudo add-apt-repository ppa:rmescandon/yq
|
||||
sudo apt update
|
||||
sudo apt install yq -y
|
||||
```
|
||||
Supported by @rmescandon
|
||||
|
||||
### Go Get:
|
||||
```
|
||||
GO111MODULE=on go get github.com/mikefarah/yq/v3
|
||||
```
|
||||
|
||||
## Run with Docker
|
||||
|
||||
Oneshot use:
|
||||
|
||||
```bash
|
||||
docker run --rm -v ${PWD}:/workdir mikefarah/yq yq [flags] <command> FILE...
|
||||
```
|
||||
|
||||
Run commands interactively:
|
||||
|
||||
```bash
|
||||
docker run --rm -it -v ${PWD}:/workdir mikefarah/yq sh
|
||||
```
|
||||
|
||||
It can be useful to have a bash function to avoid typing the whole docker command:
|
||||
|
||||
```bash
|
||||
yq() {
|
||||
docker run --rm -i -v ${PWD}:/workdir mikefarah/yq yq $@
|
||||
}
|
||||
```
|
||||
Supported by @rmescandon (https://launchpad.net/~rmescandon/+archive/ubuntu/yq)
|
||||
|
||||
## Features
|
||||
- Written in portable go, so you can download a lovely dependency free binary
|
||||
@@ -94,6 +123,7 @@ yq() {
|
||||
- [Pipe data in by using '-'](https://mikefarah.gitbook.io/yq/commands/read#from-stdin)
|
||||
- [Merge](https://mikefarah.gitbook.io/yq/commands/merge) multiple yaml files with various options for [overriding](https://mikefarah.gitbook.io/yq/commands/merge#overwrite-values) and [appending](https://mikefarah.gitbook.io/yq/commands/merge#append-values-with-arrays)
|
||||
- Supports multiple documents in a single yaml file for [reading](https://mikefarah.gitbook.io/yq/commands/read#multiple-documents), [writing](https://mikefarah.gitbook.io/yq/commands/write-update#multiple-documents) and [merging](https://mikefarah.gitbook.io/yq/commands/merge#multiple-documents)
|
||||
- General shell completion scripts (bash/zsh/fish/powershell) (https://mikefarah.gitbook.io/yq/commands/shell-completion)
|
||||
|
||||
## [Usage](https://mikefarah.gitbook.io/yq/)
|
||||
|
||||
@@ -105,18 +135,19 @@ Usage:
|
||||
yq [command]
|
||||
|
||||
Available Commands:
|
||||
compare yq x [--prettyPrint/-P] dataA.yaml dataB.yaml 'b.e(name==fr*).value'
|
||||
delete yq d [--inplace/-i] [--doc/-d index] sample.yaml 'b.e(name==fred)'
|
||||
help Help about any command
|
||||
merge yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--append/-a] sample.yaml sample2.yaml
|
||||
new yq n [--script/-s script_file] a.b.c newValue
|
||||
prefix yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c
|
||||
read yq r [--printMode/-p pv] sample.yaml 'b.e(name==fr*).value'
|
||||
validate yq v sample.yaml
|
||||
write yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml 'b.e(name==fr*).value' newValue
|
||||
compare yq x [--prettyPrint/-P] dataA.yaml dataB.yaml 'b.e(name==fr*).value'
|
||||
delete yq d [--inplace/-i] [--doc/-d index] sample.yaml 'b.e(name==fred)'
|
||||
help Help about any command
|
||||
merge yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--append/-a] sample.yaml sample2.yaml
|
||||
new yq n [--script/-s script_file] a.b.c newValue
|
||||
prefix yq p [--inplace/-i] [--doc/-d index] sample.yaml a.b.c
|
||||
read yq r [--printMode/-p pv] sample.yaml 'b.e(name==fr*).value'
|
||||
shell-completion Generates shell completion scripts
|
||||
validate yq v sample.yaml
|
||||
write yq w [--inplace/-i] [--script/-s script_file] [--doc/-d index] sample.yaml 'b.e(name==fr*).value' newValue
|
||||
|
||||
Flags:
|
||||
-C, --colors print using colors
|
||||
-C, --colors print with colors
|
||||
-h, --help help for yq
|
||||
-I, --indent int sets indent level for output (default 2)
|
||||
-P, --prettyPrint pretty print
|
||||
@@ -126,3 +157,10 @@ Flags:
|
||||
|
||||
Use "yq [command] --help" for more information about a command.
|
||||
```
|
||||
|
||||
## Upgrade from V2
|
||||
If you've been using v2 and want/need to upgrade, checkout the [upgrade guide](https://mikefarah.gitbook.io/yq/upgrading-from-v2).
|
||||
|
||||
## Known Issues / Missing Features
|
||||
- `yq` attempts to preserve comment positions and whitespace as much as possible, but it does not handle all scenarios (see https://github.com/go-yaml/yaml/tree/v3 for details)
|
||||
- You cannot (yet) select multiple paths/keys from the yaml to be printed out (https://github.com/mikefarah/yq/issues/287)
|
||||
|
||||
2600
cmd/commands_test.go
2600
cmd/commands_test.go
File diff suppressed because it is too large
Load Diff
@@ -31,6 +31,8 @@ yq x -d1 dataA.yaml dataB.yaml 'a.b.c'
|
||||
cmdCompare.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
|
||||
cmdCompare.PersistentFlags().StringVarP(&printMode, "printMode", "p", "v", "print mode (v (values, default), p (paths), pv (path and value pairs)")
|
||||
cmdCompare.PersistentFlags().StringVarP(&defaultValue, "defaultValue", "D", "", "default value printed when there are no results")
|
||||
cmdCompare.PersistentFlags().BoolVarP(&stripComments, "stripComments", "", false, "strip comments out before comparing")
|
||||
cmdCompare.PersistentFlags().BoolVarP(&explodeAnchors, "explodeAnchors", "X", false, "explode anchors")
|
||||
return cmdCompare
|
||||
}
|
||||
|
||||
|
||||
115
cmd/compare_test.go
Normal file
115
cmd/compare_test.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mikefarah/yq/v3/test"
|
||||
)
|
||||
|
||||
func TestCompareSameCmd(t *testing.T) {
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "compare ../examples/data1.yaml ../examples/data1.yaml")
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := ``
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestCompareIgnoreCommentsCmd(t *testing.T) {
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "compare --stripComments ../examples/data1.yaml ../examples/data1-no-comments.yaml")
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := ``
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestCompareDontIgnoreCommentsCmd(t *testing.T) {
|
||||
forceOsExit = false
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "compare ../examples/data1.yaml ../examples/data1-no-comments.yaml")
|
||||
|
||||
expectedOutput := `-a: simple # just the best
|
||||
+a: simple
|
||||
b: [1, 2]
|
||||
c:
|
||||
test: 1
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestCompareExplodeAnchorsCommentsCmd(t *testing.T) {
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "compare --explodeAnchors ../examples/simple-anchor.yaml ../examples/simple-anchor-exploded.yaml")
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := ``
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestCompareDontExplodeAnchorsCmd(t *testing.T) {
|
||||
forceOsExit = false
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "compare ../examples/simple-anchor.yaml ../examples/simple-anchor-exploded.yaml")
|
||||
|
||||
expectedOutput := `-foo: &foo
|
||||
+foo:
|
||||
a: 1
|
||||
foobar:
|
||||
- !!merge <<: *foo
|
||||
+ a: 1
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestCompareDifferentCmd(t *testing.T) {
|
||||
forceOsExit = false
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "compare ../examples/data1.yaml ../examples/data3.yaml")
|
||||
|
||||
expectedOutput := `-a: simple # just the best
|
||||
-b: [1, 2]
|
||||
+a: "simple" # just the best
|
||||
+b: [1, 3]
|
||||
c:
|
||||
test: 1
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestComparePrettyCmd(t *testing.T) {
|
||||
forceOsExit = false
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "compare -P ../examples/data1.yaml ../examples/data3.yaml")
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := ` a: simple # just the best
|
||||
b:
|
||||
- 1
|
||||
- - 2
|
||||
+ - 3
|
||||
c:
|
||||
test: 1
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestComparePathsCmd(t *testing.T) {
|
||||
forceOsExit = false
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "compare -P -ppv ../examples/data1.yaml ../examples/data3.yaml **")
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := ` a: simple # just the best
|
||||
b.[0]: 1
|
||||
-b.[1]: 2
|
||||
+b.[1]: 3
|
||||
c.test: 1
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
@@ -9,12 +9,16 @@ var customTag = ""
|
||||
var printMode = "v"
|
||||
var printLength = false
|
||||
var unwrapScalar = true
|
||||
var customStyle = ""
|
||||
var anchorName = ""
|
||||
var makeAlias = false
|
||||
var stripComments = false
|
||||
var collectIntoArray = false
|
||||
var writeInplace = false
|
||||
var writeScript = ""
|
||||
var sourceYamlFile = ""
|
||||
var outputToJSON = false
|
||||
var exitStatus = false
|
||||
var prettyPrint = false
|
||||
var explodeAnchors = false
|
||||
var colorsEnabled = false
|
||||
@@ -22,7 +26,8 @@ var defaultValue = ""
|
||||
var indent = 2
|
||||
var overwriteFlag = false
|
||||
var autoCreateFlag = true
|
||||
var appendFlag = false
|
||||
var arrayMergeStrategyFlag = "update"
|
||||
var commentsMergeStrategyFlag = "setWhenBlank"
|
||||
var verbose = false
|
||||
var version = false
|
||||
var docIndex = "0"
|
||||
|
||||
246
cmd/delete_test.go
Normal file
246
cmd/delete_test.go
Normal file
@@ -0,0 +1,246 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mikefarah/yq/v3/test"
|
||||
)
|
||||
|
||||
func TestDeleteYamlCmd(t *testing.T) {
|
||||
content := `a: 2
|
||||
b:
|
||||
c: things
|
||||
d: something else
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.c", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
|
||||
expectedOutput := `a: 2
|
||||
b:
|
||||
d: something else
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestDeleteDeepDoesNotExistCmd(t *testing.T) {
|
||||
content := `a: 2`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.c", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
|
||||
expectedOutput := `a: 2
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestDeleteSplatYaml(t *testing.T) {
|
||||
content := `a: other
|
||||
b: [3, 4]
|
||||
c:
|
||||
toast: leave
|
||||
test: 1
|
||||
tell: 1
|
||||
tasty.taco: cool
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("delete %s c.te*", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
|
||||
expectedOutput := `a: other
|
||||
b: [3, 4]
|
||||
c:
|
||||
toast: leave
|
||||
tasty.taco: cool
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestDeleteSplatArrayYaml(t *testing.T) {
|
||||
content := `a: 2
|
||||
b:
|
||||
hi:
|
||||
- thing: item1
|
||||
name: fred
|
||||
- thing: item2
|
||||
name: sam
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.hi[*].thing", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
|
||||
expectedOutput := `a: 2
|
||||
b:
|
||||
hi:
|
||||
- name: fred
|
||||
- name: sam
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestDeleteDeepSplatArrayYaml(t *testing.T) {
|
||||
content := `thing: 123
|
||||
b:
|
||||
hi:
|
||||
- thing: item1
|
||||
name: fred
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("delete %s **.thing", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
|
||||
expectedOutput := `b:
|
||||
hi:
|
||||
- name: fred
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestDeleteSplatPrefixYaml(t *testing.T) {
|
||||
content := `a: 2
|
||||
b:
|
||||
hi:
|
||||
c: things
|
||||
d: something else
|
||||
there:
|
||||
c: more things
|
||||
d: more something else
|
||||
there2:
|
||||
c: more things also
|
||||
d: more something else also
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("delete %s b.there*.c", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
|
||||
expectedOutput := `a: 2
|
||||
b:
|
||||
hi:
|
||||
c: things
|
||||
d: something else
|
||||
there:
|
||||
d: more something else
|
||||
there2:
|
||||
d: more something else also
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestDeleteYamlArrayCmd(t *testing.T) {
|
||||
content := `- 1
|
||||
- 2
|
||||
- 3
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("delete %s [1]", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
|
||||
expectedOutput := `- 1
|
||||
- 3
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestDeleteYamlArrayExpressionCmd(t *testing.T) {
|
||||
content := `- name: fred
|
||||
- name: cat
|
||||
- name: thing
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("delete %s (name==cat)", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
|
||||
expectedOutput := `- name: fred
|
||||
- name: thing
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestDeleteYamlMulti(t *testing.T) {
|
||||
content := `apples: great
|
||||
---
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("delete -d 1 %s [1]", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
|
||||
expectedOutput := `apples: great
|
||||
---
|
||||
- 1
|
||||
- 3
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestDeleteYamlMultiAllCmd(t *testing.T) {
|
||||
content := `b:
|
||||
c: 3
|
||||
apples: great
|
||||
---
|
||||
apples: great
|
||||
something: else
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("delete %s -d * apples", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b:
|
||||
c: 3
|
||||
---
|
||||
something: else`
|
||||
test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
|
||||
}
|
||||
66
cmd/merge.go
66
cmd/merge.go
@@ -11,14 +11,14 @@ func createMergeCmd() *cobra.Command {
|
||||
var cmdMerge = &cobra.Command{
|
||||
Use: "merge [initial_yaml_file] [additional_yaml_file]...",
|
||||
Aliases: []string{"m"},
|
||||
Short: "yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--append/-a] sample.yaml sample2.yaml",
|
||||
Short: "yq m [--inplace/-i] [--doc/-d index] [--overwrite/-x] [--arrayMerge/-a strategy] sample.yaml sample2.yaml",
|
||||
Example: `
|
||||
yq merge things.yaml other.yaml
|
||||
yq merge --inplace things.yaml other.yaml
|
||||
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 -a=append 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).
|
||||
@@ -32,7 +32,17 @@ If append flag is set then existing arrays will be merged with the arrays from e
|
||||
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().StringVarP(&arrayMergeStrategyFlag, "arrays", "a", "update", `array merge strategy (update/append/overwrite)
|
||||
update: recursively update arrays by their index
|
||||
append: concatenate arrays together
|
||||
overwrite: replace arrays
|
||||
`)
|
||||
cmdMerge.PersistentFlags().StringVarP(&commentsMergeStrategyFlag, "comments", "", "setWhenBlank", `comments merge strategy (setWhenBlank/ignore/append/overwrite)
|
||||
setWhenBlank: set comment if the original document has no comment at that node
|
||||
ignore: leave comments as-is in the original
|
||||
append: append comments together
|
||||
overwrite: overwrite comments completely
|
||||
`)
|
||||
cmdMerge.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
|
||||
return cmdMerge
|
||||
}
|
||||
@@ -41,9 +51,9 @@ If append flag is set then existing arrays will be merged with the arrays from e
|
||||
* We don't deeply traverse arrays when appending a merge, instead we want to
|
||||
* append the entire array element.
|
||||
*/
|
||||
func createReadFunctionForMerge() func(*yaml.Node) ([]*yqlib.NodeContext, error) {
|
||||
func createReadFunctionForMerge(arrayMergeStrategy yqlib.ArrayMergeStrategy) func(*yaml.Node) ([]*yqlib.NodeContext, error) {
|
||||
return func(dataBucket *yaml.Node) ([]*yqlib.NodeContext, error) {
|
||||
return lib.Get(dataBucket, "**", !appendFlag)
|
||||
return lib.GetForMerge(dataBucket, "**", arrayMergeStrategy)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,19 +63,59 @@ func mergeProperties(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return errors.New("Must provide at least 1 yaml file")
|
||||
}
|
||||
var arrayMergeStrategy yqlib.ArrayMergeStrategy
|
||||
|
||||
switch arrayMergeStrategyFlag {
|
||||
case "update":
|
||||
arrayMergeStrategy = yqlib.UpdateArrayMergeStrategy
|
||||
case "append":
|
||||
arrayMergeStrategy = yqlib.AppendArrayMergeStrategy
|
||||
case "overwrite":
|
||||
arrayMergeStrategy = yqlib.OverwriteArrayMergeStrategy
|
||||
default:
|
||||
return errors.New("Array merge strategy must be one of: update/append/overwrite")
|
||||
}
|
||||
|
||||
var commentsMergeStrategy yqlib.CommentsMergeStrategy
|
||||
|
||||
switch commentsMergeStrategyFlag {
|
||||
case "setWhenBlank":
|
||||
commentsMergeStrategy = yqlib.SetWhenBlankCommentsMergeStrategy
|
||||
case "ignore":
|
||||
commentsMergeStrategy = yqlib.IgnoreCommentsMergeStrategy
|
||||
case "append":
|
||||
commentsMergeStrategy = yqlib.AppendCommentsMergeStrategy
|
||||
case "overwrite":
|
||||
commentsMergeStrategy = yqlib.OverwriteCommentsMergeStrategy
|
||||
default:
|
||||
return errors.New("Comments merge strategy must be one of: setWhenBlank/ignore/append/overwrite")
|
||||
}
|
||||
|
||||
if len(args) > 1 {
|
||||
// first generate update commands from the file
|
||||
var filesToMerge = args[1:]
|
||||
|
||||
for _, fileToMerge := range filesToMerge {
|
||||
matchingNodes, errorProcessingFile := doReadYamlFile(fileToMerge, createReadFunctionForMerge(), false, 0)
|
||||
matchingNodes, errorProcessingFile := doReadYamlFile(fileToMerge, createReadFunctionForMerge(arrayMergeStrategy), false, 0)
|
||||
if errorProcessingFile != nil {
|
||||
return errorProcessingFile
|
||||
}
|
||||
log.Debugf("finished reading for merge!")
|
||||
for _, matchingNode := range matchingNodes {
|
||||
mergePath := lib.MergePathStackToString(matchingNode.PathStack, appendFlag)
|
||||
updateCommands = append(updateCommands, yqlib.UpdateCommand{Command: "update", Path: mergePath, Value: matchingNode.Node, Overwrite: overwriteFlag})
|
||||
log.Debugf("matched node %v", lib.PathStackToString(matchingNode.PathStack))
|
||||
yqlib.DebugNode(matchingNode.Node)
|
||||
}
|
||||
for _, matchingNode := range matchingNodes {
|
||||
mergePath := lib.MergePathStackToString(matchingNode.PathStack, arrayMergeStrategy)
|
||||
updateCommands = append(updateCommands, yqlib.UpdateCommand{
|
||||
Command: "merge",
|
||||
Path: mergePath,
|
||||
Value: matchingNode.Node,
|
||||
Overwrite: overwriteFlag,
|
||||
CommentsMergeStrategy: commentsMergeStrategy,
|
||||
// dont update the content for nodes midway, only leaf nodes
|
||||
DontUpdateNodeContent: matchingNode.IsMiddleNode && (arrayMergeStrategy != yqlib.OverwriteArrayMergeStrategy || matchingNode.Node.Kind != yaml.SequenceNode),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
551
cmd/merge_test.go
Normal file
551
cmd/merge_test.go
Normal file
@@ -0,0 +1,551 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/mikefarah/yq/v3/test"
|
||||
)
|
||||
|
||||
func TestMergeCmd(t *testing.T) {
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "merge ../examples/data1.yaml ../examples/data2.yaml")
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `a: simple # just the best
|
||||
b: [1, 2]
|
||||
c:
|
||||
test: 1
|
||||
toast: leave
|
||||
tell: 1
|
||||
tasty.taco: cool
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestMergeOneFileCmd(t *testing.T) {
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "merge ../examples/data1.yaml")
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `a: simple # just the best
|
||||
b: [1, 2]
|
||||
c:
|
||||
test: 1
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestMergeNoAutoCreateCmd(t *testing.T) {
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "merge -c=false ../examples/data1.yaml ../examples/data2.yaml")
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `a: simple # just the best
|
||||
b: [1, 2]
|
||||
c:
|
||||
test: 1
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestMergeOverwriteCmd(t *testing.T) {
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "merge -c=false --overwrite ../examples/data1.yaml ../examples/data2.yaml")
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `a: other # just the best
|
||||
b: [3, 4]
|
||||
c:
|
||||
test: 1
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestMergeOverwriteDeepExampleCmd(t *testing.T) {
|
||||
content := `c:
|
||||
test: 1
|
||||
thing: whatever
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
mergeContent := `c:
|
||||
test: 5
|
||||
`
|
||||
mergeFilename := test.WriteTempYamlFile(mergeContent)
|
||||
defer test.RemoveTempYamlFile(mergeFilename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("merge --autocreate=false --overwrite %s %s", filename, mergeFilename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
|
||||
expectedOutput := `c:
|
||||
test: 5
|
||||
thing: whatever
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestMergeAppendCmd(t *testing.T) {
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "merge --autocreate=false --arrays=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]
|
||||
c:
|
||||
test: 1
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestMergeAppendArraysCmd(t *testing.T) {
|
||||
content := `people:
|
||||
- name: Barry
|
||||
age: 21`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
mergeContent := `people:
|
||||
- name: Roger
|
||||
age: 44`
|
||||
mergeFilename := test.WriteTempYamlFile(mergeContent)
|
||||
defer test.RemoveTempYamlFile(mergeFilename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("merge --arrays=append -d* %s %s", filename, mergeFilename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `people:
|
||||
- name: Barry
|
||||
age: 21
|
||||
- name: Roger
|
||||
age: 44
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestMergeAliasArraysCmd(t *testing.T) {
|
||||
content := `
|
||||
vars:
|
||||
variable1: &var1 cat
|
||||
|
||||
usage:
|
||||
value1: *var1
|
||||
valueAnother: *var1
|
||||
valuePlain: thing
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
mergeContent := `
|
||||
vars:
|
||||
variable2: &var2 puppy
|
||||
|
||||
usage:
|
||||
value2: *var2
|
||||
valueAnother: *var2
|
||||
valuePlain: *var2
|
||||
`
|
||||
|
||||
mergeFilename := test.WriteTempYamlFile(mergeContent)
|
||||
defer test.RemoveTempYamlFile(mergeFilename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("merge -x %s %s", filename, mergeFilename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `vars:
|
||||
variable1: &var1 cat
|
||||
variable2: &var2 puppy
|
||||
usage:
|
||||
value1: *var1
|
||||
valueAnother: *var2
|
||||
valuePlain: *var2
|
||||
value2: *var2
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestMergeOverwriteAndAppendCmd(t *testing.T) {
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "merge --autocreate=false --arrays=append --overwrite ../examples/data1.yaml ../examples/data2.yaml")
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `a: other # just the best
|
||||
b: [1, 2, 3, 4]
|
||||
c:
|
||||
test: 1
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
var commentContentA = `
|
||||
a: valueA1 # commentA1
|
||||
b: valueB1
|
||||
`
|
||||
|
||||
var commentContentB = `
|
||||
a: valueA2 # commentA2
|
||||
b: valueB2 # commentB2
|
||||
c: valueC2 # commentC2
|
||||
`
|
||||
|
||||
func TestMergeCommentsSetWhenBlankCmd(t *testing.T) {
|
||||
filename := test.WriteTempYamlFile(commentContentA)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
mergeFilename := test.WriteTempYamlFile(commentContentB)
|
||||
defer test.RemoveTempYamlFile(mergeFilename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("merge --comments=setWhenBlank %s %s", filename, mergeFilename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
|
||||
expectedOutput := `a: valueA1 # commentA1
|
||||
b: valueB1 # commentB2
|
||||
c: valueC2 # commentC2
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestMergeCommentsIgnoreCmd(t *testing.T) {
|
||||
filename := test.WriteTempYamlFile(commentContentA)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
mergeFilename := test.WriteTempYamlFile(commentContentB)
|
||||
defer test.RemoveTempYamlFile(mergeFilename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("merge --comments=ignore %s %s", filename, mergeFilename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
|
||||
expectedOutput := `a: valueA1 # commentA1
|
||||
b: valueB1
|
||||
c: valueC2
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestMergeCommentsAppendCmd(t *testing.T) {
|
||||
filename := test.WriteTempYamlFile(commentContentA)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
mergeFilename := test.WriteTempYamlFile(commentContentB)
|
||||
defer test.RemoveTempYamlFile(mergeFilename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("merge --comments=append %s %s", filename, mergeFilename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
|
||||
expectedOutput := `a: valueA1 # commentA1 # commentA2
|
||||
b: valueB1 # commentB2
|
||||
c: valueC2 # commentC2
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestMergeCommentsOverwriteCmd(t *testing.T) {
|
||||
filename := test.WriteTempYamlFile(commentContentA)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
mergeFilename := test.WriteTempYamlFile(commentContentB)
|
||||
defer test.RemoveTempYamlFile(mergeFilename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("merge --comments=overwrite %s %s", filename, mergeFilename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
|
||||
expectedOutput := `a: valueA1 # commentA2
|
||||
b: valueB1 # commentB2
|
||||
c: valueC2 # commentC2
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestMergeOverwriteArraysTooCmd(t *testing.T) {
|
||||
content := `a: simple # just the best
|
||||
b: [1, 2]
|
||||
c:
|
||||
test: 1
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
mergeContent := `a: things
|
||||
b: [6]`
|
||||
mergeFilename := test.WriteTempYamlFile(mergeContent)
|
||||
defer test.RemoveTempYamlFile(mergeFilename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("merge --autocreate=false --arrays=overwrite --overwrite %s %s", filename, mergeFilename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
|
||||
expectedOutput := `a: things # just the best
|
||||
b: [6]
|
||||
c:
|
||||
test: 1
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestMergeRootArraysCmd(t *testing.T) {
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "merge --arrays=append ../examples/sample_array.yaml ../examples/sample_array_2.yaml")
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
- 5
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestMergeOverwriteArraysCmd(t *testing.T) {
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "merge --arrays=overwrite ../examples/sample_array.yaml ../examples/sample_array_2.yaml")
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `- 4
|
||||
- 5
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestMergeUpdateArraysCmd(t *testing.T) {
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "merge -x --arrays=update ../examples/sample_array.yaml ../examples/sample_array_2.yaml")
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `- 4
|
||||
- 5
|
||||
- 3
|
||||
`
|
||||
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")
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `a: Easy! as one two three
|
||||
---
|
||||
another:
|
||||
document: here
|
||||
a: simple # just the best
|
||||
b: [1, 2]
|
||||
c:
|
||||
test: 1
|
||||
---
|
||||
- 1
|
||||
- 2
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestMergeYamlMultiAllCmd(t *testing.T) {
|
||||
content := `b:
|
||||
c: 3
|
||||
apples: green
|
||||
---
|
||||
something: else`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
mergeContent := `apples: red
|
||||
something: good`
|
||||
mergeFilename := test.WriteTempYamlFile(mergeContent)
|
||||
defer test.RemoveTempYamlFile(mergeFilename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("merge -d* %s %s", filename, mergeFilename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b:
|
||||
c: 3
|
||||
apples: green
|
||||
something: good
|
||||
---
|
||||
something: else
|
||||
apples: red
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestMergeSpecialCharacterKeysCmd(t *testing.T) {
|
||||
content := ``
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
mergeContent := `key[bracket]: value
|
||||
key.bracket: value
|
||||
key"value": value
|
||||
key'value': value
|
||||
`
|
||||
mergeFilename := test.WriteTempYamlFile(mergeContent)
|
||||
defer test.RemoveTempYamlFile(mergeFilename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("merge %s %s", filename, mergeFilename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
test.AssertResult(t, mergeContent, result.Output)
|
||||
}
|
||||
|
||||
func TestMergeYamlMultiAllOverwriteCmd(t *testing.T) {
|
||||
content := `b:
|
||||
c: 3
|
||||
apples: green
|
||||
---
|
||||
something: else`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
mergeContent := `apples: red
|
||||
something: good`
|
||||
mergeFilename := test.WriteTempYamlFile(mergeContent)
|
||||
defer test.RemoveTempYamlFile(mergeFilename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("merge --overwrite -d* %s %s", filename, mergeFilename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b:
|
||||
c: 3
|
||||
apples: red
|
||||
something: good
|
||||
---
|
||||
something: good
|
||||
apples: red
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestMergeYamlNullMapCmd(t *testing.T) {
|
||||
content := `b:`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
mergeContent := `b:
|
||||
thing: a frog
|
||||
`
|
||||
mergeFilename := test.WriteTempYamlFile(mergeContent)
|
||||
defer test.RemoveTempYamlFile(mergeFilename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("merge %s %s", filename, mergeFilename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
test.AssertResult(t, mergeContent, result.Output)
|
||||
}
|
||||
|
||||
func TestMergeCmd_Error(t *testing.T) {
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "merge")
|
||||
if result.Error == nil {
|
||||
t.Error("Expected command to fail due to missing arg")
|
||||
}
|
||||
expectedOutput := `Must provide at least 1 yaml file`
|
||||
test.AssertResult(t, expectedOutput, result.Error.Error())
|
||||
}
|
||||
|
||||
func TestMergeCmd_ErrorUnreadableFile(t *testing.T) {
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "merge ../examples/data1.yaml fake-unknown")
|
||||
if result.Error == nil {
|
||||
t.Error("Expected command to fail 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, result.Error.Error())
|
||||
}
|
||||
|
||||
func TestMergeCmd_Inplace(t *testing.T) {
|
||||
filename := test.WriteTempYamlFile(test.ReadTempYamlFile("../examples/data1.yaml"))
|
||||
err := os.Chmod(filename, os.FileMode(int(0666)))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("merge -i %s ../examples/data2.yaml", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
info, _ := os.Stat(filename)
|
||||
gotOutput := test.ReadTempYamlFile(filename)
|
||||
expectedOutput := `a: simple # just the best
|
||||
b: [1, 2]
|
||||
c:
|
||||
test: 1
|
||||
toast: leave
|
||||
tell: 1
|
||||
tasty.taco: cool
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, gotOutput)
|
||||
test.AssertResult(t, os.FileMode(int(0666)), info.Mode())
|
||||
}
|
||||
|
||||
func TestMergeAllowEmptyTargetCmd(t *testing.T) {
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "merge ../examples/empty.yaml ../examples/data1.yaml")
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `a: simple # just the best
|
||||
b: [1, 2]
|
||||
c:
|
||||
test: 1
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestMergeAllowEmptyMergeCmd(t *testing.T) {
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "merge ../examples/data1.yaml ../examples/empty.yaml")
|
||||
expectedOutput := `a: simple # just the best
|
||||
b: [1, 2]
|
||||
c:
|
||||
test: 1
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
@@ -27,11 +27,15 @@ Note that you can give a create script to perform more sophisticated yaml. This
|
||||
}
|
||||
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(&customStyle, "style", "", "", "formatting style of the value: single, double, folded, flow, literal, tagged")
|
||||
cmdNew.PersistentFlags().StringVarP(&anchorName, "anchorName", "", "", "anchor name")
|
||||
cmdNew.PersistentFlags().BoolVarP(&makeAlias, "makeAlias", "", false, "create an alias using the value as the anchor name")
|
||||
return cmdNew
|
||||
}
|
||||
|
||||
func newProperty(cmd *cobra.Command, args []string) error {
|
||||
var updateCommands, updateCommandsError = readUpdateCommands(args, 2, "Must provide <path_to_update> <value>")
|
||||
var badArgsMessage = "Must provide <path_to_update> <value>"
|
||||
var updateCommands, updateCommandsError = readUpdateCommands(args, 2, badArgsMessage, false)
|
||||
if updateCommandsError != nil {
|
||||
return updateCommandsError
|
||||
}
|
||||
|
||||
120
cmd/new_test.go
Normal file
120
cmd/new_test.go
Normal file
@@ -0,0 +1,120 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/mikefarah/yq/v3/test"
|
||||
)
|
||||
|
||||
func TestNewCmd(t *testing.T) {
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "new b.c 3")
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b:
|
||||
c: 3
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestNewCmdScript(t *testing.T) {
|
||||
updateScript := `- command: update
|
||||
path: b.c
|
||||
value: 7`
|
||||
scriptFilename := test.WriteTempYamlFile(updateScript)
|
||||
defer test.RemoveTempYamlFile(scriptFilename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("new --script %s", scriptFilename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b:
|
||||
c: 7
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestNewAnchorCmd(t *testing.T) {
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "new b.c 3 --anchorName=fred")
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b:
|
||||
c: &fred 3
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestNewAliasCmd(t *testing.T) {
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "new b.c foo --makeAlias")
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b:
|
||||
c: *foo
|
||||
`
|
||||
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")
|
||||
if result.Error == nil {
|
||||
t.Error("Expected command to fail due to missing arg")
|
||||
}
|
||||
expectedOutput := `Must provide <path_to_update> <value>`
|
||||
test.AssertResult(t, expectedOutput, result.Error.Error())
|
||||
}
|
||||
|
||||
func TestNewWithTaggedStyleCmd(t *testing.T) {
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "new b.c cat --tag=!!str --style=tagged")
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b:
|
||||
c: !!str cat
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestNewWithDoubleQuotedStyleCmd(t *testing.T) {
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "new b.c cat --style=double")
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b:
|
||||
c: "cat"
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestNewWithSingleQuotedStyleCmd(t *testing.T) {
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "new b.c cat --style=single")
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b:
|
||||
c: 'cat'
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
189
cmd/prefix_test.go
Normal file
189
cmd/prefix_test.go
Normal file
@@ -0,0 +1,189 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mikefarah/yq/v3/test"
|
||||
)
|
||||
|
||||
func TestPrefixCmd(t *testing.T) {
|
||||
content := `b:
|
||||
c: 3
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("prefix %s d", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `d:
|
||||
b:
|
||||
c: 3
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestPrefixCmdArray(t *testing.T) {
|
||||
content := `b:
|
||||
c: 3
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("prefix %s [+].d.[+]", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `- d:
|
||||
- b:
|
||||
c: 3
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestPrefixCmd_MultiLayer(t *testing.T) {
|
||||
content := `b:
|
||||
c: 3
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("prefix %s d.e.f", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `d:
|
||||
e:
|
||||
f:
|
||||
b:
|
||||
c: 3
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestPrefixMultiCmd(t *testing.T) {
|
||||
content := `b:
|
||||
c: 3
|
||||
---
|
||||
apples: great
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -d 1 d", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b:
|
||||
c: 3
|
||||
---
|
||||
d:
|
||||
apples: great
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
func TestPrefixInvalidDocumentIndexCmd(t *testing.T) {
|
||||
content := `b:
|
||||
c: 3
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -df d", filename))
|
||||
if result.Error == nil {
|
||||
t.Error("Expected command to fail due to invalid path")
|
||||
}
|
||||
expectedOutput := `Document index f is not a integer or *: strconv.ParseInt: parsing "f": invalid syntax`
|
||||
test.AssertResult(t, expectedOutput, result.Error.Error())
|
||||
}
|
||||
|
||||
func TestPrefixBadDocumentIndexCmd(t *testing.T) {
|
||||
content := `b:
|
||||
c: 3
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -d 1 d", filename))
|
||||
if result.Error == nil {
|
||||
t.Error("Expected command to fail due to invalid path")
|
||||
}
|
||||
expectedOutput := `asked to process document index 1 but there are only 1 document(s)`
|
||||
test.AssertResult(t, expectedOutput, result.Error.Error())
|
||||
}
|
||||
func TestPrefixMultiAllCmd(t *testing.T) {
|
||||
content := `b:
|
||||
c: 3
|
||||
---
|
||||
apples: great
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("prefix %s -d * d", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `d:
|
||||
b:
|
||||
c: 3
|
||||
---
|
||||
d:
|
||||
apples: great`
|
||||
test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
|
||||
}
|
||||
|
||||
func TestPrefixCmd_Error(t *testing.T) {
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "prefix")
|
||||
if result.Error == nil {
|
||||
t.Error("Expected command to fail due to missing arg")
|
||||
}
|
||||
expectedOutput := `Must provide <filename> <prefixed_path>`
|
||||
test.AssertResult(t, expectedOutput, result.Error.Error())
|
||||
}
|
||||
|
||||
func TestPrefixCmd_ErrorUnreadableFile(t *testing.T) {
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "prefix fake-unknown a.b")
|
||||
if result.Error == nil {
|
||||
t.Error("Expected command to fail 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, result.Error.Error())
|
||||
}
|
||||
|
||||
func TestPrefixCmd_Inplace(t *testing.T) {
|
||||
content := `b:
|
||||
c: 3
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("prefix -i %s d", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
gotOutput := test.ReadTempYamlFile(filename)
|
||||
expectedOutput := `d:
|
||||
b:
|
||||
c: 3`
|
||||
test.AssertResult(t, expectedOutput, strings.Trim(gotOutput, "\n "))
|
||||
}
|
||||
@@ -31,6 +31,7 @@ yq r -- things.yaml '--key-starting-with-dashes.blah'
|
||||
cmdRead.PersistentFlags().BoolVarP(&unwrapScalar, "unwrapScalar", "", true, "unwrap scalar, print the value with no quotes, colors or comments")
|
||||
cmdRead.PersistentFlags().BoolVarP(&stripComments, "stripComments", "", false, "print yaml without any comments")
|
||||
cmdRead.PersistentFlags().BoolVarP(&explodeAnchors, "explodeAnchors", "X", false, "explode anchors")
|
||||
cmdRead.PersistentFlags().BoolVarP(&exitStatus, "exitStatus", "e", false, "set exit status if no matches are found")
|
||||
return cmdRead
|
||||
}
|
||||
|
||||
@@ -50,7 +51,13 @@ func readProperty(cmd *cobra.Command, args []string) error {
|
||||
|
||||
matchingNodes, errorReadingStream := readYamlFile(args[0], path, updateAll, docIndexInt)
|
||||
|
||||
if exitStatus && len(matchingNodes) == 0 {
|
||||
cmd.SilenceUsage = true
|
||||
return errors.New("No matches found")
|
||||
}
|
||||
|
||||
if errorReadingStream != nil {
|
||||
cmd.SilenceUsage = true
|
||||
return errorReadingStream
|
||||
}
|
||||
out := cmd.OutOrStdout()
|
||||
|
||||
1463
cmd/read_test.go
Normal file
1463
cmd/read_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -22,6 +22,7 @@ func New() *cobra.Command {
|
||||
return nil
|
||||
},
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
cmd.SetOut(cmd.OutOrStdout())
|
||||
var format = logging.MustStringFormatter(
|
||||
`%{color}%{time:15:04:05} %{shortfunc} [%{level:.4s}]%{color:reset} %{message}`,
|
||||
)
|
||||
@@ -54,8 +55,7 @@ func New() *cobra.Command {
|
||||
createDeleteCmd(),
|
||||
createNewCmd(),
|
||||
createMergeCmd(),
|
||||
createBashCompletionCmd(rootCmd),
|
||||
)
|
||||
rootCmd.SetOutput(os.Stdout)
|
||||
|
||||
return rootCmd
|
||||
}
|
||||
|
||||
57
cmd/shell_completion.go
Normal file
57
cmd/shell_completion.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var shellVariant = "bash"
|
||||
|
||||
func createBashCompletionCmd(rootCmd *cobra.Command) *cobra.Command {
|
||||
var completionCmd = &cobra.Command{
|
||||
Use: "shell-completion",
|
||||
Short: "Generates shell completion scripts",
|
||||
Long: `To load completion for:
|
||||
bash:
|
||||
Run
|
||||
. <(yq shell-completion)
|
||||
|
||||
To configure your bash shell to load completions for each session add to
|
||||
your bashrc
|
||||
|
||||
# ~/.bashrc or ~/.profile
|
||||
. <(yq shell-completion)
|
||||
|
||||
zsh:
|
||||
The generated completion script should be put somewhere in your $fpath named _yq
|
||||
|
||||
powershell:
|
||||
Users need PowerShell version 5.0 or above, which comes with Windows 10 and
|
||||
can be downloaded separately for Windows 7 or 8.1. They can then write the
|
||||
completions to a file and source this file from their PowerShell profile,
|
||||
which is referenced by the $Profile environment variable.
|
||||
|
||||
fish:
|
||||
Save the output to a fish file and add it to your completions directory.
|
||||
|
||||
`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
switch shellVariant {
|
||||
case "bash", "":
|
||||
return rootCmd.GenBashCompletion(os.Stdout)
|
||||
case "zsh":
|
||||
return rootCmd.GenZshCompletion(os.Stdout)
|
||||
case "fish":
|
||||
return rootCmd.GenFishCompletion(os.Stdout, true)
|
||||
case "powershell":
|
||||
return rootCmd.GenPowerShellCompletion(os.Stdout)
|
||||
default:
|
||||
return fmt.Errorf("Unknown variant %v", shellVariant)
|
||||
}
|
||||
},
|
||||
}
|
||||
completionCmd.PersistentFlags().StringVarP(&shellVariant, "variation", "V", "", "shell variation: bash (default), zsh, fish, powershell")
|
||||
return completionCmd
|
||||
}
|
||||
133
cmd/utils.go
133
cmd/utils.go
@@ -17,7 +17,7 @@ type readDataFn func(dataBucket *yaml.Node) ([]*yqlib.NodeContext, error)
|
||||
|
||||
func createReadFunction(path string) func(*yaml.Node) ([]*yqlib.NodeContext, error) {
|
||||
return func(dataBucket *yaml.Node) ([]*yqlib.NodeContext, error) {
|
||||
return lib.Get(dataBucket, path, true)
|
||||
return lib.Get(dataBucket, path)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,25 +149,100 @@ func writeString(writer io.Writer, txt string) error {
|
||||
return errorWriting
|
||||
}
|
||||
|
||||
func setIfNotThere(node *yaml.Node, key string, value *yaml.Node) {
|
||||
for index := 0; index < len(node.Content); index = index + 2 {
|
||||
keyNode := node.Content[index]
|
||||
if keyNode.Value == key {
|
||||
return
|
||||
}
|
||||
}
|
||||
// need to add it to the map
|
||||
mapEntryKey := yaml.Node{Value: key, Kind: yaml.ScalarNode}
|
||||
node.Content = append(node.Content, &mapEntryKey)
|
||||
node.Content = append(node.Content, value)
|
||||
}
|
||||
|
||||
func applyAlias(node *yaml.Node, alias *yaml.Node) {
|
||||
if alias == nil {
|
||||
return
|
||||
}
|
||||
for index := 0; index < len(alias.Content); index = index + 2 {
|
||||
keyNode := alias.Content[index]
|
||||
log.Debugf("applying alias key %v", keyNode.Value)
|
||||
valueNode := alias.Content[index+1]
|
||||
setIfNotThere(node, keyNode.Value, valueNode)
|
||||
}
|
||||
}
|
||||
|
||||
func explodeNode(node *yaml.Node) error {
|
||||
node.Anchor = ""
|
||||
switch node.Kind {
|
||||
case yaml.SequenceNode, yaml.DocumentNode:
|
||||
for index, contentNode := range node.Content {
|
||||
log.Debugf("exploding index %v", index)
|
||||
errorInContent := explodeNode(contentNode)
|
||||
if errorInContent != nil {
|
||||
return errorInContent
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case yaml.AliasNode:
|
||||
log.Debugf("its an alias!")
|
||||
if node.Alias != nil {
|
||||
node.Kind = node.Alias.Kind
|
||||
node.Style = node.Alias.Style
|
||||
node.Tag = node.Alias.Tag
|
||||
node.Content = node.Alias.Content
|
||||
node.Value = node.Alias.Value
|
||||
node.Alias = nil
|
||||
}
|
||||
return nil
|
||||
case yaml.MappingNode:
|
||||
for index := 0; index < len(node.Content); index = index + 2 {
|
||||
keyNode := node.Content[index]
|
||||
valueNode := node.Content[index+1]
|
||||
log.Debugf("traversing %v", keyNode.Value)
|
||||
if keyNode.Value != "<<" {
|
||||
errorInContent := explodeNode(valueNode)
|
||||
if errorInContent != nil {
|
||||
return errorInContent
|
||||
}
|
||||
errorInContent = explodeNode(keyNode)
|
||||
if errorInContent != nil {
|
||||
return errorInContent
|
||||
}
|
||||
} else {
|
||||
if valueNode.Kind == yaml.SequenceNode {
|
||||
log.Debugf("an alias merge list!")
|
||||
for index := len(valueNode.Content) - 1; index >= 0; index = index - 1 {
|
||||
aliasNode := valueNode.Content[index]
|
||||
applyAlias(node, aliasNode.Alias)
|
||||
}
|
||||
} else {
|
||||
log.Debugf("an alias merge!")
|
||||
applyAlias(node, valueNode.Alias)
|
||||
}
|
||||
node.Content = append(node.Content[:index], node.Content[index+2:]...)
|
||||
//replay that index, since the array is shorter now.
|
||||
index = index - 2
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func explode(matchingNodes []*yqlib.NodeContext) error {
|
||||
log.Debug("exploding nodes")
|
||||
for _, nodeContext := range matchingNodes {
|
||||
var targetNode = yaml.Node{Kind: nodeContext.Node.Kind}
|
||||
explodedNodes, errorRetrieving := lib.Get(nodeContext.Node, "**", true)
|
||||
if errorRetrieving != nil {
|
||||
return errorRetrieving
|
||||
log.Debugf("exploding %v", nodeContext.Head)
|
||||
errorExplodingNode := explodeNode(nodeContext.Node)
|
||||
if errorExplodingNode != nil {
|
||||
return errorExplodingNode
|
||||
}
|
||||
for _, matchingNode := range explodedNodes {
|
||||
mergePath := lib.MergePathStackToString(matchingNode.PathStack, appendFlag)
|
||||
updateCommand := yqlib.UpdateCommand{Command: "update", Path: mergePath, Value: matchingNode.Node, Overwrite: overwriteFlag}
|
||||
errorUpdating := lib.Update(&targetNode, updateCommand, true)
|
||||
if errorUpdating != nil {
|
||||
return errorUpdating
|
||||
}
|
||||
}
|
||||
nodeContext.Node = &targetNode
|
||||
}
|
||||
log.Debug("done exploding nodes")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -357,6 +432,16 @@ func readAndUpdate(stdOut io.Writer, inputFile string, updateData updateDataFn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// mkdir temp dir as some docker images does not have temp dir
|
||||
_, err = os.Stat(os.TempDir())
|
||||
if os.IsNotExist(err) {
|
||||
err = os.Mkdir(os.TempDir(), 0700)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
tempFile, err := ioutil.TempFile("", "temp")
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -401,7 +486,7 @@ type updateCommandParsed struct {
|
||||
Value yaml.Node
|
||||
}
|
||||
|
||||
func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string) ([]yqlib.UpdateCommand, error) {
|
||||
func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string, allowNoValue bool) ([]yqlib.UpdateCommand, error) {
|
||||
var updateCommands []yqlib.UpdateCommand = make([]yqlib.UpdateCommand, 0)
|
||||
if writeScript != "" {
|
||||
var parsedCommands = make([]updateCommandParsed, 0)
|
||||
@@ -430,15 +515,21 @@ func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string)
|
||||
log.Debug("args %v", args[expectedArgs-2])
|
||||
updateCommands = make([]yqlib.UpdateCommand, 1)
|
||||
updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: value.Content[0], Overwrite: true}
|
||||
|
||||
} else if len(args) < expectedArgs {
|
||||
return nil, errors.New(badArgsMessage)
|
||||
} else {
|
||||
} else if len(args) == expectedArgs {
|
||||
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}
|
||||
value := valueParser.Parse(args[expectedArgs-1], customTag, customStyle, anchorName, makeAlias)
|
||||
updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: value, Overwrite: true, CommentsMergeStrategy: yqlib.IgnoreCommentsMergeStrategy}
|
||||
} else if len(args) == expectedArgs-1 && allowNoValue {
|
||||
// don't update the value
|
||||
updateCommands = make([]yqlib.UpdateCommand, 1)
|
||||
log.Debug("args %v", args)
|
||||
log.Debug("path %v", args[expectedArgs-2])
|
||||
updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: valueParser.Parse("", customTag, customStyle, anchorName, makeAlias), Overwrite: true, DontUpdateNodeValue: true}
|
||||
} else {
|
||||
return nil, errors.New(badArgsMessage)
|
||||
}
|
||||
return updateCommands, nil
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ yq v - # reads from stdin
|
||||
`,
|
||||
RunE: validateProperty,
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
SilenceErrors: false,
|
||||
}
|
||||
cmdRead.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
|
||||
return cmdRead
|
||||
|
||||
31
cmd/validate_test.go
Normal file
31
cmd/validate_test.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/mikefarah/yq/v3/test"
|
||||
)
|
||||
|
||||
func TestValidateCmd(t *testing.T) {
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "validate ../examples/sample.yaml b.c")
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
test.AssertResult(t, "", result.Output)
|
||||
}
|
||||
|
||||
func TestValidateBadDataCmd(t *testing.T) {
|
||||
content := `[!Whatever]`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("validate %s", filename))
|
||||
if result.Error == nil {
|
||||
t.Error("Expected command to fail")
|
||||
}
|
||||
expectedOutput := `yaml: line 1: did not find expected ',' or ']'`
|
||||
test.AssertResult(t, expectedOutput, result.Error.Error())
|
||||
}
|
||||
@@ -11,7 +11,7 @@ var (
|
||||
GitDescribe string
|
||||
|
||||
// Version is main version number that is being run at the moment.
|
||||
Version = "3.2.2"
|
||||
Version = "3.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
|
||||
|
||||
@@ -46,11 +46,14 @@ format is list of update commands (update or delete) like so:
|
||||
cmdWrite.PersistentFlags().StringVarP(&sourceYamlFile, "from", "f", "", "yaml file for updating yaml (as-is)")
|
||||
cmdWrite.PersistentFlags().StringVarP(&customTag, "tag", "t", "", "set yaml tag (e.g. !!int)")
|
||||
cmdWrite.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
|
||||
cmdWrite.PersistentFlags().StringVarP(&customStyle, "style", "", "", "formatting style of the value: single, double, folded, flow, literal, tagged")
|
||||
cmdWrite.PersistentFlags().StringVarP(&anchorName, "anchorName", "", "", "anchor name")
|
||||
cmdWrite.PersistentFlags().BoolVarP(&makeAlias, "makeAlias", "", false, "create an alias using the value as the anchor name")
|
||||
return cmdWrite
|
||||
}
|
||||
|
||||
func writeProperty(cmd *cobra.Command, args []string) error {
|
||||
var updateCommands, updateCommandsError = readUpdateCommands(args, 3, "Must provide <filename> <path_to_update> <value>")
|
||||
var updateCommands, updateCommandsError = readUpdateCommands(args, 3, "Must provide <filename> <path_to_update> <value>", true)
|
||||
if updateCommandsError != nil {
|
||||
return updateCommandsError
|
||||
}
|
||||
|
||||
611
cmd/write_test.go
Normal file
611
cmd/write_test.go
Normal file
@@ -0,0 +1,611 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mikefarah/yq/v3/test"
|
||||
)
|
||||
|
||||
func TestWriteCmd(t *testing.T) {
|
||||
content := `b:
|
||||
c: 3
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("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 TestWriteKeepCommentsCmd(t *testing.T) {
|
||||
content := `b:
|
||||
c: 3 # comment
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c 7", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b:
|
||||
c: 7 # comment
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestWriteWithTaggedStyleCmd(t *testing.T) {
|
||||
content := `b:
|
||||
c: dog
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --tag=!!str --style=tagged", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b:
|
||||
c: !!str cat
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestWriteWithDoubleQuotedStyleCmd(t *testing.T) {
|
||||
content := `b:
|
||||
c: dog
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=double", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b:
|
||||
c: "cat"
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestWriteUpdateStyleOnlyCmd(t *testing.T) {
|
||||
content := `b:
|
||||
c: dog
|
||||
d: things
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* --style=single", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b:
|
||||
c: 'dog'
|
||||
d: 'things'
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestWriteUpdateTagOnlyCmd(t *testing.T) {
|
||||
content := `b:
|
||||
c: true
|
||||
d: false
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* --tag=!!str", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b:
|
||||
c: "true"
|
||||
d: "false"
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestWriteWithSingleQuotedStyleCmd(t *testing.T) {
|
||||
content := `b:
|
||||
c: dog
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=single", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b:
|
||||
c: 'cat'
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestWriteWithLiteralStyleCmd(t *testing.T) {
|
||||
content := `b:
|
||||
c: dog
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=literal", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b:
|
||||
c: |-
|
||||
cat
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestWriteWithFoldedStyleCmd(t *testing.T) {
|
||||
content := `b:
|
||||
c: dog
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c cat --style=folded", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b:
|
||||
c: >-
|
||||
cat
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestWriteEmptyMultiDocCmd(t *testing.T) {
|
||||
content := `# this is empty
|
||||
---
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("write %s c 7", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `c: 7
|
||||
|
||||
# this is empty
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestWriteSurroundingEmptyMultiDocCmd(t *testing.T) {
|
||||
content := `---
|
||||
# empty
|
||||
---
|
||||
cat: frog
|
||||
---
|
||||
|
||||
# empty
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("write %s -d1 c 7", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `
|
||||
|
||||
# empty
|
||||
---
|
||||
cat: frog
|
||||
c: 7
|
||||
---
|
||||
|
||||
|
||||
# empty
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestWriteFromFileCmd(t *testing.T) {
|
||||
content := `b:
|
||||
c: 3
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
source := `kittens: are cute # sure are!`
|
||||
fromFilename := test.WriteTempYamlFile(source)
|
||||
defer test.RemoveTempYamlFile(fromFilename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c -f %s", filename, fromFilename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b:
|
||||
c:
|
||||
kittens: are cute # sure are!
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestWriteEmptyCmd(t *testing.T) {
|
||||
content := ``
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("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 TestWriteAutoCreateCmd(t *testing.T) {
|
||||
content := `applications:
|
||||
- name: app
|
||||
env:`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("write %s applications[0].env.hello world", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `applications:
|
||||
- name: app
|
||||
env:
|
||||
hello: world
|
||||
`
|
||||
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
|
||||
---
|
||||
apples: great
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("write %s -d 1 apples ok", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b:
|
||||
c: 3
|
||||
---
|
||||
apples: ok
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
func TestWriteInvalidDocumentIndexCmd(t *testing.T) {
|
||||
content := `b:
|
||||
c: 3
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("write %s -df apples ok", filename))
|
||||
if result.Error == nil {
|
||||
t.Error("Expected command to fail due to invalid path")
|
||||
}
|
||||
expectedOutput := `Document index f is not a integer or *: strconv.ParseInt: parsing "f": invalid syntax`
|
||||
test.AssertResult(t, expectedOutput, result.Error.Error())
|
||||
}
|
||||
|
||||
func TestWriteBadDocumentIndexCmd(t *testing.T) {
|
||||
content := `b:
|
||||
c: 3
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("write %s -d 1 apples ok", filename))
|
||||
if result.Error == nil {
|
||||
t.Error("Expected command to fail due to invalid path")
|
||||
}
|
||||
expectedOutput := `asked to process document index 1 but there are only 1 document(s)`
|
||||
test.AssertResult(t, expectedOutput, result.Error.Error())
|
||||
}
|
||||
func TestWriteMultiAllCmd(t *testing.T) {
|
||||
content := `b:
|
||||
c: 3
|
||||
---
|
||||
apples: great
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("write %s -d * apples ok", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b:
|
||||
c: 3
|
||||
apples: ok
|
||||
---
|
||||
apples: ok`
|
||||
test.AssertResult(t, expectedOutput, strings.Trim(result.Output, "\n "))
|
||||
}
|
||||
|
||||
func TestWriteCmd_EmptyArray(t *testing.T) {
|
||||
content := `b: 3`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("write %s a []", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b: 3
|
||||
a: []
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestWriteCmd_Error(t *testing.T) {
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "write")
|
||||
if result.Error == nil {
|
||||
t.Error("Expected command to fail due to missing arg")
|
||||
}
|
||||
expectedOutput := `Must provide <filename> <path_to_update> <value>`
|
||||
test.AssertResult(t, expectedOutput, result.Error.Error())
|
||||
}
|
||||
|
||||
func TestWriteCmd_ErrorUnreadableFile(t *testing.T) {
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, "write fake-unknown a.b 3")
|
||||
if result.Error == nil {
|
||||
t.Error("Expected command to fail 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, result.Error.Error())
|
||||
}
|
||||
|
||||
func TestWriteCmd_Inplace(t *testing.T) {
|
||||
content := `b:
|
||||
c: 3
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("write -i %s b.c 7", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
gotOutput := test.ReadTempYamlFile(filename)
|
||||
expectedOutput := `b:
|
||||
c: 7`
|
||||
test.AssertResult(t, expectedOutput, strings.Trim(gotOutput, "\n "))
|
||||
}
|
||||
|
||||
func TestWriteCmd_InplaceError(t *testing.T) {
|
||||
content := `b: cat
|
||||
c: 3
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("write -i %s b.c 7", filename))
|
||||
if result.Error == nil {
|
||||
t.Error("Expected Error to occur!")
|
||||
}
|
||||
gotOutput := test.ReadTempYamlFile(filename)
|
||||
test.AssertResult(t, content, gotOutput)
|
||||
}
|
||||
|
||||
func TestWriteCmd_Append(t *testing.T) {
|
||||
content := `b:
|
||||
- foo
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] 7", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b:
|
||||
- foo
|
||||
- 7
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestWriteCmd_AppendInline(t *testing.T) {
|
||||
content := `b: [foo]`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] 7", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b: [foo, 7]
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestWriteCmd_AppendInlinePretty(t *testing.T) {
|
||||
content := `b: [foo]`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("write %s -P b[+] 7", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b:
|
||||
- foo
|
||||
- 7
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestWriteCmd_AppendEmptyArray(t *testing.T) {
|
||||
content := `a: 2
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("write %s b[+] v", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `a: 2
|
||||
b:
|
||||
- v
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestWriteCmd_SplatArray(t *testing.T) {
|
||||
content := `b:
|
||||
- c: thing
|
||||
- c: another thing
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("write %s b[*].c new", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b:
|
||||
- c: new
|
||||
- c: new
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestWriteCmd_SplatMap(t *testing.T) {
|
||||
content := `b:
|
||||
c: thing
|
||||
d: another thing
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("write %s b.* new", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b:
|
||||
c: new
|
||||
d: new
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
|
||||
func TestWriteCmd_SplatMapEmpty(t *testing.T) {
|
||||
content := `b:
|
||||
c: thing
|
||||
d: another thing
|
||||
`
|
||||
filename := test.WriteTempYamlFile(content)
|
||||
defer test.RemoveTempYamlFile(filename)
|
||||
|
||||
cmd := getRootCommand()
|
||||
result := test.RunCmd(cmd, fmt.Sprintf("write %s b.c.* new", filename))
|
||||
if result.Error != nil {
|
||||
t.Error(result.Error)
|
||||
}
|
||||
expectedOutput := `b:
|
||||
c: {}
|
||||
d: another thing
|
||||
`
|
||||
test.AssertResult(t, expectedOutput, result.Output)
|
||||
}
|
||||
17
debian/changelog
vendored
17
debian/changelog
vendored
@@ -1,3 +1,20 @@
|
||||
yq (3.3.2) focal; urgency=medium
|
||||
|
||||
* Bug fix: existStatus bug (#459)
|
||||
* Automatically makes a os temp directory if it does not exist (#461)
|
||||
|
||||
-- Roberto Mier Escandon <rmescandon@gmail.com> Fri, 07 Aug 2020 18:53:01 +0200
|
||||
|
||||
yq (3.3-0) focal; urgency=medium
|
||||
|
||||
* You can control string styles (quotes) using the new --style flag
|
||||
* String values now always have quotes when outputting to json
|
||||
* Negative array indices now traverse the array backwards
|
||||
* Added a --stripComments flag to print yaml without any comments
|
||||
* Bumped go to version 1.14
|
||||
|
||||
-- Roberto Mier Escandon <rmescandon@gmail.com> Thu, 30 Apr 2020 20:45:44 +0200
|
||||
|
||||
yq (3.1-2) eoan; urgency=medium
|
||||
|
||||
* Bug fix: yq 3 was removing empty inline-style objects and arrays (#355)
|
||||
|
||||
1
debian/files
vendored
1
debian/files
vendored
@@ -1 +0,0 @@
|
||||
yq_3.1-2_source.buildinfo devel optional
|
||||
4
examples/data1-no-comments.yaml
Normal file
4
examples/data1-no-comments.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
a: simple
|
||||
b: [1, 2]
|
||||
c:
|
||||
test: 1
|
||||
@@ -1 +1 @@
|
||||
{"a":"Easy! as one two three","b":{"c":2,"d":[3,4],"e":[{"name":"fred","value":3},{"name":"sam","value":4}]}}
|
||||
{"a":"Easy! as one two three","b":{"c":2,"d":[3,4],"e":[{"name":"fred","value":3},{"name":"sam","value":4}]},"ab":"must appear last"}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
b:
|
||||
c: things
|
||||
5
examples/simple-anchor-exploded.yaml
Normal file
5
examples/simple-anchor-exploded.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
foo:
|
||||
a: 1
|
||||
|
||||
foobar:
|
||||
a: 1
|
||||
12
go.mod
12
go.mod
@@ -2,16 +2,16 @@ module github.com/mikefarah/yq/v3
|
||||
|
||||
require (
|
||||
github.com/fatih/color v1.9.0
|
||||
github.com/goccy/go-yaml v1.4.3
|
||||
github.com/goccy/go-yaml v1.8.1
|
||||
github.com/kylelemons/godebug v1.1.0
|
||||
github.com/mattn/go-colorable v0.1.6 // indirect
|
||||
github.com/mattn/go-colorable v0.1.7 // indirect
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/spf13/cobra v1.0.0
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
|
||||
)
|
||||
|
||||
go 1.14
|
||||
go 1.15
|
||||
|
||||
22
go.sum
22
go.sum
@@ -29,8 +29,8 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/goccy/go-yaml v1.4.3 h1:+1jK1ost1TBEfWjciIMU8rciBq0poxurgS7XvLgQInM=
|
||||
github.com/goccy/go-yaml v1.4.3/go.mod h1:PsEEJ29nIFZL07P/c8dv4P6rQkVFFXafQee85U+ERHA=
|
||||
github.com/goccy/go-yaml v1.8.1 h1:JuZRFlqLM5cWF6A+waL8AKVuCcqvKOuhJtUQI+L3ez0=
|
||||
github.com/goccy/go-yaml v1.8.1/go.mod h1:wS4gNoLalDSJxo/SpngzPQ2BN4uuZVLCmbM4S3vd4+Y=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
@@ -64,8 +64,8 @@ github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgx
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
|
||||
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM=
|
||||
@@ -144,19 +144,20 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037 h1:YyJpGZS1sBuBCzLAR1VEpK193
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY=
|
||||
golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f h1:Fqb3ao1hUmOR3GkUOg/Y+BadLwykBIzs5q8Ez2SbHyc=
|
||||
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd h1:/e+gpKk9r3dJobndpTytxS2gOy6m5uvpg+ISQoEcusQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
@@ -175,7 +176,6 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
||||
@@ -22,12 +22,8 @@ func NewDataNavigator(NavigationStrategy NavigationStrategy) DataNavigator {
|
||||
}
|
||||
|
||||
func (n *navigator) Traverse(value *yaml.Node, path []interface{}) 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)
|
||||
}
|
||||
log.Debugf("Traversing path %v", pathStackToString(path))
|
||||
return n.doTraverse(value, "", path, emptyArray)
|
||||
}
|
||||
|
||||
@@ -38,7 +34,8 @@ func (n *navigator) doTraverse(value *yaml.Node, head interface{}, tail []interf
|
||||
var nodeContext = NewNodeContext(value, head, tail, pathStack)
|
||||
|
||||
var errorDeepSplatting error
|
||||
if head == "**" && value.Kind != yaml.ScalarNode && n.navigationStrategy.ShouldDeeplyTraverse(nodeContext) {
|
||||
// no need to deeply traverse the DocumentNode, as it's already covered by its first child.
|
||||
if head == "**" && value.Kind != yaml.DocumentNode && value.Kind != yaml.ScalarNode && n.navigationStrategy.ShouldDeeplyTraverse(nodeContext) {
|
||||
if len(pathStack) == 0 || pathStack[len(pathStack)-1] != "<<" {
|
||||
errorDeepSplatting = n.recurse(value, head, tail, pathStack)
|
||||
}
|
||||
@@ -50,7 +47,11 @@ func (n *navigator) doTraverse(value *yaml.Node, head interface{}, tail []interf
|
||||
return errorDeepSplatting
|
||||
}
|
||||
|
||||
if len(tail) > 0 && value.Kind != yaml.ScalarNode {
|
||||
if value.Kind == yaml.DocumentNode {
|
||||
log.Debugf("its a document, diving into %v", head)
|
||||
DebugNode(value)
|
||||
return n.recurse(value, head, tail, pathStack)
|
||||
} else if len(tail) > 0 && value.Kind != yaml.ScalarNode {
|
||||
log.Debugf("diving into %v", tail[0])
|
||||
DebugNode(value)
|
||||
return n.recurse(value, tail[0], tail[1:], pathStack)
|
||||
@@ -68,6 +69,17 @@ func (n *navigator) getOrReplace(original *yaml.Node, expectedKind yaml.Kind) *y
|
||||
|
||||
func (n *navigator) recurse(value *yaml.Node, head interface{}, tail []interface{}, pathStack []interface{}) error {
|
||||
log.Debug("recursing, processing %v, pathStack %v", head, pathStackToString(pathStack))
|
||||
|
||||
nodeContext := NewNodeContext(value, head, tail, pathStack)
|
||||
|
||||
if head == "**" && !n.navigationStrategy.ShouldOnlyDeeplyVisitLeaves(nodeContext) {
|
||||
nodeContext.IsMiddleNode = true
|
||||
errorVisitingDeeply := n.navigationStrategy.Visit(nodeContext)
|
||||
if errorVisitingDeeply != nil {
|
||||
return errorVisitingDeeply
|
||||
}
|
||||
}
|
||||
|
||||
switch value.Kind {
|
||||
case yaml.MappingNode:
|
||||
log.Debug("its a map with %v entries", len(value.Content)/2)
|
||||
@@ -85,20 +97,22 @@ func (n *navigator) recurse(value *yaml.Node, head interface{}, tail []interface
|
||||
if head == "+" {
|
||||
return n.appendArray(value, head, tail, pathStack)
|
||||
} else if len(value.Content) == 0 && head == "**" {
|
||||
return n.navigationStrategy.Visit(NewNodeContext(value, head, tail, pathStack))
|
||||
return n.navigationStrategy.Visit(nodeContext)
|
||||
}
|
||||
return n.splatArray(value, head, tail, pathStack)
|
||||
}
|
||||
case yaml.AliasNode:
|
||||
log.Debug("its an alias!")
|
||||
DebugNode(value.Alias)
|
||||
if n.navigationStrategy.FollowAlias(NewNodeContext(value, head, tail, pathStack)) {
|
||||
if n.navigationStrategy.FollowAlias(nodeContext) {
|
||||
log.Debug("following the alias")
|
||||
return n.recurse(value.Alias, head, tail, pathStack)
|
||||
}
|
||||
return nil
|
||||
case yaml.DocumentNode:
|
||||
return n.doTraverse(value.Content[0], head, tail, pathStack)
|
||||
default:
|
||||
return n.navigationStrategy.Visit(NewNodeContext(value, head, tail, pathStack))
|
||||
return n.navigationStrategy.Visit(nodeContext)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,15 +12,12 @@ func DeleteNavigationStrategy(pathElementToDelete interface{}) NavigationStrateg
|
||||
followAlias: func(nodeContext NodeContext) bool {
|
||||
return false
|
||||
},
|
||||
autoCreateMap: func(nodeContext NodeContext) bool {
|
||||
shouldOnlyDeeplyVisitLeaves: func(nodeContext NodeContext) bool {
|
||||
return false
|
||||
},
|
||||
shouldDeeplyTraverse: 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)
|
||||
log.Debug("need to find and delete %v in here (%v)", pathElementToDelete, pathStackToString(nodeContext.PathStack))
|
||||
DebugNode(node)
|
||||
if node.Kind == yaml.SequenceNode {
|
||||
newContent := deleteFromArray(parser, node.Content, nodeContext.PathStack, pathElementToDelete)
|
||||
|
||||
@@ -3,6 +3,7 @@ package yqlib
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
@@ -58,6 +59,21 @@ type jsonEncoder struct {
|
||||
encoder *json.Encoder
|
||||
}
|
||||
|
||||
func mapKeysToStrings(node *yaml.Node) {
|
||||
|
||||
if node.Kind == yaml.MappingNode {
|
||||
for index, child := range node.Content {
|
||||
if index%2 == 0 { // its a map key
|
||||
child.Tag = "!!str"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, child := range node.Content {
|
||||
mapKeysToStrings(child)
|
||||
}
|
||||
}
|
||||
|
||||
func NewJsonEncoder(destination io.Writer, prettyPrint bool, indent int) Encoder {
|
||||
var encoder = json.NewEncoder(destination)
|
||||
var indentString = ""
|
||||
@@ -72,10 +88,162 @@ func NewJsonEncoder(destination io.Writer, prettyPrint bool, indent int) Encoder
|
||||
}
|
||||
|
||||
func (je *jsonEncoder) Encode(node *yaml.Node) error {
|
||||
var dataBucket interface{}
|
||||
var dataBucket orderedMap
|
||||
// firstly, convert all map keys to strings
|
||||
mapKeysToStrings(node)
|
||||
errorDecoding := node.Decode(&dataBucket)
|
||||
if errorDecoding != nil {
|
||||
return errorDecoding
|
||||
}
|
||||
return je.encoder.Encode(dataBucket)
|
||||
}
|
||||
|
||||
// orderedMap allows to marshal and unmarshal JSON and YAML values keeping the
|
||||
// order of keys and values in a map or an object.
|
||||
type orderedMap struct {
|
||||
// if this is an object, kv != nil. If this is not an object, kv == nil.
|
||||
kv []orderedMapKV
|
||||
altVal interface{}
|
||||
}
|
||||
|
||||
type orderedMapKV struct {
|
||||
K string
|
||||
V orderedMap
|
||||
}
|
||||
|
||||
func (o *orderedMap) UnmarshalJSON(data []byte) error {
|
||||
switch data[0] {
|
||||
case '{':
|
||||
// initialise so that even if the object is empty it is not nil
|
||||
o.kv = []orderedMapKV{}
|
||||
|
||||
// create decoder
|
||||
dec := json.NewDecoder(bytes.NewReader(data))
|
||||
_, err := dec.Token() // open object
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// cycle through k/v
|
||||
var tok json.Token
|
||||
for tok, err = dec.Token(); err != io.EOF; tok, err = dec.Token() {
|
||||
// we can expect two types: string or Delim. Delim automatically means
|
||||
// that it is the closing bracket of the object, whereas string means
|
||||
// that there is another key.
|
||||
if _, ok := tok.(json.Delim); ok {
|
||||
break
|
||||
}
|
||||
kv := orderedMapKV{
|
||||
K: tok.(string),
|
||||
}
|
||||
if err := dec.Decode(&kv.V); err != nil {
|
||||
return err
|
||||
}
|
||||
o.kv = append(o.kv, kv)
|
||||
}
|
||||
// unexpected error
|
||||
if err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case '[':
|
||||
var arr []orderedMap
|
||||
return json.Unmarshal(data, &arr)
|
||||
}
|
||||
|
||||
return json.Unmarshal(data, &o.altVal)
|
||||
}
|
||||
|
||||
func (o orderedMap) MarshalJSON() ([]byte, error) {
|
||||
if o.kv == nil {
|
||||
return json.Marshal(o.altVal)
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
enc := json.NewEncoder(buf)
|
||||
buf.WriteByte('{')
|
||||
for idx, el := range o.kv {
|
||||
if err := enc.Encode(el.K); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
buf.WriteByte(':')
|
||||
if err := enc.Encode(el.V); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if idx != len(o.kv)-1 {
|
||||
buf.WriteByte(',')
|
||||
}
|
||||
}
|
||||
buf.WriteByte('}')
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func (o *orderedMap) UnmarshalYAML(node *yaml.Node) error {
|
||||
switch node.Kind {
|
||||
case yaml.DocumentNode:
|
||||
if len(node.Content) == 0 {
|
||||
return nil
|
||||
}
|
||||
return o.UnmarshalYAML(node.Content[0])
|
||||
case yaml.AliasNode:
|
||||
return o.UnmarshalYAML(node.Alias)
|
||||
case yaml.ScalarNode:
|
||||
return node.Decode(&o.altVal)
|
||||
case yaml.MappingNode:
|
||||
// set kv to non-nil
|
||||
o.kv = []orderedMapKV{}
|
||||
for i := 0; i < len(node.Content); i += 2 {
|
||||
var key string
|
||||
var val orderedMap
|
||||
if err := node.Content[i].Decode(&key); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := node.Content[i+1].Decode(&val); err != nil {
|
||||
return err
|
||||
}
|
||||
o.kv = append(o.kv, orderedMapKV{
|
||||
K: key,
|
||||
V: val,
|
||||
})
|
||||
}
|
||||
return nil
|
||||
case yaml.SequenceNode:
|
||||
var res []orderedMap
|
||||
if err := node.Decode(&res); err != nil {
|
||||
return err
|
||||
}
|
||||
o.altVal = res
|
||||
o.kv = nil
|
||||
return nil
|
||||
case 0:
|
||||
// null
|
||||
o.kv = nil
|
||||
o.altVal = nil
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("orderedMap: invalid yaml node")
|
||||
}
|
||||
}
|
||||
|
||||
func (o *orderedMap) MarshalYAML() (interface{}, error) {
|
||||
// fast path: kv is nil, use altVal
|
||||
if o.kv == nil {
|
||||
return o.altVal, nil
|
||||
}
|
||||
content := make([]*yaml.Node, 0, len(o.kv)*2)
|
||||
for _, val := range o.kv {
|
||||
n := new(yaml.Node)
|
||||
if err := n.Encode(val.V); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
content = append(content, &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: "!!str",
|
||||
Value: val.K,
|
||||
}, n)
|
||||
}
|
||||
return &yaml.Node{
|
||||
Kind: yaml.MappingNode,
|
||||
Tag: "!!map",
|
||||
Content: content,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -4,12 +4,6 @@ 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
|
||||
},
|
||||
|
||||
@@ -13,10 +13,13 @@ import (
|
||||
var log = logging.MustGetLogger("yq")
|
||||
|
||||
type UpdateCommand struct {
|
||||
Command string
|
||||
Path string
|
||||
Value *yaml.Node
|
||||
Overwrite bool
|
||||
Command string
|
||||
Path string
|
||||
Value *yaml.Node
|
||||
Overwrite bool
|
||||
DontUpdateNodeValue bool
|
||||
DontUpdateNodeContent bool
|
||||
CommentsMergeStrategy CommentsMergeStrategy
|
||||
}
|
||||
|
||||
func KindString(kind yaml.Kind) string {
|
||||
@@ -47,21 +50,24 @@ func DebugNode(value *yaml.Node) {
|
||||
log.Error("Error debugging node, %v", errorEncoding.Error())
|
||||
}
|
||||
encoder.Close()
|
||||
log.Debug("Tag: %v, Kind: %v", value.Tag, KindString(value.Kind))
|
||||
log.Debug("%v", buf.String())
|
||||
log.Debug("Tag: %v, Kind: %v, Anchor: %v", value.Tag, KindString(value.Kind), value.Anchor)
|
||||
log.Debug("Head Comment: %v", value.HeadComment)
|
||||
log.Debug("Line Comment: %v", value.LineComment)
|
||||
log.Debug("FootComment Comment: %v", value.FootComment)
|
||||
log.Debug("\n%v", buf.String())
|
||||
}
|
||||
}
|
||||
|
||||
func pathStackToString(pathStack []interface{}) string {
|
||||
return mergePathStackToString(pathStack, false)
|
||||
return mergePathStackToString(pathStack, UpdateArrayMergeStrategy)
|
||||
}
|
||||
|
||||
func mergePathStackToString(pathStack []interface{}, appendArrays bool) string {
|
||||
func mergePathStackToString(pathStack []interface{}, arrayMergeStrategy ArrayMergeStrategy) string {
|
||||
var sb strings.Builder
|
||||
for index, path := range pathStack {
|
||||
switch path.(type) {
|
||||
case int, int64:
|
||||
if appendArrays {
|
||||
if arrayMergeStrategy == AppendArrayMergeStrategy {
|
||||
sb.WriteString("[+]")
|
||||
} else {
|
||||
sb.WriteString(fmt.Sprintf("[%v]", path))
|
||||
@@ -92,9 +98,7 @@ func mergePathStackToString(pathStack []interface{}, appendArrays bool) string {
|
||||
sb.WriteString(".")
|
||||
}
|
||||
}
|
||||
var pathString = sb.String()
|
||||
log.Debug("got a path string: %v", pathString)
|
||||
return pathString
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func guessKind(head interface{}, tail []interface{}, guess yaml.Kind) yaml.Kind {
|
||||
@@ -132,12 +136,13 @@ func guessKind(head interface{}, tail []interface{}, guess yaml.Kind) yaml.Kind
|
||||
}
|
||||
|
||||
type YqLib interface {
|
||||
Get(rootNode *yaml.Node, path string, deeplyTraverseArrays bool) ([]*NodeContext, error)
|
||||
Get(rootNode *yaml.Node, path string) ([]*NodeContext, error)
|
||||
GetForMerge(rootNode *yaml.Node, path string, arrayMergeStrategy ArrayMergeStrategy) ([]*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
|
||||
MergePathStackToString(pathStack []interface{}, arrayMergeStrategy ArrayMergeStrategy) string
|
||||
}
|
||||
|
||||
type lib struct {
|
||||
@@ -150,21 +155,28 @@ func NewYqLib() YqLib {
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lib) Get(rootNode *yaml.Node, path string, deeplyTraverseArrays bool) ([]*NodeContext, error) {
|
||||
func (l *lib) Get(rootNode *yaml.Node, path string) ([]*NodeContext, error) {
|
||||
var paths = l.parser.ParsePath(path)
|
||||
navigationStrategy := ReadNavigationStrategy(deeplyTraverseArrays)
|
||||
navigationStrategy := ReadNavigationStrategy()
|
||||
navigator := NewDataNavigator(navigationStrategy)
|
||||
error := navigator.Traverse(rootNode, paths)
|
||||
return navigationStrategy.GetVisitedNodes(), error
|
||||
}
|
||||
|
||||
func (l *lib) GetForMerge(rootNode *yaml.Node, path string, arrayMergeStrategy ArrayMergeStrategy) ([]*NodeContext, error) {
|
||||
var paths = l.parser.ParsePath(path)
|
||||
navigationStrategy := ReadForMergeNavigationStrategy(arrayMergeStrategy)
|
||||
navigator := NewDataNavigator(navigationStrategy)
|
||||
error := navigator.Traverse(rootNode, paths)
|
||||
return navigationStrategy.GetVisitedNodes(), error
|
||||
}
|
||||
|
||||
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) MergePathStackToString(pathStack []interface{}, arrayMergeStrategy ArrayMergeStrategy) string {
|
||||
return mergePathStackToString(pathStack, arrayMergeStrategy)
|
||||
}
|
||||
|
||||
func (l *lib) New(path string) yaml.Node {
|
||||
@@ -180,6 +192,10 @@ func (l *lib) Update(rootNode *yaml.Node, updateCommand UpdateCommand, autoCreat
|
||||
var paths = l.parser.ParsePath(updateCommand.Path)
|
||||
navigator := NewDataNavigator(UpdateNavigationStrategy(updateCommand, autoCreate))
|
||||
return navigator.Traverse(rootNode, paths)
|
||||
case "merge":
|
||||
var paths = l.parser.ParsePath(updateCommand.Path)
|
||||
navigator := NewDataNavigator(MergeNavigationStrategy(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]
|
||||
|
||||
@@ -30,7 +30,7 @@ func TestLib(t *testing.T) {
|
||||
array[0] = "a"
|
||||
array[1] = 0
|
||||
array[2] = "b"
|
||||
got := subject.MergePathStackToString(array, true)
|
||||
got := subject.MergePathStackToString(array, AppendArrayMergeStrategy)
|
||||
test.AssertResult(t, `a.[+].b`, got)
|
||||
})
|
||||
|
||||
|
||||
103
pkg/yqlib/merge_navigation_strategy.go
Normal file
103
pkg/yqlib/merge_navigation_strategy.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package yqlib
|
||||
|
||||
import "gopkg.in/yaml.v3"
|
||||
|
||||
type ArrayMergeStrategy uint32
|
||||
|
||||
const (
|
||||
UpdateArrayMergeStrategy ArrayMergeStrategy = 1 << iota
|
||||
OverwriteArrayMergeStrategy
|
||||
AppendArrayMergeStrategy
|
||||
)
|
||||
|
||||
type CommentsMergeStrategy uint32
|
||||
|
||||
const (
|
||||
SetWhenBlankCommentsMergeStrategy CommentsMergeStrategy = 1 << iota
|
||||
IgnoreCommentsMergeStrategy
|
||||
OverwriteCommentsMergeStrategy
|
||||
AppendCommentsMergeStrategy
|
||||
)
|
||||
|
||||
func MergeNavigationStrategy(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 node.Kind == yaml.DocumentNode && changesToApply.Kind != yaml.DocumentNode {
|
||||
// when the path is empty, it matches both the top level pseudo document node
|
||||
// and the actual top level node (e.g. map/sequence/whatever)
|
||||
// so when we are updating with no path, make sure we update the right node.
|
||||
node = node.Content[0]
|
||||
}
|
||||
|
||||
log.Debug("going to update")
|
||||
DebugNode(node)
|
||||
log.Debug("with")
|
||||
DebugNode(changesToApply)
|
||||
|
||||
if updateCommand.Overwrite || node.Value == "" {
|
||||
node.Value = changesToApply.Value
|
||||
node.Tag = changesToApply.Tag
|
||||
node.Kind = changesToApply.Kind
|
||||
node.Style = changesToApply.Style
|
||||
node.Anchor = changesToApply.Anchor
|
||||
node.Alias = changesToApply.Alias
|
||||
|
||||
if !updateCommand.DontUpdateNodeContent {
|
||||
node.Content = changesToApply.Content
|
||||
}
|
||||
} else {
|
||||
log.Debug("skipping update as node already has value %v and overwriteFlag is ", node.Value, updateCommand.Overwrite)
|
||||
}
|
||||
|
||||
switch updateCommand.CommentsMergeStrategy {
|
||||
case OverwriteCommentsMergeStrategy:
|
||||
node.HeadComment = changesToApply.HeadComment
|
||||
node.LineComment = changesToApply.LineComment
|
||||
node.FootComment = changesToApply.FootComment
|
||||
case SetWhenBlankCommentsMergeStrategy:
|
||||
if node.HeadComment == "" {
|
||||
node.HeadComment = changesToApply.HeadComment
|
||||
}
|
||||
if node.LineComment == "" {
|
||||
node.LineComment = changesToApply.LineComment
|
||||
}
|
||||
if node.FootComment == "" {
|
||||
node.FootComment = changesToApply.FootComment
|
||||
}
|
||||
case AppendCommentsMergeStrategy:
|
||||
if node.HeadComment == "" {
|
||||
node.HeadComment = changesToApply.HeadComment
|
||||
} else {
|
||||
node.HeadComment = node.HeadComment + "\n" + changesToApply.HeadComment
|
||||
}
|
||||
if node.LineComment == "" {
|
||||
node.LineComment = changesToApply.LineComment
|
||||
} else {
|
||||
node.LineComment = node.LineComment + " " + changesToApply.LineComment
|
||||
}
|
||||
if node.FootComment == "" {
|
||||
node.FootComment = changesToApply.FootComment
|
||||
} else {
|
||||
node.FootComment = node.FootComment + "\n" + changesToApply.FootComment
|
||||
}
|
||||
default:
|
||||
}
|
||||
|
||||
log.Debug("result")
|
||||
DebugNode(node)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,9 @@ type NodeContext struct {
|
||||
Head interface{}
|
||||
Tail []interface{}
|
||||
PathStack []interface{}
|
||||
// middle nodes are nodes that match along the original path, but not a
|
||||
// target match of the path. This is only relevant when ShouldOnlyDeeplyVisitLeaves is false.
|
||||
IsMiddleNode bool
|
||||
}
|
||||
|
||||
func NewNodeContext(node *yaml.Node, head interface{}, tail []interface{}, pathStack []interface{}) NodeContext {
|
||||
@@ -35,19 +38,22 @@ type NavigationStrategy interface {
|
||||
// we use it to match against the pathExpression in head.
|
||||
ShouldTraverse(nodeContext NodeContext, nodeKey string) bool
|
||||
ShouldDeeplyTraverse(nodeContext NodeContext) bool
|
||||
// when deeply traversing, should we visit all matching nodes, or just leaves?
|
||||
ShouldOnlyDeeplyVisitLeaves(NodeContext) 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
|
||||
shouldDeeplyTraverse func(nodeContext NodeContext) bool
|
||||
visitedNodes []*NodeContext
|
||||
pathParser PathParser
|
||||
followAlias func(nodeContext NodeContext) bool
|
||||
autoCreateMap func(nodeContext NodeContext) bool
|
||||
visit func(nodeContext NodeContext) error
|
||||
shouldVisitExtraFn func(nodeContext NodeContext) bool
|
||||
shouldDeeplyTraverse func(nodeContext NodeContext) bool
|
||||
shouldOnlyDeeplyVisitLeaves func(nodeContext NodeContext) bool
|
||||
visitedNodes []*NodeContext
|
||||
pathParser PathParser
|
||||
}
|
||||
|
||||
func (ns *NavigationStrategyImpl) GetPathParser() PathParser {
|
||||
@@ -59,15 +65,32 @@ func (ns *NavigationStrategyImpl) GetVisitedNodes() []*NodeContext {
|
||||
}
|
||||
|
||||
func (ns *NavigationStrategyImpl) FollowAlias(nodeContext NodeContext) bool {
|
||||
return ns.followAlias(nodeContext)
|
||||
if ns.followAlias != nil {
|
||||
return ns.followAlias(nodeContext)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (ns *NavigationStrategyImpl) AutoCreateMap(nodeContext NodeContext) bool {
|
||||
return ns.autoCreateMap(nodeContext)
|
||||
if ns.autoCreateMap != nil {
|
||||
return ns.autoCreateMap(nodeContext)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (ns *NavigationStrategyImpl) ShouldDeeplyTraverse(nodeContext NodeContext) bool {
|
||||
return ns.shouldDeeplyTraverse(nodeContext)
|
||||
if ns.shouldDeeplyTraverse != nil {
|
||||
return ns.shouldDeeplyTraverse(nodeContext)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (ns *NavigationStrategyImpl) ShouldOnlyDeeplyVisitLeaves(nodeContext NodeContext) bool {
|
||||
if ns.shouldOnlyDeeplyVisitLeaves != nil {
|
||||
return ns.shouldOnlyDeeplyVisitLeaves(nodeContext)
|
||||
}
|
||||
return true
|
||||
|
||||
}
|
||||
|
||||
func (ns *NavigationStrategyImpl) ShouldTraverse(nodeContext NodeContext, nodeKey string) bool {
|
||||
|
||||
37
pkg/yqlib/read_for_merge_navigation_strategy.go
Normal file
37
pkg/yqlib/read_for_merge_navigation_strategy.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package yqlib
|
||||
|
||||
import "gopkg.in/yaml.v3"
|
||||
|
||||
func ReadForMergeNavigationStrategy(arrayMergeStrategy ArrayMergeStrategy) NavigationStrategy {
|
||||
return &NavigationStrategyImpl{
|
||||
visitedNodes: []*NodeContext{},
|
||||
pathParser: NewPathParser(),
|
||||
followAlias: func(nodeContext NodeContext) bool {
|
||||
return false
|
||||
},
|
||||
shouldOnlyDeeplyVisitLeaves: func(nodeContext NodeContext) bool {
|
||||
return false
|
||||
},
|
||||
visit: func(nodeContext NodeContext) error {
|
||||
return nil
|
||||
},
|
||||
shouldDeeplyTraverse: func(nodeContext NodeContext) bool {
|
||||
if nodeContext.Node.Kind == yaml.SequenceNode && arrayMergeStrategy == OverwriteArrayMergeStrategy {
|
||||
nodeContext.IsMiddleNode = false
|
||||
return false
|
||||
}
|
||||
|
||||
var isInArray = false
|
||||
if len(nodeContext.PathStack) > 0 {
|
||||
var lastElement = nodeContext.PathStack[len(nodeContext.PathStack)-1]
|
||||
switch lastElement.(type) {
|
||||
case int:
|
||||
isInArray = true
|
||||
default:
|
||||
isInArray = false
|
||||
}
|
||||
}
|
||||
return arrayMergeStrategy == UpdateArrayMergeStrategy || !isInArray
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,11 @@
|
||||
package yqlib
|
||||
|
||||
func ReadNavigationStrategy(deeplyTraverseArrays bool) NavigationStrategy {
|
||||
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
|
||||
},
|
||||
shouldDeeplyTraverse: func(nodeContext NodeContext) bool {
|
||||
var isInArray = false
|
||||
if len(nodeContext.PathStack) > 0 {
|
||||
var lastElement = nodeContext.PathStack[len(nodeContext.PathStack)-1]
|
||||
switch lastElement.(type) {
|
||||
case int:
|
||||
isInArray = true
|
||||
default:
|
||||
isInArray = false
|
||||
}
|
||||
}
|
||||
return deeplyTraverseArrays || !isInArray
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,6 @@ func UpdateNavigationStrategy(updateCommand UpdateCommand, autoCreate bool) Navi
|
||||
autoCreateMap: func(nodeContext NodeContext) bool {
|
||||
return autoCreate
|
||||
},
|
||||
shouldDeeplyTraverse: func(nodeContext NodeContext) bool {
|
||||
return true
|
||||
},
|
||||
visit: func(nodeContext NodeContext) error {
|
||||
node := nodeContext.Node
|
||||
changesToApply := updateCommand.Value
|
||||
@@ -21,14 +18,22 @@ func UpdateNavigationStrategy(updateCommand UpdateCommand, autoCreate bool) Navi
|
||||
DebugNode(node)
|
||||
log.Debug("with")
|
||||
DebugNode(changesToApply)
|
||||
node.Value = changesToApply.Value
|
||||
if !updateCommand.DontUpdateNodeValue {
|
||||
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
|
||||
if !updateCommand.DontUpdateNodeContent {
|
||||
node.Content = changesToApply.Content
|
||||
}
|
||||
node.Anchor = changesToApply.Anchor
|
||||
node.Alias = changesToApply.Alias
|
||||
if updateCommand.CommentsMergeStrategy != IgnoreCommentsMergeStrategy {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
)
|
||||
|
||||
type ValueParser interface {
|
||||
Parse(argument string, customTag string) *yaml.Node
|
||||
Parse(argument string, customTag string, customStyle string, anchorName string, createAlias bool) *yaml.Node
|
||||
}
|
||||
|
||||
type valueParser struct {
|
||||
@@ -15,9 +15,32 @@ func NewValueParser() ValueParser {
|
||||
return &valueParser{}
|
||||
}
|
||||
|
||||
func (v *valueParser) Parse(argument string, customTag string) *yaml.Node {
|
||||
if argument == "[]" {
|
||||
return &yaml.Node{Tag: "!!seq", Kind: yaml.SequenceNode}
|
||||
func (v *valueParser) Parse(argument string, customTag string, customStyle string, anchorName string, createAlias bool) *yaml.Node {
|
||||
var style yaml.Style
|
||||
if customStyle == "tagged" {
|
||||
style = yaml.TaggedStyle
|
||||
} else if customStyle == "double" {
|
||||
style = yaml.DoubleQuotedStyle
|
||||
} else if customStyle == "single" {
|
||||
style = yaml.SingleQuotedStyle
|
||||
} else if customStyle == "literal" {
|
||||
style = yaml.LiteralStyle
|
||||
} else if customStyle == "folded" {
|
||||
style = yaml.FoldedStyle
|
||||
} else if customStyle == "flow" {
|
||||
style = yaml.FlowStyle
|
||||
} else if customStyle != "" {
|
||||
log.Error("Unknown style %v, ignoring", customStyle)
|
||||
}
|
||||
return &yaml.Node{Value: argument, Tag: customTag, Kind: yaml.ScalarNode}
|
||||
if argument == "[]" {
|
||||
return &yaml.Node{Tag: "!!seq", Kind: yaml.SequenceNode, Style: style}
|
||||
}
|
||||
|
||||
kind := yaml.ScalarNode
|
||||
|
||||
if createAlias {
|
||||
kind = yaml.AliasNode
|
||||
}
|
||||
|
||||
return &yaml.Node{Value: argument, Tag: customTag, Kind: kind, Style: style, Anchor: anchorName}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,26 @@ import (
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var parseStyleTests = []struct {
|
||||
customStyle string
|
||||
expectedStyle yaml.Style
|
||||
}{
|
||||
{"", 0},
|
||||
{"tagged", yaml.TaggedStyle},
|
||||
{"double", yaml.DoubleQuotedStyle},
|
||||
{"single", yaml.SingleQuotedStyle},
|
||||
{"folded", yaml.FoldedStyle},
|
||||
{"flow", yaml.FlowStyle},
|
||||
{"literal", yaml.LiteralStyle},
|
||||
}
|
||||
|
||||
func TestValueParserStyleTag(t *testing.T) {
|
||||
for _, tt := range parseStyleTests {
|
||||
actual := NewValueParser().Parse("cat", "", tt.customStyle, "", false)
|
||||
test.AssertResultWithContext(t, tt.expectedStyle, actual.Style, tt.customStyle)
|
||||
}
|
||||
}
|
||||
|
||||
var parseValueTests = []struct {
|
||||
argument string
|
||||
customTag string
|
||||
@@ -20,7 +40,7 @@ var parseValueTests = []struct {
|
||||
|
||||
func TestValueParserParse(t *testing.T) {
|
||||
for _, tt := range parseValueTests {
|
||||
actual := NewValueParser().Parse(tt.argument, tt.customTag)
|
||||
actual := NewValueParser().Parse(tt.argument, tt.customTag, "", "", false)
|
||||
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)
|
||||
@@ -28,7 +48,18 @@ func TestValueParserParse(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestValueParserParseEmptyArray(t *testing.T) {
|
||||
actual := NewValueParser().Parse("[]", "")
|
||||
actual := NewValueParser().Parse("[]", "", "", "", false)
|
||||
test.AssertResult(t, "!!seq", actual.Tag)
|
||||
test.AssertResult(t, yaml.SequenceNode, actual.Kind)
|
||||
}
|
||||
|
||||
func TestValueParserParseAlias(t *testing.T) {
|
||||
actual := NewValueParser().Parse("bob", "", "", "", true)
|
||||
test.AssertResult(t, "bob", actual.Value)
|
||||
test.AssertResult(t, yaml.AliasNode, actual.Kind)
|
||||
}
|
||||
|
||||
func TestValueParserAnchorname(t *testing.T) {
|
||||
actual := NewValueParser().Parse("caterpillar", "", "", "foo", false)
|
||||
test.AssertResult(t, "foo", actual.Anchor)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
- tag git with same version number
|
||||
- make sure local build passes
|
||||
- push tag to git
|
||||
- 3.4.0, v3
|
||||
- git push --tags
|
||||
- make local xcompile (builds binaries for all platforms)
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
brew install mkdocs libyaml
|
||||
pip3 install markdown-include
|
||||
pip3 install mkdocs-material
|
||||
|
||||
@@ -32,5 +32,5 @@ upload() {
|
||||
done < <(find ./build -mindepth 1 -maxdepth 1 -print0)
|
||||
}
|
||||
|
||||
release
|
||||
# release
|
||||
upload
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
# 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}}"
|
||||
|
||||
CGO_ENABLED=0 gox -ldflags "${LDFLAGS}" -output="build/yq_{{.OS}}_{{.Arch}}"
|
||||
# include non-default linux builds too
|
||||
gox -ldflags "${LDFLAGS}" -os=linux -output="build/yq_{{.OS}}_{{.Arch}}"
|
||||
CGO_ENABLED=0 gox -ldflags "${LDFLAGS}" -os=linux -output="build/yq_{{.OS}}_{{.Arch}}"
|
||||
|
||||
cd build
|
||||
rhash -r -a . -P -o checksums
|
||||
|
||||
rhash --list-hashes > checksums_hashes_order
|
||||
@@ -1,5 +1,5 @@
|
||||
name: yq
|
||||
version: '3.2.2'
|
||||
version: '3.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.
|
||||
@@ -16,7 +16,7 @@ apps:
|
||||
parts:
|
||||
yq:
|
||||
plugin: go
|
||||
go-channel: 1.14/stable
|
||||
go-channel: 1.15/stable
|
||||
source: .
|
||||
source-type: git
|
||||
go-importpath: github.com/mikefarah/yq
|
||||
|
||||
Reference in New Issue
Block a user