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

Compare commits

...

16 Commits

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
14 changed files with 320 additions and 67 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 # Choose alpine as a base image to make this useful for CI, as many
# CI tools expect an interactive shell inside the container # 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 COPY --from=builder /go/src/mikefarah/yq/yq /usr/bin/yq
RUN chmod +x /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. 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 ## Install
### [Download the latest binary](https://github.com/mikefarah/yq/releases/latest) ### [Download the latest binary](https://github.com/mikefarah/yq/releases/latest)
@@ -19,21 +16,6 @@ V3 is officially out - if you've been using v2 and want/need to upgrade, checkou
brew install yq brew install yq
``` ```
### Windows:
```
choco install yq
```
Supported by @chillum (https://chocolatey.org/packages/yq)
### Alpine Linux
- Enable community repo by adding ```$MIRROR/alpine/v$VERSION/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
### Ubuntu and other Linux distros supporting `snap` packages: ### Ubuntu and other Linux distros supporting `snap` packages:
``` ```
snap install yq snap install yq
@@ -57,29 +39,27 @@ sudo mv /etc/myfile.tmp /etc/myfile
rm /etc/myfile.tmp rm /etc/myfile.tmp
``` ```
### On Ubuntu 16.04 or higher from Debian package: ### wget
```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)
### Go Get: Use wget to download the pre-compiled binaries:
```
GO111MODULE=on go get github.com/mikefarah/yq/v3 ```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 ```bash
docker run --rm -v "${PWD}":/workdir mikefarah/yq yq [flags] <command> FILE... docker run --rm -v "${PWD}":/workdir mikefarah/yq yq [flags] <command> FILE...
``` ```
Run commands interactively: #### Run commands interactively:
```bash ```bash
docker run --rm -it -v "${PWD}":/workdir mikefarah/yq sh docker run --rm -it -v "${PWD}":/workdir mikefarah/yq sh
@@ -93,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 ## Features
- Written in portable go, so you can download a lovely dependency free binary - 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) - [Colorize the output](https://mikefarah.gitbook.io/yq/usage/output-format#colorize-output)
@@ -145,5 +158,9 @@ Flags:
Use "yq [command] --help" for more information about a command. 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) - `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

