mirror of
https://github.com/mikefarah/yq.git
synced 2026-08-24 16:39:58 +08:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48d6ad58e7 |
@@ -10,10 +10,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
- name: Set up Go 1.19
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '^1.20'
|
||||
go-version: 1.19
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
|
||||
@@ -10,18 +10,19 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v4
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '^1.20'
|
||||
go-version: '^1.19'
|
||||
check-latest: true
|
||||
- name: Compile man page markup
|
||||
id: gen-man-page-md
|
||||
run: |
|
||||
./scripts/generate-man-page-md.sh
|
||||
echo "::set-output name=man-page-md::man.md"
|
||||
|
||||
- name: Get the version
|
||||
id: get_version
|
||||
run: echo "VERSION=${GITHUB_REF##*/}" >> "${GITHUB_OUTPUT}"
|
||||
run: echo ::set-output name=VERSION::${GITHUB_REF##*/}
|
||||
|
||||
- name: Generate man page
|
||||
uses: docker://pandoc/core:2.14.2
|
||||
@@ -35,7 +36,7 @@ jobs:
|
||||
--variable=header:"yq (https://github.com/mikefarah/yq/) version ${{ steps.get_version.outputs.VERSION }}"
|
||||
--variable=author:"Mike Farah"
|
||||
--output=yq.1
|
||||
man.md
|
||||
${{ steps.gen-man-page-md.outputs.man-page-md }}
|
||||
|
||||
- name: Cross compile
|
||||
run: |
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
name: Release Snap
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
buildSnap:
|
||||
environment: snap
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: snapcore/action-build@v1
|
||||
id: build
|
||||
- uses: snapcore/action-publish@v1
|
||||
env:
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.STORE_LOGIN }}
|
||||
with:
|
||||
snap: ${{ steps.build.outputs.snap }}
|
||||
release: stable
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Development
|
||||
|
||||
1. Install (Golang)[https://golang.org/]
|
||||
1. Install (golang)[https://golang.org/]
|
||||
1. Run `scripts/devtools.sh` to install the required devtools
|
||||
2. Run `make [local] vendor` to install the vendor dependencies
|
||||
2. Run `make [local] test` to ensure you can run the existing tests
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
FROM golang:1.20.2 as builder
|
||||
FROM golang:1.19.2 as builder
|
||||
|
||||
WORKDIR /go/src/mikefarah/yq
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN CGO_ENABLED=0 go build -ldflags "-s -w" .
|
||||
RUN CGO_ENABLED=0 go build .
|
||||
# RUN ./scripts/test.sh -- this too often times out in the github pipeline.
|
||||
RUN ./scripts/acceptance.sh
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.20.2
|
||||
FROM golang:1.19.2
|
||||
|
||||
COPY scripts/devtools.sh /opt/devtools.sh
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ export GIT_DESCRIBE = $(shell git describe --tags --always)
|
||||
LDFLAGS :=
|
||||
LDFLAGS += -X main.GitCommit=${GIT_COMMIT}${GIT_DIRTY}
|
||||
LDFLAGS += -X main.GitDescribe=${GIT_DESCRIBE}
|
||||
LDFLAGS += -w -s
|
||||
|
||||
GITHUB_TOKEN ?=
|
||||
|
||||
|
||||
@@ -61,9 +61,6 @@ Take a look at the discussions for [common questions](https://github.com/mikefar
|
||||
### wget
|
||||
Use wget to download, gzipped pre-compiled binaries:
|
||||
|
||||
|
||||
For instance, VERSION=v4.2.0 and BINARY=yq_linux_amd64
|
||||
|
||||
#### Compressed via tar.gz
|
||||
```bash
|
||||
wget https://github.com/mikefarah/yq/releases/download/${VERSION}/${BINARY}.tar.gz -O - |\
|
||||
@@ -77,7 +74,9 @@ wget https://github.com/mikefarah/yq/releases/download/${VERSION}/${BINARY} -O /
|
||||
chmod +x /usr/bin/yq
|
||||
```
|
||||
|
||||
#### Latest version
|
||||
For instance, VERSION=v4.2.0 and BINARY=yq_linux_amd64
|
||||
|
||||
Use VERSION=latest to always get the latest version:
|
||||
|
||||
```bash
|
||||
wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/bin/yq &&\
|
||||
@@ -229,8 +228,6 @@ go install github.com/mikefarah/yq/v4@latest
|
||||
## Community Supported Installation methods
|
||||
As these are supported by the community :heart: - however, they may be out of date with the officially supported releases.
|
||||
|
||||
_Please note that the Debian package (previously supported by @rmescandon) is no longer maintained. Please use an alternative installation method._
|
||||
|
||||
|
||||
### Nix
|
||||
|
||||
@@ -288,6 +285,15 @@ 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
|
||||
- [Detailed documentation with many examples](https://mikefarah.gitbook.io/yq/)
|
||||
- Written in portable go, so you can download a lovely dependency free binary
|
||||
|
||||
@@ -1,211 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
setUp() {
|
||||
rm test*.yml 2>/dev/null || true
|
||||
rm test*.json 2>/dev/null || true
|
||||
rm test*.properties 2>/dev/null || true
|
||||
rm test*.csv 2>/dev/null || true
|
||||
rm test*.tsv 2>/dev/null || true
|
||||
rm test*.xml 2>/dev/null || true
|
||||
}
|
||||
|
||||
testInputJson() {
|
||||
cat >test.json <<EOL
|
||||
{ "mike" : { "things": "cool" } }
|
||||
EOL
|
||||
|
||||
read -r -d '' expected << EOM
|
||||
{
|
||||
"mike": {
|
||||
"things": "cool"
|
||||
}
|
||||
}
|
||||
EOM
|
||||
|
||||
X=$(./yq test.json)
|
||||
assertEquals "$expected" "$X"
|
||||
|
||||
X=$(./yq ea test.json)
|
||||
assertEquals "$expected" "$X"
|
||||
}
|
||||
|
||||
testInputJsonOutputYaml() {
|
||||
cat >test.json <<EOL
|
||||
{ "mike" : { "things": "cool" } }
|
||||
EOL
|
||||
|
||||
read -r -d '' expected << EOM
|
||||
mike:
|
||||
things: cool
|
||||
EOM
|
||||
|
||||
X=$(./yq test.json -oy)
|
||||
assertEquals "$expected" "$X"
|
||||
|
||||
X=$(./yq ea test.json -oy)
|
||||
assertEquals "$expected" "$X"
|
||||
}
|
||||
|
||||
testInputProperties() {
|
||||
cat >test.properties <<EOL
|
||||
mike.things = hello
|
||||
EOL
|
||||
|
||||
read -r -d '' expected << EOM
|
||||
mike.things = hello
|
||||
EOM
|
||||
|
||||
X=$(./yq e test.properties)
|
||||
assertEquals "$expected" "$X"
|
||||
|
||||
X=$(./yq test.properties)
|
||||
assertEquals "$expected" "$X"
|
||||
|
||||
X=$(./yq ea test.properties)
|
||||
assertEquals "$expected" "$X"
|
||||
}
|
||||
|
||||
testInputPropertiesGitHubAction() {
|
||||
cat >test.properties <<EOL
|
||||
mike.things = hello
|
||||
EOL
|
||||
|
||||
read -r -d '' expected << EOM
|
||||
mike.things = hello
|
||||
EOM
|
||||
|
||||
X=$(cat /dev/null | ./yq e test.properties)
|
||||
assertEquals "$expected" "$X"
|
||||
|
||||
X=$(cat /dev/null | ./yq ea test.properties)
|
||||
assertEquals "$expected" "$X"
|
||||
}
|
||||
|
||||
testInputCSV() {
|
||||
cat >test.csv <<EOL
|
||||
fruit,yumLevel
|
||||
apple,5
|
||||
banana,4
|
||||
EOL
|
||||
|
||||
read -r -d '' expected << EOM
|
||||
fruit,yumLevel
|
||||
apple,5
|
||||
banana,4
|
||||
EOM
|
||||
|
||||
X=$(./yq e test.csv)
|
||||
assertEquals "$expected" "$X"
|
||||
|
||||
X=$(./yq ea test.csv)
|
||||
assertEquals "$expected" "$X"
|
||||
}
|
||||
|
||||
testInputCSVUTF8() {
|
||||
read -r -d '' expected << EOM
|
||||
id,first,last
|
||||
1,john,smith
|
||||
1,jane,smith
|
||||
EOM
|
||||
|
||||
X=$(./yq utf8.csv)
|
||||
assertEquals "$expected" "$X"
|
||||
}
|
||||
|
||||
testInputTSV() {
|
||||
cat >test.tsv <<EOL
|
||||
fruit yumLevel
|
||||
apple 5
|
||||
banana 4
|
||||
EOL
|
||||
|
||||
read -r -d '' expected << EOM
|
||||
fruit yumLevel
|
||||
apple 5
|
||||
banana 4
|
||||
EOM
|
||||
|
||||
X=$(./yq e test.tsv)
|
||||
assertEquals "$expected" "$X"
|
||||
|
||||
X=$(./yq ea test.tsv)
|
||||
assertEquals "$expected" "$X"
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
testInputXml() {
|
||||
cat >test.xml <<EOL
|
||||
<cat legs="4">BiBi</cat>
|
||||
EOL
|
||||
|
||||
read -r -d '' expected << EOM
|
||||
<cat legs="4">BiBi</cat>
|
||||
EOM
|
||||
|
||||
X=$(./yq e test.xml)
|
||||
assertEquals "$expected" "$X"
|
||||
|
||||
X=$(./yq ea test.xml)
|
||||
assertEquals "$expected" "$X"
|
||||
}
|
||||
|
||||
testInputXmlNamespaces() {
|
||||
cat >test.xml <<EOL
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url">
|
||||
</map>
|
||||
EOL
|
||||
|
||||
read -r -d '' expected << EOM
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url"></map>
|
||||
EOM
|
||||
|
||||
X=$(./yq e test.xml)
|
||||
assertEquals "$expected" "$X"
|
||||
|
||||
X=$(./yq ea test.xml)
|
||||
assertEquals "$expected" "$X"
|
||||
}
|
||||
|
||||
|
||||
|
||||
testInputXmlStrict() {
|
||||
cat >test.xml <<EOL
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE root [
|
||||
<!ENTITY writer "Catherine.">
|
||||
<!ENTITY copyright "(r) Great">
|
||||
]>
|
||||
<root>
|
||||
<item>&writer;©right;</item>
|
||||
</root>
|
||||
EOL
|
||||
|
||||
X=$(./yq --xml-strict-mode test.xml 2>&1)
|
||||
assertEquals 1 $?
|
||||
assertEquals "Error: bad file 'test.xml': XML syntax error on line 7: invalid character entity &writer;" "$X"
|
||||
|
||||
X=$(./yq ea --xml-strict-mode test.xml 2>&1)
|
||||
assertEquals "Error: bad file 'test.xml': XML syntax error on line 7: invalid character entity &writer;" "$X"
|
||||
}
|
||||
|
||||
testInputXmlGithubAction() {
|
||||
cat >test.xml <<EOL
|
||||
<cat legs="4">BiBi</cat>
|
||||
EOL
|
||||
|
||||
read -r -d '' expected << EOM
|
||||
<cat legs="4">BiBi</cat>
|
||||
EOM
|
||||
|
||||
X=$(cat /dev/null | ./yq e test.xml)
|
||||
assertEquals "$expected" "$X"
|
||||
|
||||
X=$(cat /dev/null | ./yq ea test.xml)
|
||||
assertEquals "$expected" "$X"
|
||||
}
|
||||
|
||||
source ./scripts/shunit2
|
||||
@@ -109,7 +109,7 @@ EOL
|
||||
read -r -d '' expected << EOM
|
||||
cat:
|
||||
+content: BiBi
|
||||
+@legs: "4"
|
||||
+legs: "4"
|
||||
EOM
|
||||
|
||||
X=$(./yq e -p=xml test.yml)
|
||||
@@ -120,7 +120,7 @@ EOM
|
||||
}
|
||||
|
||||
testInputXmlNamespaces() {
|
||||
cat >test.xml <<EOL
|
||||
cat >test.yml <<EOL
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url">
|
||||
</map>
|
||||
@@ -129,15 +129,15 @@ EOL
|
||||
read -r -d '' expected << EOM
|
||||
+p_xml: version="1.0"
|
||||
map:
|
||||
+@xmlns: some-namespace
|
||||
+@xmlns:xsi: some-instance
|
||||
+@xsi:schemaLocation: some-url
|
||||
+xmlns: some-namespace
|
||||
+xmlns:xsi: some-instance
|
||||
+xsi:schemaLocation: some-url
|
||||
EOM
|
||||
|
||||
X=$(./yq e -p=xml test.xml)
|
||||
X=$(./yq e -p=xml test.yml)
|
||||
assertEquals "$expected" "$X"
|
||||
|
||||
X=$(./yq ea -p=xml test.xml)
|
||||
X=$(./yq ea -p=xml test.yml)
|
||||
assertEquals "$expected" "$X"
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ EOL
|
||||
read -r -d '' expected << EOM
|
||||
cat:
|
||||
+content: BiBi
|
||||
+@legs: "4"
|
||||
+legs: "4"
|
||||
EOM
|
||||
|
||||
X=$(cat /dev/null | ./yq e -p=xml test.yml)
|
||||
|
||||
@@ -34,82 +34,6 @@ EOM
|
||||
assertEquals "$expected" "$X"
|
||||
}
|
||||
|
||||
|
||||
testLeadingSeperatorWithNewlinesNewDoc() {
|
||||
cat >test.yml <<EOL
|
||||
# hi peeps
|
||||
# cool
|
||||
|
||||
|
||||
---
|
||||
a: test
|
||||
---
|
||||
b: cool
|
||||
EOL
|
||||
|
||||
read -r -d '' expected << EOM
|
||||
# hi peeps
|
||||
# cool
|
||||
|
||||
|
||||
---
|
||||
a: thing
|
||||
---
|
||||
b: cool
|
||||
EOM
|
||||
|
||||
X=$(./yq e '(select(di == 0) | .a) = "thing"' - < test.yml)
|
||||
assertEquals "$expected" "$X"
|
||||
}
|
||||
|
||||
testLeadingSeperatorWithNewlinesMoreComments() {
|
||||
cat >test.yml <<EOL
|
||||
# hi peeps
|
||||
# cool
|
||||
|
||||
---
|
||||
# great
|
||||
|
||||
a: test
|
||||
---
|
||||
b: cool
|
||||
EOL
|
||||
|
||||
read -r -d '' expected << EOM
|
||||
# hi peeps
|
||||
# cool
|
||||
|
||||
---
|
||||
# great
|
||||
|
||||
a: thing
|
||||
---
|
||||
b: cool
|
||||
EOM
|
||||
|
||||
X=$(./yq e '(select(di == 0) | .a) = "thing"' - < test.yml)
|
||||
assertEquals "$expected" "$X"
|
||||
}
|
||||
|
||||
|
||||
testLeadingSeperatorWithDirective() {
|
||||
cat >test.yml <<EOL
|
||||
%YAML 1.1
|
||||
---
|
||||
this: should really work
|
||||
EOL
|
||||
|
||||
read -r -d '' expected << EOM
|
||||
%YAML 1.1
|
||||
---
|
||||
this: should really work
|
||||
EOM
|
||||
|
||||
X=$(./yq < test.yml)
|
||||
assertEquals "$expected" "$X"
|
||||
}
|
||||
|
||||
|
||||
testLeadingSeperatorPipeIntoEvalSeq() {
|
||||
X=$(./yq e - < test.yml)
|
||||
expected=$(cat test.yml)
|
||||
|
||||
@@ -48,55 +48,6 @@ EOM
|
||||
assertEquals "$expected" "$X"
|
||||
}
|
||||
|
||||
testOutputYamlRawDefault() {
|
||||
cat >test.yml <<EOL
|
||||
a: "cat"
|
||||
EOL
|
||||
|
||||
X=$(./yq e '.a' test.yml)
|
||||
assertEquals "cat" "$X"
|
||||
|
||||
X=$(./yq ea '.a' test.yml)
|
||||
assertEquals "cat" "$X"
|
||||
}
|
||||
|
||||
testOutputYamlRawOff() {
|
||||
cat >test.yml <<EOL
|
||||
a: "cat"
|
||||
EOL
|
||||
|
||||
X=$(./yq e -r=false '.a' test.yml)
|
||||
assertEquals "\"cat\"" "$X"
|
||||
|
||||
X=$(./yq ea -r=false '.a' test.yml)
|
||||
assertEquals "\"cat\"" "$X"
|
||||
}
|
||||
|
||||
testOutputJsonRaw() {
|
||||
cat >test.yml <<EOL
|
||||
a: cat
|
||||
EOL
|
||||
|
||||
X=$(./yq e -r --output-format=json '.a' test.yml)
|
||||
assertEquals "cat" "$X"
|
||||
|
||||
X=$(./yq ea -r --output-format=json '.a' test.yml)
|
||||
assertEquals "cat" "$X"
|
||||
}
|
||||
|
||||
testOutputJsonDefault() {
|
||||
cat >test.yml <<EOL
|
||||
a: cat
|
||||
EOL
|
||||
|
||||
X=$(./yq e --output-format=json '.a' test.yml)
|
||||
assertEquals "\"cat\"" "$X"
|
||||
|
||||
X=$(./yq ea --output-format=json '.a' test.yml)
|
||||
assertEquals "\"cat\"" "$X"
|
||||
}
|
||||
|
||||
|
||||
testOutputJsonShort() {
|
||||
cat >test.yml <<EOL
|
||||
a: {b: ["cat"]}
|
||||
@@ -121,11 +72,11 @@ EOM
|
||||
|
||||
testOutputProperties() {
|
||||
cat >test.yml <<EOL
|
||||
a: {b: {c: ["cat cat"]}}
|
||||
a: {b: {c: ["cat"]}}
|
||||
EOL
|
||||
|
||||
read -r -d '' expected << EOM
|
||||
a.b.c.0 = cat cat
|
||||
a.b.c.0 = cat
|
||||
EOM
|
||||
|
||||
X=$(./yq e --output-format=props test.yml)
|
||||
@@ -135,30 +86,13 @@ EOM
|
||||
assertEquals "$expected" "$X"
|
||||
}
|
||||
|
||||
testOutputPropertiesDontUnwrap() {
|
||||
cat >test.yml <<EOL
|
||||
a: {b: {c: ["cat cat"]}}
|
||||
EOL
|
||||
|
||||
read -r -d '' expected << EOM
|
||||
a.b.c.0 = "cat cat"
|
||||
EOM
|
||||
|
||||
X=$(./yq e -r=false --output-format=props test.yml)
|
||||
assertEquals "$expected" "$X"
|
||||
|
||||
X=$(./yq ea -r=false --output-format=props test.yml)
|
||||
assertEquals "$expected" "$X"
|
||||
}
|
||||
|
||||
|
||||
testOutputPropertiesShort() {
|
||||
cat >test.yml <<EOL
|
||||
a: {b: {c: ["cat cat"]}}
|
||||
a: {b: {c: ["cat"]}}
|
||||
EOL
|
||||
|
||||
read -r -d '' expected << EOM
|
||||
a.b.c.0 = cat cat
|
||||
a.b.c.0 = cat
|
||||
EOM
|
||||
|
||||
X=$(./yq e -o=p test.yml)
|
||||
@@ -252,7 +186,7 @@ EOM
|
||||
|
||||
testOutputXmComplex() {
|
||||
cat >test.yml <<EOL
|
||||
a: {b: {c: ["cat", "dog"], +@f: meow}}
|
||||
a: {b: {c: ["cat", "dog"], +f: meow}}
|
||||
EOL
|
||||
|
||||
read -r -d '' expected << EOM
|
||||
|
||||
+3
-7
@@ -1,15 +1,11 @@
|
||||
package cmd
|
||||
|
||||
var unwrapScalarFlag = newUnwrapFlag()
|
||||
|
||||
var unwrapScalar = false
|
||||
var unwrapScalar = true
|
||||
|
||||
var writeInplace = false
|
||||
var outputToJSON = false
|
||||
|
||||
var outputFormat = ""
|
||||
|
||||
var inputFormat = ""
|
||||
var outputFormat = "yaml"
|
||||
var inputFormat = "yaml"
|
||||
|
||||
var exitStatus = false
|
||||
var forceColor = false
|
||||
|
||||
@@ -84,10 +84,7 @@ func evaluateAll(cmd *cobra.Command, args []string) (cmdError error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
encoder, err := configureEncoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
encoder := configureEncoder(format)
|
||||
|
||||
printer := yqlib.NewPrinter(encoder, printerWriter)
|
||||
|
||||
|
||||
@@ -93,10 +93,7 @@ func evaluateSequence(cmd *cobra.Command, args []string) (cmdError error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
encoder, err := configureEncoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
encoder := configureEncoder(format)
|
||||
|
||||
printer := yqlib.NewPrinter(encoder, printerWriter)
|
||||
|
||||
|
||||
+24
-16
@@ -35,7 +35,7 @@ yq -P sample.json
|
||||
return evaluateSequence(cmd, args)
|
||||
|
||||
},
|
||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
cmd.SetOut(cmd.OutOrStdout())
|
||||
|
||||
var format = logging.MustStringFormatter(
|
||||
@@ -52,8 +52,18 @@ yq -P sample.json
|
||||
|
||||
logging.SetBackend(backend)
|
||||
yqlib.InitExpressionParser()
|
||||
if (inputFormat == "x" || inputFormat == "xml") &&
|
||||
outputFormat != "x" && outputFormat != "xml" &&
|
||||
yqlib.ConfiguredXMLPreferences.AttributePrefix == "+" {
|
||||
yqlib.GetLogger().Warning("The default xml-attribute-prefix will change in the v4.30 to `+@` to avoid " +
|
||||
"naming conflicts with the default content name, directive name and proc inst prefix. If you need to keep " +
|
||||
"`+` please set that value explicityly with --xml-attribute-prefix.")
|
||||
}
|
||||
|
||||
return nil
|
||||
//copy preference form global setting
|
||||
yqlib.ConfiguredYamlPreferences.UnwrapScalar = unwrapScalar
|
||||
|
||||
yqlib.ConfiguredYamlPreferences.PrintDocSeparators = !noDocSeparators
|
||||
},
|
||||
}
|
||||
|
||||
@@ -65,18 +75,18 @@ yq -P sample.json
|
||||
panic(err)
|
||||
}
|
||||
|
||||
rootCmd.PersistentFlags().StringVarP(&outputFormat, "output-format", "o", "auto", "[auto|a|yaml|y|json|j|props|p|xml|x] output format type.")
|
||||
rootCmd.PersistentFlags().StringVarP(&inputFormat, "input-format", "p", "auto", "[auto|a|yaml|y|props|p|xml|x] parse format for input. Note that json is a subset of yaml.")
|
||||
rootCmd.PersistentFlags().StringVarP(&outputFormat, "output-format", "o", "yaml", "[yaml|y|json|j|props|p|xml|x] output format type.")
|
||||
rootCmd.PersistentFlags().StringVarP(&inputFormat, "input-format", "p", "yaml", "[yaml|y|props|p|xml|x] parse format for input. Note that json is a subset of yaml.")
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&yqlib.ConfiguredXMLPreferences.AttributePrefix, "xml-attribute-prefix", yqlib.ConfiguredXMLPreferences.AttributePrefix, "prefix for xml attributes")
|
||||
rootCmd.PersistentFlags().StringVar(&yqlib.ConfiguredXMLPreferences.ContentName, "xml-content-name", yqlib.ConfiguredXMLPreferences.ContentName, "name for xml content (if no attribute name is present).")
|
||||
rootCmd.PersistentFlags().BoolVar(&yqlib.ConfiguredXMLPreferences.StrictMode, "xml-strict-mode", yqlib.ConfiguredXMLPreferences.StrictMode, "enables strict parsing of XML. See https://pkg.go.dev/encoding/xml for more details.")
|
||||
rootCmd.PersistentFlags().BoolVar(&yqlib.ConfiguredXMLPreferences.KeepNamespace, "xml-keep-namespace", yqlib.ConfiguredXMLPreferences.KeepNamespace, "enables keeping namespace after parsing attributes")
|
||||
rootCmd.PersistentFlags().BoolVar(&yqlib.ConfiguredXMLPreferences.UseRawToken, "xml-raw-token", yqlib.ConfiguredXMLPreferences.UseRawToken, "enables using RawToken method instead Token. Commonly disables namespace translations. See https://pkg.go.dev/encoding/xml#Decoder.RawToken for details.")
|
||||
rootCmd.PersistentFlags().StringVar(&yqlib.ConfiguredXMLPreferences.ProcInstPrefix, "xml-proc-inst-prefix", yqlib.ConfiguredXMLPreferences.ProcInstPrefix, "prefix for xml processing instructions (e.g. <?xml version=\"1\"?>)")
|
||||
rootCmd.PersistentFlags().StringVar(&yqlib.ConfiguredXMLPreferences.DirectiveName, "xml-directive-name", yqlib.ConfiguredXMLPreferences.DirectiveName, "name for xml directives (e.g. <!DOCTYPE thing cat>)")
|
||||
rootCmd.PersistentFlags().BoolVar(&yqlib.ConfiguredXMLPreferences.SkipProcInst, "xml-skip-proc-inst", yqlib.ConfiguredXMLPreferences.SkipProcInst, "skip over process instructions (e.g. <?xml version=\"1\"?>)")
|
||||
rootCmd.PersistentFlags().BoolVar(&yqlib.ConfiguredXMLPreferences.SkipDirectives, "xml-skip-directives", yqlib.ConfiguredXMLPreferences.SkipDirectives, "skip over directives (e.g. <!DOCTYPE thing cat>)")
|
||||
rootCmd.PersistentFlags().StringVar(&yqlib.ConfiguredXMLPreferences.AttributePrefix, "xml-attribute-prefix", "+", "prefix for xml attributes")
|
||||
rootCmd.PersistentFlags().StringVar(&yqlib.ConfiguredXMLPreferences.ContentName, "xml-content-name", "+content", "name for xml content (if no attribute name is present).")
|
||||
rootCmd.PersistentFlags().BoolVar(&yqlib.ConfiguredXMLPreferences.StrictMode, "xml-strict-mode", false, "enables strict parsing of XML. See https://pkg.go.dev/encoding/xml for more details.")
|
||||
rootCmd.PersistentFlags().BoolVar(&yqlib.ConfiguredXMLPreferences.KeepNamespace, "xml-keep-namespace", true, "enables keeping namespace after parsing attributes")
|
||||
rootCmd.PersistentFlags().BoolVar(&yqlib.ConfiguredXMLPreferences.UseRawToken, "xml-raw-token", true, "enables using RawToken method instead Token. Commonly disables namespace translations. See https://pkg.go.dev/encoding/xml#Decoder.RawToken for details.")
|
||||
rootCmd.PersistentFlags().StringVar(&yqlib.ConfiguredXMLPreferences.ProcInstPrefix, "xml-proc-inst-prefix", "+p_", "prefix for xml processing instructions (e.g. <?xml version=\"1\"?>)")
|
||||
rootCmd.PersistentFlags().StringVar(&yqlib.ConfiguredXMLPreferences.DirectiveName, "xml-directive-name", "+directive", "name for xml directives (e.g. <!DOCTYPE thing cat>)")
|
||||
rootCmd.PersistentFlags().BoolVar(&yqlib.ConfiguredXMLPreferences.SkipProcInst, "xml-skip-proc-inst", false, "skip over process instructions (e.g. <?xml version=\"1\"?>)")
|
||||
rootCmd.PersistentFlags().BoolVar(&yqlib.ConfiguredXMLPreferences.SkipDirectives, "xml-skip-directives", false, "skip over directives (e.g. <!DOCTYPE thing cat>)")
|
||||
|
||||
rootCmd.PersistentFlags().BoolVarP(&nullInput, "null-input", "n", false, "Don't read input, simply evaluate the expression given. Useful for creating docs from scratch.")
|
||||
rootCmd.PersistentFlags().BoolVarP(&noDocSeparators, "no-doc", "N", false, "Don't print document separators (---)")
|
||||
@@ -84,9 +94,7 @@ yq -P sample.json
|
||||
rootCmd.PersistentFlags().IntVarP(&indent, "indent", "I", 2, "sets indent level for output")
|
||||
rootCmd.Flags().BoolVarP(&version, "version", "V", false, "Print version information and quit")
|
||||
rootCmd.PersistentFlags().BoolVarP(&writeInplace, "inplace", "i", false, "update the file inplace of first file given.")
|
||||
rootCmd.PersistentFlags().VarP(unwrapScalarFlag, "unwrapScalar", "r", "unwrap scalar, print the value with no quotes, colors or comments. Defaults to true for yaml")
|
||||
rootCmd.PersistentFlags().Lookup("unwrapScalar").NoOptDefVal = "true"
|
||||
|
||||
rootCmd.PersistentFlags().BoolVarP(&unwrapScalar, "unwrapScalar", "r", true, "unwrap scalar, print the value with no quotes, colors or comments")
|
||||
rootCmd.PersistentFlags().BoolVarP(&prettyPrint, "prettyPrint", "P", false, "pretty print, shorthand for '... style = \"\"'")
|
||||
rootCmd.PersistentFlags().BoolVarP(&exitStatus, "exit-status", "e", false, "set exit status if there are no matches or null or false is returned")
|
||||
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/spf13/pflag"
|
||||
)
|
||||
|
||||
type boolFlag interface {
|
||||
pflag.Value
|
||||
IsExplicitySet() bool
|
||||
IsSet() bool
|
||||
}
|
||||
|
||||
type unwrapScalarFlagStrc struct {
|
||||
explicitySet bool
|
||||
value bool
|
||||
}
|
||||
|
||||
func newUnwrapFlag() boolFlag {
|
||||
return &unwrapScalarFlagStrc{value: true}
|
||||
}
|
||||
|
||||
func (f *unwrapScalarFlagStrc) IsExplicitySet() bool {
|
||||
return f.explicitySet
|
||||
}
|
||||
|
||||
func (f *unwrapScalarFlagStrc) IsSet() bool {
|
||||
return f.value
|
||||
}
|
||||
|
||||
func (f *unwrapScalarFlagStrc) String() string {
|
||||
return strconv.FormatBool(f.value)
|
||||
}
|
||||
|
||||
func (f *unwrapScalarFlagStrc) Set(value string) error {
|
||||
|
||||
v, err := strconv.ParseBool(value)
|
||||
f.value = v
|
||||
f.explicitySet = true
|
||||
return err
|
||||
}
|
||||
|
||||
func (*unwrapScalarFlagStrc) Type() string {
|
||||
return "bool"
|
||||
}
|
||||
+12
-76
@@ -53,48 +53,6 @@ func initCommand(cmd *cobra.Command, args []string) (string, []string, error) {
|
||||
return "", nil, fmt.Errorf("cannot pass files in when using null-input flag")
|
||||
}
|
||||
|
||||
inputFilename := ""
|
||||
if len(args) > 0 {
|
||||
inputFilename = args[0]
|
||||
}
|
||||
if inputFormat == "" || inputFormat == "auto" || inputFormat == "a" {
|
||||
|
||||
inputFormat = yqlib.FormatFromFilename(inputFilename)
|
||||
if outputFormat == "" || outputFormat == "auto" || outputFormat == "a" {
|
||||
outputFormat = yqlib.FormatFromFilename(inputFilename)
|
||||
}
|
||||
} else if outputFormat == "" || outputFormat == "auto" || outputFormat == "a" {
|
||||
// backwards compatibility -
|
||||
// before this was introduced, `yq -pcsv things.csv`
|
||||
// would produce *yaml* output.
|
||||
//
|
||||
outputFormat = yqlib.FormatFromFilename(inputFilename)
|
||||
if inputFilename != "-" {
|
||||
yqlib.GetLogger().Warning("yq default output is now 'auto' (based on the filename extension). Normally yq would output '%v', but for backwards compatibility 'yaml' has been set. Please use -oy to specify yaml, or drop the -p flag.", outputFormat)
|
||||
}
|
||||
outputFormat = "yaml"
|
||||
}
|
||||
|
||||
outputFormatType, err := yqlib.OutputFormatFromString(outputFormat)
|
||||
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
yqlib.GetLogger().Debug("Using outputformat %v", outputFormat)
|
||||
|
||||
if outputFormatType == yqlib.YamlOutputFormat ||
|
||||
outputFormatType == yqlib.PropsOutputFormat {
|
||||
unwrapScalar = true
|
||||
}
|
||||
if unwrapScalarFlag.IsExplicitySet() {
|
||||
unwrapScalar = unwrapScalarFlag.IsSet()
|
||||
}
|
||||
|
||||
//copy preference form global setting
|
||||
yqlib.ConfiguredYamlPreferences.UnwrapScalar = unwrapScalar
|
||||
|
||||
yqlib.ConfiguredYamlPreferences.PrintDocSeparators = !noDocSeparators
|
||||
|
||||
return expression, args, nil
|
||||
}
|
||||
|
||||
@@ -103,15 +61,7 @@ func configureDecoder(evaluateTogether bool) (yqlib.Decoder, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
yqlibDecoder, err := createDecoder(yqlibInputFormat, evaluateTogether)
|
||||
if yqlibDecoder == nil {
|
||||
return nil, fmt.Errorf("no support for %s input format", inputFormat)
|
||||
}
|
||||
return yqlibDecoder, err
|
||||
}
|
||||
|
||||
func createDecoder(format yqlib.InputFormat, evaluateTogether bool) (yqlib.Decoder, error) {
|
||||
switch format {
|
||||
switch yqlibInputFormat {
|
||||
case yqlib.XMLInputFormat:
|
||||
return yqlib.NewXMLDecoder(yqlib.ConfiguredXMLPreferences), nil
|
||||
case yqlib.PropertiesInputFormat:
|
||||
@@ -122,12 +72,10 @@ func createDecoder(format yqlib.InputFormat, evaluateTogether bool) (yqlib.Decod
|
||||
return yqlib.NewCSVObjectDecoder(','), nil
|
||||
case yqlib.TSVObjectInputFormat:
|
||||
return yqlib.NewCSVObjectDecoder('\t'), nil
|
||||
case yqlib.YamlInputFormat:
|
||||
prefs := yqlib.ConfiguredYamlPreferences
|
||||
prefs.EvaluateTogether = evaluateTogether
|
||||
return yqlib.NewYamlDecoder(prefs), nil
|
||||
}
|
||||
return nil, fmt.Errorf("invalid decoder: %v", format)
|
||||
prefs := yqlib.ConfiguredYamlPreferences
|
||||
prefs.EvaluateTogether = evaluateTogether
|
||||
return yqlib.NewYamlDecoder(prefs), nil
|
||||
}
|
||||
|
||||
func configurePrinterWriter(format yqlib.PrinterOutputFormat, out io.Writer) (yqlib.PrinterWriter, error) {
|
||||
@@ -147,34 +95,22 @@ func configurePrinterWriter(format yqlib.PrinterOutputFormat, out io.Writer) (yq
|
||||
return printerWriter, nil
|
||||
}
|
||||
|
||||
func configureEncoder() (yqlib.Encoder, error) {
|
||||
yqlibOutputFormat, err := yqlib.OutputFormatFromString(outputFormat)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
yqlibEncoder, err := createEncoder(yqlibOutputFormat)
|
||||
if yqlibEncoder == nil {
|
||||
return nil, fmt.Errorf("no support for %s output format", outputFormat)
|
||||
}
|
||||
return yqlibEncoder, err
|
||||
}
|
||||
|
||||
func createEncoder(format yqlib.PrinterOutputFormat) (yqlib.Encoder, error) {
|
||||
func configureEncoder(format yqlib.PrinterOutputFormat) yqlib.Encoder {
|
||||
switch format {
|
||||
case yqlib.JSONOutputFormat:
|
||||
return yqlib.NewJSONEncoder(indent, colorsEnabled, unwrapScalar), nil
|
||||
return yqlib.NewJSONEncoder(indent, colorsEnabled)
|
||||
case yqlib.PropsOutputFormat:
|
||||
return yqlib.NewPropertiesEncoder(unwrapScalar), nil
|
||||
return yqlib.NewPropertiesEncoder(unwrapScalar)
|
||||
case yqlib.CSVOutputFormat:
|
||||
return yqlib.NewCsvEncoder(','), nil
|
||||
return yqlib.NewCsvEncoder(',')
|
||||
case yqlib.TSVOutputFormat:
|
||||
return yqlib.NewCsvEncoder('\t'), nil
|
||||
return yqlib.NewCsvEncoder('\t')
|
||||
case yqlib.YamlOutputFormat:
|
||||
return yqlib.NewYamlEncoder(indent, colorsEnabled, yqlib.ConfiguredYamlPreferences), nil
|
||||
return yqlib.NewYamlEncoder(indent, colorsEnabled, yqlib.ConfiguredYamlPreferences)
|
||||
case yqlib.XMLOutputFormat:
|
||||
return yqlib.NewXMLEncoder(indent, yqlib.ConfiguredXMLPreferences), nil
|
||||
return yqlib.NewXMLEncoder(indent, yqlib.ConfiguredXMLPreferences)
|
||||
}
|
||||
return nil, fmt.Errorf("invalid encoder: %v", format)
|
||||
panic("invalid encoder")
|
||||
}
|
||||
|
||||
// this is a hack to enable backwards compatibility with githubactions (which pipe /dev/null into everything)
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ var (
|
||||
GitDescribe string
|
||||
|
||||
// Version is main version number that is being run at the moment.
|
||||
Version = "v4.32.1"
|
||||
Version = "4.28.2"
|
||||
|
||||
// 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
|
||||
|
||||
Vendored
+1
-1
@@ -103,7 +103,7 @@ yq (4.16.2) focal; urgency=medium
|
||||
* Improved extract-checksum.sh
|
||||
* Bump github.com/spf13/cobra from 1.2.1 to 1.3.0 (#1039)
|
||||
* enable more linters (#1043)
|
||||
* Bump Golang compiler #1037
|
||||
* Bump golang compiler #1037
|
||||
|
||||
-- Roberto Mier Escandon <rmescandon@gmail.com> Tue, 21 Dec 2021 09:41:44 +0000
|
||||
|
||||
|
||||
+5
-1
@@ -1 +1,5 @@
|
||||
["foobar", "foobaz", "blarp"]
|
||||
volumes:
|
||||
beehive-conf:
|
||||
driver: local
|
||||
services:
|
||||
- beehive
|
||||
+7
-1
@@ -1 +1,7 @@
|
||||
<cat>3</cat>
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE config SYSTEM "/etc/iwatch/iwatch.dtd" >
|
||||
<apple>
|
||||
<?coolioo version="1.0"?>
|
||||
<!DOCTYPE config SYSTEM "/etc/iwatch/iwatch.dtd" >
|
||||
<b>things</b>
|
||||
</apple>
|
||||
@@ -1,3 +1,3 @@
|
||||
name,numberOfCats,likesApples,height
|
||||
,1,true,168.8
|
||||
Gary,1,true,168.8
|
||||
Samantha's Rabbit,2,false,-188.8
|
||||
|
@@ -1,4 +0,0 @@
|
||||
---
|
||||
# comment
|
||||
# about things
|
||||
a: cat
|
||||
@@ -1,31 +1,31 @@
|
||||
module github.com/mikefarah/yq/v4
|
||||
|
||||
require (
|
||||
github.com/a8m/envsubst v1.4.2
|
||||
github.com/a8m/envsubst v1.3.0
|
||||
github.com/alecthomas/participle/v2 v2.0.0-beta.5
|
||||
github.com/alecthomas/repr v0.2.0
|
||||
github.com/alecthomas/repr v0.1.1
|
||||
github.com/dimchansky/utfbom v1.1.1
|
||||
github.com/elliotchance/orderedmap v1.5.0
|
||||
github.com/fatih/color v1.15.0
|
||||
github.com/goccy/go-json v0.10.1
|
||||
github.com/goccy/go-yaml v1.10.0
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/goccy/go-json v0.9.11
|
||||
github.com/goccy/go-yaml v1.9.6
|
||||
github.com/jinzhu/copier v0.3.5
|
||||
github.com/magiconair/properties v1.8.7
|
||||
github.com/magiconair/properties v1.8.6
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
golang.org/x/net v0.8.0
|
||||
golang.org/x/net v0.0.0-20220708220712-1185a9018129
|
||||
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
golang.org/x/text v0.8.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
|
||||
)
|
||||
|
||||
go 1.20
|
||||
go 1.19
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
github.com/a8m/envsubst v1.4.2 h1:4yWIHXOLEJHQEFd4UjrWDrYeYlV7ncFWJOCBRLOZHQg=
|
||||
github.com/a8m/envsubst v1.4.2/go.mod h1:MVUTQNGQ3tsjOOtKCNd+fl8RzhsXcDvvAEzkhGtlsbY=
|
||||
github.com/a8m/envsubst v1.3.0 h1:GmXKmVssap0YtlU3E230W98RWtWCyIZzjtf1apWWyAg=
|
||||
github.com/a8m/envsubst v1.3.0/go.mod h1:MVUTQNGQ3tsjOOtKCNd+fl8RzhsXcDvvAEzkhGtlsbY=
|
||||
github.com/alecthomas/assert/v2 v2.0.3 h1:WKqJODfOiQG0nEJKFKzDIG3E29CN2/4zR9XGJzKIkbg=
|
||||
github.com/alecthomas/participle/v2 v2.0.0-beta.5 h1:y6dsSYVb1G5eK6mgmy+BgI3Mw35a3WghArZ/Hbebrjo=
|
||||
github.com/alecthomas/participle/v2 v2.0.0-beta.5/go.mod h1:RC764t6n4L8D8ITAJv0qdokritYSNR3wV5cVwmIEaMM=
|
||||
github.com/alecthomas/repr v0.2.0 h1:HAzS41CIzNW5syS8Mf9UwXhNH1J9aix/BvDRf1Ml2Yk=
|
||||
github.com/alecthomas/repr v0.2.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/alecthomas/repr v0.1.1 h1:87P60cSmareLAxMc4Hro0r2RBY4ROm0dYwkJNpS4pPs=
|
||||
github.com/alecthomas/repr v0.1.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -13,32 +13,31 @@ github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/
|
||||
github.com/elliotchance/orderedmap v1.5.0 h1:1IsExUsjv5XNBD3ZdC7jkAAqLWOOKdbPTmkHx63OsBg=
|
||||
github.com/elliotchance/orderedmap v1.5.0/go.mod h1:wsDwEaX5jEoyhbs7x93zk2H/qv0zwuhg4inXhDkYqys=
|
||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
github.com/goccy/go-json v0.10.1 h1:lEs5Ob+oOG/Ze199njvzHbhn6p9T+h64F5hRj69iTTo=
|
||||
github.com/goccy/go-json v0.10.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-yaml v1.10.0 h1:rBi+5HGuznOxx0JZ+60LDY85gc0dyIJCIMvsMJTKSKQ=
|
||||
github.com/goccy/go-yaml v1.10.0/go.mod h1:h/18Lr6oSQ3mvmqFoWmQ47KChOgpfHpTyIHl3yVmpiY=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
|
||||
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-yaml v1.9.6 h1:KhAu1zf9JXnm3vbG49aDE0E5uEBUsM4uwD31/58ZWyI=
|
||||
github.com/goccy/go-yaml v1.9.6/go.mod h1:JubOolP3gh0HpiBc4BLRD4YmjEjHAmIIB2aaXKkTfoE=
|
||||
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
|
||||
github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
||||
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
@@ -55,20 +54,21 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.0.0-20220708220712-1185a9018129 h1:vucSRfWwTsoXro7P+3Cjlr6flUMtzCwzlvkxEQtHHB0=
|
||||
golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e h1:NHvCuwuS43lGnYhten69ZWqi2QOj/CiDNcKbVqwVoew=
|
||||
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0=
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
//go:build linux
|
||||
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func changeOwner(info fs.FileInfo, file *os.File) error {
|
||||
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
|
||||
uid := int(stat.Uid)
|
||||
gid := int(stat.Gid)
|
||||
|
||||
err := os.Chown(file.Name(), uid, gid)
|
||||
if err != nil {
|
||||
// this happens with snap confinement
|
||||
// not really a big issue as users can chown
|
||||
// the file themselves if required.
|
||||
log.Info("Skipping chown: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
//go:build !linux
|
||||
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
)
|
||||
|
||||
func changeOwner(info fs.FileInfo, file *os.File) error {
|
||||
return nil
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package yqlib
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
@@ -15,7 +16,7 @@ type compareTypePref struct {
|
||||
func compareOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
log.Debugf("-- compareOperator")
|
||||
prefs := expressionNode.Operation.Preferences.(compareTypePref)
|
||||
return crossFunction(d, context, expressionNode, compare(prefs), true)
|
||||
return crossFunction(d, context.ReadOnlyClone(), expressionNode, compare(prefs), true)
|
||||
}
|
||||
|
||||
func compare(prefs compareTypePref) func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
@@ -53,12 +54,12 @@ func compare(prefs compareTypePref) func(d *dataTreeNavigator, context Context,
|
||||
}
|
||||
|
||||
func compareDateTime(layout string, prefs compareTypePref, lhs *yaml.Node, rhs *yaml.Node) (bool, error) {
|
||||
lhsTime, err := parseDateTime(layout, lhs.Value)
|
||||
lhsTime, err := time.Parse(layout, lhs.Value)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
rhsTime, err := parseDateTime(layout, rhs.Value)
|
||||
rhsTime, err := time.Parse(layout, rhs.Value)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
@@ -79,8 +80,8 @@ func compareScalars(context Context, prefs compareTypePref, lhs *yaml.Node, rhs
|
||||
|
||||
isDateTime := lhs.Tag == "!!timestamp"
|
||||
// if the lhs is a string, it might be a timestamp in a custom format.
|
||||
if lhsTag == "!!str" {
|
||||
_, err := parseDateTime(context.GetDateTimeLayout(), lhs.Value)
|
||||
if lhsTag == "!!str" && context.GetDateTimeLayout() != time.RFC3339 {
|
||||
_, err := time.Parse(context.GetDateTimeLayout(), lhs.Value)
|
||||
isDateTime = err == nil
|
||||
}
|
||||
if isDateTime {
|
||||
@@ -126,10 +127,6 @@ func compareScalars(context Context, prefs compareTypePref, lhs *yaml.Node, rhs
|
||||
return lhs.Value > rhs.Value, nil
|
||||
}
|
||||
return lhs.Value < rhs.Value, nil
|
||||
} else if lhsTag == "!!null" && rhsTag == "!!null" && prefs.OrEqual {
|
||||
return true, nil
|
||||
} else if lhsTag == "!!null" || rhsTag == "!!null" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("%v not yet supported for comparison", lhs.Tag)
|
||||
@@ -14,20 +14,6 @@ var compareOperatorScenarios = []expressionScenario{
|
||||
"D0, P[a], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
expression: "(.k | length) >= 0",
|
||||
expected: []string{
|
||||
"D0, P[k], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
expression: `"2022-01-30T15:53:09Z" > "2020-01-30T15:53:09Z"`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: "a: 5\nb: 4",
|
||||
@@ -299,28 +285,28 @@ var compareOperatorScenarios = []expressionScenario{
|
||||
description: "Both sides are null: > is false",
|
||||
expression: ".a > .b",
|
||||
expected: []string{
|
||||
"D0, P[a], (!!bool)::false\n",
|
||||
"D0, P[], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
expression: ".a < .b",
|
||||
expected: []string{
|
||||
"D0, P[a], (!!bool)::false\n",
|
||||
"D0, P[], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Both sides are null: >= is true",
|
||||
expression: ".a >= .b",
|
||||
expected: []string{
|
||||
"D0, P[a], (!!bool)::true\n",
|
||||
"D0, P[], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
expression: ".a <= .b",
|
||||
expected: []string{
|
||||
"D0, P[a], (!!bool)::true\n",
|
||||
"D0, P[], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -364,7 +350,7 @@ var compareOperatorScenarios = []expressionScenario{
|
||||
document: `a: 5`,
|
||||
expression: ".b <= .a",
|
||||
expected: []string{
|
||||
"D0, P[b], (!!bool)::false\n",
|
||||
"D0, P[a], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -372,7 +358,7 @@ var compareOperatorScenarios = []expressionScenario{
|
||||
document: `a: 5`,
|
||||
expression: ".b < .a",
|
||||
expected: []string{
|
||||
"D0, P[b], (!!bool)::false\n",
|
||||
"D0, P[a], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
+9
-18
@@ -12,9 +12,7 @@ const csvSimple = `name,numberOfCats,likesApples,height
|
||||
Gary,1,true,168.8
|
||||
Samantha's Rabbit,2,false,-188.8
|
||||
`
|
||||
const csvMissing = `name,numberOfCats,likesApples,height
|
||||
,null,,168.8
|
||||
`
|
||||
|
||||
const expectedUpdatedSimpleCsv = `name,numberOfCats,likesApples,height
|
||||
Gary,3,true,168.8
|
||||
Samantha's Rabbit,2,false,-188.8
|
||||
@@ -112,13 +110,6 @@ var csvScenarios = []formatScenario{
|
||||
expected: csvSimpleMissingData,
|
||||
scenarioType: "encode-csv",
|
||||
},
|
||||
{
|
||||
description: "decode csv missing",
|
||||
skipDoc: true,
|
||||
input: csvMissing,
|
||||
expected: csvMissing,
|
||||
scenarioType: "roundtrip-csv",
|
||||
},
|
||||
{
|
||||
description: "Parse CSV into an array of objects",
|
||||
subdescription: "First row is assumed to be the header row.",
|
||||
@@ -145,15 +136,15 @@ var csvScenarios = []formatScenario{
|
||||
func testCSVScenario(t *testing.T, s formatScenario) {
|
||||
switch s.scenarioType {
|
||||
case "encode-csv":
|
||||
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewCsvEncoder(',')), s.description)
|
||||
test.AssertResultWithContext(t, s.expected, processFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewCsvEncoder(',')), s.description)
|
||||
case "encode-tsv":
|
||||
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewCsvEncoder('\t')), s.description)
|
||||
test.AssertResultWithContext(t, s.expected, processFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewCsvEncoder('\t')), s.description)
|
||||
case "decode-csv-object":
|
||||
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewCSVObjectDecoder(','), NewYamlEncoder(2, false, ConfiguredYamlPreferences)), s.description)
|
||||
test.AssertResultWithContext(t, s.expected, processFormatScenario(s, NewCSVObjectDecoder(','), NewYamlEncoder(2, false, ConfiguredYamlPreferences)), s.description)
|
||||
case "decode-tsv-object":
|
||||
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewCSVObjectDecoder('\t'), NewYamlEncoder(2, false, ConfiguredYamlPreferences)), s.description)
|
||||
test.AssertResultWithContext(t, s.expected, processFormatScenario(s, NewCSVObjectDecoder('\t'), NewYamlEncoder(2, false, ConfiguredYamlPreferences)), s.description)
|
||||
case "roundtrip-csv":
|
||||
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewCSVObjectDecoder(','), NewCsvEncoder(',')), s.description)
|
||||
test.AssertResultWithContext(t, s.expected, processFormatScenario(s, NewCSVObjectDecoder(','), NewCsvEncoder(',')), s.description)
|
||||
default:
|
||||
panic(fmt.Sprintf("unhandled scenario type %q", s.scenarioType))
|
||||
}
|
||||
@@ -180,7 +171,7 @@ func documentCSVDecodeObjectScenario(w *bufio.Writer, s formatScenario, formatTy
|
||||
}
|
||||
|
||||
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n",
|
||||
mustProcessFormatScenario(s, NewCSVObjectDecoder(separator), NewYamlEncoder(s.indent, false, ConfiguredYamlPreferences))),
|
||||
processFormatScenario(s, NewCSVObjectDecoder(separator), NewYamlEncoder(s.indent, false, ConfiguredYamlPreferences))),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -212,7 +203,7 @@ func documentCSVEncodeScenario(w *bufio.Writer, s formatScenario, formatType str
|
||||
}
|
||||
|
||||
writeOrPanic(w, fmt.Sprintf("```%v\n%v```\n\n", formatType,
|
||||
mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewCsvEncoder(separator))),
|
||||
processFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewCsvEncoder(separator))),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -244,7 +235,7 @@ func documentCSVRoundTripScenario(w *bufio.Writer, s formatScenario, formatType
|
||||
}
|
||||
|
||||
writeOrPanic(w, fmt.Sprintf("```%v\n%v```\n\n", formatType,
|
||||
mustProcessFormatScenario(s, NewCSVObjectDecoder(separator), NewCsvEncoder(separator))),
|
||||
processFormatScenario(s, NewCSVObjectDecoder(separator), NewCsvEncoder(separator))),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+3
-21
@@ -3,7 +3,6 @@ package yqlib
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type InputFormat uint
|
||||
@@ -16,7 +15,6 @@ const (
|
||||
JsonInputFormat
|
||||
CSVObjectInputFormat
|
||||
TSVObjectInputFormat
|
||||
UriInputFormat
|
||||
)
|
||||
|
||||
type Decoder interface {
|
||||
@@ -26,11 +24,11 @@ type Decoder interface {
|
||||
|
||||
func InputFormatFromString(format string) (InputFormat, error) {
|
||||
switch format {
|
||||
case "yaml", "yml", "y":
|
||||
case "yaml", "y":
|
||||
return YamlInputFormat, nil
|
||||
case "xml", "x":
|
||||
return XMLInputFormat, nil
|
||||
case "properties", "props", "p":
|
||||
case "props", "p":
|
||||
return PropertiesInputFormat, nil
|
||||
case "json", "ndjson", "j":
|
||||
return JsonInputFormat, nil
|
||||
@@ -39,22 +37,6 @@ func InputFormatFromString(format string) (InputFormat, error) {
|
||||
case "tsv", "t":
|
||||
return TSVObjectInputFormat, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("unknown format '%v' please use [yaml|json|props|csv|tsv|xml]", format)
|
||||
return 0, fmt.Errorf("unknown format '%v' please use [yaml|xml|props]", format)
|
||||
}
|
||||
}
|
||||
|
||||
func FormatFromFilename(filename string) string {
|
||||
|
||||
if filename != "" {
|
||||
GetLogger().Debugf("checking file extension '%s' for auto format detection", filename)
|
||||
nPos := strings.LastIndex(filename, ".")
|
||||
if nPos > -1 {
|
||||
format := filename[nPos+1:]
|
||||
GetLogger().Debugf("detected format '%s'", format)
|
||||
return format
|
||||
}
|
||||
}
|
||||
|
||||
GetLogger().Debugf("using default inputFormat 'yaml'")
|
||||
return "yaml"
|
||||
}
|
||||
|
||||
@@ -4,33 +4,10 @@ import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type base64Padder struct {
|
||||
count uint64
|
||||
io.Reader
|
||||
}
|
||||
|
||||
func (c *base64Padder) pad(buf []byte) (int, error) {
|
||||
pad := strings.Repeat("=", int(4-c.count%4))
|
||||
n, err := strings.NewReader(pad).Read(buf)
|
||||
c.count += uint64(n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (c *base64Padder) Read(buf []byte) (int, error) {
|
||||
n, err := c.Reader.Read(buf)
|
||||
c.count += uint64(n)
|
||||
|
||||
if err == io.EOF && c.count%4 != 0 {
|
||||
return c.pad(buf)
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
type base64Decoder struct {
|
||||
reader io.Reader
|
||||
finished bool
|
||||
@@ -43,7 +20,7 @@ func NewBase64Decoder() Decoder {
|
||||
}
|
||||
|
||||
func (dec *base64Decoder) Init(reader io.Reader) error {
|
||||
dec.reader = &base64Padder{Reader: reader}
|
||||
dec.reader = reader
|
||||
dec.readAnything = false
|
||||
dec.finished = false
|
||||
return nil
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build !yq_nojson
|
||||
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
@@ -62,7 +60,7 @@ func (dec *jsonDecoder) convertToYamlNode(data *orderedMap) (*yaml.Node, error)
|
||||
}
|
||||
}
|
||||
|
||||
var yamlMap = &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}
|
||||
var yamlMap = &yaml.Node{Kind: yaml.MappingNode}
|
||||
for _, keyValuePair := range data.kv {
|
||||
yamlValue, err := dec.convertToYamlNode(&keyValuePair.V)
|
||||
if err != nil {
|
||||
@@ -76,7 +74,7 @@ func (dec *jsonDecoder) convertToYamlNode(data *orderedMap) (*yaml.Node, error)
|
||||
|
||||
func (dec *jsonDecoder) parseArray(dataArray []*orderedMap) (*yaml.Node, error) {
|
||||
|
||||
var yamlMap = &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
|
||||
var yamlMap = &yaml.Node{Kind: yaml.SequenceNode}
|
||||
|
||||
for _, value := range dataArray {
|
||||
yamlValue, err := dec.convertToYamlNode(value)
|
||||
|
||||
@@ -63,7 +63,7 @@ func (dec *propertiesDecoder) applyPropertyComments(context Context, path []inte
|
||||
|
||||
rhsCandidateNode.Node.Tag = guessTagFromCustomType(rhsCandidateNode.Node)
|
||||
|
||||
rhsOp := &Operation{OperationType: referenceOpType, CandidateNode: rhsCandidateNode}
|
||||
rhsOp := &Operation{OperationType: valueOpType, CandidateNode: rhsCandidateNode}
|
||||
|
||||
assignmentOpNode := &ExpressionNode{
|
||||
Operation: assignmentOp,
|
||||
@@ -102,7 +102,7 @@ func (dec *propertiesDecoder) applyProperty(context Context, properties *propert
|
||||
|
||||
assignmentOp := &Operation{OperationType: assignOpType, Preferences: assignPreferences{}}
|
||||
|
||||
rhsOp := &Operation{OperationType: referenceOpType, CandidateNode: rhsCandidateNode}
|
||||
rhsOp := &Operation{OperationType: valueOpType, CandidateNode: rhsCandidateNode}
|
||||
|
||||
assignmentOpNode := &ExpressionNode{
|
||||
Operation: assignmentOp,
|
||||
|
||||
@@ -15,10 +15,10 @@ type formatScenario struct {
|
||||
subdescription string
|
||||
skipDoc bool
|
||||
scenarioType string
|
||||
expectedError string
|
||||
}
|
||||
|
||||
func processFormatScenario(s formatScenario, decoder Decoder, encoder Encoder) (string, error) {
|
||||
func processFormatScenario(s formatScenario, decoder Decoder, encoder Encoder) string {
|
||||
|
||||
var output bytes.Buffer
|
||||
writer := bufio.NewWriter(&output)
|
||||
|
||||
@@ -28,7 +28,7 @@ func processFormatScenario(s formatScenario, decoder Decoder, encoder Encoder) (
|
||||
|
||||
inputs, err := readDocuments(strings.NewReader(s.input), "sample.yml", 0, decoder)
|
||||
if err != nil {
|
||||
return "", err
|
||||
panic(err)
|
||||
}
|
||||
|
||||
expression := s.expression
|
||||
@@ -39,31 +39,22 @@ func processFormatScenario(s formatScenario, decoder Decoder, encoder Encoder) (
|
||||
exp, err := getExpressionParser().ParseExpression(expression)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
panic(err)
|
||||
}
|
||||
|
||||
context, err := NewDataTreeNavigator().GetMatchingNodes(Context{MatchingNodes: inputs}, exp)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
panic(err)
|
||||
}
|
||||
|
||||
printer := NewPrinter(encoder, NewSinglePrinterWriter(writer))
|
||||
err = printer.PrintResults(context.MatchingNodes)
|
||||
if err != nil {
|
||||
return "", err
|
||||
panic(err)
|
||||
}
|
||||
writer.Flush()
|
||||
|
||||
return output.String(), nil
|
||||
}
|
||||
|
||||
func mustProcessFormatScenario(s formatScenario, decoder Decoder, encoder Encoder) string {
|
||||
|
||||
result, err := processFormatScenario(s, decoder, encoder)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return result
|
||||
return output.String()
|
||||
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/url"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type uriDecoder struct {
|
||||
reader io.Reader
|
||||
finished bool
|
||||
readAnything bool
|
||||
}
|
||||
|
||||
func NewUriDecoder() Decoder {
|
||||
return &uriDecoder{finished: false}
|
||||
}
|
||||
|
||||
func (dec *uriDecoder) Init(reader io.Reader) error {
|
||||
dec.reader = reader
|
||||
dec.readAnything = false
|
||||
dec.finished = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dec *uriDecoder) Decode() (*CandidateNode, error) {
|
||||
if dec.finished {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
if _, err := buf.ReadFrom(dec.reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf.Len() == 0 {
|
||||
dec.finished = true
|
||||
|
||||
// if we've read _only_ an empty string, lets return that
|
||||
// otherwise if we've already read some bytes, and now we get
|
||||
// an empty string, then we are done.
|
||||
if dec.readAnything {
|
||||
return nil, io.EOF
|
||||
}
|
||||
}
|
||||
newValue, err := url.QueryUnescape(buf.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dec.readAnything = true
|
||||
return &CandidateNode{
|
||||
Node: &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: "!!str",
|
||||
Value: newValue,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
+24
-85
@@ -1,13 +1,9 @@
|
||||
//go:build !yq_noxml
|
||||
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
@@ -49,33 +45,23 @@ func (dec *xmlDecoder) createSequence(nodes []*xmlNode) (*yaml.Node, error) {
|
||||
return yamlNode, nil
|
||||
}
|
||||
|
||||
var decoderCommentPrefix = regexp.MustCompile(`(^|\n)([[:alpha:]])`)
|
||||
|
||||
func (dec *xmlDecoder) processComment(c string) string {
|
||||
if c == "" {
|
||||
return ""
|
||||
}
|
||||
//need to replace "cat " with "# cat"
|
||||
// "\ncat\n" with "\n cat\n"
|
||||
// ensure non-empty comments starting with newline have a space in front
|
||||
|
||||
replacement := decoderCommentPrefix.ReplaceAllString(c, "$1 $2")
|
||||
replacement = "#" + strings.ReplaceAll(strings.TrimRight(replacement, " "), "\n", "\n#")
|
||||
return replacement
|
||||
return "#" + strings.TrimRight(c, " ")
|
||||
}
|
||||
|
||||
func (dec *xmlDecoder) createMap(n *xmlNode) (*yaml.Node, error) {
|
||||
log.Debug("createMap: headC: %v, lineC: %v, footC: %v", n.HeadComment, n.LineComment, n.FootComment)
|
||||
log.Debug("createMap: headC: %v, footC: %v", n.HeadComment, n.FootComment)
|
||||
yamlNode := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}
|
||||
|
||||
if len(n.Data) > 0 {
|
||||
log.Debugf("creating content node for map: %v", dec.prefs.ContentName)
|
||||
label := dec.prefs.ContentName
|
||||
labelNode := createScalarNode(label, label)
|
||||
labelNode.HeadComment = dec.processComment(n.HeadComment)
|
||||
labelNode.LineComment = dec.processComment(n.LineComment)
|
||||
labelNode.FootComment = dec.processComment(n.FootComment)
|
||||
yamlNode.Content = append(yamlNode.Content, labelNode, dec.createValueNodeFromData(n.Data))
|
||||
yamlNode.Content = append(yamlNode.Content, labelNode, createScalarNode(n.Data, n.Data))
|
||||
}
|
||||
|
||||
for i, keyValuePair := range n.Children {
|
||||
@@ -86,11 +72,10 @@ func (dec *xmlDecoder) createMap(n *xmlNode) (*yaml.Node, error) {
|
||||
var err error
|
||||
|
||||
if i == 0 {
|
||||
log.Debugf("head comment here")
|
||||
labelNode.HeadComment = dec.processComment(n.HeadComment)
|
||||
|
||||
}
|
||||
log.Debugf("label=%v, i=%v, keyValuePair.FootComment: %v", label, i, keyValuePair.FootComment)
|
||||
|
||||
labelNode.FootComment = dec.processComment(keyValuePair.FootComment)
|
||||
|
||||
log.Debug("len of children in %v is %v", label, len(children))
|
||||
@@ -104,16 +89,8 @@ func (dec *xmlDecoder) createMap(n *xmlNode) (*yaml.Node, error) {
|
||||
// if the value is a scalar, the head comment of the scalar needs to go on the key?
|
||||
// add tests for <z/> as well as multiple <ds> of inputXmlWithComments > yaml
|
||||
if len(children[0].Children) == 0 && children[0].HeadComment != "" {
|
||||
if len(children[0].Data) > 0 {
|
||||
|
||||
log.Debug("scalar comment hack, currentlabel [%v]", labelNode.HeadComment)
|
||||
labelNode.HeadComment = joinComments([]string{labelNode.HeadComment, strings.TrimSpace(children[0].HeadComment)}, "\n")
|
||||
children[0].HeadComment = ""
|
||||
} else {
|
||||
// child is null, put the headComment as a linecomment for reasons
|
||||
children[0].LineComment = children[0].HeadComment
|
||||
children[0].HeadComment = ""
|
||||
}
|
||||
labelNode.HeadComment = labelNode.HeadComment + "\n" + strings.TrimSpace(children[0].HeadComment)
|
||||
children[0].HeadComment = ""
|
||||
}
|
||||
valueNode, err = dec.convertToYamlNode(children[0])
|
||||
if err != nil {
|
||||
@@ -126,40 +103,17 @@ func (dec *xmlDecoder) createMap(n *xmlNode) (*yaml.Node, error) {
|
||||
return yamlNode, nil
|
||||
}
|
||||
|
||||
func (dec *xmlDecoder) createValueNodeFromData(values []string) *yaml.Node {
|
||||
switch len(values) {
|
||||
case 0:
|
||||
return createScalarNode(nil, "")
|
||||
case 1:
|
||||
return createScalarNode(values[0], values[0])
|
||||
default:
|
||||
content := make([]*yaml.Node, 0)
|
||||
for _, value := range values {
|
||||
content = append(content, createScalarNode(value, value))
|
||||
}
|
||||
return &yaml.Node{
|
||||
Kind: yaml.SequenceNode,
|
||||
Tag: "!!seq",
|
||||
Content: content,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (dec *xmlDecoder) convertToYamlNode(n *xmlNode) (*yaml.Node, error) {
|
||||
if len(n.Children) > 0 {
|
||||
return dec.createMap(n)
|
||||
}
|
||||
|
||||
scalar := dec.createValueNodeFromData(n.Data)
|
||||
|
||||
log.Debug("scalar (%v), headC: %v, lineC: %v, footC: %v", scalar.Tag, n.HeadComment, n.LineComment, n.FootComment)
|
||||
scalar := createScalarNode(n.Data, n.Data)
|
||||
if n.Data == "" {
|
||||
scalar = createScalarNode(nil, "")
|
||||
}
|
||||
log.Debug("scalar headC: %v, footC: %v", n.HeadComment, n.FootComment)
|
||||
scalar.HeadComment = dec.processComment(n.HeadComment)
|
||||
scalar.LineComment = dec.processComment(n.LineComment)
|
||||
if scalar.Tag == "!!seq" {
|
||||
scalar.Content[0].HeadComment = scalar.LineComment
|
||||
scalar.LineComment = ""
|
||||
}
|
||||
|
||||
scalar.FootComment = dec.processComment(n.FootComment)
|
||||
|
||||
return scalar, nil
|
||||
@@ -202,7 +156,7 @@ type xmlNode struct {
|
||||
HeadComment string
|
||||
FootComment string
|
||||
LineComment string
|
||||
Data []string
|
||||
Data string
|
||||
}
|
||||
|
||||
type xmlChildrenKv struct {
|
||||
@@ -247,8 +201,6 @@ func (dec *xmlDecoder) decodeXML(root *xmlNode) error {
|
||||
// That will convert the charset if the provided XML is non-UTF-8
|
||||
xmlDec.CharsetReader = charset.NewReaderLabel
|
||||
|
||||
started := false
|
||||
|
||||
// Create first element from the root node
|
||||
elem := &element{
|
||||
parent: nil,
|
||||
@@ -289,18 +241,12 @@ func (dec *xmlDecoder) decodeXML(root *xmlNode) error {
|
||||
a.Name.Local = a.Name.Space + ":" + a.Name.Local
|
||||
}
|
||||
}
|
||||
elem.n.AddChild(dec.prefs.AttributePrefix+a.Name.Local, &xmlNode{Data: []string{a.Value}})
|
||||
elem.n.AddChild(dec.prefs.AttributePrefix+a.Name.Local, &xmlNode{Data: a.Value})
|
||||
}
|
||||
case xml.CharData:
|
||||
|
||||
// Extract XML data (if any)
|
||||
newBit := trimNonGraphic(string(se))
|
||||
if !started && len(newBit) > 0 {
|
||||
return fmt.Errorf("invalid XML: Encountered chardata [%v] outside of XML node", newBit)
|
||||
}
|
||||
|
||||
if len(newBit) > 0 {
|
||||
elem.n.Data = append(elem.n.Data, newBit)
|
||||
elem.n.Data = trimNonGraphic(string(se))
|
||||
if elem.n.Data != "" {
|
||||
elem.state = "chardata"
|
||||
log.Debug("chardata [%v] for %v", elem.n.Data, elem.label)
|
||||
}
|
||||
@@ -322,22 +268,21 @@ func (dec *xmlDecoder) decodeXML(root *xmlNode) error {
|
||||
|
||||
} else if elem.state == "chardata" {
|
||||
log.Debug("got a line comment for (%v) %v: [%v]", elem.state, elem.label, commentStr)
|
||||
elem.n.LineComment = joinComments([]string{elem.n.LineComment, commentStr}, " ")
|
||||
elem.n.LineComment = joinFilter([]string{elem.n.LineComment, commentStr})
|
||||
} else {
|
||||
log.Debug("got a head comment for (%v) %v: [%v]", elem.state, elem.label, commentStr)
|
||||
elem.n.HeadComment = joinComments([]string{elem.n.HeadComment, commentStr}, " ")
|
||||
elem.n.HeadComment = joinFilter([]string{elem.n.HeadComment, commentStr})
|
||||
}
|
||||
|
||||
case xml.ProcInst:
|
||||
if !dec.prefs.SkipProcInst {
|
||||
elem.n.AddChild(dec.prefs.ProcInstPrefix+se.Target, &xmlNode{Data: []string{string(se.Inst)}})
|
||||
elem.n.AddChild(dec.prefs.ProcInstPrefix+se.Target, &xmlNode{Data: string(se.Inst)})
|
||||
}
|
||||
case xml.Directive:
|
||||
if !dec.prefs.SkipDirectives {
|
||||
elem.n.AddChild(dec.prefs.DirectiveName, &xmlNode{Data: []string{string(se)}})
|
||||
elem.n.AddChild(dec.prefs.DirectiveName, &xmlNode{Data: string(se)})
|
||||
}
|
||||
}
|
||||
started = true
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -349,28 +294,22 @@ func applyFootComment(elem *element, commentStr string) {
|
||||
if len(elem.n.Children) > 0 {
|
||||
lastChildIndex := len(elem.n.Children) - 1
|
||||
childKv := elem.n.Children[lastChildIndex]
|
||||
log.Debug("got a foot comment, putting on last child for %v: [%v]", childKv.K, commentStr)
|
||||
// if it's an array of scalars, put the foot comment on the scalar itself
|
||||
if len(childKv.V) > 0 && len(childKv.V[0].Children) == 0 {
|
||||
nodeToUpdate := childKv.V[len(childKv.V)-1]
|
||||
nodeToUpdate.FootComment = joinComments([]string{nodeToUpdate.FootComment, commentStr}, " ")
|
||||
} else {
|
||||
childKv.FootComment = joinComments([]string{elem.n.FootComment, commentStr}, " ")
|
||||
}
|
||||
log.Debug("got a foot comment for %v: [%v]", childKv.K, commentStr)
|
||||
childKv.FootComment = joinFilter([]string{elem.n.FootComment, commentStr})
|
||||
} else {
|
||||
log.Debug("got a foot comment for %v: [%v]", elem.label, commentStr)
|
||||
elem.n.FootComment = joinComments([]string{elem.n.FootComment, commentStr}, " ")
|
||||
elem.n.FootComment = joinFilter([]string{elem.n.FootComment, commentStr})
|
||||
}
|
||||
}
|
||||
|
||||
func joinComments(rawStrings []string, joinStr string) string {
|
||||
func joinFilter(rawStrings []string) string {
|
||||
stringsToJoin := make([]string, 0)
|
||||
for _, str := range rawStrings {
|
||||
if str != "" {
|
||||
stringsToJoin = append(stringsToJoin, str)
|
||||
}
|
||||
}
|
||||
return strings.Join(stringsToJoin, joinStr)
|
||||
return strings.Join(stringsToJoin, " ")
|
||||
}
|
||||
|
||||
// trimNonGraphic returns a slice of the string s, with all leading and trailing
|
||||
|
||||
@@ -2,7 +2,6 @@ package yqlib
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"regexp"
|
||||
@@ -13,15 +12,11 @@ import (
|
||||
|
||||
type yamlDecoder struct {
|
||||
decoder yaml.Decoder
|
||||
|
||||
prefs YamlPreferences
|
||||
|
||||
// work around of various parsing issues by yaml.v3 with document headers
|
||||
prefs YamlPreferences
|
||||
leadingContent string
|
||||
bufferRead bytes.Buffer
|
||||
|
||||
readAnything bool
|
||||
firstFile bool
|
||||
readAnything bool
|
||||
firstFile bool
|
||||
}
|
||||
|
||||
func NewYamlDecoder(prefs YamlPreferences) Decoder {
|
||||
@@ -30,7 +25,6 @@ func NewYamlDecoder(prefs YamlPreferences) Decoder {
|
||||
|
||||
func (dec *yamlDecoder) processReadStream(reader *bufio.Reader) (io.Reader, string, error) {
|
||||
var commentLineRegEx = regexp.MustCompile(`^\s*#`)
|
||||
var yamlDirectiveLineRegEx = regexp.MustCompile(`^\s*%YA`)
|
||||
var sb strings.Builder
|
||||
for {
|
||||
peekBytes, err := reader.Peek(3)
|
||||
@@ -39,14 +33,6 @@ func (dec *yamlDecoder) processReadStream(reader *bufio.Reader) (io.Reader, stri
|
||||
return reader, sb.String(), nil
|
||||
} else if err != nil {
|
||||
return reader, sb.String(), err
|
||||
} else if string(peekBytes[0]) == "\n" {
|
||||
_, err := reader.ReadString('\n')
|
||||
sb.WriteString("\n")
|
||||
if errors.Is(err, io.EOF) {
|
||||
return reader, sb.String(), nil
|
||||
} else if err != nil {
|
||||
return reader, sb.String(), err
|
||||
}
|
||||
} else if string(peekBytes) == "---" {
|
||||
_, err := reader.ReadString('\n')
|
||||
sb.WriteString("$yqDocSeperator$\n")
|
||||
@@ -55,7 +41,7 @@ func (dec *yamlDecoder) processReadStream(reader *bufio.Reader) (io.Reader, stri
|
||||
} else if err != nil {
|
||||
return reader, sb.String(), err
|
||||
}
|
||||
} else if commentLineRegEx.MatchString(string(peekBytes)) || yamlDirectiveLineRegEx.MatchString(string(peekBytes)) {
|
||||
} else if commentLineRegEx.MatchString(string(peekBytes)) {
|
||||
line, err := reader.ReadString('\n')
|
||||
sb.WriteString(line)
|
||||
if errors.Is(err, io.EOF) {
|
||||
@@ -72,7 +58,6 @@ func (dec *yamlDecoder) processReadStream(reader *bufio.Reader) (io.Reader, stri
|
||||
func (dec *yamlDecoder) Init(reader io.Reader) error {
|
||||
readerToUse := reader
|
||||
leadingContent := ""
|
||||
dec.bufferRead = bytes.Buffer{}
|
||||
var err error
|
||||
// if we 'evaluating together' - we only process the leading content
|
||||
// of the first file - this ensures comments from subsequent files are
|
||||
@@ -82,12 +67,6 @@ func (dec *yamlDecoder) Init(reader io.Reader) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if !dec.prefs.LeadingContentPreProcessing {
|
||||
// if we're not process the leading content
|
||||
// keep a copy of what we've read. This is incase its a
|
||||
// doc with only comments - the decoder will return nothing
|
||||
// then we can read the comments from bufferRead
|
||||
readerToUse = io.TeeReader(reader, &dec.bufferRead)
|
||||
}
|
||||
dec.leadingContent = leadingContent
|
||||
dec.readAnything = false
|
||||
@@ -98,20 +77,13 @@ func (dec *yamlDecoder) Init(reader io.Reader) error {
|
||||
|
||||
func (dec *yamlDecoder) Decode() (*CandidateNode, error) {
|
||||
var dataBucket yaml.Node
|
||||
|
||||
err := dec.decoder.Decode(&dataBucket)
|
||||
if errors.Is(err, io.EOF) && dec.leadingContent != "" && !dec.readAnything {
|
||||
// force returning an empty node with a comment.
|
||||
dec.readAnything = true
|
||||
return dec.blankNodeWithComment(), nil
|
||||
} else if errors.Is(err, io.EOF) && !dec.prefs.LeadingContentPreProcessing && !dec.readAnything {
|
||||
// didn't find any yaml,
|
||||
// check the tee buffer, maybe there were comments
|
||||
dec.readAnything = true
|
||||
dec.leadingContent = dec.bufferRead.String()
|
||||
if dec.leadingContent != "" {
|
||||
return dec.blankNodeWithComment(), nil
|
||||
}
|
||||
return nil, err
|
||||
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -124,7 +96,6 @@ func (dec *yamlDecoder) Decode() (*CandidateNode, error) {
|
||||
candidateNode.LeadingContent = dec.leadingContent
|
||||
dec.leadingContent = ""
|
||||
}
|
||||
dec.readAnything = true
|
||||
// move document comments into candidate node
|
||||
// otherwise unwrap drops them.
|
||||
candidateNode.TrailingContent = dataBucket.FootComment
|
||||
|
||||
@@ -86,23 +86,6 @@ will output
|
||||
a: ['dog', 'cat']
|
||||
```
|
||||
|
||||
## Prepend to existing array
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
- dog
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq '.a = ["cat"] + .a' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a:
|
||||
- cat
|
||||
- dog
|
||||
```
|
||||
|
||||
## Add new object to array
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
|
||||
@@ -302,7 +302,7 @@ foobar:
|
||||
```
|
||||
|
||||
## Dereference and update a field
|
||||
Use explode with multiply to dereference an object
|
||||
`Use explode with multiply to dereference an object
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
# Array to Map
|
||||
|
||||
Use this operator to convert an array to..a map. The indices are used as map keys, null values in the array are skipped over.
|
||||
|
||||
Behind the scenes, this is implemented using reduce:
|
||||
|
||||
```
|
||||
(.[] | select(. != null) ) as $i ireduce({}; .[$i | key] = $i)
|
||||
```
|
||||
|
||||
## Simple example
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
cool:
|
||||
- null
|
||||
- null
|
||||
- hello
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq '.cool |= array_to_map' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
cool:
|
||||
2: hello
|
||||
```
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
This operator is used to update node values. It can be used in either the:
|
||||
|
||||
### plain form: `=`
|
||||
Which will set the LHS node values equal to the RHS node values. The RHS expression is run against the matching nodes in the pipeline.
|
||||
Which will assign the LHS node values to the RHS node values. The RHS expression is run against the matching nodes in the pipeline.
|
||||
|
||||
### relative form: `|=`
|
||||
This will do a similar thing to the plain form, but the RHS expression is run with _each LHS node as context_. This is useful for updating values based on old values, e.g. increment.
|
||||
This will do a similar thing to the plain form, however, the RHS expression is run against _the LHS nodes_. This is useful for updating values based on old values, e.g. increment.
|
||||
|
||||
|
||||
### Flags
|
||||
@@ -203,7 +203,7 @@ a:
|
||||
```
|
||||
|
||||
## Update node value that has an anchor
|
||||
Anchor will remain
|
||||
Anchor will remaple
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
@@ -250,7 +250,7 @@ a: !cat woof
|
||||
b: !dog woof
|
||||
```
|
||||
|
||||
## Custom types: clobber
|
||||
## Custom types: clovver
|
||||
Use the `c` option to clobber custom tags
|
||||
|
||||
Given a sample.yml file of:
|
||||
|
||||
@@ -5,10 +5,10 @@ Use these comment operators to set or retrieve comments. Note that line comments
|
||||
Like the `=` and `|=` assign operators, the same syntax applies when updating comments:
|
||||
|
||||
### plain form: `=`
|
||||
This will set the LHS nodes' comments equal to the expression on the RHS. The RHS is run against the matching nodes in the pipeline
|
||||
This will assign the LHS nodes comments to the expression on the RHS. The RHS is run against the matching nodes in the pipeline
|
||||
|
||||
### relative form: `|=`
|
||||
This is similar to the plain form, but it evaluates the RHS with _each matching LHS node as context_. This is useful if you want to set the comments as a relative expression of the node, for instance its value or path.
|
||||
Similar to the plain form, however the RHS evaluates against each matching LHS node! This is useful if you want to set the comments as a relative expression of the node, for instance its value or path.
|
||||
|
||||
## Set line comment
|
||||
Set the comment on the key node for more reliability (see below).
|
||||
@@ -247,7 +247,6 @@ Note the use of `...` to ensure key nodes are included.
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
# hi
|
||||
|
||||
a: cat # comment
|
||||
# great
|
||||
b: # key comment
|
||||
@@ -266,7 +265,6 @@ b:
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
# welcome!
|
||||
|
||||
a: cat # meow
|
||||
# have a great day
|
||||
```
|
||||
@@ -295,7 +293,6 @@ yq '. | head_comment' sample.yml
|
||||
will output
|
||||
```yaml
|
||||
welcome!
|
||||
|
||||
```
|
||||
|
||||
## Head comment with document split
|
||||
|
||||
@@ -1,14 +1,6 @@
|
||||
# Contains
|
||||
|
||||
This returns `true` if the context contains the passed in parameter, and false otherwise. For arrays, this will return true if the passed in array is contained within the array. For strings, it will return true if the string is a substring.
|
||||
|
||||
{% hint style="warning" %}
|
||||
|
||||
_Note_ that, just like jq, when checking if an array of strings `contains` another, this will use `contains` and _not_ equals to check each string. This means an expression like `contains(["cat"])` will return true for an array `["cats"]`.
|
||||
|
||||
See the "Array has a subset array" example below on how to check for a subset.
|
||||
|
||||
{% endhint %}
|
||||
This returns `true` if the context contains the passed in parameter, and false otherwise.
|
||||
|
||||
## Array contains array
|
||||
Array is equal or subset of
|
||||
@@ -28,24 +20,6 @@ will output
|
||||
true
|
||||
```
|
||||
|
||||
## Array has a subset array
|
||||
Subtract the superset array from the subset, if there's anything left, it's not a subset
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- foobar
|
||||
- foobaz
|
||||
- blarp
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq '["baz", "bar"] - . | length == 0' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
false
|
||||
```
|
||||
|
||||
## Object included in array
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Various operators for parsing and manipulating dates.
|
||||
|
||||
## Date time formattings
|
||||
This uses Golang's built in time library for parsing and formatting date times.
|
||||
This uses the golangs built in time library for parsing and formatting date times.
|
||||
|
||||
When not specified, the RFC3339 standard is assumed `2006-01-02T15:04:05Z07:00` for parsing.
|
||||
|
||||
@@ -17,13 +17,13 @@ See the [library docs](https://pkg.go.dev/time#pkg-constants) for examples of fo
|
||||
|
||||
|
||||
## Timezones
|
||||
This uses Golang's built in LoadLocation function to parse timezones strings. See the [library docs](https://pkg.go.dev/time#LoadLocation) for more details.
|
||||
This uses golangs built in LoadLocation function to parse timezones strings. See the [library docs](https://pkg.go.dev/time#LoadLocation) for more details.
|
||||
|
||||
|
||||
## Durations
|
||||
Durations are parsed using Golang's built in [ParseDuration](https://pkg.go.dev/time#ParseDuration) function.
|
||||
Durations are parsed using golangs built in [ParseDuration](https://pkg.go.dev/time#ParseDuration) function.
|
||||
|
||||
You can add durations to time using the `+` operator.
|
||||
You can durations to time using the `+` operator.
|
||||
|
||||
## Format: from standard RFC3339 format
|
||||
Providing a single parameter assumes a standard RFC3339 datetime format. If the target format is not a valid yaml datetime format, the result will be a string tagged node.
|
||||
@@ -60,7 +60,7 @@ a: 2001-12-15
|
||||
## Format: get the day of the week
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: 2001-12-15
|
||||
a: 2001-12-15T02:59:43.1Z
|
||||
```
|
||||
then
|
||||
```bash
|
||||
@@ -86,30 +86,6 @@ a: cool
|
||||
updated: 2021-05-19T01:02:03Z
|
||||
```
|
||||
|
||||
## From Unix
|
||||
Converts from unix time. Note, you don't have to pipe through the tz operator :)
|
||||
|
||||
Running
|
||||
```bash
|
||||
yq --null-input '1675301929 | from_unix | tz("UTC")'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
2023-02-02T01:38:49Z
|
||||
```
|
||||
|
||||
## To Unix
|
||||
Converts to unix time
|
||||
|
||||
Running
|
||||
```bash
|
||||
yq --null-input 'now | to_unix'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
1621386123
|
||||
```
|
||||
|
||||
## Timezone: from standard RFC3339 format
|
||||
Returns a new datetime in the specified timezone. Specify standard IANA Time Zone format or 'utc', 'local'. When given a single parameter, this assumes the datetime is in RFC3339 format.
|
||||
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
# Divide
|
||||
|
||||
Divide behaves differently according to the type of the LHS:
|
||||
* strings: split by the divider
|
||||
* number: arithmetic division
|
||||
|
||||
## String split
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: cat_meow
|
||||
b: _
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq '.c = .a / .b' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: cat_meow
|
||||
b: _
|
||||
c:
|
||||
- cat
|
||||
- meow
|
||||
```
|
||||
|
||||
## Number division
|
||||
The result during division is calculated as a float
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: 12
|
||||
b: 2.5
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq '.a = .a / .b' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: 4.8
|
||||
b: 2.5
|
||||
```
|
||||
|
||||
## Number division by zero
|
||||
Dividing by zero results in +Inf or -Inf
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: 1
|
||||
b: -1
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq '.a = .a / 0 | .b = .b / 0' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: !!float +Inf
|
||||
b: !!float -Inf
|
||||
```
|
||||
|
||||
@@ -16,8 +16,6 @@ These operators are useful to process yaml documents that have stringified embed
|
||||
| TSV | from_tsv/@tsvd | to_tsv/@tsv |
|
||||
| XML | from_xml/@xmld | to_xml(i)/@xml |
|
||||
| Base64 | @base64d | @base64 |
|
||||
| URI | @urid | @uri |
|
||||
| Shell | | @sh |
|
||||
|
||||
|
||||
See CSV and TSV [documentation](https://mikefarah.gitbook.io/yq/usage/csv-tsv) for accepted formats.
|
||||
@@ -309,7 +307,7 @@ cat,"thing1,thing2",true,3.40
|
||||
dog,thing3,false,12
|
||||
```
|
||||
|
||||
## Encode array of arrays as tsv string
|
||||
## Encode array of array scalars as tsv string
|
||||
Scalars are strings, numbers and booleans.
|
||||
|
||||
Given a sample.yml file of:
|
||||
@@ -339,7 +337,7 @@ Given a sample.yml file of:
|
||||
a:
|
||||
cool:
|
||||
foo: bar
|
||||
+@id: hi
|
||||
+id: hi
|
||||
```
|
||||
then
|
||||
```bash
|
||||
@@ -359,7 +357,7 @@ Given a sample.yml file of:
|
||||
a:
|
||||
cool:
|
||||
foo: bar
|
||||
+@id: hi
|
||||
+id: hi
|
||||
```
|
||||
then
|
||||
```bash
|
||||
@@ -377,7 +375,7 @@ Given a sample.yml file of:
|
||||
a:
|
||||
cool:
|
||||
foo: bar
|
||||
+@id: hi
|
||||
+id: hi
|
||||
```
|
||||
then
|
||||
```bash
|
||||
@@ -437,50 +435,6 @@ will output
|
||||
YTogYXBwbGUK
|
||||
```
|
||||
|
||||
## Encode a string to uri
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
coolData: this has & special () characters *
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq '.coolData | @uri' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
this+has+%26+special+%28%29+characters+%2A
|
||||
```
|
||||
|
||||
## Decode a URI to a string
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
this+has+%26+special+%28%29+characters+%2A
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq '@urid' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
this has & special () characters *
|
||||
```
|
||||
|
||||
## Encode a string to sh
|
||||
Sh/Bash friendly string
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
coolData: strings with spaces and a 'quote'
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq '.coolData | @sh' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
strings' with spaces and a '\'quote\'
|
||||
```
|
||||
|
||||
## Decode a base64 encoded string
|
||||
Decoded data is assumed to be a string.
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ a: 1
|
||||
b: 2
|
||||
```
|
||||
|
||||
## from_entries with numeric key indices
|
||||
## from_entries with numeric key indexes
|
||||
from_entries always creates a map, even for numeric keys
|
||||
|
||||
Given a sample.yml file of:
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
Use `eval` to dynamically process an expression - for instance from an environment variable.
|
||||
|
||||
`eval` takes a single argument, and evaluates that as a `yq` expression. Any valid expression can be used, be it a path `.a.b.c | select(. == "cat")`, or an update `.a.b.c = "gogo"`.
|
||||
`eval` takes a single argument, and evaluates that as a `yq` expression. Any valid expression can be used, beit a path `.a.b.c | select(. == "cat")`, or an update `.a.b.c = "gogo"`.
|
||||
|
||||
Tip: This can be a useful way to parameterise complex scripts.
|
||||
Tip: This can be useful way parameterise complex scripts.
|
||||
|
||||
## Dynamically evaluate a path
|
||||
Given a sample.yml file of:
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
# Filter
|
||||
|
||||
Filters an array (or map values) by the expression given. Equivalent to doing `map(select(exp))`.
|
||||
|
||||
|
||||
## Filter array
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq 'filter(. < 3)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- 1
|
||||
- 2
|
||||
```
|
||||
|
||||
## Filter map values
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
c:
|
||||
things: cool
|
||||
frog: yes
|
||||
d:
|
||||
things: hot
|
||||
frog: false
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq 'filter(.things == "cool")' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- things: cool
|
||||
frog: yes
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Has
|
||||
|
||||
This operation returns true if the key exists in a map (or index in an array), false otherwise.
|
||||
This is operation that returns true if the key exists in a map (or index in an array), false otherwise.
|
||||
|
||||
## Has map key
|
||||
Given a sample.yml file of:
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# Array to Map
|
||||
|
||||
Use this operator to convert an array to..a map. The indices are used as map keys, null values in the array are skipped over.
|
||||
|
||||
Behind the scenes, this is implemented using reduce:
|
||||
|
||||
```
|
||||
(.[] | select(. != null) ) as $i ireduce({}; .[$i | key] = $i)
|
||||
```
|
||||
@@ -3,10 +3,10 @@
|
||||
This operator is used to update node values. It can be used in either the:
|
||||
|
||||
### plain form: `=`
|
||||
Which will set the LHS node values equal to the RHS node values. The RHS expression is run against the matching nodes in the pipeline.
|
||||
Which will assign the LHS node values to the RHS node values. The RHS expression is run against the matching nodes in the pipeline.
|
||||
|
||||
### relative form: `|=`
|
||||
This will do a similar thing to the plain form, but the RHS expression is run with _each LHS node as context_. This is useful for updating values based on old values, e.g. increment.
|
||||
This will do a similar thing to the plain form, however, the RHS expression is run against _the LHS nodes_. This is useful for updating values based on old values, e.g. increment.
|
||||
|
||||
|
||||
### Flags
|
||||
|
||||
@@ -5,7 +5,7 @@ Use these comment operators to set or retrieve comments. Note that line comments
|
||||
Like the `=` and `|=` assign operators, the same syntax applies when updating comments:
|
||||
|
||||
### plain form: `=`
|
||||
This will set the LHS nodes' comments equal to the expression on the RHS. The RHS is run against the matching nodes in the pipeline
|
||||
This will assign the LHS nodes comments to the expression on the RHS. The RHS is run against the matching nodes in the pipeline
|
||||
|
||||
### relative form: `|=`
|
||||
This is similar to the plain form, but it evaluates the RHS with _each matching LHS node as context_. This is useful if you want to set the comments as a relative expression of the node, for instance its value or path.
|
||||
Similar to the plain form, however the RHS evaluates against each matching LHS node! This is useful if you want to set the comments as a relative expression of the node, for instance its value or path.
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
# Contains
|
||||
|
||||
This returns `true` if the context contains the passed in parameter, and false otherwise. For arrays, this will return true if the passed in array is contained within the array. For strings, it will return true if the string is a substring.
|
||||
|
||||
{% hint style="warning" %}
|
||||
|
||||
_Note_ that, just like jq, when checking if an array of strings `contains` another, this will use `contains` and _not_ equals to check each string. This means an expression like `contains(["cat"])` will return true for an array `["cats"]`.
|
||||
|
||||
See the "Array has a subset array" example below on how to check for a subset.
|
||||
|
||||
{% endhint %}
|
||||
This returns `true` if the context contains the passed in parameter, and false otherwise.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Various operators for parsing and manipulating dates.
|
||||
|
||||
## Date time formattings
|
||||
This uses Golang's built in time library for parsing and formatting date times.
|
||||
This uses the golangs built in time library for parsing and formatting date times.
|
||||
|
||||
When not specified, the RFC3339 standard is assumed `2006-01-02T15:04:05Z07:00` for parsing.
|
||||
|
||||
@@ -17,10 +17,10 @@ See the [library docs](https://pkg.go.dev/time#pkg-constants) for examples of fo
|
||||
|
||||
|
||||
## Timezones
|
||||
This uses Golang's built in LoadLocation function to parse timezones strings. See the [library docs](https://pkg.go.dev/time#LoadLocation) for more details.
|
||||
This uses golangs built in LoadLocation function to parse timezones strings. See the [library docs](https://pkg.go.dev/time#LoadLocation) for more details.
|
||||
|
||||
|
||||
## Durations
|
||||
Durations are parsed using Golang's built in [ParseDuration](https://pkg.go.dev/time#ParseDuration) function.
|
||||
Durations are parsed using golangs built in [ParseDuration](https://pkg.go.dev/time#ParseDuration) function.
|
||||
|
||||
You can add durations to time using the `+` operator.
|
||||
You can durations to time using the `+` operator.
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# Divide
|
||||
|
||||
Divide behaves differently according to the type of the LHS:
|
||||
* strings: split by the divider
|
||||
* number: arithmetic division
|
||||
@@ -16,8 +16,6 @@ These operators are useful to process yaml documents that have stringified embed
|
||||
| TSV | from_tsv/@tsvd | to_tsv/@tsv |
|
||||
| XML | from_xml/@xmld | to_xml(i)/@xml |
|
||||
| Base64 | @base64d | @base64 |
|
||||
| URI | @urid | @uri |
|
||||
| Shell | | @sh |
|
||||
|
||||
|
||||
See CSV and TSV [documentation](https://mikefarah.gitbook.io/yq/usage/csv-tsv) for accepted formats.
|
||||
|
||||
@@ -2,6 +2,6 @@
|
||||
|
||||
Use `eval` to dynamically process an expression - for instance from an environment variable.
|
||||
|
||||
`eval` takes a single argument, and evaluates that as a `yq` expression. Any valid expression can be used, be it a path `.a.b.c | select(. == "cat")`, or an update `.a.b.c = "gogo"`.
|
||||
`eval` takes a single argument, and evaluates that as a `yq` expression. Any valid expression can be used, beit a path `.a.b.c | select(. == "cat")`, or an update `.a.b.c = "gogo"`.
|
||||
|
||||
Tip: This can be a useful way to parameterise complex scripts.
|
||||
Tip: This can be useful way parameterise complex scripts.
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# Filter
|
||||
|
||||
Filters an array (or map values) by the expression given. Equivalent to doing `map(select(exp))`.
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Has
|
||||
|
||||
This operation returns true if the key exists in a map (or index in an array), false otherwise.
|
||||
This is operation that returns true if the key exists in a map (or index in an array), false otherwise.
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
# Modulo
|
||||
|
||||
Arithmetic modulo operator, returns the remainder from dividing two numbers.
|
||||
@@ -1,6 +1,6 @@
|
||||
# Recursive Descent (Glob)
|
||||
|
||||
This operator recursively matches (or globs) all children nodes given of a particular element, including that node itself. This is most often used to apply a filter recursively against all matches.
|
||||
This operator recursively matches (or globs) all children nodes given of a particular element, including that node itself. This is most often used to apply a filter recursively against all matches. It can be used in either the
|
||||
|
||||
## match values form `..`
|
||||
This will, like the `jq` equivalent, recursively match all _value_ nodes. Use it to find/manipulate particular values.
|
||||
@@ -12,7 +12,7 @@ yq '.. style= "flow"' file.yaml
|
||||
```
|
||||
|
||||
## match values and map keys form `...`
|
||||
The also includes map keys in the results set. This is particularly useful in YAML as unlike JSON, map keys can have their own styling and tags and also use anchors and aliases.
|
||||
The also includes map keys in the results set. This is particularly useful in YAML as unlike JSON, map keys can have their own styling, tags and use anchors and aliases.
|
||||
|
||||
For instance to set the `style` of all nodes in a yaml doc, including the map keys:
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# Shuffle
|
||||
|
||||
Shuffles an array. Note that this command does _not_ use a cryptographically secure random number generator to randomise the array order.
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
# Slice/Splice Array
|
||||
|
||||
The slice array operator takes an array as input and returns a subarray. Like the `jq` equivalent, `.[10:15]` will return an array of length 5, starting from index 10 inclusive, up to index 15 exclusive. Negative numbers count backwards from the end of the array.
|
||||
|
||||
You may leave out the first or second number, which will will refer to the start or end of the array respectively.
|
||||
@@ -10,7 +10,7 @@ yq -i -P 'sort_keys(..)' file2.yml
|
||||
diff file1.yml file2.yml
|
||||
```
|
||||
|
||||
Note that `yq` does not yet consider anchors when sorting by keys - this may result in invalid yaml documents if you are using merge anchors.
|
||||
Note that `yq` does not yet consider anchors when sorting by keys - this may result in invalid yaml documents if your are using merge anchors.
|
||||
|
||||
For more advanced sorting, using `to_entries` to convert the map to an array, then sort/process the array as you like (e.g. using `sort_by`) and convert back to a map using `from_entries`.
|
||||
See [here](https://mikefarah.gitbook.io/yq/operators/entries#custom-sort-map-keys) for an example.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# String Operators
|
||||
|
||||
## RegEx
|
||||
This uses Golang's native regex functions under the hood - See their [docs](https://github.com/google/re2/wiki/Syntax) for the supported syntax.
|
||||
This uses golangs native regex functions under the hood - See their [docs](https://github.com/google/re2/wiki/Syntax) for the supported syntax.
|
||||
|
||||
Case insensitive tip: prefix the regex with `(?i)` - e.g. `test("(?i)cats)"`.
|
||||
|
||||
@@ -15,7 +15,7 @@ Capture returns named RegEx capture groups in a map. Can be more convenient than
|
||||
Returns true if the string matches the RegEx, false otherwise.
|
||||
|
||||
## sub(regEx, replacement)
|
||||
Substitutes matched substrings. The first parameter is the regEx to match substrings within the original string. The second parameter specifies what to replace those matches with. This can refer to capture groups from the first RegEx.
|
||||
Substitutes matched substrings. The first parameter is the regEx to match substrings within the original string. The second is a what to replace those matches with. This can refer to capture groups from the first RegEx.
|
||||
|
||||
## String blocks, bash and newlines
|
||||
Bash is notorious for chomping on precious trailing newline characters, making it tricky to set strings with newlines properly. In particular, the `$( exp )` _will trim trailing newlines_.
|
||||
@@ -27,7 +27,7 @@ a: |
|
||||
cat
|
||||
```
|
||||
|
||||
Using `$( exp )` wont work, as it will trim the trailing newline.
|
||||
Using `$( exp )` wont work, as it will trim the trailing new line.
|
||||
|
||||
```
|
||||
m=$(echo "cat\n") yq -n '.a = strenv(m)'
|
||||
@@ -49,7 +49,7 @@ a: |
|
||||
cat
|
||||
```
|
||||
|
||||
Similarly, if you're trying to set the content from a file, and want a trailing newline:
|
||||
Similarly, if you're trying to set the content from a file, and want a trailing new line:
|
||||
|
||||
```
|
||||
IFS= read -rd '' output < <(cat my_file)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Subtract
|
||||
|
||||
You can use subtract to subtract numbers as well as remove elements from an array.
|
||||
You can use subtract to subtract numbers, as well as removing elements from an array.
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Traverse (Read)
|
||||
|
||||
This is the simplest (and perhaps most used) operator. It is used to navigate deeply into yaml structures.
|
||||
This is the simplest (and perhaps most used) operator, it is used to navigate deeply into yaml structures.
|
||||
|
||||
@@ -29,7 +29,7 @@ b:
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq '.b | key | line' sample.yml
|
||||
yq '.b | key| line' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
# Modulo
|
||||
|
||||
Arithmetic modulo operator, returns the remainder from dividing two numbers.
|
||||
|
||||
## Number modulo - int
|
||||
If the lhs and rhs are ints then the expression will be calculated with ints.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: 13
|
||||
b: 2
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq '.a = .a % .b' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: 1
|
||||
b: 2
|
||||
```
|
||||
|
||||
## Number modulo - float
|
||||
If the lhs or rhs are floats then the expression will be calculated with floats.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: 12
|
||||
b: 2.5
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq '.a = .a % .b' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: !!float 2
|
||||
b: 2.5
|
||||
```
|
||||
|
||||
## Number modulo - int by zero
|
||||
If the lhs is an int and rhs is a 0 the result is an error.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: 1
|
||||
b: 0
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq '.a = .a % .b' sample.yml
|
||||
```
|
||||
will output
|
||||
```bash
|
||||
Error: cannot modulo by 0
|
||||
```
|
||||
|
||||
## Number modulo - float by zero
|
||||
If the lhs is a float and rhs is a 0 the result is NaN.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: 1.1
|
||||
b: 0
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq '.a = .a % .b' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: !!float NaN
|
||||
b: 0
|
||||
```
|
||||
|
||||
@@ -250,7 +250,7 @@ thing:
|
||||
```
|
||||
|
||||
## Merge, deeply merging arrays
|
||||
Merging arrays deeply means arrays are merged like objects, with indices as their key. In this case, we merge the first item in the array and do nothing with the second.
|
||||
Merging arrays deeply means arrays are merge like objects, with indexes as their key. In this case, we merge the first item in the array, and do nothing with the second.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
@@ -477,7 +477,7 @@ b: !goat
|
||||
```
|
||||
|
||||
## Custom types: clobber tags
|
||||
Use the `c` option to clobber custom tags. Note that the second tag is now used.
|
||||
Use the `c` option to clobber custom tags. Note that the second tag is now used
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
@@ -499,43 +499,3 @@ b: !goat
|
||||
dog: woof
|
||||
```
|
||||
|
||||
## Merging a null with a map
|
||||
Running
|
||||
```bash
|
||||
yq --null-input 'null * {"some": "thing"}'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
some: thing
|
||||
```
|
||||
|
||||
## Merging a map with null
|
||||
Running
|
||||
```bash
|
||||
yq --null-input '{"some": "thing"} * null'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
some: thing
|
||||
```
|
||||
|
||||
## Merging an null with an array
|
||||
Running
|
||||
```bash
|
||||
yq --null-input 'null * ["some"]'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- some
|
||||
```
|
||||
|
||||
## Merging an array with null
|
||||
Running
|
||||
```bash
|
||||
yq --null-input '["some"] * null'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- some
|
||||
```
|
||||
|
||||
|
||||
@@ -122,33 +122,6 @@ a:
|
||||
b: things
|
||||
```
|
||||
|
||||
## Set path to prune deep paths
|
||||
Like pick but recursive. This uses `ireduce` to deeply set the selected paths into an empty object.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
|
||||
parentA: bob
|
||||
parentB:
|
||||
child1: i am child1
|
||||
child2: i am child2
|
||||
parentC:
|
||||
child1: me child1
|
||||
child2: me child2
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq '(.parentB.child2, .parentC.child1) as $i
|
||||
ireduce({}; setpath($i | path; $i))' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
parentB:
|
||||
child2: i am child2
|
||||
parentC:
|
||||
child1: me child1
|
||||
```
|
||||
|
||||
## Set array path
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
|
||||
@@ -27,7 +27,7 @@ myMap:
|
||||
```
|
||||
|
||||
## Pick indices from array
|
||||
Note that the order of the indices matches the pick order and non existent indices are skipped.
|
||||
Note that the order of the indexes matches the pick order and non existent indexes are skipped.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Recursive Descent (Glob)
|
||||
|
||||
This operator recursively matches (or globs) all children nodes given of a particular element, including that node itself. This is most often used to apply a filter recursively against all matches.
|
||||
This operator recursively matches (or globs) all children nodes given of a particular element, including that node itself. This is most often used to apply a filter recursively against all matches. It can be used in either the
|
||||
|
||||
## match values form `..`
|
||||
This will, like the `jq` equivalent, recursively match all _value_ nodes. Use it to find/manipulate particular values.
|
||||
@@ -12,7 +12,7 @@ yq '.. style= "flow"' file.yaml
|
||||
```
|
||||
|
||||
## match values and map keys form `...`
|
||||
The also includes map keys in the results set. This is particularly useful in YAML as unlike JSON, map keys can have their own styling and tags and also use anchors and aliases.
|
||||
The also includes map keys in the results set. This is particularly useful in YAML as unlike JSON, map keys can have their own styling, tags and use anchors and aliases.
|
||||
|
||||
For instance to set the `style` of all nodes in a yaml doc, including the map keys:
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ going
|
||||
```
|
||||
|
||||
## Select elements from array with regular expression
|
||||
See more regular expression examples under the [`string` operator docs](https://mikefarah.gitbook.io/yq/operators/string-operators).
|
||||
See more regular expression examples under the `string` operator docs.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
# Shuffle
|
||||
|
||||
Shuffles an array. Note that this command does _not_ use a cryptographically secure random number generator to randomise the array order.
|
||||
|
||||
|
||||
## Shuffle array
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
- 5
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq 'shuffle' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- 5
|
||||
- 2
|
||||
- 4
|
||||
- 1
|
||||
- 3
|
||||
```
|
||||
|
||||
## Shuffle array in place
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
cool:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
- 5
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq '.cool |= shuffle' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
cool:
|
||||
- 5
|
||||
- 2
|
||||
- 4
|
||||
- 1
|
||||
- 3
|
||||
```
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
# Slice/Splice Array
|
||||
|
||||
The slice array operator takes an array as input and returns a subarray. Like the `jq` equivalent, `.[10:15]` will return an array of length 5, starting from index 10 inclusive, up to index 15 exclusive. Negative numbers count backwards from the end of the array.
|
||||
|
||||
You may leave out the first or second number, which will will refer to the start or end of the array respectively.
|
||||
|
||||
## Slicing arrays
|
||||
Given a sample.yml file of:
|
||||
@@ -80,26 +75,3 @@ will output
|
||||
- frog
|
||||
```
|
||||
|
||||
## Inserting into the middle of an array
|
||||
using an expression to find the index
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- cat
|
||||
- dog
|
||||
- frog
|
||||
- cow
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq '(.[] | select(. == "dog") | key + 1) as $pos | .[0:($pos)] + ["rabbit"] + .[$pos:]' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- cat
|
||||
- dog
|
||||
- rabbit
|
||||
- frog
|
||||
- cow
|
||||
```
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ yq -i -P 'sort_keys(..)' file2.yml
|
||||
diff file1.yml file2.yml
|
||||
```
|
||||
|
||||
Note that `yq` does not yet consider anchors when sorting by keys - this may result in invalid yaml documents if you are using merge anchors.
|
||||
Note that `yq` does not yet consider anchors when sorting by keys - this may result in invalid yaml documents if your are using merge anchors.
|
||||
|
||||
For more advanced sorting, using `to_entries` to convert the map to an array, then sort/process the array as you like (e.g. using `sort_by`) and convert back to a map using `from_entries`.
|
||||
See [here](https://mikefarah.gitbook.io/yq/operators/entries#custom-sort-map-keys) for an example.
|
||||
|
||||
@@ -25,28 +25,6 @@ will output
|
||||
- a: cat
|
||||
```
|
||||
|
||||
## Sort by multiple fields
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- a: dog
|
||||
- a: cat
|
||||
b: banana
|
||||
- a: cat
|
||||
b: apple
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq 'sort_by(.a, .b)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- a: cat
|
||||
b: apple
|
||||
- a: cat
|
||||
b: banana
|
||||
- a: dog
|
||||
```
|
||||
|
||||
## Sort descending by string field
|
||||
Use sort with reverse to sort in descending order.
|
||||
|
||||
@@ -157,24 +135,6 @@ will output
|
||||
- a: 100
|
||||
```
|
||||
|
||||
## Sort by custom date field
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- a: 12-Jun-2011
|
||||
- a: 23-Dec-2010
|
||||
- a: 10-Aug-2011
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq 'with_dtf("02-Jan-2006"; sort_by(.a))' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- a: 23-Dec-2010
|
||||
- a: 12-Jun-2011
|
||||
- a: 10-Aug-2011
|
||||
```
|
||||
|
||||
## Sort, nulls come first
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# String Operators
|
||||
|
||||
## RegEx
|
||||
This uses Golang's native regex functions under the hood - See their [docs](https://github.com/google/re2/wiki/Syntax) for the supported syntax.
|
||||
This uses golangs native regex functions under the hood - See their [docs](https://github.com/google/re2/wiki/Syntax) for the supported syntax.
|
||||
|
||||
Case insensitive tip: prefix the regex with `(?i)` - e.g. `test("(?i)cats)"`.
|
||||
|
||||
@@ -15,7 +15,7 @@ Capture returns named RegEx capture groups in a map. Can be more convenient than
|
||||
Returns true if the string matches the RegEx, false otherwise.
|
||||
|
||||
## sub(regEx, replacement)
|
||||
Substitutes matched substrings. The first parameter is the regEx to match substrings within the original string. The second parameter specifies what to replace those matches with. This can refer to capture groups from the first RegEx.
|
||||
Substitutes matched substrings. The first parameter is the regEx to match substrings within the original string. The second is a what to replace those matches with. This can refer to capture groups from the first RegEx.
|
||||
|
||||
## String blocks, bash and newlines
|
||||
Bash is notorious for chomping on precious trailing newline characters, making it tricky to set strings with newlines properly. In particular, the `$( exp )` _will trim trailing newlines_.
|
||||
@@ -27,7 +27,7 @@ a: |
|
||||
cat
|
||||
```
|
||||
|
||||
Using `$( exp )` wont work, as it will trim the trailing newline.
|
||||
Using `$( exp )` wont work, as it will trim the trailing new line.
|
||||
|
||||
```
|
||||
m=$(echo "cat\n") yq -n '.a = strenv(m)'
|
||||
@@ -49,7 +49,7 @@ a: |
|
||||
cat
|
||||
```
|
||||
|
||||
Similarly, if you're trying to set the content from a file, and want a trailing newline:
|
||||
Similarly, if you're trying to set the content from a file, and want a trailing new line:
|
||||
|
||||
```
|
||||
IFS= read -rd '' output < <(cat my_file)
|
||||
@@ -298,7 +298,7 @@ false
|
||||
```
|
||||
|
||||
## Substitute / Replace string
|
||||
This uses Golang's regex, described [here](https://github.com/google/re2/wiki/Syntax).
|
||||
This uses golang regex, described [here](https://github.com/google/re2/wiki/Syntax)
|
||||
Note the use of `|=` to run in context of the current string value.
|
||||
|
||||
Given a sample.yml file of:
|
||||
@@ -315,7 +315,7 @@ a: cats are great
|
||||
```
|
||||
|
||||
## Substitute / Replace string with regex
|
||||
This uses Golang's regex, described [here](https://github.com/google/re2/wiki/Syntax).
|
||||
This uses golang regex, described [here](https://github.com/google/re2/wiki/Syntax)
|
||||
Note the use of `|=` to run in context of the current string value.
|
||||
|
||||
Given a sample.yml file of:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Subtract
|
||||
|
||||
You can use subtract to subtract numbers as well as remove elements from an array.
|
||||
You can use subtract to subtract numbers, as well as removing elements from an array.
|
||||
|
||||
## Array subtraction
|
||||
Running
|
||||
@@ -59,6 +59,24 @@ a: -1.5
|
||||
b: 4.5
|
||||
```
|
||||
|
||||
## Number subtraction - float
|
||||
If the lhs or rhs are floats then the expression will be calculated with floats.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a: 3
|
||||
b: 4.5
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq '.a = .a - .b' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a: -1.5
|
||||
b: 4.5
|
||||
```
|
||||
|
||||
## Number subtraction - int
|
||||
If both the lhs and rhs are ints then the expression will be calculated with ints.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Traverse (Read)
|
||||
|
||||
This is the simplest (and perhaps most used) operator. It is used to navigate deeply into yaml structures.
|
||||
This is the simplest (and perhaps most used) operator, it is used to navigate deeply into yaml structures.
|
||||
|
||||
## Simple map navigation
|
||||
Given a sample.yml file of:
|
||||
@@ -51,7 +51,7 @@ will output
|
||||
```
|
||||
|
||||
## Special characters
|
||||
Use quotes with square brackets around path elements with special characters
|
||||
Use quotes with brackets around path elements with special characters
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
@@ -83,7 +83,7 @@ apple
|
||||
```
|
||||
|
||||
## Keys with spaces
|
||||
Use quotes with square brackets around path elements with special characters
|
||||
Use quotes with brackets around path elements with special characters
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
|
||||
@@ -149,23 +149,6 @@ person:
|
||||
- pizza
|
||||
```
|
||||
|
||||
## Decode properties - array should be a map
|
||||
If you have a numeric map key in your property files, use array_to_map to convert them to maps.
|
||||
|
||||
Given a sample.properties file of:
|
||||
```properties
|
||||
things.10 = mike
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq -p=props '.things |= array_to_map' sample.properties
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
things:
|
||||
10: mike
|
||||
```
|
||||
|
||||
## Roundtrip
|
||||
Given a sample.properties file of:
|
||||
```properties
|
||||
|
||||
+38
-64
@@ -128,7 +128,7 @@ will output
|
||||
```yaml
|
||||
+p_xml: version="1.0" encoding="UTF-8"
|
||||
cat:
|
||||
+@legs: "4"
|
||||
+legs: "4"
|
||||
legs: "7"
|
||||
```
|
||||
|
||||
@@ -149,28 +149,7 @@ will output
|
||||
+p_xml: version="1.0" encoding="UTF-8"
|
||||
cat:
|
||||
+content: meow
|
||||
+@legs: "4"
|
||||
```
|
||||
|
||||
## Parse xml: content split between comments/children
|
||||
Multiple content texts are collected into a sequence.
|
||||
|
||||
Given a sample.xml file of:
|
||||
```xml
|
||||
<root> value <!-- comment-->anotherValue <a>frog</a> cool!</root>
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq -p=xml '.' sample.xml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
root:
|
||||
+content: # comment
|
||||
- value
|
||||
- anotherValue
|
||||
- cool!
|
||||
a: frog
|
||||
+legs: "4"
|
||||
```
|
||||
|
||||
## Parse xml: custom dtd
|
||||
@@ -279,42 +258,17 @@ cat:
|
||||
```
|
||||
|
||||
## Parse xml: keep attribute namespace
|
||||
Defaults to true
|
||||
|
||||
Given a sample.xml file of:
|
||||
```xml
|
||||
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url"></map>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url">
|
||||
</map>
|
||||
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq -p=xml -o=xml --xml-keep-namespace=false '.' sample.xml
|
||||
```
|
||||
will output
|
||||
```xml
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xsi="some-instance" schemaLocation="some-url"></map>
|
||||
```
|
||||
|
||||
instead of
|
||||
```xml
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url"></map>
|
||||
```
|
||||
|
||||
## Parse xml: keep raw attribute namespace
|
||||
Defaults to true
|
||||
|
||||
Given a sample.xml file of:
|
||||
```xml
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url"></map>
|
||||
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq -p=xml -o=xml --xml-raw-token=false '.' sample.xml
|
||||
yq -p=xml -o=xml --xml-keep-namespace '.' sample.xml
|
||||
```
|
||||
will output
|
||||
```xml
|
||||
@@ -325,7 +279,32 @@ will output
|
||||
instead of
|
||||
```xml
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url"></map>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" some-instance:schemaLocation="some-url"></map>
|
||||
```
|
||||
|
||||
## Parse xml: keep raw attribute namespace
|
||||
Given a sample.xml file of:
|
||||
```xml
|
||||
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url">
|
||||
</map>
|
||||
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq -p=xml -o=xml --xml-keep-namespace --xml-raw-token '.' sample.xml
|
||||
```
|
||||
will output
|
||||
```xml
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" some-instance:schemaLocation="some-url"></map>
|
||||
```
|
||||
|
||||
instead of
|
||||
```xml
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xsi="some-instance" schemaLocation="some-url"></map>
|
||||
```
|
||||
|
||||
## Encode xml: simple
|
||||
@@ -368,7 +347,7 @@ Fields with the matching xml-attribute-prefix are assumed to be attributes.
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
cat:
|
||||
+@name: tiger
|
||||
+name: tiger
|
||||
meows: true
|
||||
|
||||
```
|
||||
@@ -389,7 +368,7 @@ Fields with the matching xml-content-name is assumed to be content.
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
cat:
|
||||
+@name: tiger
|
||||
+name: tiger
|
||||
+content: cool
|
||||
|
||||
```
|
||||
@@ -407,10 +386,8 @@ A best attempt is made to copy comments to xml.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
#
|
||||
# header comment
|
||||
# above_cat
|
||||
#
|
||||
cat: # inline_cat
|
||||
# above_array
|
||||
array: # inline_array
|
||||
@@ -427,11 +404,9 @@ yq -o=xml '.' sample.yml
|
||||
will output
|
||||
```xml
|
||||
<!--
|
||||
header comment
|
||||
above_cat
|
||||
-->
|
||||
<!-- inline_cat -->
|
||||
<cat><!-- above_array inline_array -->
|
||||
header comment
|
||||
above_cat
|
||||
--><!-- inline_cat --><cat><!-- above_array inline_array -->
|
||||
<array>val1<!-- inline_val1 --></array>
|
||||
<array><!-- above_val2 -->val2<!-- inline_val2 --></array>
|
||||
</cat><!-- below_cat -->
|
||||
@@ -493,8 +468,7 @@ yq -p=xml -o=xml '.' sample.xml
|
||||
```
|
||||
will output
|
||||
```xml
|
||||
<!-- before cat -->
|
||||
<cat><!-- in cat before -->
|
||||
<!-- before cat --><cat><!-- in cat before -->
|
||||
<x>3<!-- multi
|
||||
line comment
|
||||
for x --></x><!-- before y -->
|
||||
|
||||
+156
-7
@@ -1,6 +1,10 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
@@ -13,17 +17,162 @@ type Encoder interface {
|
||||
CanHandleAliases() bool
|
||||
}
|
||||
|
||||
func mapKeysToStrings(node *yaml.Node) {
|
||||
// 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{}
|
||||
}
|
||||
|
||||
if node.Kind == yaml.MappingNode {
|
||||
for index, child := range node.Content {
|
||||
if index%2 == 0 { // its a map key
|
||||
child.Tag = "!!str"
|
||||
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(); !errors.Is(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 && !errors.Is(err, io.EOF) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
case '[':
|
||||
var res []*orderedMap
|
||||
if err := json.Unmarshal(data, &res); err != nil {
|
||||
return err
|
||||
}
|
||||
o.altVal = res
|
||||
o.kv = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
return json.Unmarshal(data, &o.altVal)
|
||||
}
|
||||
|
||||
func (o orderedMap) MarshalJSON() ([]byte, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
enc := json.NewEncoder(buf)
|
||||
enc.SetEscapeHTML(false) // do not escape html chars e.g. &, <, >
|
||||
if o.kv == nil {
|
||||
if err := enc.Encode(o.altVal); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
for _, child := range node.Content {
|
||||
mapKeysToStrings(child)
|
||||
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:
|
||||
// note that this has to be a pointer, so that nulls can be represented.
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build !yq_nojson
|
||||
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
@@ -13,17 +11,31 @@ import (
|
||||
type jsonEncoder struct {
|
||||
indentString string
|
||||
colorise bool
|
||||
UnwrapScalar bool
|
||||
}
|
||||
|
||||
func NewJSONEncoder(indent int, colorise bool, unwrapScalar bool) 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(indent int, colorise bool) Encoder {
|
||||
var indentString = ""
|
||||
|
||||
for index := 0; index < indent; index++ {
|
||||
indentString = indentString + " "
|
||||
}
|
||||
|
||||
return &jsonEncoder{indentString, colorise, unwrapScalar}
|
||||
return &jsonEncoder{indentString, colorise}
|
||||
}
|
||||
|
||||
func (je *jsonEncoder) CanHandleAliases() bool {
|
||||
@@ -40,10 +52,6 @@ func (je *jsonEncoder) PrintLeadingContent(writer io.Writer, content string) err
|
||||
|
||||
func (je *jsonEncoder) Encode(writer io.Writer, node *yaml.Node) error {
|
||||
|
||||
if node.Kind == yaml.ScalarNode && je.UnwrapScalar {
|
||||
return writeString(writer, node.Value+"\n")
|
||||
}
|
||||
|
||||
destination := writer
|
||||
tempBuffer := bytes.NewBuffer(nil)
|
||||
if je.colorise {
|
||||
|
||||
@@ -51,7 +51,7 @@ func (pe *propertiesEncoder) PrintLeadingContent(writer io.Writer, content strin
|
||||
|
||||
if errors.Is(errReading, io.EOF) {
|
||||
if readline != "" {
|
||||
// the last comment we read didn't have a newline, put one in
|
||||
// the last comment we read didn't have a new line, put one in
|
||||
if err := writeString(writer, "\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var unsafeChars = regexp.MustCompile(`[^\w@%+=:,./-]`)
|
||||
|
||||
type shEncoder struct {
|
||||
quoteAll bool
|
||||
}
|
||||
|
||||
func NewShEncoder() Encoder {
|
||||
return &shEncoder{false}
|
||||
}
|
||||
|
||||
func (e *shEncoder) CanHandleAliases() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (e *shEncoder) PrintDocumentSeparator(writer io.Writer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *shEncoder) PrintLeadingContent(writer io.Writer, content string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *shEncoder) Encode(writer io.Writer, originalNode *yaml.Node) error {
|
||||
node := unwrapDoc(originalNode)
|
||||
if guessTagFromCustomType(node) != "!!str" {
|
||||
return fmt.Errorf("cannot encode %v as URI, can only operate on strings. Please first pipe through another encoding operator to convert the value to a string", node.Tag)
|
||||
}
|
||||
|
||||
return writeString(writer, e.encode(originalNode.Value))
|
||||
}
|
||||
|
||||
// put any (shell-unsafe) characters into a single-quoted block, close the block lazily
|
||||
func (e *shEncoder) encode(input string) string {
|
||||
const quote = '\''
|
||||
var inQuoteBlock = false
|
||||
var encoded strings.Builder
|
||||
encoded.Grow(len(input))
|
||||
|
||||
for _, ir := range input {
|
||||
// open or close a single-quote block
|
||||
if ir == quote {
|
||||
if inQuoteBlock {
|
||||
// get out of a quote block for an input quote
|
||||
encoded.WriteRune(quote)
|
||||
inQuoteBlock = !inQuoteBlock
|
||||
}
|
||||
// escape the quote with a backslash
|
||||
encoded.WriteRune('\\')
|
||||
} else {
|
||||
if e.shouldQuote(ir) && !inQuoteBlock {
|
||||
// start a quote block for any (unsafe) characters
|
||||
encoded.WriteRune(quote)
|
||||
inQuoteBlock = !inQuoteBlock
|
||||
}
|
||||
}
|
||||
// pass on the input character
|
||||
encoded.WriteRune(ir)
|
||||
}
|
||||
// close any pending quote block
|
||||
if inQuoteBlock {
|
||||
encoded.WriteRune(quote)
|
||||
}
|
||||
return encoded.String()
|
||||
}
|
||||
|
||||
func (e *shEncoder) shouldQuote(ir rune) bool {
|
||||
return e.quoteAll || unsafeChars.MatchString(string(ir))
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build !yq_nojson
|
||||
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
@@ -15,7 +13,7 @@ func yamlToJSON(sampleYaml string, indent int) string {
|
||||
var output bytes.Buffer
|
||||
writer := bufio.NewWriter(&output)
|
||||
|
||||
var jsonEncoder = NewJSONEncoder(indent, false, false)
|
||||
var jsonEncoder = NewJSONEncoder(indent, false)
|
||||
inputs, err := readDocuments(strings.NewReader(sampleYaml), "sample.yml", 0, NewYamlDecoder(ConfiguredYamlPreferences))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type uriEncoder struct {
|
||||
}
|
||||
|
||||
func NewUriEncoder() Encoder {
|
||||
return &uriEncoder{}
|
||||
}
|
||||
|
||||
func (e *uriEncoder) CanHandleAliases() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (e *uriEncoder) PrintDocumentSeparator(writer io.Writer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *uriEncoder) PrintLeadingContent(writer io.Writer, content string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *uriEncoder) Encode(writer io.Writer, originalNode *yaml.Node) error {
|
||||
node := unwrapDoc(originalNode)
|
||||
if guessTagFromCustomType(node) != "!!str" {
|
||||
return fmt.Errorf("cannot encode %v as URI, can only operate on strings. Please first pipe through another encoding operator to convert the value to a string", node.Tag)
|
||||
}
|
||||
_, err := writer.Write([]byte(url.QueryEscape(originalNode.Value)))
|
||||
return err
|
||||
}
|
||||
+25
-67
@@ -1,5 +1,3 @@
|
||||
//go:build !yq_noxml
|
||||
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
@@ -19,6 +17,8 @@ type xmlEncoder struct {
|
||||
leadingContent string
|
||||
}
|
||||
|
||||
var commentPrefix = regexp.MustCompile(`(^|\n)\s*#`)
|
||||
|
||||
func NewXMLEncoder(indent int, prefs XmlPreferences) Encoder {
|
||||
var indentString = ""
|
||||
|
||||
@@ -37,7 +37,7 @@ func (e *xmlEncoder) PrintDocumentSeparator(writer io.Writer) error {
|
||||
}
|
||||
|
||||
func (e *xmlEncoder) PrintLeadingContent(writer io.Writer, content string) error {
|
||||
e.leadingContent = content
|
||||
e.leadingContent = commentPrefix.ReplaceAllString(content, "\n")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -46,39 +46,12 @@ func (e *xmlEncoder) Encode(writer io.Writer, node *yaml.Node) error {
|
||||
// hack so we can manually add newlines to procInst and directives
|
||||
e.writer = writer
|
||||
encoder.Indent("", e.indentString)
|
||||
var newLine xml.CharData = []byte("\n")
|
||||
|
||||
mapNode := unwrapDoc(node)
|
||||
if mapNode.Tag == "!!map" {
|
||||
// make sure <?xml .. ?> processing instructions are encoded first
|
||||
for i := 0; i < len(mapNode.Content); i += 2 {
|
||||
key := mapNode.Content[i]
|
||||
value := mapNode.Content[i+1]
|
||||
|
||||
if key.Value == (e.prefs.ProcInstPrefix + "xml") {
|
||||
name := strings.Replace(key.Value, e.prefs.ProcInstPrefix, "", 1)
|
||||
procInst := xml.ProcInst{Target: name, Inst: []byte(value.Value)}
|
||||
if err := encoder.EncodeToken(procInst); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := e.writer.Write([]byte("\n")); err != nil {
|
||||
log.Warning("Unable to write newline, skipping: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if e.leadingContent != "" {
|
||||
|
||||
// remove first and last newlines if present
|
||||
err := e.encodeComment(encoder, e.leadingContent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = encoder.EncodeToken(newLine)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
switch node.Kind {
|
||||
@@ -92,11 +65,7 @@ func (e *xmlEncoder) Encode(writer io.Writer, node *yaml.Node) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
unwrappedNode := unwrapDoc(node)
|
||||
if unwrappedNode.Kind != yaml.MappingNode {
|
||||
return fmt.Errorf("cannot encode %v to XML - only maps can be encoded", unwrappedNode.Tag)
|
||||
}
|
||||
err = e.encodeTopLevelMap(encoder, unwrappedNode)
|
||||
err = e.encodeTopLevelMap(encoder, unwrapDoc(node))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -114,12 +83,29 @@ func (e *xmlEncoder) Encode(writer io.Writer, node *yaml.Node) error {
|
||||
default:
|
||||
return fmt.Errorf("unsupported type %v", node.Tag)
|
||||
}
|
||||
|
||||
return encoder.EncodeToken(newLine)
|
||||
var charData xml.CharData = []byte("\n")
|
||||
return encoder.EncodeToken(charData)
|
||||
|
||||
}
|
||||
|
||||
func (e *xmlEncoder) encodeTopLevelMap(encoder *xml.Encoder, node *yaml.Node) error {
|
||||
// make sure <?xml .. ?> processing instructions are encoded first
|
||||
for i := 0; i < len(node.Content); i += 2 {
|
||||
key := node.Content[i]
|
||||
value := node.Content[i+1]
|
||||
|
||||
if key.Value == (e.prefs.ProcInstPrefix + "xml") {
|
||||
name := strings.Replace(key.Value, e.prefs.ProcInstPrefix, "", 1)
|
||||
procInst := xml.ProcInst{Target: name, Inst: []byte(value.Value)}
|
||||
if err := encoder.EncodeToken(procInst); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := e.writer.Write([]byte("\n")); err != nil {
|
||||
log.Warning("Unable to write newline, skipping: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err := e.encodeComment(encoder, headAndLineComment(node))
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -134,13 +120,6 @@ func (e *xmlEncoder) encodeTopLevelMap(encoder *xml.Encoder, node *yaml.Node) er
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if headAndLineComment(key) != "" {
|
||||
var newLine xml.CharData = []byte("\n")
|
||||
err = encoder.EncodeToken(newLine)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if key.Value == (e.prefs.ProcInstPrefix + "xml") {
|
||||
// dont double process these.
|
||||
@@ -221,33 +200,12 @@ func (e *xmlEncoder) doEncode(encoder *xml.Encoder, node *yaml.Node, start xml.S
|
||||
return fmt.Errorf("unsupported type %v", node.Tag)
|
||||
}
|
||||
|
||||
var xmlEncodeMultilineCommentRegex = regexp.MustCompile(`(^|\n) *# ?(.*)`)
|
||||
var xmlEncodeSingleLineCommentRegex = regexp.MustCompile(`^\s*#(.*)\n?`)
|
||||
var chompRegexp = regexp.MustCompile(`\n$`)
|
||||
|
||||
func (e *xmlEncoder) encodeComment(encoder *xml.Encoder, commentStr string) error {
|
||||
if commentStr != "" {
|
||||
log.Debugf("got comment [%v]", commentStr)
|
||||
// multi line string
|
||||
if len(commentStr) > 2 && strings.Contains(commentStr[1:len(commentStr)-1], "\n") {
|
||||
commentStr = chompRegexp.ReplaceAllString(commentStr, "")
|
||||
log.Debugf("chompRegexp [%v]", commentStr)
|
||||
commentStr = xmlEncodeMultilineCommentRegex.ReplaceAllString(commentStr, "$1$2")
|
||||
log.Debugf("processed multine [%v]", commentStr)
|
||||
// if the first line is non blank, add a space
|
||||
if commentStr[0] != '\n' && commentStr[0] != ' ' {
|
||||
commentStr = " " + commentStr
|
||||
}
|
||||
|
||||
} else {
|
||||
commentStr = xmlEncodeSingleLineCommentRegex.ReplaceAllString(commentStr, "$1")
|
||||
}
|
||||
|
||||
if !strings.HasSuffix(commentStr, " ") && !strings.HasSuffix(commentStr, "\n") {
|
||||
log.Debugf("encoding comment %v", commentStr)
|
||||
if !strings.HasSuffix(commentStr, " ") {
|
||||
commentStr = commentStr + " "
|
||||
log.Debugf("added suffix [%v]", commentStr)
|
||||
}
|
||||
log.Debugf("encoding comment [%v]", commentStr)
|
||||
|
||||
var comment xml.Comment = []byte(commentStr)
|
||||
err := encoder.EncodeToken(comment)
|
||||
|
||||
@@ -61,7 +61,7 @@ func (ye *yamlEncoder) PrintLeadingContent(writer io.Writer, content string) err
|
||||
|
||||
if errors.Is(errReading, io.EOF) {
|
||||
if readline != "" {
|
||||
// the last comment we read didn't have a newline, put one in
|
||||
// the last comment we read didn't have a new line, put one in
|
||||
if err := writeString(writer, "\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+8
-31
@@ -1,5 +1,3 @@
|
||||
//go:build !yq_nojson
|
||||
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
@@ -80,27 +78,12 @@ const roundTripMultiLineJson = `{
|
||||
`
|
||||
|
||||
var jsonScenarios = []formatScenario{
|
||||
{
|
||||
description: "set tags",
|
||||
skipDoc: true,
|
||||
input: "[{}]",
|
||||
expression: `[.. | type]`,
|
||||
scenarioType: "roundtrip-ndjson",
|
||||
expected: "[\"!!seq\",\"!!map\"]\n",
|
||||
},
|
||||
{
|
||||
description: "Parse json: simple",
|
||||
subdescription: "JSON is a subset of yaml, so all you need to do is prettify the output",
|
||||
input: `{"cat": "meow"}`,
|
||||
expected: "D0, P[], (!!map)::cat: meow\n",
|
||||
},
|
||||
{
|
||||
description: "bad json",
|
||||
skipDoc: true,
|
||||
input: `{"a": 1 "b": 2}`,
|
||||
expectedError: `bad file 'sample.yml': invalid character '"' after object key:value pair`,
|
||||
scenarioType: "decode-error",
|
||||
},
|
||||
{
|
||||
description: "Parse json: complex",
|
||||
subdescription: "JSON is a subset of yaml, so all you need to do is prettify the output",
|
||||
@@ -254,7 +237,7 @@ func documentRoundtripNdJsonScenario(w *bufio.Writer, s formatScenario, indent i
|
||||
|
||||
writeOrPanic(w, "will output\n")
|
||||
|
||||
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n", mustProcessFormatScenario(s, NewJSONDecoder(), NewJSONEncoder(indent, false, false))))
|
||||
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n", processFormatScenario(s, NewJSONDecoder(), NewJSONEncoder(indent, false))))
|
||||
}
|
||||
|
||||
func documentDecodeNdJsonScenario(w *bufio.Writer, s formatScenario) {
|
||||
@@ -279,7 +262,7 @@ func documentDecodeNdJsonScenario(w *bufio.Writer, s formatScenario) {
|
||||
|
||||
writeOrPanic(w, "will output\n")
|
||||
|
||||
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n", mustProcessFormatScenario(s, NewJSONDecoder(), NewYamlEncoder(s.indent, false, ConfiguredYamlPreferences))))
|
||||
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n", processFormatScenario(s, NewJSONDecoder(), NewYamlEncoder(s.indent, false, ConfiguredYamlPreferences))))
|
||||
}
|
||||
|
||||
func decodeJSON(t *testing.T, jsonString string) *CandidateNode {
|
||||
@@ -310,23 +293,17 @@ func decodeJSON(t *testing.T, jsonString string) *CandidateNode {
|
||||
func testJSONScenario(t *testing.T, s formatScenario) {
|
||||
switch s.scenarioType {
|
||||
case "encode", "decode":
|
||||
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewJSONEncoder(s.indent, false, false)), s.description)
|
||||
test.AssertResultWithContext(t, s.expected, processFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewJSONEncoder(s.indent, false)), s.description)
|
||||
case "":
|
||||
var actual = resultToString(t, decodeJSON(t, s.input))
|
||||
test.AssertResultWithContext(t, s.expected, actual, s.description)
|
||||
case "decode-ndjson":
|
||||
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewJSONDecoder(), NewYamlEncoder(2, false, ConfiguredYamlPreferences)), s.description)
|
||||
test.AssertResultWithContext(t, s.expected, processFormatScenario(s, NewJSONDecoder(), NewYamlEncoder(2, false, ConfiguredYamlPreferences)), s.description)
|
||||
case "roundtrip-ndjson":
|
||||
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewJSONDecoder(), NewJSONEncoder(0, false, false)), s.description)
|
||||
test.AssertResultWithContext(t, s.expected, processFormatScenario(s, NewJSONDecoder(), NewJSONEncoder(0, false)), s.description)
|
||||
case "roundtrip-multi":
|
||||
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewJSONDecoder(), NewJSONEncoder(2, false, false)), s.description)
|
||||
case "decode-error":
|
||||
result, err := processFormatScenario(s, NewJSONDecoder(), NewJSONEncoder(2, false, false))
|
||||
if err == nil {
|
||||
t.Errorf("Expected error '%v' but it worked: %v", s.expectedError, result)
|
||||
} else {
|
||||
test.AssertResultComplexWithContext(t, s.expectedError, err.Error(), s.description)
|
||||
}
|
||||
test.AssertResultWithContext(t, s.expected, processFormatScenario(s, NewJSONDecoder(), NewJSONEncoder(2, false)), s.description)
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unhandled scenario type %q", s.scenarioType))
|
||||
}
|
||||
@@ -408,7 +385,7 @@ func documentJSONEncodeScenario(w *bufio.Writer, s formatScenario) {
|
||||
}
|
||||
writeOrPanic(w, "will output\n")
|
||||
|
||||
writeOrPanic(w, fmt.Sprintf("```json\n%v```\n\n", mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewJSONEncoder(s.indent, false, false))))
|
||||
writeOrPanic(w, fmt.Sprintf("```json\n%v```\n\n", processFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewJSONEncoder(s.indent, false))))
|
||||
}
|
||||
|
||||
func TestJSONScenarios(t *testing.T) {
|
||||
|
||||
+3
-28
@@ -97,10 +97,6 @@ func postProcessTokens(tokens []*token) []*token {
|
||||
return postProcessedTokens
|
||||
}
|
||||
|
||||
func tokenIsOpType(token *token, opType *operationType) bool {
|
||||
return token.TokenType == operationToken && token.Operation.OperationType == opType
|
||||
}
|
||||
|
||||
func handleToken(tokens []*token, index int, postProcessedTokens []*token) (tokensAccum []*token, skipNextToken bool) {
|
||||
skipNextToken = false
|
||||
currentToken := tokens[index]
|
||||
@@ -124,18 +120,9 @@ func handleToken(tokens []*token, index int, postProcessedTokens []*token) (toke
|
||||
|
||||
}
|
||||
|
||||
if tokenIsOpType(currentToken, createMapOpType) {
|
||||
log.Debugf("tokenIsOpType: createMapOpType")
|
||||
// check the previous token is '[', means we are slice, but dont have a first number
|
||||
if tokens[index-1].TokenType == traverseArrayCollect {
|
||||
log.Debugf("previous token is : traverseArrayOpType")
|
||||
// need to put the number 0 before this token, as that is implied
|
||||
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: createValueOperation(0, "0")})
|
||||
}
|
||||
}
|
||||
|
||||
if index != len(tokens)-1 && currentToken.AssignOperation != nil &&
|
||||
tokenIsOpType(tokens[index+1], assignOpType) {
|
||||
tokens[index+1].TokenType == operationToken &&
|
||||
tokens[index+1].Operation.OperationType == assignOpType {
|
||||
log.Debug(" its an update assign")
|
||||
currentToken.Operation = currentToken.AssignOperation
|
||||
currentToken.Operation.UpdateAssign = tokens[index+1].Operation.UpdateAssign
|
||||
@@ -145,17 +132,6 @@ func handleToken(tokens []*token, index int, postProcessedTokens []*token) (toke
|
||||
log.Debug(" adding token to the fixed list")
|
||||
postProcessedTokens = append(postProcessedTokens, currentToken)
|
||||
|
||||
if tokenIsOpType(currentToken, createMapOpType) {
|
||||
log.Debugf("tokenIsOpType: createMapOpType")
|
||||
// check the next token is ']', means we are slice, but dont have a second number
|
||||
if index != len(tokens)-1 && tokens[index+1].TokenType == closeCollect {
|
||||
log.Debugf("next token is : closeCollect")
|
||||
// need to put the number 0 before this token, as that is implied
|
||||
lengthOp := &Operation{OperationType: lengthOpType}
|
||||
postProcessedTokens = append(postProcessedTokens, &token{TokenType: operationToken, Operation: lengthOp})
|
||||
}
|
||||
}
|
||||
|
||||
if index != len(tokens)-1 &&
|
||||
((currentToken.TokenType == openCollect && tokens[index+1].TokenType == closeCollect) ||
|
||||
(currentToken.TokenType == openCollectObject && tokens[index+1].TokenType == closeCollectObject)) {
|
||||
@@ -165,8 +141,7 @@ func handleToken(tokens []*token, index int, postProcessedTokens []*token) (toke
|
||||
}
|
||||
|
||||
if index != len(tokens)-1 && currentToken.CheckForPostTraverse &&
|
||||
|
||||
(tokenIsOpType(tokens[index+1], traversePathOpType) ||
|
||||
((tokens[index+1].TokenType == operationToken && (tokens[index+1].Operation.OperationType == traversePathOpType)) ||
|
||||
(tokens[index+1].TokenType == traverseArrayCollect)) {
|
||||
log.Debug(" adding pipe because the next thing is traverse")
|
||||
op := &Operation{OperationType: shortPipeOpType, Value: "PIPE", StringValue: "."}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -12,6 +13,10 @@ var participleYqRules = []*participleYqRule{
|
||||
{"HEAD_COMMENT", `head_?comment|headComment`, opTokenWithPrefs(getCommentOpType, assignCommentOpType, commentOpPreferences{HeadComment: true}), 0},
|
||||
{"FOOT_COMMENT", `foot_?comment|footComment`, opTokenWithPrefs(getCommentOpType, assignCommentOpType, commentOpPreferences{FootComment: true}), 0},
|
||||
|
||||
{"SliceArray", `\.\[-?[0-9]+:-?[0-9]+\]`, sliceArrayTwoNumbers(), 0},
|
||||
{"SliceArraySecond", `\.\[\:-?[0-9]+\]`, sliceArraySecondNumberOnly(), 0},
|
||||
{"SliceArrayFirst", `\.\[-?[0-9]+\:\]`, sliceArrayFirstNumberOnly(), 0},
|
||||
|
||||
{"OpenBracket", `\(`, literalToken(openBracket, false), 0},
|
||||
{"CloseBracket", `\)`, literalToken(closeBracket, true), 0},
|
||||
{"OpenTraverseArrayCollect", `\.\[`, literalToken(traverseArrayCollect, false), 0},
|
||||
@@ -37,7 +42,6 @@ var participleYqRules = []*participleYqRule{
|
||||
|
||||
{"MapValues", `map_?values`, opToken(mapValuesOpType), 0},
|
||||
simpleOp("map", mapOpType),
|
||||
simpleOp("filter", filterOpType),
|
||||
simpleOp("pick", pickOpType),
|
||||
|
||||
{"FlattenWithDepth", `flatten\([0-9]+\)`, flattenWithDepth(), 0},
|
||||
@@ -46,16 +50,11 @@ var participleYqRules = []*participleYqRule{
|
||||
simpleOp("format_datetime", formatDateTimeOpType),
|
||||
simpleOp("now", nowOpType),
|
||||
simpleOp("tz", tzOpType),
|
||||
simpleOp("from_?unix", fromUnixOpType),
|
||||
simpleOp("to_?unix", toUnixOpType),
|
||||
simpleOp("with_dtf", withDtFormatOpType),
|
||||
simpleOp("error", errorOpType),
|
||||
simpleOp("shuffle", shuffleOpType),
|
||||
simpleOp("sortKeys", sortKeysOpType),
|
||||
simpleOp("sort_?keys", sortKeysOpType),
|
||||
|
||||
{"ArrayToMap", "array_?to_?map", expressionOpToken(`(.[] | select(. != null) ) as $i ireduce({}; .[$i | key] = $i)`), 0},
|
||||
|
||||
{"YamlEncodeWithIndent", `to_?yaml\([0-9]+\)`, encodeParseIndent(YamlOutputFormat), 0},
|
||||
{"XMLEncodeWithIndent", `to_?xml\([0-9]+\)`, encodeParseIndent(XMLOutputFormat), 0},
|
||||
{"JSONEncodeWithIndent", `to_?json\([0-9]+\)`, encodeParseIndent(JSONOutputFormat), 0},
|
||||
@@ -82,10 +81,6 @@ var participleYqRules = []*participleYqRule{
|
||||
{"Base64d", `@base64d`, decodeOp(Base64InputFormat), 0},
|
||||
{"Base64", `@base64`, encodeWithIndent(Base64OutputFormat, 0), 0},
|
||||
|
||||
{"Urid", `@urid`, decodeOp(UriInputFormat), 0},
|
||||
{"Uri", `@uri`, encodeWithIndent(UriOutputFormat, 0), 0},
|
||||
{"SH", `@sh`, encodeWithIndent(ShOutputFormat, 0), 0},
|
||||
|
||||
{"LoadXML", `load_?xml|xml_?load`, loadOp(NewXMLDecoder(ConfiguredXMLPreferences), false), 0},
|
||||
|
||||
{"LoadBase64", `load_?base64`, loadOp(NewBase64Decoder(), false), 0},
|
||||
@@ -94,7 +89,7 @@ var participleYqRules = []*participleYqRule{
|
||||
|
||||
{"LoadString", `load_?str|str_?load`, loadOp(nil, true), 0},
|
||||
|
||||
{"LoadYaml", `load`, loadOp(NewYamlDecoder(LoadYamlPreferences), false), 0},
|
||||
{"LoadYaml", `load`, loadOp(NewYamlDecoder(ConfiguredYamlPreferences), false), 0},
|
||||
|
||||
{"SplitDocument", `splitDoc|split_?doc`, opToken(splitDocumentOpType), 0},
|
||||
|
||||
@@ -210,10 +205,6 @@ var participleYqRules = []*participleYqRule{
|
||||
{"MultiplyAssign", `\*=[\+|\?cdn]*`, multiplyWithPrefs(multiplyAssignOpType), 0},
|
||||
{"Multiply", `\*[\+|\?cdn]*`, multiplyWithPrefs(multiplyOpType), 0},
|
||||
|
||||
{"Divide", `\/`, opToken(divideOpType), 0},
|
||||
|
||||
{"Modulo", `%`, opToken(moduloOpType), 0},
|
||||
|
||||
{"AddAssign", `\+=`, opToken(addAssignOpType), 0},
|
||||
{"Add", `\+`, opToken(addOpType), 0},
|
||||
|
||||
@@ -300,14 +291,6 @@ func opTokenWithPrefs(opType *operationType, assignOpType *operationType, prefer
|
||||
}
|
||||
}
|
||||
|
||||
func expressionOpToken(expression string) yqAction {
|
||||
return func(rawToken lexer.Token) (*token, error) {
|
||||
prefs := expressionOpPreferences{expression: expression}
|
||||
expressionOp := &Operation{OperationType: expressionOpType, Preferences: prefs}
|
||||
return &token{TokenType: operationToken, Operation: expressionOp}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func flattenWithDepth() yqAction {
|
||||
return func(rawToken lexer.Token) (*token, error) {
|
||||
value := rawToken.Value
|
||||
@@ -322,6 +305,84 @@ func flattenWithDepth() yqAction {
|
||||
}
|
||||
}
|
||||
|
||||
func sliceArrayTwoNumbers() yqAction {
|
||||
return func(rawToken lexer.Token) (*token, error) {
|
||||
value := rawToken.Value
|
||||
sliceArrayNumbers := regexp.MustCompile(`\.\[(-?[0-9]+)\:(-?[0-9]+)\]`)
|
||||
matches := sliceArrayNumbers.FindStringSubmatch(value)
|
||||
log.Debug("sliceArrayTwoNumbers value: %v", value)
|
||||
log.Debug("Matches: %v", matches)
|
||||
|
||||
firstNumber, err := parseInt(matches[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secondNumber, err := parseInt(matches[2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prefs := sliceArrayPreferences{
|
||||
firstNumber: firstNumber,
|
||||
secondNumber: secondNumber,
|
||||
secondNumberDefined: true,
|
||||
}
|
||||
log.Debug("%v", prefs)
|
||||
|
||||
op := &Operation{OperationType: sliceArrayOpType, Value: sliceArrayOpType.Type, StringValue: value, Preferences: prefs}
|
||||
return &token{TokenType: operationToken, Operation: op}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func sliceArraySecondNumberOnly() yqAction {
|
||||
return func(rawToken lexer.Token) (*token, error) {
|
||||
value := rawToken.Value
|
||||
sliceArrayNumbers := regexp.MustCompile(`\.\[\:(-?[0-9]+)\]`)
|
||||
matches := sliceArrayNumbers.FindStringSubmatch(value)
|
||||
log.Debug("sliceArraySecondNumberOnly value: %v", value)
|
||||
log.Debug("Matches: %v", matches)
|
||||
|
||||
secondNumber, err := parseInt(matches[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prefs := sliceArrayPreferences{
|
||||
firstNumber: 0,
|
||||
secondNumber: secondNumber,
|
||||
secondNumberDefined: true,
|
||||
}
|
||||
log.Debug("%v", prefs)
|
||||
|
||||
op := &Operation{OperationType: sliceArrayOpType, Value: sliceArrayOpType.Type, StringValue: value, Preferences: prefs}
|
||||
return &token{TokenType: operationToken, Operation: op}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func sliceArrayFirstNumberOnly() yqAction {
|
||||
return func(rawToken lexer.Token) (*token, error) {
|
||||
value := rawToken.Value
|
||||
sliceArrayNumbers := regexp.MustCompile(`\.\[(-?[0-9]+)\:\]`)
|
||||
matches := sliceArrayNumbers.FindStringSubmatch(value)
|
||||
log.Debug("sliceArrayFirstNumberOnly value: %v", value)
|
||||
log.Debug("Matches: %v", matches)
|
||||
|
||||
firstNumber, err := parseInt(matches[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prefs := sliceArrayPreferences{
|
||||
firstNumber: firstNumber,
|
||||
secondNumberDefined: false,
|
||||
}
|
||||
log.Debug("%v", prefs)
|
||||
|
||||
op := &Operation{OperationType: sliceArrayOpType, Value: sliceArrayOpType.Type, StringValue: value, Preferences: prefs}
|
||||
return &token{TokenType: operationToken, Operation: op}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func assignAllCommentsOp(updateAssign bool) yqAction {
|
||||
return func(rawToken lexer.Token) (*token, error) {
|
||||
log.Debug("assignAllCommentsOp %v", rawToken.Value)
|
||||
@@ -364,12 +425,8 @@ func nullValue() yqAction {
|
||||
|
||||
func stringValue() yqAction {
|
||||
return func(rawToken lexer.Token) (*token, error) {
|
||||
log.Debug("rawTokenvalue: %v", rawToken.Value)
|
||||
value := unwrap(rawToken.Value)
|
||||
log.Debug("unwrapped: %v", value)
|
||||
value = strings.ReplaceAll(value, "\\\"", "\"")
|
||||
value = strings.ReplaceAll(value, "\\n", "\n")
|
||||
log.Debug("replaced: %v", value)
|
||||
return &token{TokenType: operationToken, Operation: createValueOperation(value, value)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/alecthomas/repr"
|
||||
"github.com/mikefarah/yq/v4/test"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type participleLexerScenario struct {
|
||||
@@ -14,125 +14,60 @@ type participleLexerScenario struct {
|
||||
}
|
||||
|
||||
var participleLexerScenarios = []participleLexerScenario{
|
||||
{
|
||||
expression: ".[1:3]",
|
||||
tokens: []*token{
|
||||
{
|
||||
TokenType: operationToken,
|
||||
Operation: &Operation{
|
||||
OperationType: sliceArrayOpType,
|
||||
Value: "SLICE",
|
||||
StringValue: ".[1:3]",
|
||||
Preferences: sliceArrayPreferences{firstNumber: 1, secondNumber: 3, secondNumberDefined: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
expression: ".[:3]",
|
||||
tokens: []*token{
|
||||
{
|
||||
TokenType: operationToken,
|
||||
Operation: &Operation{
|
||||
OperationType: selfReferenceOpType,
|
||||
StringValue: "SELF",
|
||||
OperationType: sliceArrayOpType,
|
||||
Value: "SLICE",
|
||||
StringValue: ".[:3]",
|
||||
Preferences: sliceArrayPreferences{firstNumber: 0, secondNumber: 3, secondNumberDefined: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
TokenType: operationToken,
|
||||
Operation: &Operation{
|
||||
OperationType: traverseArrayOpType,
|
||||
StringValue: "TRAVERSE_ARRAY",
|
||||
},
|
||||
},
|
||||
{
|
||||
TokenType: openCollect,
|
||||
},
|
||||
{
|
||||
TokenType: operationToken,
|
||||
Operation: &Operation{
|
||||
OperationType: valueOpType,
|
||||
Value: 0,
|
||||
StringValue: "0",
|
||||
CandidateNode: &CandidateNode{
|
||||
Node: &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: "!!int",
|
||||
Value: "0",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
TokenType: operationToken,
|
||||
Operation: &Operation{
|
||||
OperationType: createMapOpType,
|
||||
Value: "CREATE_MAP",
|
||||
StringValue: ":",
|
||||
},
|
||||
},
|
||||
{
|
||||
TokenType: operationToken,
|
||||
Operation: &Operation{
|
||||
OperationType: valueOpType,
|
||||
Value: int64(3),
|
||||
StringValue: "3",
|
||||
CandidateNode: &CandidateNode{
|
||||
Node: &yaml.Node{
|
||||
Kind: yaml.Kind(8),
|
||||
Tag: "!!int",
|
||||
Value: "3",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
TokenType: closeCollect,
|
||||
CheckForPostTraverse: true,
|
||||
Match: "]",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
expression: ".[-2:]",
|
||||
expression: ".[1:]",
|
||||
tokens: []*token{
|
||||
{
|
||||
TokenType: operationToken,
|
||||
Operation: &Operation{
|
||||
OperationType: selfReferenceOpType,
|
||||
StringValue: "SELF",
|
||||
OperationType: sliceArrayOpType,
|
||||
Value: "SLICE",
|
||||
StringValue: ".[1:]",
|
||||
Preferences: sliceArrayPreferences{firstNumber: 1, secondNumber: 0, secondNumberDefined: false},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
expression: ".[-100:-54]",
|
||||
tokens: []*token{
|
||||
{
|
||||
TokenType: operationToken,
|
||||
Operation: &Operation{
|
||||
OperationType: traverseArrayOpType,
|
||||
StringValue: "TRAVERSE_ARRAY",
|
||||
OperationType: sliceArrayOpType,
|
||||
Value: "SLICE",
|
||||
StringValue: ".[-100:-54]",
|
||||
Preferences: sliceArrayPreferences{firstNumber: -100, secondNumber: -54, secondNumberDefined: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
TokenType: openCollect,
|
||||
},
|
||||
{
|
||||
TokenType: operationToken,
|
||||
Operation: &Operation{
|
||||
OperationType: valueOpType,
|
||||
Value: int64(-2),
|
||||
StringValue: "-2",
|
||||
CandidateNode: &CandidateNode{
|
||||
Node: &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: "!!int",
|
||||
Value: "-2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
TokenType: operationToken,
|
||||
Operation: &Operation{
|
||||
OperationType: createMapOpType,
|
||||
Value: "CREATE_MAP",
|
||||
StringValue: ":",
|
||||
},
|
||||
},
|
||||
{
|
||||
TokenType: operationToken,
|
||||
Operation: &Operation{
|
||||
OperationType: lengthOpType,
|
||||
},
|
||||
},
|
||||
{
|
||||
TokenType: closeCollect,
|
||||
CheckForPostTraverse: true,
|
||||
Match: "]",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -620,27 +555,6 @@ var participleLexerScenarios = []participleLexerScenario{
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
expression: `"string with a\n"`,
|
||||
tokens: []*token{
|
||||
{
|
||||
TokenType: operationToken,
|
||||
Operation: &Operation{
|
||||
OperationType: valueOpType,
|
||||
Value: "string with a\n",
|
||||
StringValue: "string with a\n",
|
||||
Preferences: nil,
|
||||
CandidateNode: &CandidateNode{
|
||||
Node: &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: "!!str",
|
||||
Value: "string with a\n",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
expression: `"string with a \""`,
|
||||
tokens: []*token{
|
||||
|
||||
+6
-29
@@ -53,7 +53,7 @@ var subtractAssignOpType = &operationType{Type: "SUBTRACT_ASSIGN", NumArgs: 2, P
|
||||
|
||||
var assignAttributesOpType = &operationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: assignAttributesOperator}
|
||||
var assignStyleOpType = &operationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: assignStyleOperator}
|
||||
var assignVariableOpType = &operationType{Type: "ASSIGN_VARIABLE", NumArgs: 2, Precedence: 40, Handler: useWithPipe}
|
||||
var assignVariableOpType = &operationType{Type: "ASSIGN_VARIABLE", NumArgs: 2, Precedence: 40, Handler: assignVariableOperator}
|
||||
var assignTagOpType = &operationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: assignTagOperator}
|
||||
var assignCommentOpType = &operationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: assignCommentsOperator}
|
||||
var assignAnchorOpType = &operationType{Type: "ASSIGN_ANCHOR", NumArgs: 2, Precedence: 40, Handler: assignAnchorOperator}
|
||||
@@ -62,10 +62,6 @@ var assignAliasOpType = &operationType{Type: "ASSIGN_ALIAS", NumArgs: 2, Precede
|
||||
var multiplyOpType = &operationType{Type: "MULTIPLY", NumArgs: 2, Precedence: 42, Handler: multiplyOperator}
|
||||
var multiplyAssignOpType = &operationType{Type: "MULTIPLY_ASSIGN", NumArgs: 2, Precedence: 42, Handler: multiplyAssignOperator}
|
||||
|
||||
var divideOpType = &operationType{Type: "DIVIDE", NumArgs: 2, Precedence: 42, Handler: divideOperator}
|
||||
|
||||
var moduloOpType = &operationType{Type: "MODULO", NumArgs: 2, Precedence: 42, Handler: moduloOperator}
|
||||
|
||||
var addOpType = &operationType{Type: "ADD", NumArgs: 2, Precedence: 42, Handler: addOperator}
|
||||
var subtractOpType = &operationType{Type: "SUBTRACT", NumArgs: 2, Precedence: 42, Handler: subtractOperator}
|
||||
var alternativeOpType = &operationType{Type: "ALTERNATIVE", NumArgs: 2, Precedence: 42, Handler: alternativeOperator}
|
||||
@@ -84,11 +80,9 @@ var lengthOpType = &operationType{Type: "LENGTH", NumArgs: 0, Precedence: 50, Ha
|
||||
var lineOpType = &operationType{Type: "LINE", NumArgs: 0, Precedence: 50, Handler: lineOperator}
|
||||
var columnOpType = &operationType{Type: "LINE", NumArgs: 0, Precedence: 50, Handler: columnOperator}
|
||||
|
||||
var expressionOpType = &operationType{Type: "EXP", NumArgs: 0, Precedence: 50, Handler: expressionOperator}
|
||||
|
||||
var collectOpType = &operationType{Type: "COLLECT", NumArgs: 1, Precedence: 50, Handler: collectOperator}
|
||||
var sliceArrayOpType = &operationType{Type: "SLICE", NumArgs: 0, Precedence: 50, Handler: sliceArrayOperator}
|
||||
var mapOpType = &operationType{Type: "MAP", NumArgs: 1, Precedence: 50, Handler: mapOperator}
|
||||
var filterOpType = &operationType{Type: "FILTER", NumArgs: 1, Precedence: 50, Handler: filterOperator}
|
||||
var errorOpType = &operationType{Type: "ERROR", NumArgs: 1, Precedence: 50, Handler: errorOperator}
|
||||
var pickOpType = &operationType{Type: "PICK", NumArgs: 1, Precedence: 50, Handler: pickOperator}
|
||||
var evalOpType = &operationType{Type: "EVAL", NumArgs: 1, Precedence: 50, Handler: evalOperator}
|
||||
@@ -98,8 +92,6 @@ var formatDateTimeOpType = &operationType{Type: "FORMAT_DATE_TIME", NumArgs: 1,
|
||||
var withDtFormatOpType = &operationType{Type: "WITH_DATE_TIME_FORMAT", NumArgs: 1, Precedence: 50, Handler: withDateTimeFormat}
|
||||
var nowOpType = &operationType{Type: "NOW", NumArgs: 0, Precedence: 50, Handler: nowOp}
|
||||
var tzOpType = &operationType{Type: "TIMEZONE", NumArgs: 1, Precedence: 50, Handler: tzOp}
|
||||
var fromUnixOpType = &operationType{Type: "FROM_UNIX", NumArgs: 0, Precedence: 50, Handler: fromUnixOp}
|
||||
var toUnixOpType = &operationType{Type: "TO_UNIX", NumArgs: 0, Precedence: 50, Handler: toUnixOp}
|
||||
|
||||
var encodeOpType = &operationType{Type: "ENCODE", NumArgs: 0, Precedence: 50, Handler: encodeOperator}
|
||||
var decodeOpType = &operationType{Type: "DECODE", NumArgs: 0, Precedence: 50, Handler: decodeOperator}
|
||||
@@ -140,7 +132,6 @@ var explodeOpType = &operationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50,
|
||||
var sortByOpType = &operationType{Type: "SORT_BY", NumArgs: 1, Precedence: 50, Handler: sortByOperator}
|
||||
var reverseOpType = &operationType{Type: "REVERSE", NumArgs: 0, Precedence: 50, Handler: reverseOperator}
|
||||
var sortOpType = &operationType{Type: "SORT", NumArgs: 0, Precedence: 50, Handler: sortOperator}
|
||||
var shuffleOpType = &operationType{Type: "SHUFFLE", NumArgs: 0, Precedence: 50, Handler: shuffleOperator}
|
||||
|
||||
var sortKeysOpType = &operationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: sortKeysOperator}
|
||||
|
||||
@@ -163,7 +154,6 @@ var traverseArrayOpType = &operationType{Type: "TRAVERSE_ARRAY", NumArgs: 2, Pre
|
||||
|
||||
var selfReferenceOpType = &operationType{Type: "SELF", NumArgs: 0, Precedence: 55, Handler: selfOperator}
|
||||
var valueOpType = &operationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: valueOperator}
|
||||
var referenceOpType = &operationType{Type: "REF", NumArgs: 0, Precedence: 50, Handler: referenceOperator}
|
||||
var envOpType = &operationType{Type: "ENV", NumArgs: 0, Precedence: 50, Handler: envOperator}
|
||||
var notOpType = &operationType{Type: "NOT", NumArgs: 0, Precedence: 50, Handler: notOperator}
|
||||
var emptyOpType = &operationType{Type: "EMPTY", Precedence: 50, Handler: emptyOperator}
|
||||
@@ -244,13 +234,13 @@ func guessTagFromCustomType(node *yaml.Node) string {
|
||||
if strings.HasPrefix(node.Tag, "!!") {
|
||||
return node.Tag
|
||||
} else if node.Value == "" {
|
||||
log.Debug("guessTagFromCustomType: node has no value to guess the type with")
|
||||
log.Warning("node has no value to guess the type with")
|
||||
return node.Tag
|
||||
}
|
||||
dataBucket, errorReading := parseSnippet(node.Value)
|
||||
|
||||
if errorReading != nil {
|
||||
log.Debug("guessTagFromCustomType: could not guess underlying tag type %v", errorReading)
|
||||
log.Warning("could not guess underlying tag type %v", errorReading)
|
||||
return node.Tag
|
||||
}
|
||||
guessedTag := unwrapDoc(dataBucket).Tag
|
||||
@@ -259,28 +249,16 @@ func guessTagFromCustomType(node *yaml.Node) string {
|
||||
}
|
||||
|
||||
func parseSnippet(value string) (*yaml.Node, error) {
|
||||
if value == "" {
|
||||
return &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: "!!null",
|
||||
}, nil
|
||||
}
|
||||
decoder := NewYamlDecoder(ConfiguredYamlPreferences)
|
||||
err := decoder.Init(strings.NewReader(value))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
parsedNode, err := decoder.Decode()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(parsedNode.Node.Content) == 0 {
|
||||
return nil, fmt.Errorf("bad data")
|
||||
}
|
||||
result := unwrapDoc(parsedNode.Node)
|
||||
result.Line = 0
|
||||
result.Column = 0
|
||||
return result, err
|
||||
return unwrapDoc(parsedNode.Node), err
|
||||
}
|
||||
|
||||
func recursiveNodeEqual(lhs *yaml.Node, rhs *yaml.Node) bool {
|
||||
@@ -343,7 +321,7 @@ func deepCloneWithOptions(node *yaml.Node, cloneContent bool) *yaml.Node {
|
||||
Tag: node.Tag,
|
||||
Value: node.Value,
|
||||
Anchor: node.Anchor,
|
||||
Alias: node.Alias,
|
||||
Alias: deepClone(node.Alias),
|
||||
HeadComment: node.HeadComment,
|
||||
LineComment: node.LineComment,
|
||||
FootComment: node.FootComment,
|
||||
@@ -425,7 +403,6 @@ func footComment(node *yaml.Node) string {
|
||||
}
|
||||
|
||||
func createValueOperation(value interface{}, stringValue string) *Operation {
|
||||
log.Debug("creating value op for string %v", stringValue)
|
||||
var node = createScalarNode(value, stringValue)
|
||||
|
||||
return &Operation{
|
||||
|
||||
+1
-95
@@ -1,11 +1,6 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mikefarah/yq/v4/test"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
import "testing"
|
||||
|
||||
func TestGetLogger(t *testing.T) {
|
||||
l := GetLogger()
|
||||
@@ -13,92 +8,3 @@ func TestGetLogger(t *testing.T) {
|
||||
t.Fatal("GetLogger should return the yq logger instance, not a copy")
|
||||
}
|
||||
}
|
||||
|
||||
type parseSnippetScenario struct {
|
||||
snippet string
|
||||
expected *yaml.Node
|
||||
expectedError string
|
||||
}
|
||||
|
||||
var parseSnippetScenarios = []parseSnippetScenario{
|
||||
{
|
||||
snippet: ":",
|
||||
expectedError: "yaml: did not find expected key",
|
||||
},
|
||||
{
|
||||
snippet: "",
|
||||
expected: &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: "!!null",
|
||||
},
|
||||
},
|
||||
{
|
||||
snippet: "null",
|
||||
expected: &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: "!!null",
|
||||
Value: "null",
|
||||
Line: 0,
|
||||
Column: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
snippet: "3",
|
||||
expected: &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: "!!int",
|
||||
Value: "3",
|
||||
Line: 0,
|
||||
Column: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
snippet: "cat",
|
||||
expected: &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: "!!str",
|
||||
Value: "cat",
|
||||
Line: 0,
|
||||
Column: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
snippet: "3.1",
|
||||
expected: &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: "!!float",
|
||||
Value: "3.1",
|
||||
Line: 0,
|
||||
Column: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
snippet: "true",
|
||||
expected: &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: "!!bool",
|
||||
Value: "true",
|
||||
Line: 0,
|
||||
Column: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestParseSnippet(t *testing.T) {
|
||||
for _, tt := range parseSnippetScenarios {
|
||||
actual, err := parseSnippet(tt.snippet)
|
||||
if tt.expectedError != "" {
|
||||
if err == nil {
|
||||
t.Errorf("Expected error '%v' but it worked!", tt.expectedError)
|
||||
} else {
|
||||
test.AssertResultComplexWithContext(t, tt.expectedError, err.Error(), tt.snippet)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
t.Error(tt.snippet)
|
||||
t.Error(err)
|
||||
}
|
||||
test.AssertResultComplexWithContext(t, tt.expected, actual, tt.snippet)
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user