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

Compare commits

...

32 Commits
3.3.4 ... 3.4.1

Author SHA1 Message Date
Mike Farah
6afc2e9189 3.4.1 2020-10-19 08:40:59 +11:00
Morgan Bazalgette
996ee0b433 add test for key order 2020-10-09 08:38:42 +11:00
Morgan Bazalgette
bb9cb0c60e fix tests 2020-10-09 08:38:42 +11:00
Morgan Bazalgette
a125495eec keep order of keys when json marshalling 2020-10-09 08:38:42 +11:00
Peter Benjamin
7fa2835e13 fix(image): bump alpine image minor version
This is to patch a high security vulnerability.
Closes #550

Signed-off-by: Peter Benjamin <petermbenjamin@gmail.com>
2020-10-08 12:29:04 +11:00
Mike Farah
e0f5cb3c59 Update README.md
Fixing alpine instructions
2020-10-01 09:20:32 +10:00
Mike Farah
87550b7fe5 Show errors on validate 2020-09-21 20:34:29 +10:00
Mike Farah
5554301c29 Updated install instructions, added wget 2020-09-21 11:06:57 +10:00
Mike Farah
3b0aaac626 Added checksum hashes order to release 2020-09-18 16:37:45 +10:00
Mike Farah
65cb472604 Update README.md 2020-09-16 15:43:08 +10:00
Mike Farah
fbba38c9b7 Update README.md 2020-09-16 15:42:09 +10:00
Mike Farah
e5948c4f16 Fixed potential npe 2020-09-13 12:14:20 +10:00
Mike Farah
4eaadf98d0 Set STDOUT to default out when printing via cobra 2020-09-13 11:49:55 +10:00
Mike Farah
eedbb0a99f Explode anchors now applies to map keys too 2020-09-13 11:26:07 +10:00
Mike Farah
7dabc57b65 Added array update merge example 2020-09-13 11:12:52 +10:00
Mike Farah
fcd3a90f67 Bumping minor version to indicate new merge features and slight backwards incompatibility 2020-09-13 11:06:56 +10:00
Mike Farah
88e99e5336 New line in docs for better readability 2020-09-13 11:04:48 +10:00
Mike Farah
a8cfccd3af Merge master fix 2020-09-13 10:59:40 +10:00
Mike Farah
3355e80d85 Merge branch 'master' into new-merge2 2020-09-13 10:52:31 +10:00
Mike Farah
f528b28938 Convert to JSON now handles non string keys 2020-09-13 10:44:22 +10:00
Mike Farah
5b7b390a33 Force static linking 2020-09-13 10:32:45 +10:00
Mike Farah
4f12e09e78 More info on other installation methods 2020-09-13 10:16:24 +10:00
Mike Farah
ee732fbf0b Added Alpine Linux instructions 2020-09-13 10:09:02 +10:00
Mike Farah
1507f929a2 Fixing version stuffup - now 3.3.4 2020-09-11 11:11:49 +10:00
Mike Farah
fea8510061 Added comments merge strategy 2020-07-17 15:51:03 +10:00
Mike Farah
b380ea2892 better array merge strategy name 2020-07-17 13:27:27 +10:00
Mike Farah
d66a709213 refactored array merge flags into a strategy 2020-07-17 13:26:20 +10:00
Mike Farah
2fc39b3865 Can overwrite arrays when merging 2020-07-17 13:07:32 +10:00
Mike Farah
ee07edbd88 Added merge alias test 2020-06-18 09:56:36 +10:00
Mike Farah
b11661a1be Refactored merge - will allow more sophisticated mergin 2020-06-18 09:44:36 +10:00
Mike Farah
eac218980e Visit document node 2020-06-18 09:03:40 +10:00
Mike Farah
80e7f46538 Dont log mergePathStackToString - end up with duplicate logs 2020-06-18 09:03:40 +10:00
24 changed files with 829 additions and 126 deletions

View File

@@ -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

