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

Compare commits

...

23 Commits

Author SHA1 Message Date
Mike Farah
2743a7e058 Can assign-update tag 2021-01-06 20:44:28 +11:00
Mike Farah
f95862fba3 Can assign-update style 2021-01-06 20:37:53 +11:00
Mike Farah
3f58c4bc38 Can assign-update aliases and anchors 2021-01-06 20:30:48 +11:00
Mike Farah
a7975df7cd Can assign-update comments 2021-01-06 20:22:50 +11:00
Mike Farah
3d5a5e0360 updating readme 2021-01-06 15:33:20 +11:00
Mike Farah
155755ae2f brew v3! 2021-01-06 15:22:29 +11:00
Mike Farah
601c13531c Updated collect objcet doc 2021-01-06 15:20:54 +11:00
Mike Farah
69d00c89df Added shorthand document index selection 2021-01-05 13:28:37 +11:00
Mike Farah
2faff7b05f Unwrap node in get tag to return proper tag at root level 2021-01-05 13:23:03 +11:00
Mike Farah
165949041d Added v3 snap instructions 2021-01-05 13:05:03 +11:00
Mike Farah
dbd7ab0f13 Refactored doc generation, add fi fileIndex alias 2021-01-02 10:49:33 +11:00
Mike Farah
6d512ad718 Fixed updating yaml from other files 2021-01-02 10:27:32 +11:00
Mike Farah
4fef4a7ab1 update issue template, instruct questions to be raised in disussion 2020-12-31 13:43:44 +11:00
Mike Farah
dcb17b51a9 fixed heading 2020-12-31 09:33:15 +11:00
Mike Farah
385417556d added tar.gz instructions 2020-12-31 09:30:59 +11:00
Mike Farah
edb5f213d7 updating readme 2020-12-31 09:22:22 +11:00
Mike Farah
5cfd9b05ee updating readme 2020-12-31 09:22:18 +11:00
jonatasrenan
0e764c59ce wget version var missing a 'v' prefix. 2020-12-31 08:59:38 +11:00
Mike Farah
1b887e23b3 scripts/check works for local and docker build 2020-12-30 10:40:41 +11:00
Chris Warth
a76b72e691 find golangci_lint through PATH 2020-12-30 10:30:53 +11:00
Mike Farah
9509831cff Updated docs 2020-12-29 22:35:57 +11:00
Mike Farah
e92180e89d updated release instructions 2020-12-29 09:59:16 +11:00
Mike Farah
a6ae33c3f1 Cleaning up release process, fixed github action version 2020-12-29 09:50:21 +11:00
46 changed files with 641 additions and 226 deletions

View File

@@ -10,6 +10,8 @@ assignees: ''
**Describe the bug** **Describe the bug**
A clear and concise description of what the bug is. A clear and concise description of what the bug is.
Note that any how to questions should be posted in the discussion board and not raised as an issue.
Version of yq: 3.X.X Version of yq: 3.X.X
Operating system: mac/linux/windows/.... Operating system: mac/linux/windows/....
Installed via: docker/binary release/homebrew/snap/... Installed via: docker/binary release/homebrew/snap/...

View File

@@ -10,6 +10,8 @@ assignees: ''
**Describe the bug** **Describe the bug**
A clear and concise description of what the bug is. A clear and concise description of what the bug is.
Note that any how to questions should be posted in the discussion board and not raised as an issue.
Version of yq: 4.X.X Version of yq: 4.X.X
Operating system: mac/linux/windows/.... Operating system: mac/linux/windows/....
Installed via: docker/binary release/homebrew/snap/... Installed via: docker/binary release/homebrew/snap/...

View File

@@ -9,9 +9,11 @@ assignees: ''
**Please describe your feature request.** **Please describe your feature request.**
A clear and concise description of what the request is and what it would solve. A clear and concise description of what the request is and what it would solve.
Ex. I wish I could use yq to [...] Eg. I wish I could use yq to [...]
Please note that V3 will no longer have any enhancements. Note:
- how to questions should be posted in the discussion board and not raised as an issue.
- V3 will no longer have any enhancements.
**Describe the solution you'd like** **Describe the solution you'd like**
If we have data1.yml like: If we have data1.yml like:

View File

@@ -68,7 +68,9 @@ jobs:
- name: Build and push image - name: Build and push image
run: | run: |
IMAGE_VERSION="$(git describe --tags --abbrev=0)" IMAGE_V_VERSION="$(git describe --tags --abbrev=0)"
IMAGE_VERSION=${IMAGE_V_VERSION:1}
SHORT_SHA1=$(git rev-parse --short HEAD) SHORT_SHA1=$(git rev-parse --short HEAD)
PLATFORMS="linux/amd64,linux/ppc64le,linux/arm64" PLATFORMS="linux/amd64,linux/ppc64le,linux/arm64"
echo "Building and pushing version ${IMAGE_VERSION} of image ${IMAGE_NAME}" echo "Building and pushing version ${IMAGE_VERSION} of image ${IMAGE_NAME}"

View File

@@ -4,6 +4,7 @@ COPY scripts/devtools.sh /opt/devtools.sh
RUN set -e -x \ RUN set -e -x \
&& /opt/devtools.sh && /opt/devtools.sh
ENV PATH=/go/bin:$PATH
# install mkdocs # install mkdocs
RUN set -ex \ RUN set -ex \

View File