@@ -339,6 +339,19 @@ func TestMergeOverwriteArraysCmd(t *testing.T) {
test.AssertResult(t, expectedOutput, result.Output) 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) { func TestMergeCmd_Multi(t *testing.T) {
cmd := getRootCommand() cmd := getRootCommand()
result := test.RunCmd(cmd, "merge -d1 ../examples/multiple_docs_small.yaml ../examples/data1.yaml") result := test.RunCmd(cmd, "merge -d1 ../examples/multiple_docs_small.yaml ../examples/data1.yaml")

View File

@@ -111,7 +111,7 @@ true: true
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) t.Error(result.Error)
} }
expectedOutput := `{"5":{"null":{"0.1":"deeply","false":"things"}},"true":true} expectedOutput := `{"true":true,"5":{"null":{"0.1":"deeply","false":"things"}}}
` `
test.AssertResult(t, expectedOutput, result.Output) test.AssertResult(t, expectedOutput, result.Output)
} }
@@ -583,7 +583,7 @@ func TestReadMergeAnchorsExplodeJsonCmd(t *testing.T) {
if result.Error != nil { if result.Error != nil {
t.Error(result.Error) 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) test.AssertResult(t, expectedOutput, result.Output)
} }
@@ -617,6 +617,44 @@ pointer: *value-pointer`
test.AssertResult(t, expectedOutput, result.Output) 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) { func TestReadMergeAnchorsExplodeSimpleArrayCmd(t *testing.T) {
content := `- things` content := `- things`
filename := test.WriteTempYamlFile(content) filename := test.WriteTempYamlFile(content)
@@ -924,6 +962,7 @@ b:
value: 3 value: 3
- name: sam - name: sam
value: 4 value: 4
ab: must appear last
` `
test.AssertResult(t, expectedOutput, result.Output) test.AssertResult(t, expectedOutput, result.Output)
} }
@@ -963,6 +1002,7 @@ b:
value: 3 value: 3
- name: sam - name: sam
value: 4 value: 4
ab: must appear last
` `
test.AssertResult(t, expectedOutput, result.Output) test.AssertResult(t, expectedOutput, result.Output)
} }
@@ -1401,3 +1441,23 @@ func TestReadFindValueDeepObjectCmd(t *testing.T) {
` `
test.AssertResult(t, expectedOutput, result.Output) 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 return nil
}, },
PersistentPreRun: func(cmd *cobra.Command, args []string) { PersistentPreRun: func(cmd *cobra.Command, args []string) {
cmd.SetOut(cmd.OutOrStdout())
var format = logging.MustStringFormatter( var format = logging.MustStringFormatter(
`%{color}%{time:15:04:05} %{shortfunc} [%{level:.4s}]%{color:reset} %{message}`, `%{color}%{time:15:04:05} %{shortfunc} [%{level:.4s}]%{color:reset} %{message}`,
) )

View File

@@ -163,6 +163,9 @@ func setIfNotThere(node *yaml.Node, key string, value *yaml.Node) {
} }
func applyAlias(node *yaml.Node, alias *yaml.Node) { func applyAlias(node *yaml.Node, alias *yaml.Node) {
if alias == nil {
return
}
for index := 0; index < len(alias.Content); index = index + 2 { for index := 0; index < len(alias.Content); index = index + 2 {
keyNode := alias.Content[index] keyNode := alias.Content[index]
log.Debugf("applying alias key %v", keyNode.Value) log.Debugf("applying alias key %v", keyNode.Value)
@@ -185,12 +188,14 @@ func explodeNode(node *yaml.Node) error {
return nil return nil
case yaml.AliasNode: case yaml.AliasNode:
log.Debugf("its an alias!") log.Debugf("its an alias!")
node.Kind = node.Alias.Kind if node.Alias != nil {
node.Style = node.Alias.Style node.Kind = node.Alias.Kind
node.Tag = node.Alias.Tag node.Style = node.Alias.Style
node.Content = node.Alias.Content node.Tag = node.Alias.Tag
node.Value = node.Alias.Value node.Content = node.Alias.Content
node.Alias = nil node.Value = node.Alias.Value
node.Alias = nil
}
return nil return nil
case yaml.MappingNode: case yaml.MappingNode:
for index := 0; index < len(node.Content); index = index + 2 { for index := 0; index < len(node.Content); index = index + 2 {
@@ -202,6 +207,10 @@ func explodeNode(node *yaml.Node) error {
if errorInContent != nil { if errorInContent != nil {
return errorInContent return errorInContent
} }
errorInContent = explodeNode(keyNode)
if errorInContent != nil {
return errorInContent
}
} else { } else {
if valueNode.Kind == yaml.SequenceNode { if valueNode.Kind == yaml.SequenceNode {
log.Debugf("an alias merge list!") log.Debugf("an alias merge list!")

View File

@@ -15,7 +15,7 @@ yq v - # reads from stdin
`, `,
RunE: validateProperty, RunE: validateProperty,
SilenceUsage: true, SilenceUsage: true,
SilenceErrors: true, SilenceErrors: false,
} }
cmdRead.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)") cmdRead.PersistentFlags().StringVarP(&docIndex, "doc", "d", "0", "process document index number (0 based, * for all documents)")
return cmdRead return cmdRead

View File

@@ -11,7 +11,7 @@ var (
GitDescribe string GitDescribe string
// Version is main version number that is being run at the moment. // Version is main version number that is being run at the moment.
Version = "3.3.4" Version = "3.4.1"
// VersionPrerelease is a pre-release marker for the version. If this is "" (empty string) // VersionPrerelease is a pre-release marker for the version. If this is "" (empty string)
// then it means that it is a final release. Otherwise, this is a pre-release // then it means that it is a final release. Otherwise, this is a pre-release

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

@@ -3,6 +3,7 @@ package yqlib
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt"
"io" "io"
yaml "gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
@@ -87,7 +88,7 @@ func NewJsonEncoder(destination io.Writer, prettyPrint bool, indent int) Encoder
} }
func (je *jsonEncoder) Encode(node *yaml.Node) error { func (je *jsonEncoder) Encode(node *yaml.Node) error {
var dataBucket interface{} var dataBucket orderedMap
// firstly, convert all map keys to strings // firstly, convert all map keys to strings
mapKeysToStrings(node) mapKeysToStrings(node)
errorDecoding := node.Decode(&dataBucket) errorDecoding := node.Decode(&dataBucket)
@@ -96,3 +97,153 @@ func (je *jsonEncoder) Encode(node *yaml.Node) error {
} }
return je.encoder.Encode(dataBucket) 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

@@ -4,6 +4,7 @@
- tag git with same version number - tag git with same version number
- make sure local build passes - make sure local build passes
- push tag to git - push tag to git
- 3.4.0, v3
- git push --tags - git push --tags
- make local xcompile (builds binaries for all platforms) - make local xcompile (builds binaries for all platforms)

View File

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

View File

@@ -11,3 +11,4 @@ CGO_ENABLED=0 gox -ldflags "${LDFLAGS}" -os=linux -output="build/yq_{{.OS}}_{{.
cd build cd build
rhash -r -a . -P -o checksums rhash -r -a . -P -o checksums
rhash --list-hashes > checksums_hashes_order

View File

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