View File

@@ -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)
@@ -19,12 +16,6 @@ V3 is officially out - if you've been using v2 and want/need to upgrade, checkou
brew install yq
```
### Windows:
```
choco install yq
```
Supported by @chillum
### Ubuntu and other Linux distros supporting `snap` packages:
```
snap install yq
@@ -48,29 +39,27 @@ sudo mv /etc/myfile.tmp /etc/myfile
rm /etc/myfile.tmp
```
### On Ubuntu 16.04 or higher from Debian package:
```sh
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys CC86BB64
sudo add-apt-repository ppa:rmescandon/yq
sudo apt update
sudo apt install yq -y
```
Supported by @rmescandon
### wget
### Go Get:
```
GO111MODULE=on go get github.com/mikefarah/yq/v3
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
```
## Run with Docker
For instance, VERSION=3.4.0 and BINARY=yq_linux_amd64
Oneshot use:
### Run with Docker
#### Oneshot use:
```bash
docker run --rm -v "${PWD}":/workdir mikefarah/yq yq [flags] <command> FILE...
```
Run commands interactively:
#### Run commands interactively:
```bash
docker run --rm -it -v "${PWD}":/workdir mikefarah/yq sh
@@ -84,6 +73,39 @@ 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
sudo add-apt-repository ppa:rmescandon/yq
sudo apt update
sudo apt install yq -y
```
Supported by @rmescandon (https://launchpad.net/~rmescandon/+archive/ubuntu/yq)
## Features
- Written in portable go, so you can download a lovely dependency free binary
- [Colorize the output](https://mikefarah.gitbook.io/yq/usage/output-format#colorize-output)
@@ -136,5 +158,9 @@ Flags:
Use "yq [command] --help" for more information about a command.
```
## Known Issues
## 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)

View File

@@ -26,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"

View File

@@ -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),
})
}
}
}

View File