@@ -3,30 +3,64 @@
![Build](https://github.com/mikefarah/yq/workflows/Build/badge.svg) ![Docker Pulls](https://img.shields.io/docker/pulls/mikefarah/yq.svg) ![Github Releases (by Release)](https://img.shields.io/github/downloads/mikefarah/yq/total.svg) ![Go Report](https://goreportcard.com/badge/github.com/mikefarah/yq) ![Build](https://github.com/mikefarah/yq/workflows/Build/badge.svg) ![Docker Pulls](https://img.shields.io/docker/pulls/mikefarah/yq.svg) ![Github Releases (by Release)](https://img.shields.io/github/downloads/mikefarah/yq/total.svg) ![Go Report](https://goreportcard.com/badge/github.com/mikefarah/yq)
a lightweight and portable command-line YAML processor a lightweight and portable command-line YAML processor. `yq` uses [jq](https://github.com/stedolan/jq) like syntax but works with yaml files as well as json. It doesn't yet support everything `jq` does - but it does support the most common operations and functions, and more is being added continuously.
The aim of the project is to be the [jq](https://github.com/stedolan/jq) or sed of yaml files. yq is written in go - so you can download a dependency free binary for your platform and you are good to go! If you prefer there are a variety of package managers that can be used as well as docker, all listed below.
## V4 released! ## V4 released!
V4 is now officially released, it's quite different from V3 (sorry for the migration), however it is much more similar to ```jq```, using a similar expression syntax and therefore support much more complex functionality! V4 is now officially released, it's quite different from V3 (sorry for the migration), however it is much more similar to ```jq```, using a similar expression syntax and therefore support much more complex functionality!
If you've been using v3 and want/need to upgrade, checkout the [upgrade guide](https://mikefarah.gitbook.io/yq/v/v4.x/upgrading-from-v3). If you've been using v3 and want/need to upgrade, checkout the [upgrade guide](https://mikefarah.gitbook.io/yq/v/v4.x/upgrading-from-v3).
Support for v3 will cease August 2021, until then, critical bug and security fixes will still get applied if required.
## Install ## Install
### [Download the latest binary](https://github.com/mikefarah/yq/releases/latest) ### [Download the latest binary](https://github.com/mikefarah/yq/releases/latest)
### MacOS: ### wget
Use wget to download the pre-compiled binaries:
#### Compressed via tar.gz
```bash
wget https://github.com/mikefarah/yq/releases/download/${VERSION}/${BINARY}.tar.gz -O - |\
tar xz && mv ${BINARY} /usr/bin/yq
```
#### Plain binary
```bash
wget https://github.com/mikefarah/yq/releases/download/${VERSION}/${BINARY} -O /usr/bin/yq &&\
chmod +x /usr/bin/yq
```
For instance, VERSION=v4.2.0 and BINARY=yq_linux_amd64
### MacOS / Linux via Homebrew:
Using [Homebrew](https://brew.sh/) Using [Homebrew](https://brew.sh/)
``` ```
brew install yq brew install yq
``` ```
### Ubuntu and other Linux distros supporting `snap` packages: or, for the (deprecated) v3 version:
```
brew install yq@3
```
Note that for v3, as it is a versioned brew it will not add the `yq` command to your path automatically. Please follow the instructions given by brew upon installation.
### Linux via snap:
``` ```
snap install yq snap install yq
``` ```
or, for the (deprecated) v3 version:
```
snap install yq --channel=v3/stable
```
#### Snap notes #### Snap notes
`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: `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:
@@ -45,17 +79,6 @@ sudo mv /etc/myfile.tmp /etc/myfile
rm /etc/myfile.tmp 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=4.0.0 and BINARY=yq_linux_amd64
### Run with Docker ### Run with Docker
#### Oneshot use: #### Oneshot use:

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 = "4.2.0" Version = "4.2.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,4 +1,4 @@
FROM mikefarah/yq:3 FROM mikefarah/yq:4.2.1
COPY entrypoint.sh /entrypoint.sh COPY entrypoint.sh /entrypoint.sh

View File

@@ -40,6 +40,7 @@ func (n *CandidateNode) Copy() (*CandidateNode, error) {
// updates this candidate from the given candidate node // updates this candidate from the given candidate node
func (n *CandidateNode) UpdateFrom(other *CandidateNode) { func (n *CandidateNode) UpdateFrom(other *CandidateNode) {
n.UpdateAttributesFrom(other) n.UpdateAttributesFrom(other)
n.Node.Content = other.Node.Content n.Node.Content = other.Node.Content
n.Node.Value = other.Node.Value n.Node.Value = other.Node.Value

View File

@@ -31,6 +31,22 @@ will output
a: &foobar cat a: &foobar cat
``` ```
## Set anchor relatively using assign-update
Given a sample.yml file of:
```yaml
a:
b: cat
```
then
```bash
yq eval '.a anchor |= .b' sample.yml
```
will output
```yaml
a: &cat
b: cat
```
## Get alias ## Get alias
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
@@ -62,6 +78,23 @@ b: &meow purr
a: *meow a: *meow
``` ```
## Set alias relatively using assign-update
Given a sample.yml file of:
```yaml
b: &meow purr
a:
f: meow
```
then
```bash
yq eval '.a alias |= .f' sample.yml
```
will output
```yaml
b: &meow purr
a: *meow
```
## Explode alias and anchor ## Explode alias and anchor
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml

View File

@@ -34,6 +34,27 @@ a:
g: foof g: foof
``` ```
## Update node from another file
Note this will also work when the second file is a scalar (string/number)
Given a sample.yml file of:
```yaml
a: apples
```
And another sample another.yml file of:
```yaml
b: bob
```
then
```bash
yq eval-all 'select(fileIndex==0).a = select(fileIndex==1) | select(fileIndex==0)' sample.yml another.yml
```
will output
```yaml
a:
b: bob
```
## Update node to be the sibling value ## Update node to be the sibling value
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml

View File

@@ -13,6 +13,22 @@ will output
a: cat # single a: cat # single
``` ```
## Use update assign to perform relative updates
Given a sample.yml file of:
```yaml
a: cat
b: dog
```
then
```bash
yq eval '.. lineComment |= .' sample.yml
```
will output
```yaml
a: cat # cat
b: dog # dog
```
## Set head comment ## Set head comment
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml

View File

@@ -17,6 +17,24 @@ will output
1 1
``` ```
## Retrieve a document index, shorthand
Given a sample.yml file of:
```yaml
a: cat
---
a: frog
```
then
```bash
yq eval '.a | di' sample.yml
```
will output
```yaml
0
---
1
```
## Filter by document index ## Filter by document index
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
@@ -26,7 +44,23 @@ a: frog
``` ```
then then
```bash ```bash
yq eval 'select(. | documentIndex == 1)' sample.yml yq eval 'select(documentIndex == 1)' sample.yml
```
will output
```yaml
a: frog
```
## Filter by document index shorthand
Given a sample.yml file of:
```yaml
a: cat
---
a: frog
```
then
```bash
yq eval 'select(di == 1)' sample.yml
``` ```
will output will output
```yaml ```yaml
@@ -42,7 +76,7 @@ a: frog
``` ```
then then
```bash ```bash
yq eval '.a | ({"match": ., "doc": (. | documentIndex)})' sample.yml yq eval '.a | ({"match": ., "doc": documentIndex})' sample.yml
``` ```
will output will output
```yaml ```yaml

View File

@@ -1,9 +1,11 @@
File operators are most often used with merge when needing to merge specific files together. Note that when doing this, you will need to use `eval-all` to ensure all yaml documents are loaded into memory before performing the merge (as opposed to `eval` which runs the expression once per document). File operators are most often used with merge when needing to merge specific files together. Note that when doing this, you will need to use `eval-all` to ensure all yaml documents are loaded into memory before performing the merge (as opposed to `eval` which runs the expression once per document).
Note that the `fileIndex` operator has a short alias of `fi`.
## Merging files ## Merging files
Note the use of eval-all to ensure all documents are loaded into memory. Note the use of eval-all to ensure all documents are loaded into memory.
```bash ```bash
yq eval-all 'select(fileIndex == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml yq eval-all 'select(fi == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
``` ```
## Get filename ## Get filename
Given a sample.yml file of: Given a sample.yml file of:
@@ -16,7 +18,7 @@ yq eval 'filename' sample.yml
``` ```
will output will output
```yaml ```yaml
sample.yaml sample.yml
``` ```
## Get file index ## Get file index
@@ -33,3 +35,17 @@ will output
0 0
``` ```
## Get file index alias
Given a sample.yml file of:
```yaml
a: cat
```
then
```bash
yq eval 'fi' sample.yml
```
will output
```yaml
0
```

View File

@@ -145,8 +145,8 @@ will output
{a: cat, b: 5, c: 3.2, e: true} {a: cat, b: 5, c: 3.2, e: true}
``` ```
## Pretty print ## Reset style - or pretty print
Set empty (default) quote style, note the usage of `...` to match keys too. Set empty (default) quote style, note the usage of `...` to match keys too. Note that there is a `--prettyPrint/-P` short flag for this.
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
@@ -167,6 +167,22 @@ c: 3.2
e: true e: true
``` ```
## Set style relatively with assign-update
Given a sample.yml file of:
```yaml
a: single
b: double
```
then
```bash
yq eval '.[] style |= .' sample.yml
```
will output
```yaml
a: 'single'
b: "double"
```
## Read style ## Read style
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml

View File

@@ -22,7 +22,21 @@ will output
!!seq !!seq
``` ```
## Convert numbers to strings ## Set custom tag
Given a sample.yml file of:
```yaml
a: str
```
then
```bash
yq eval '.a tag = "!!mikefarah"' sample.yml
```
will output
```yaml
a: !!mikefarah str
```
## Find numbers and convert them to strings
Given a sample.yml file of: Given a sample.yml file of:
```yaml ```yaml
a: cat a: cat

View File

@@ -1,7 +1,9 @@
File operators are most often used with merge when needing to merge specific files together. Note that when doing this, you will need to use `eval-all` to ensure all yaml documents are loaded into memory before performing the merge (as opposed to `eval` which runs the expression once per document). File operators are most often used with merge when needing to merge specific files together. Note that when doing this, you will need to use `eval-all` to ensure all yaml documents are loaded into memory before performing the merge (as opposed to `eval` which runs the expression once per document).
Note that the `fileIndex` operator has a short alias of `fi`.
## Merging files ## Merging files
Note the use of eval-all to ensure all documents are loaded into memory. Note the use of eval-all to ensure all documents are loaded into memory.
```bash ```bash
yq eval-all 'select(fileIndex == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml yq eval-all 'select(fi == 0) * select(filename == "file2.yaml")' file1.yaml file2.yaml
``` ```

View File

@@ -86,6 +86,7 @@ type Operation struct {
StringValue string StringValue string
CandidateNode *CandidateNode // used for Value Path elements CandidateNode *CandidateNode // used for Value Path elements
Preferences interface{} Preferences interface{}
UpdateAssign bool // used for assign ops, when true it means we evaluate the rhs given the lhs (instead of matching nodes)
} }
func CreateValueOperation(value interface{}, stringValue string) *Operation { func CreateValueOperation(value interface{}, stringValue string) *Operation {

View File

@@ -16,7 +16,7 @@ func createSelfAddOp(rhs *PathTreeNode) *PathTreeNode {
func AddAssignOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { func AddAssignOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
assignmentOp := &Operation{OperationType: Assign} assignmentOp := &Operation{OperationType: Assign}
assignmentOp.Preferences = &AssignOpPreferences{true} assignmentOp.UpdateAssign = true
assignmentOpNode := &PathTreeNode{Operation: assignmentOp, Lhs: pathNode.Lhs, Rhs: createSelfAddOp(pathNode.Rhs)} assignmentOpNode := &PathTreeNode{Operation: assignmentOp, Lhs: pathNode.Lhs, Rhs: createSelfAddOp(pathNode.Rhs)}
return d.GetMatchingNodes(matchingNodes, assignmentOpNode) return d.GetMatchingNodes(matchingNodes, assignmentOpNode)

View File

@@ -3,20 +3,22 @@ package yqlib
import ( import (
"container/list" "container/list"
"gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
func AssignAliasOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { func AssignAliasOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("AssignAlias operator!") log.Debugf("AssignAlias operator!")
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
aliasName := "" aliasName := ""
if rhs.Front() != nil { if !pathNode.Operation.UpdateAssign {
aliasName = rhs.Front().Value.(*CandidateNode).Node.Value rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil {
aliasName = rhs.Front().Value.(*CandidateNode).Node.Value
}
} }
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs) lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
@@ -28,6 +30,17 @@ func AssignAliasOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNod
for el := lhs.Front(); el != nil; el = el.Next() { for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
log.Debugf("Setting aliasName : %v", candidate.GetKey()) log.Debugf("Setting aliasName : %v", candidate.GetKey())
if pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil {
aliasName = rhs.Front().Value.(*CandidateNode).Node.Value
}
}
candidate.Node.Kind = yaml.AliasNode candidate.Node.Kind = yaml.AliasNode
candidate.Node.Value = aliasName candidate.Node.Value = aliasName
} }
@@ -51,13 +64,16 @@ func AssignAnchorOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNo
log.Debugf("AssignAnchor operator!") log.Debugf("AssignAnchor operator!")
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
anchorName := "" anchorName := ""
if rhs.Front() != nil { if !pathNode.Operation.UpdateAssign {
anchorName = rhs.Front().Value.(*CandidateNode).Node.Value rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil {
anchorName = rhs.Front().Value.(*CandidateNode).Node.Value
}
} }
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs) lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
@@ -69,6 +85,18 @@ func AssignAnchorOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNo
for el := lhs.Front(); el != nil; el = el.Next() { for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
log.Debugf("Setting anchorName of : %v", candidate.GetKey()) log.Debugf("Setting anchorName of : %v", candidate.GetKey())
if pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil {
anchorName = rhs.Front().Value.(*CandidateNode).Node.Value
}
}
candidate.Node.Anchor = anchorName candidate.Node.Anchor = anchorName
} }
return matchingNodes, nil return matchingNodes, nil

View File

@@ -21,6 +21,14 @@ var anchorOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::a: &foobar cat\n", "D0, P[], (doc)::a: &foobar cat\n",
}, },
}, },
{
description: "Set anchor relatively using assign-update",
document: `a: {b: cat}`,
expression: `.a anchor |= .b`,
expected: []string{
"D0, P[], (doc)::a: &cat {b: cat}\n",
},
},
{ {
description: "Get alias", description: "Get alias",
document: `{b: &billyBob meow, a: *billyBob}`, document: `{b: &billyBob meow, a: *billyBob}`,
@@ -37,6 +45,14 @@ var anchorOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::{b: &meow purr, a: *meow}\n", "D0, P[], (doc)::{b: &meow purr, a: *meow}\n",
}, },
}, },
{
description: "Set alias relatively using assign-update",
document: `{b: &meow purr, a: {f: meow}}`,
expression: `.a alias |= .f`,
expected: []string{
"D0, P[], (doc)::{b: &meow purr, a: *meow}\n",
},
},
{ {
description: "Explode alias and anchor", description: "Explode alias and anchor",
document: `{f : {a: &a cat, b: *a}}`, document: `{f : {a: &a cat, b: *a}}`,

View File

@@ -2,26 +2,20 @@ package yqlib
import "container/list" import "container/list"
type AssignOpPreferences struct {
UpdateAssign bool
}
func AssignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { func AssignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs) lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
preferences := pathNode.Operation.Preferences.(*AssignOpPreferences)
var rhs *list.List var rhs *list.List
if !preferences.UpdateAssign { if !pathNode.Operation.UpdateAssign {
rhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Rhs) rhs, err = d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
} }
for el := lhs.Front(); el != nil; el = el.Next() { for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
if preferences.UpdateAssign { if pathNode.Operation.UpdateAssign {
rhs, err = d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs) rhs, err = d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
} }
@@ -33,7 +27,9 @@ func AssignUpdateOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNo
first := rhs.Front() first := rhs.Front()
if first != nil { if first != nil {
candidate.UpdateFrom(first.Value.(*CandidateNode)) rhsCandidate := first.Value.(*CandidateNode)
rhsCandidate.Node = UnwrapDoc(rhsCandidate.Node)
candidate.UpdateFrom(rhsCandidate)
} }
} }
// // if there was nothing given, perhaps we are creating a new yaml doc // // if there was nothing given, perhaps we are creating a new yaml doc

View File

@@ -20,6 +20,16 @@ var assignOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::{a: {g: foof}}\n", "D0, P[], (doc)::{a: {g: foof}}\n",
}, },
}, },
{
description: "Update node from another file",
subdescription: "Note this will also work when the second file is a scalar (string/number)",
document: `{a: apples}`,
document2: "{b: bob}",
expression: `select(fileIndex==0).a = select(fileIndex==1) | select(fileIndex==0)`,
expected: []string{
"D0, P[], (doc)::{a: {b: bob}}\n",
},
},
{ {
description: "Update node to be the sibling value", description: "Update node to be the sibling value",
document: `{a: {b: child}, b: sibling}`, document: `{a: {b: child}, b: sibling}`,

View File

@@ -110,5 +110,5 @@ func TestCollectObjectOperatorScenarios(t *testing.T) {
for _, tt := range collectObjectOperatorScenarios { for _, tt := range collectObjectOperatorScenarios {
testScenario(t, &tt) testScenario(t, &tt)
} }
documentScenarios(t, "Collect into Object", collectObjectOperatorScenarios) documentScenarios(t, "Create, Collect into Object", collectObjectOperatorScenarios)
} }

View File

@@ -4,7 +4,7 @@ import (
"container/list" "container/list"
"strings" "strings"
"gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
type CommentOpPreferences struct { type CommentOpPreferences struct {
@@ -17,15 +17,6 @@ func AssignCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, path
log.Debugf("AssignComments operator!") log.Debugf("AssignComments operator!")
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
comment := ""
if rhs.Front() != nil {
comment = rhs.Front().Value.(*CandidateNode).Node.Value
}
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs) lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil { if err != nil {
@@ -34,8 +25,32 @@ func AssignCommentsOperator(d *dataTreeNavigator, matchingNodes *list.List, path
preferences := pathNode.Operation.Preferences.(*CommentOpPreferences) preferences := pathNode.Operation.Preferences.(*CommentOpPreferences)
comment := ""
if !pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil {
comment = rhs.Front().Value.(*CandidateNode).Node.Value
}
}
for el := lhs.Front(); el != nil; el = el.Next() { for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
if pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil {
comment = rhs.Front().Value.(*CandidateNode).Node.Value
}
}
log.Debugf("Setting comment of : %v", candidate.GetKey()) log.Debugf("Setting comment of : %v", candidate.GetKey())
if preferences.LineComment { if preferences.LineComment {
candidate.Node.LineComment = comment candidate.Node.LineComment = comment

View File

@@ -13,6 +13,39 @@ var commentOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::a: cat # single\n", "D0, P[], (doc)::a: cat # single\n",
}, },
}, },
{
skipDoc: true,
document: "a: cat\nb: dog",
expression: `.a lineComment=.b`,
expected: []string{
"D0, P[], (doc)::a: cat # dog\nb: dog\n",
},
},
{
skipDoc: true,
document: "a: cat\n---\na: dog",
expression: `.a lineComment |= documentIndex`,
expected: []string{
"D0, P[], (doc)::a: cat # 0\n",
"D1, P[], (doc)::a: dog # 1\n",
},
},
{
description: "Use update assign to perform relative updates",
document: "a: cat\nb: dog",
expression: `.. lineComment |= .`,
expected: []string{
"D0, P[], (!!map)::a: cat # cat\nb: dog # dog\n",
},
},
{
skipDoc: true,
document: "a: cat\nb: dog",
expression: `.. comments |= .`,
expected: []string{
"D0, P[], (!!map)::a: cat # cat\n# cat\n\n# cat\nb: dog # dog\n# dog\n\n# dog\n",
},
},
{ {
description: "Set head comment", description: "Set head comment",
document: `a: cat`, document: `a: cat`,

View File

@@ -14,10 +14,27 @@ var documentIndexScenarios = []expressionScenario{
"D1, P[a], (!!int)::1\n", "D1, P[a], (!!int)::1\n",
}, },
}, },
{
description: "Retrieve a document index, shorthand",
document: "a: cat\n---\na: frog\n",
expression: `.a | di`,
expected: []string{
"D0, P[a], (!!int)::0\n",
"D1, P[a], (!!int)::1\n",
},
},
{ {
description: "Filter by document index", description: "Filter by document index",
document: "a: cat\n---\na: frog\n", document: "a: cat\n---\na: frog\n",
expression: `select(. | documentIndex == 1)`, expression: `select(documentIndex == 1)`,
expected: []string{
"D1, P[], (doc)::a: frog\n",
},
},
{
description: "Filter by document index shorthand",
document: "a: cat\n---\na: frog\n",
expression: `select(di == 1)`,
expected: []string{ expected: []string{
"D1, P[], (doc)::a: frog\n", "D1, P[], (doc)::a: frog\n",
}, },
@@ -25,7 +42,7 @@ var documentIndexScenarios = []expressionScenario{
{ {
description: "Print Document Index with matches", description: "Print Document Index with matches",
document: "a: cat\n---\na: frog\n", document: "a: cat\n---\na: frog\n",
expression: `.a | ({"match": ., "doc": (. | documentIndex)})`, expression: `.a | ({"match": ., "doc": documentIndex})`,
expected: []string{ expected: []string{
"D0, P[], (!!map)::match: cat\ndoc: 0\n", "D0, P[], (!!map)::match: cat\ndoc: 0\n",
"D0, P[], (!!map)::match: frog\ndoc: 1\n", "D0, P[], (!!map)::match: frog\ndoc: 1\n",

View File

@@ -21,6 +21,14 @@ var fileOperatorScenarios = []expressionScenario{
"D0, P[], (!!int)::0\n", "D0, P[], (!!int)::0\n",
}, },
}, },
{
description: "Get file index alias",
document: `{a: cat}`,
expression: `fi`,
expected: []string{
"D0, P[], (!!int)::0\n",
},
},
} }
func TestFileOperatorsScenarios(t *testing.T) { func TestFileOperatorsScenarios(t *testing.T) {

View File

@@ -115,7 +115,7 @@ func applyAssignment(d *dataTreeNavigator, pathIndexToStartFrom int, lhs *Candid
assignmentOp := &Operation{OperationType: AssignAttributes} assignmentOp := &Operation{OperationType: AssignAttributes}
if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode { if rhs.Node.Kind == yaml.ScalarNode || rhs.Node.Kind == yaml.AliasNode {
assignmentOp.OperationType = Assign assignmentOp.OperationType = Assign
assignmentOp.Preferences = &AssignOpPreferences{false} assignmentOp.UpdateAssign = false
} else if shouldAppendArrays && rhs.Node.Kind == yaml.SequenceNode { } else if shouldAppendArrays && rhs.Node.Kind == yaml.SequenceNode {
assignmentOp.OperationType = AddAssign assignmentOp.OperationType = AddAssign
} }

View File

@@ -4,39 +4,46 @@ import (
"container/list" "container/list"
"fmt" "fmt"
"gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
func parseStyle(customStyle string) (yaml.Style, error) {
if customStyle == "tagged" {
return yaml.TaggedStyle, nil
} else if customStyle == "double" {
return yaml.DoubleQuotedStyle, nil
} else if customStyle == "single" {
return yaml.SingleQuotedStyle, nil
} else if customStyle == "literal" {
return yaml.LiteralStyle, nil
} else if customStyle == "folded" {
return yaml.FoldedStyle, nil
} else if customStyle == "flow" {
return yaml.FlowStyle, nil
} else if customStyle != "" {
return 0, fmt.Errorf("Unknown style %v", customStyle)
}
return 0, nil
}
func AssignStyleOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { func AssignStyleOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("AssignStyleOperator: %v") log.Debugf("AssignStyleOperator: %v")
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
customStyle := ""
if rhs.Front() != nil {
customStyle = rhs.Front().Value.(*CandidateNode).Node.Value
}
var style yaml.Style var style yaml.Style
if customStyle == "tagged" { if !pathNode.Operation.UpdateAssign {
style = yaml.TaggedStyle rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
} else if customStyle == "double" { if err != nil {
style = yaml.DoubleQuotedStyle return nil, err
} else if customStyle == "single" { }
style = yaml.SingleQuotedStyle
} else if customStyle == "literal" { if rhs.Front() != nil {
style = yaml.LiteralStyle style, err = parseStyle(rhs.Front().Value.(*CandidateNode).Node.Value)
} else if customStyle == "folded" { if err != nil {
style = yaml.FoldedStyle return nil, err
} else if customStyle == "flow" { }
style = yaml.FlowStyle }
} else if customStyle != "" {
return nil, fmt.Errorf("Unknown style %v", customStyle)
} }
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs) lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
if err != nil { if err != nil {
@@ -46,6 +53,20 @@ func AssignStyleOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNod
for el := lhs.Front(); el != nil; el = el.Next() { for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
log.Debugf("Setting style of : %v", candidate.GetKey()) log.Debugf("Setting style of : %v", candidate.GetKey())
if pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil {
style, err = parseStyle(rhs.Front().Value.(*CandidateNode).Node.Value)
if err != nil {
return nil, err
}
}
}
candidate.Node.Style = style candidate.Node.Style = style
} }

View File

@@ -86,7 +86,7 @@ e: >-
}, },
}, },
{ {
description: "Pretty print", description: "Reset style - or pretty print",
subdescription: "Set empty (default) quote style, note the usage of `...` to match keys too. Note that there is a `--prettyPrint/-P` short flag for this.", subdescription: "Set empty (default) quote style, note the usage of `...` to match keys too. Note that there is a `--prettyPrint/-P` short flag for this.",
document: `{a: cat, "b": 5, 'c': 3.2, "e": true}`, document: `{a: cat, "b": 5, 'c': 3.2, "e": true}`,
expression: `... style=""`, expression: `... style=""`,
@@ -94,6 +94,14 @@ e: >-
"D0, P[], (!!map)::a: cat\nb: 5\nc: 3.2\ne: true\n", "D0, P[], (!!map)::a: cat\nb: 5\nc: 3.2\ne: true\n",
}, },
}, },
{
description: "Set style relatively with assign-update",
document: `{a: single, b: double}`,
expression: `.[] style |= .`,
expected: []string{
"D0, P[], (doc)::{a: 'single', b: \"double\"}\n",
},
},
{ {
skipDoc: true, skipDoc: true,
document: `{a: cat, b: double}`, document: `{a: cat, b: double}`,

View File

@@ -3,21 +3,23 @@ package yqlib
import ( import (
"container/list" "container/list"
"gopkg.in/yaml.v3" yaml "gopkg.in/yaml.v3"
) )
func AssignTagOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) { func AssignTagOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *PathTreeNode) (*list.List, error) {
log.Debugf("AssignTagOperator: %v") log.Debugf("AssignTagOperator: %v")
rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
tag := "" tag := ""
if rhs.Front() != nil { if !pathNode.Operation.UpdateAssign {
tag = rhs.Front().Value.(*CandidateNode).Node.Value rhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil {
tag = rhs.Front().Value.(*CandidateNode).Node.Value
}
} }
lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs) lhs, err := d.GetMatchingNodes(matchingNodes, pathNode.Lhs)
@@ -29,6 +31,16 @@ func AssignTagOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode
for el := lhs.Front(); el != nil; el = el.Next() { for el := lhs.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
log.Debugf("Setting tag of : %v", candidate.GetKey()) log.Debugf("Setting tag of : %v", candidate.GetKey())
if pathNode.Operation.UpdateAssign {
rhs, err := d.GetMatchingNodes(nodeToMap(candidate), pathNode.Rhs)
if err != nil {
return nil, err
}
if rhs.Front() != nil {
tag = rhs.Front().Value.(*CandidateNode).Node.Value
}
}
candidate.Node.Tag = tag candidate.Node.Tag = tag
} }
@@ -42,7 +54,7 @@ func GetTagOperator(d *dataTreeNavigator, matchingNodes *list.List, pathNode *Pa
for el := matchingNodes.Front(); el != nil; el = el.Next() { for el := matchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode) candidate := el.Value.(*CandidateNode)
node := &yaml.Node{Kind: yaml.ScalarNode, Value: candidate.Node.Tag, Tag: "!!str"} node := &yaml.Node{Kind: yaml.ScalarNode, Value: UnwrapDoc(candidate.Node).Tag, Tag: "!!str"}
lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path} lengthCand := &CandidateNode{Node: node, Document: candidate.Document, Path: candidate.Path}
results.PushBack(lengthCand) results.PushBack(lengthCand)
} }

View File

@@ -19,13 +19,37 @@ var tagOperatorScenarios = []expressionScenario{
}, },
}, },
{ {
description: "Convert numbers to strings", skipDoc: true,
document: `{a: cat, b: 5, c: 3.2, e: true, f: []}`,
expression: `tag`,
expected: []string{
"D0, P[], (!!str)::'!!map'\n",
},
},
{
description: "Set custom tag",
document: `{a: str}`,
expression: `.a tag = "!!mikefarah"`,
expected: []string{
"D0, P[], (doc)::{a: !!mikefarah str}\n",
},
},
{
description: "Find numbers and convert them to strings",
document: `{a: cat, b: 5, c: 3.2, e: true}`, document: `{a: cat, b: 5, c: 3.2, e: true}`,
expression: `(.. | select(tag == "!!int")) tag= "!!str"`, expression: `(.. | select(tag == "!!int")) tag= "!!str"`,
expected: []string{ expected: []string{
"D0, P[], (!!map)::{a: cat, b: \"5\", c: 3.2, e: true}\n", "D0, P[], (!!map)::{a: cat, b: \"5\", c: 3.2, e: true}\n",
}, },
}, },
{
skipDoc: true,
document: `{a: "!!frog", b: "!!customTag"}`,
expression: `.[] tag |= .`,
expected: []string{
"D0, P[], (doc)::{a: !!frog \"!!frog\", b: !!customTag \"!!customTag\"}\n",
},
},
} }
func TestTagOperatorScenarios(t *testing.T) { func TestTagOperatorScenarios(t *testing.T) {

View File

@@ -18,6 +18,7 @@ type expressionScenario struct {
description string description string
subdescription string subdescription string
document string document string
document2 string
expression string expression string
expected []string expected []string
skipDoc bool skipDoc bool
@@ -41,6 +42,14 @@ func testScenario(t *testing.T, s *expressionScenario) {
t.Error(err, s.document, s.expression) t.Error(err, s.document, s.expression)
return return
} }
if s.document2 != "" {
moreInputs, err := readDocuments(strings.NewReader(s.document2), "another.yml", 1)
if err != nil {
t.Error(err, s.document, s.expression)
return
}
inputs.PushBackList(moreInputs)
}
} else { } else {
candidateNode := &CandidateNode{ candidateNode := &CandidateNode{
Document: 0, Document: 0,
@@ -92,7 +101,7 @@ func copyFromHeader(title string, out *os.File) error {
return err return err
} }
func formatYaml(yaml string) string { func formatYaml(yaml string, filename string) string {
var output bytes.Buffer var output bytes.Buffer
printer := NewPrinter(bufio.NewWriter(&output), false, true, false, 2, true) printer := NewPrinter(bufio.NewWriter(&output), false, true, false, 2, true)
@@ -101,7 +110,7 @@ func formatYaml(yaml string) string {
panic(err) panic(err)
} }
streamEvaluator := NewStreamEvaluator() streamEvaluator := NewStreamEvaluator()
err = streamEvaluator.Evaluate("sample.yaml", strings.NewReader(yaml), node, printer) err = streamEvaluator.Evaluate(filename, strings.NewReader(yaml), node, printer)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -128,63 +137,116 @@ func documentScenarios(t *testing.T, title string, scenarios []expressionScenari
for _, s := range scenarios { for _, s := range scenarios {
if !s.skipDoc { if !s.skipDoc {
documentScenario(t, w, s)
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
if s.subdescription != "" {
writeOrPanic(w, s.subdescription)
writeOrPanic(w, "\n\n")
}
formattedDoc := ""
if s.document != "" {
if s.dontFormatInputForDoc {
formattedDoc = s.document + "\n"
} else {
formattedDoc = formatYaml(s.document)
}
//TODO: pretty here
writeOrPanic(w, "Given a sample.yml file of:\n")
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n", formattedDoc))
writeOrPanic(w, "then\n")
if s.expression != "" {
writeOrPanic(w, fmt.Sprintf("```bash\nyq eval '%v' sample.yml\n```\n", s.expression))
} else {
writeOrPanic(w, "```bash\nyq eval sample.yml\n```\n")
}
} else {
writeOrPanic(w, "Running\n")
writeOrPanic(w, fmt.Sprintf("```bash\nyq eval --null-input '%v'\n```\n", s.expression))
}
writeOrPanic(w, "will output\n")
var output bytes.Buffer
var err error
printer := NewPrinter(bufio.NewWriter(&output), false, true, false, 2, true)
streamEvaluator := NewStreamEvaluator()
if s.document != "" {
node, err := treeCreator.ParsePath(s.expression)
if err != nil {
t.Error(err, s.expression)
}
err = streamEvaluator.Evaluate("sample.yaml", strings.NewReader(formattedDoc), node, printer)
if err != nil {
t.Error(err, s.expression)
}
} else {
err = streamEvaluator.EvaluateNew(s.expression, printer)
if err != nil {
t.Error(err, s.expression)
}
}
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n", output.String()))
} }
} }
w.Flush() w.Flush()
} }
func documentScenario(t *testing.T, w *bufio.Writer, s expressionScenario) {
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
if s.subdescription != "" {
writeOrPanic(w, s.subdescription)
writeOrPanic(w, "\n\n")
}
formattedDoc, formattedDoc2 := documentInput(w, s)
writeOrPanic(w, "will output\n")
documentOutput(t, w, s, formattedDoc, formattedDoc2)
}
func documentInput(w *bufio.Writer, s expressionScenario) (string, string) {
formattedDoc := ""
formattedDoc2 := ""
command := "eval"
if s.document != "" {
if s.dontFormatInputForDoc {
formattedDoc = s.document + "\n"
} else {
formattedDoc = formatYaml(s.document, "sample.yml")
}
writeOrPanic(w, "Given a sample.yml file of:\n")
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n", formattedDoc))
files := "sample.yml"
if s.document2 != "" {
if s.dontFormatInputForDoc {
formattedDoc2 = s.document2 + "\n"
} else {
formattedDoc2 = formatYaml(s.document2, "another.yml")
}
writeOrPanic(w, "And another sample another.yml file of:\n")
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n", formattedDoc2))
files = "sample.yml another.yml"
command = "eval-all"
}
writeOrPanic(w, "then\n")
if s.expression != "" {
writeOrPanic(w, fmt.Sprintf("```bash\nyq %v '%v' %v\n```\n", command, s.expression, files))
} else {
writeOrPanic(w, fmt.Sprintf("```bash\nyq %v %v\n```\n", command, files))
}
} else {
writeOrPanic(w, "Running\n")
writeOrPanic(w, fmt.Sprintf("```bash\nyq %v --null-input '%v'\n```\n", command, s.expression))
}
return formattedDoc, formattedDoc2
}
func documentOutput(t *testing.T, w *bufio.Writer, s expressionScenario, formattedDoc string, formattedDoc2 string) {
var output bytes.Buffer
var err error
printer := NewPrinter(bufio.NewWriter(&output), false, true, false, 2, true)
node, err := treeCreator.ParsePath(s.expression)
if err != nil {
t.Error(fmt.Errorf("Error parsing expression %v of %v: %v", s.expression, s.description, err))
return
}
inputs := list.New()
if s.document != "" {
inputs, err = readDocuments(strings.NewReader(formattedDoc), "sample.yml", 0)
if err != nil {
t.Error(err, s.document, s.expression)
return
}
if s.document2 != "" {
moreInputs, err := readDocuments(strings.NewReader(formattedDoc2), "another.yml", 1)
if err != nil {
t.Error(err, s.document, s.expression)
return
}
inputs.PushBackList(moreInputs)
}
} else {
candidateNode := &CandidateNode{
Document: 0,
Filename: "",
Node: &yaml.Node{Tag: "!!null"},
FileIndex: 0,
}
inputs.PushBack(candidateNode)
}
results, err := treeNavigator.GetMatchingNodes(inputs, node)
if err != nil {
t.Error(err, s.expression)
}
err = printer.PrintResults(results)
if err != nil {
t.Error(err, s.expression)
}
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n", output.String()))
}

View File

@@ -137,6 +137,11 @@ var pathTests = []struct {
append(make([]interface{}, 0), "SELF", "ASSIGN_COMMENT", "str (string)"), append(make([]interface{}, 0), "SELF", "ASSIGN_COMMENT", "str (string)"),
append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_COMMENT"), append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_COMMENT"),
}, },
{
`. lineComment |= "str"`,
append(make([]interface{}, 0), "SELF", "ASSIGN_COMMENT", "str (string)"),
append(make([]interface{}, 0), "SELF", "str (string)", "ASSIGN_COMMENT"),
},
{ {
`.a.b tag="!!str"`, `.a.b tag="!!str"`,
append(make([]interface{}, 0), "a", "SHORT_PIPE", "b", "ASSIGN_TAG", "!!str (string)"), append(make([]interface{}, 0), "a", "SHORT_PIPE", "b", "ASSIGN_TAG", "!!str (string)"),

View File

@@ -92,6 +92,15 @@ func opAssignableToken(opType *OperationType, assignOpType *OperationType) lex.A
return opTokenWithPrefs(opType, assignOpType, nil) return opTokenWithPrefs(opType, assignOpType, nil)
} }
func assignOpToken(updateAssign bool) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
log.Debug("assignOpToken %v", string(m.Bytes))
value := string(m.Bytes)
op := &Operation{OperationType: Assign, Value: Assign.Type, StringValue: value, UpdateAssign: updateAssign}
return &Token{TokenType: OperationToken, Operation: op}, nil
}
}
func opTokenWithPrefs(op *OperationType, assignOpType *OperationType, preferences interface{}) lex.Action { func opTokenWithPrefs(op *OperationType, assignOpType *OperationType, preferences interface{}) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
log.Debug("opTokenWithPrefs %v", string(m.Bytes)) log.Debug("opTokenWithPrefs %v", string(m.Bytes))
@@ -105,6 +114,21 @@ func opTokenWithPrefs(op *OperationType, assignOpType *OperationType, preference
} }
} }
func assignAllCommentsOp(updateAssign bool) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
log.Debug("assignAllCommentsOp %v", string(m.Bytes))
value := string(m.Bytes)
op := &Operation{
OperationType: AssignComment,
Value: AssignComment.Type,
StringValue: value,
UpdateAssign: updateAssign,
Preferences: &CommentOpPreferences{LineComment: true, HeadComment: true, FootComment: true},
}
return &Token{TokenType: OperationToken, Operation: op}, nil
}
}
func literalToken(pType TokenType, checkForPost bool) lex.Action { func literalToken(pType TokenType, checkForPost bool) lex.Action {
return func(s *lex.Scanner, m *machines.Match) (interface{}, error) { return func(s *lex.Scanner, m *machines.Match) (interface{}, error) {
return &Token{TokenType: pType, CheckForPostTraverse: checkForPost}, nil return &Token{TokenType: pType, CheckForPostTraverse: checkForPost}, nil
@@ -192,6 +216,7 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`\/\/`), opToken(Alternative)) lexer.Add([]byte(`\/\/`), opToken(Alternative))
lexer.Add([]byte(`documentIndex`), opToken(GetDocumentIndex)) lexer.Add([]byte(`documentIndex`), opToken(GetDocumentIndex))
lexer.Add([]byte(`di`), opToken(GetDocumentIndex))
lexer.Add([]byte(`style`), opAssignableToken(GetStyle, AssignStyle)) lexer.Add([]byte(`style`), opAssignableToken(GetStyle, AssignStyle))
@@ -200,6 +225,7 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`alias`), opAssignableToken(GetAlias, AssignAlias)) lexer.Add([]byte(`alias`), opAssignableToken(GetAlias, AssignAlias))
lexer.Add([]byte(`filename`), opToken(GetFilename)) lexer.Add([]byte(`filename`), opToken(GetFilename))
lexer.Add([]byte(`fileIndex`), opToken(GetFileIndex)) lexer.Add([]byte(`fileIndex`), opToken(GetFileIndex))
lexer.Add([]byte(`fi`), opToken(GetFileIndex))
lexer.Add([]byte(`path`), opToken(GetPath)) lexer.Add([]byte(`path`), opToken(GetPath))
lexer.Add([]byte(`lineComment`), opTokenWithPrefs(GetComment, AssignComment, &CommentOpPreferences{LineComment: true})) lexer.Add([]byte(`lineComment`), opTokenWithPrefs(GetComment, AssignComment, &CommentOpPreferences{LineComment: true}))
@@ -208,16 +234,17 @@ func initLexer() (*lex.Lexer, error) {
lexer.Add([]byte(`footComment`), opTokenWithPrefs(GetComment, AssignComment, &CommentOpPreferences{FootComment: true})) lexer.Add([]byte(`footComment`), opTokenWithPrefs(GetComment, AssignComment, &CommentOpPreferences{FootComment: true}))
lexer.Add([]byte(`comments\s*=`), opTokenWithPrefs(AssignComment, nil, &CommentOpPreferences{LineComment: true, HeadComment: true, FootComment: true})) lexer.Add([]byte(`comments\s*=`), assignAllCommentsOp(false))
lexer.Add([]byte(`comments\s*\|=`), assignAllCommentsOp(true))
lexer.Add([]byte(`collect`), opToken(Collect)) lexer.Add([]byte(`collect`), opToken(Collect))
lexer.Add([]byte(`\s*==\s*`), opToken(Equals)) lexer.Add([]byte(`\s*==\s*`), opToken(Equals))
lexer.Add([]byte(`\s*=\s*`), opTokenWithPrefs(Assign, nil, &AssignOpPreferences{false})) lexer.Add([]byte(`\s*=\s*`), assignOpToken(false))
lexer.Add([]byte(`del`), opToken(DeleteChild)) lexer.Add([]byte(`del`), opToken(DeleteChild))
lexer.Add([]byte(`\s*\|=\s*`), opTokenWithPrefs(Assign, nil, &AssignOpPreferences{true})) lexer.Add([]byte(`\s*\|=\s*`), assignOpToken(true))
lexer.Add([]byte("( |\t|\n|\r)+"), skip) lexer.Add([]byte("( |\t|\n|\r)+"), skip)
@@ -324,6 +351,7 @@ func (p *pathTokeniser) handleToken(tokens []*Token, index int, postProcessedTok
tokens[index+1].TokenType == OperationToken && tokens[index+1].TokenType == OperationToken &&
tokens[index+1].Operation.OperationType == Assign { tokens[index+1].Operation.OperationType == Assign {
token.Operation = token.AssignOperation token.Operation = token.AssignOperation
token.Operation.UpdateAssign = tokens[index+1].Operation.UpdateAssign
skipNextToken = true skipNextToken = true
} }

View File

@@ -1,9 +1,11 @@
- increment version in version.go - increment version in version.go
- increment version in snapcraft.yaml - increment version in snapcraft.yaml
- increment version in github-action/Dockerfile
- make sure local build passes - make sure local build passes
- tag git with same version number - tag git with same version number
- commit vX tag - this will trigger github actions - commit vX tag - this will trigger github actions
- use github actions to publish docker and make github release - use github actions to publish docker and make github release
- check github updated yq action in marketplace
- snapcraft - snapcraft
- will auto create a candidate, test it works then promote - will auto create a candidate, test it works then promote

View File

@@ -3,7 +3,12 @@
set -o errexit set -o errexit
set -o pipefail set -o pipefail
./bin/golangci-lint run --timeout=5m if command -v golangci-lint &> /dev/null
then
golangci-lint run --timeout=5m
else
./bin/golangci-lint run --timeout=5m
fi
# ./bin/golangci-lint \ # ./bin/golangci-lint \
# --tests \ # --tests \

View File

@@ -1,12 +0,0 @@
#!/bin/bash
set -ex
VERSION="$(git describe --tags --abbrev=0)"
docker build \
--target production \
--build-arg VERSION=${VERSION} \
-t mikefarah/yq:latest \
-t mikefarah/yq:${VERSION} \
-t mikefarah/yq:4 \
.
trivy image mikefarah/yq:${VERSION}

View File

@@ -1,21 +0,0 @@
#!/bin/bash
set -ex
GITHUB_TOKEN="${GITHUB_TOKEN:?missing required input \'GITHUB_TOKEN\'}"
CURRENT="$(git describe --tags --abbrev=0)"
PREVIOUS="$(git describe --tags --abbrev=0 --always "${CURRENT}"^)"
OWNER="mikefarah"
REPO="yq"
release() {
github-release release \
--user "$OWNER" \
--draft \
--repo "$REPO" \
--tag "$CURRENT"
}
release

View File

@@ -1,28 +0,0 @@
#!/bin/bash
set -ex
GITHUB_TOKEN="${GITHUB_TOKEN:?missing required input \'GITHUB_TOKEN\'}"
CURRENT="$(git describe --tags --abbrev=0)"
PREVIOUS="$(git describe --tags --abbrev=0 --always "${CURRENT}"^)"
OWNER="mikefarah"
REPO="yq"
upload() {
mkdir -p ./build-done
while IFS= read -r -d $'\0'; do
file=$REPLY
BINARY=$(basename "${file}")
echo "--> ${BINARY}"
github-release upload \
--replace \
--user "$OWNER" \
--repo "$REPO" \
--tag "$CURRENT" \
--name "${BINARY}" \
--file "$file"
mv "$file" "./build-done/${BINARY}"
done < <(find ./build -mindepth 1 -maxdepth 1 -print0)
}
upload

View File

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

BIN
yq_linux_amd64.tar.gz Normal file

Binary file not shown.

BIN
yqt Executable file

Binary file not shown.