@@ -60,7 +60,7 @@ func TestMergeOverwriteCmd(t *testing.T) {
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `a: other # better than the original
expectedOutput := `a: other # just the best
b: [3, 4]
c:
test: 1
@@ -68,9 +68,36 @@ c:
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 --append ../examples/data1.yaml ../examples/data2.yaml")
result := test.RunCmd(cmd, "merge --autocreate=false --arrays=append ../examples/data1.yaml ../examples/data2.yaml")
if result.Error != nil {
t.Error(result.Error)
}
@@ -96,7 +123,7 @@ func TestMergeAppendArraysCmd(t *testing.T) {
defer test.RemoveTempYamlFile(mergeFilename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("merge --append -d* %s %s", filename, mergeFilename))
result := test.RunCmd(cmd, fmt.Sprintf("merge --arrays=append -d* %s %s", filename, mergeFilename))
if result.Error != nil {
t.Error(result.Error)
}
@@ -109,13 +136,56 @@ func TestMergeAppendArraysCmd(t *testing.T) {
test.AssertResult(t, expectedOutput, result.Output)
}
func TestMergeOverwriteAndAppendCmd(t *testing.T) {
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, "merge --autocreate=false --append --overwrite ../examples/data1.yaml ../examples/data2.yaml")
result := test.RunCmd(cmd, fmt.Sprintf("merge -x %s %s", filename, mergeFilename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `a: other # better than the original
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
@@ -123,13 +193,161 @@ c:
test.AssertResult(t, expectedOutput, result.Output)
}
func TestMergeArraysCmd(t *testing.T) {
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, "merge --append ../examples/sample_array.yaml ../examples/sample_array_2.yaml")
result := test.RunCmd(cmd, fmt.Sprintf("merge --comments=setWhenBlank %s %s", filename, mergeFilename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `[1, 2, 3, 4, 5]
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)
}
@@ -145,9 +363,7 @@ func TestMergeCmd_Multi(t *testing.T) {
another:
document: here
a: simple # just the best
b:
- 1
- 2
b: [1, 2]
c:
test: 1
---
@@ -316,9 +532,7 @@ func TestMergeAllowEmptyTargetCmd(t *testing.T) {
t.Error(result.Error)
}
expectedOutput := `a: simple # just the best
b:
- 1
- 2
b: [1, 2]
c:
test: 1
`

View File

@@ -94,6 +94,28 @@ func TestReadUnwrapJsonByDefaultCmd(t *testing.T) {
test.AssertResult(t, "\"frog\"\n", result.Output)
}
func TestReadOutputJsonNonStringKeysCmd(t *testing.T) {
content := `
true: true
5:
null:
0.1: deeply
false: things`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("read %s -j", filename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `{"true":true,"5":{"null":{"0.1":"deeply","false":"things"}}}
`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestReadWithAdvancedFilterCmd(t *testing.T) {
cmd := getRootCommand()
result := test.RunCmd(cmd, "read ../examples/sample.yaml b.e(name==sam).value")
@@ -561,7 +583,7 @@ func TestReadMergeAnchorsExplodeJsonCmd(t *testing.T) {
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `{"bar":{"b":2,"c":"oldbar","thing":"coconut"},"foo":{"a":"original","thing":"coolasdf","thirsty":"yep"},"foobar":{"a":"original","c":3,"thing":"ice","thirsty":"yep","thirty":"well beyond"},"foobarList":{"a":"original","b":2,"c":"newbar","thing":"coconut","thirsty":"yep"}}
expectedOutput := `{"foo":{"a":"original","thing":"coolasdf","thirsty":"yep"},"bar":{"b":2,"thing":"coconut","c":"oldbar"},"foobarList":{"c":"newbar","b":2,"thing":"coconut","a":"original","thirsty":"yep"},"foobar":{"thirty":"well beyond","thing":"ice","c":3,"a":"original","thirsty":"yep"}}
`
test.AssertResult(t, expectedOutput, result.Output)
}
@@ -595,6 +617,44 @@ pointer: *value-pointer`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestReadMergeAnchorsExplodeMissingCmd(t *testing.T) {
content := `a:
<<: &anchor
c: d
e: f
`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("read -X %s", filename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `a:
c: d
e: f
`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestReadMergeAnchorsExplodeKeyCmd(t *testing.T) {
content := `name: &nameField Mike
*nameField: Great Guy`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("read -X %s", filename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `name: Mike
Mike: Great Guy
`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestReadMergeAnchorsExplodeSimpleArrayCmd(t *testing.T) {
content := `- things`
filename := test.WriteTempYamlFile(content)
@@ -902,6 +962,7 @@ b:
value: 3
- name: sam
value: 4
ab: must appear last
`
test.AssertResult(t, expectedOutput, result.Output)
}
@@ -941,6 +1002,7 @@ b:
value: 3
- name: sam
value: 4
ab: must appear last
`
test.AssertResult(t, expectedOutput, result.Output)
}
@@ -1379,3 +1441,23 @@ func TestReadFindValueDeepObjectCmd(t *testing.T) {
`
test.AssertResult(t, expectedOutput, result.Output)
}
func TestReadKeepsKeyOrderInJson(t *testing.T) {
const content = `{
"z": "One",
"a": 1,
"w": ["a", "r"],
"u": {"d": "o", "0": 11.5},
}`
filename := test.WriteTempYamlFile(content)
defer test.RemoveTempYamlFile(filename)
cmd := getRootCommand()
result := test.RunCmd(cmd, fmt.Sprintf("r -j %s", filename))
if result.Error != nil {
t.Error(result.Error)
}
expectedOutput := `{"z":"One","a":1,"w":["a","r"],"u":{"d":"o","0":11.5}}
`
test.AssertResult(t, expectedOutput, result.Output)
}

View File

@@ -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}`,
)

View File

@@ -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)
}
}
@@ -163,6 +163,9 @@ func setIfNotThere(node *yaml.Node, key string, value *yaml.Node) {
}
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)
@@ -185,12 +188,14 @@ func explodeNode(node *yaml.Node) error {
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 {
@@ -202,6 +207,10 @@ func explodeNode(node *yaml.Node) error {
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!")
@@ -512,7 +521,7 @@ func readUpdateCommands(args []string, expectedArgs int, badArgsMessage string,
log.Debug("path %v", args[expectedArgs-2])
log.Debug("Value %v", args[expectedArgs-1])
value := valueParser.Parse(args[expectedArgs-1], customTag, customStyle, anchorName, makeAlias)
updateCommands[0] = yqlib.UpdateCommand{Command: "update", Path: args[expectedArgs-2], Value: value, Overwrite: true, DontUpdateComments: true}
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)

View File

@@ -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

View File

@@ -11,7 +11,7 @@ var (
GitDescribe string
// Version is main version number that is being run at the moment.
Version = "3.3.3"
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

View File

@@ -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"}

View File

@@ -22,13 +22,8 @@ func NewDataNavigator(NavigationStrategy NavigationStrategy) DataNavigator {
}
func (n *navigator) Traverse(value *yaml.Node, path []interface{}) error {
realValue := value
emptyArray := make([]interface{}, 0)
log.Debugf("Traversing path %v", pathStackToString(path))
if realValue.Kind == yaml.DocumentNode {
log.Debugf("its a document! returning the first child")
return n.doTraverse(value.Content[0], "", path, emptyArray)
}
return n.doTraverse(value, "", path, emptyArray)
}
@@ -39,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)
}
@@ -51,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)
@@ -73,6 +73,7 @@ func (n *navigator) recurse(value *yaml.Node, head interface{}, tail []interface
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
@@ -108,6 +109,8 @@ func (n *navigator) recurse(value *yaml.Node, head interface{}, tail []interface
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(nodeContext)
}

View File

@@ -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
}

View File

@@ -18,7 +18,8 @@ type UpdateCommand struct {
Value *yaml.Node
Overwrite bool
DontUpdateNodeValue bool
DontUpdateComments bool
DontUpdateNodeContent bool
CommentsMergeStrategy CommentsMergeStrategy
}
func KindString(kind yaml.Kind) string {
@@ -50,20 +51,23 @@ func DebugNode(value *yaml.Node) {
}
encoder.Close()
log.Debug("Tag: %v, Kind: %v, Anchor: %v", value.Tag, KindString(value.Kind), value.Anchor)
log.Debug("%v", buf.String())
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))
@@ -94,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 {
@@ -134,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 {
@@ -152,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 {
@@ -182,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]

View File

@@ -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)
})

View 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
},
}
}

View File

@@ -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 {

View 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
},
}
}

View File

@@ -1,24 +1,11 @@
package yqlib
func ReadNavigationStrategy(deeplyTraverseArrays bool) NavigationStrategy {
func ReadNavigationStrategy() NavigationStrategy {
return &NavigationStrategyImpl{
visitedNodes: []*NodeContext{},
pathParser: NewPathParser(),
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
},
}
}

View File

@@ -24,10 +24,12 @@ func UpdateNavigationStrategy(updateCommand UpdateCommand, autoCreate bool) Navi
node.Tag = changesToApply.Tag
node.Kind = changesToApply.Kind
node.Style = changesToApply.Style
if !updateCommand.DontUpdateNodeContent {
node.Content = changesToApply.Content
}
node.Anchor = changesToApply.Anchor
node.Alias = changesToApply.Alias
if !updateCommand.DontUpdateComments {
if updateCommand.CommentsMergeStrategy != IgnoreCommentsMergeStrategy {
node.HeadComment = changesToApply.HeadComment
node.LineComment = changesToApply.LineComment
node.FootComment = changesToApply.FootComment

View File

@@ -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)

View File

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

View File

@@ -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

View File

@@ -1,5 +1,5 @@
name: yq
version: '3.3.3'
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.