mirror of
https://github.com/mikefarah/yq.git
synced 2026-08-24 16:39:58 +08:00
Compare commits
70
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5ef537f3fd | ||
|
|
f7fc4edce2 | ||
|
|
af177f9605 | ||
|
|
4d50b001d1 | ||
|
|
d14eaa9b49 | ||
|
|
91f47ff307 | ||
|
|
80b42b81fd | ||
|
|
bbe3055006 | ||
|
|
d2f1ae9db7 | ||
|
|
3aa4c83337 | ||
|
|
385921a3e2 | ||
|
|
9d3c402923 | ||
|
|
cb9076479c | ||
|
|
a793468048 | ||
|
|
f58b1e5263 | ||
|
|
eba075294a | ||
|
|
a389bb64b8 | ||
|
|
496035c75a | ||
|
|
d00ffd9ce8 | ||
|
|
d5bbe5bea5 | ||
|
|
6a52760415 | ||
|
|
1fa8804107 | ||
|
|
4cbede618e | ||
|
|
5fd2890d1b | ||
|
|
311622d14b | ||
|
|
8d516ce535 | ||
|
|
1b0a62d08d | ||
|
|
ac5346b138 | ||
|
|
ebd1548e96 | ||
|
|
aebdbb4113 | ||
|
|
7103b78d38 | ||
|
|
47f4ddc910 | ||
|
|
4ee4b09e12 | ||
|
|
df5737ab46 | ||
|
|
48b481b68d | ||
|
|
7305b50ffe | ||
|
|
875cfdc945 | ||
|
|
95aafdd8d5 | ||
|
|
fdce8fce13 | ||
|
|
d8324dee0d | ||
|
|
dccba10997 | ||
|
|
69d3e3f3f8 | ||
|
|
99b2ab610a | ||
|
|
cbe84232a3 | ||
|
|
dd4306b00a | ||
|
|
ac8ffa287e | ||
|
|
fa6fac1a76 | ||
|
|
a6f0495b1f | ||
|
|
e7f4c6ca8d | ||
|
|
f2dd29995b | ||
|
|
3404cc0d29 | ||
|
|
9d2210b152 | ||
|
|
a466821e8f | ||
|
|
360a47fddc | ||
|
|
08a6cb65fe | ||
|
|
2c14c98408 | ||
|
|
eafcc3bafa | ||
|
|
9539877ff6 | ||
|
|
d30941b575 | ||
|
|
fed96f67ea | ||
|
|
2340ce6a8c | ||
|
|
e5260d855f | ||
|
|
47f4f8c793 | ||
|
|
2195df9e7a | ||
|
|
fdb14875c0 | ||
|
|
3f1f66a8ee | ||
|
|
cf8cfbd865 | ||
|
|
62d167c141 | ||
|
|
cb27444e54 | ||
|
|
ce3d8378e7 |
@@ -11,7 +11,7 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '^1.20'
|
||||
id: go
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v3
|
||||
- uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '^1.20'
|
||||
check-latest: true
|
||||
|
||||
@@ -41,6 +41,8 @@ yq*.snap
|
||||
|
||||
test.yml
|
||||
test*.yml
|
||||
test*.xml
|
||||
test*.toml
|
||||
test*.yaml
|
||||
0.yml
|
||||
1.yml
|
||||
|
||||
+2
-2
@@ -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
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
# Documentation
|
||||
|
||||
The documentation is a bit of a mixed bag (sorry in advanced, I do plan on simplifying it...) - with some parts automatically generated and stiched together and some statically defined.
|
||||
The documentation is a bit of a mixed bag (sorry in advance, I do plan on simplifying it...) - with some parts automatically generated and stiched together and some statically defined.
|
||||
|
||||
Documentation is written in markdown, and is published in the 'gitbook' branch.
|
||||
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
FROM golang:1.20.1 as builder
|
||||
FROM golang:1.20.4 as builder
|
||||
|
||||
WORKDIR /go/src/mikefarah/yq
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN CGO_ENABLED=0 go build .
|
||||
RUN CGO_ENABLED=0 go build -ldflags "-s -w" .
|
||||
# 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.1
|
||||
FROM golang:1.20.4
|
||||
|
||||
COPY scripts/devtools.sh /opt/devtools.sh
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ 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 ?=
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ yq -i '
|
||||
|
||||
Convert JSON to YAML
|
||||
```bash
|
||||
yq -P sample.json
|
||||
yq -Poy sample.json
|
||||
```
|
||||
|
||||
See the [documentation](https://mikefarah.gitbook.io/yq/) for more examples.
|
||||
@@ -229,6 +229,8 @@ 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
|
||||
|
||||
@@ -286,15 +288,6 @@ 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
|
||||
|
||||
Executable
+249
@@ -0,0 +1,249 @@
|
||||
#!/bin/bash
|
||||
|
||||
setUp() {
|
||||
rm test*.yml 2>/dev/null || true
|
||||
rm test*.toml 2>/dev/null || true
|
||||
rm test*.tfstate 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"
|
||||
}
|
||||
|
||||
testInputToml() {
|
||||
cat >test.toml <<EOL
|
||||
[owner]
|
||||
name = "Tom Preston-Werner"
|
||||
dob = 1979-05-27T07:32:00-08:00
|
||||
EOL
|
||||
|
||||
read -r -d '' expected << EOM
|
||||
owner:
|
||||
name: Tom Preston-Werner
|
||||
dob: 1979-05-27T07:32:00-08:00
|
||||
EOM
|
||||
|
||||
X=$(./yq -oy test.toml)
|
||||
assertEquals "$expected" "$X"
|
||||
|
||||
X=$(./yq ea -oy test.toml)
|
||||
assertEquals "$expected" "$X"
|
||||
}
|
||||
|
||||
testInputTfstate() {
|
||||
cat >test.tfstate <<EOL
|
||||
{ "mike" : { "things": "cool" } }
|
||||
EOL
|
||||
|
||||
read -r -d '' expected << EOM
|
||||
{"mike": {"things": "cool"}}
|
||||
EOM
|
||||
|
||||
X=$(./yq test.tfstate)
|
||||
assertEquals "$expected" "$X"
|
||||
|
||||
X=$(./yq ea test.tfstate)
|
||||
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
|
||||
@@ -120,7 +120,7 @@ EOM
|
||||
}
|
||||
|
||||
testInputXmlNamespaces() {
|
||||
cat >test.yml <<EOL
|
||||
cat >test.xml <<EOL
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url">
|
||||
</map>
|
||||
@@ -134,10 +134,10 @@ map:
|
||||
+@xsi:schemaLocation: some-url
|
||||
EOM
|
||||
|
||||
X=$(./yq e -p=xml test.yml)
|
||||
X=$(./yq e -p=xml test.xml)
|
||||
assertEquals "$expected" "$X"
|
||||
|
||||
X=$(./yq ea -p=xml test.yml)
|
||||
X=$(./yq ea -p=xml test.xml)
|
||||
assertEquals "$expected" "$X"
|
||||
}
|
||||
|
||||
|
||||
Executable
+286
@@ -0,0 +1,286 @@
|
||||
#!/bin/bash
|
||||
|
||||
setUp() {
|
||||
rm test*.yml || true
|
||||
}
|
||||
|
||||
## Convenient bash shortcut to read records of NUL separated values
|
||||
## from stdin the safe way. See example usage in the next tests.
|
||||
read-0() {
|
||||
local eof="" IFS=''
|
||||
while [ "$1" ]; do
|
||||
## - The `-r` avoids bad surprise with '\n' and other interpreted
|
||||
## sequences that can be read.
|
||||
## - The `-d ''` is the (strange?) way to refer to NUL delimiter.
|
||||
## - The `--` is how to avoid unpleasant surprises if your
|
||||
## "$1" starts with "-" (minus) sign. This protection also
|
||||
## will produce a readable error if you want to try to start
|
||||
## your variable names with a "-".
|
||||
read -r -d '' -- "$1" || eof=1
|
||||
shift
|
||||
done
|
||||
[ -z "$eof" ] ## fail on EOF
|
||||
}
|
||||
|
||||
## Convenient bash shortcut to be used with the next function `p-err`
|
||||
## to read NUL separated values the safe way AND catch any errors from
|
||||
## the process creating the stream of NUL separated data. See example
|
||||
## usage in the tests.
|
||||
read-0-err() {
|
||||
local ret="$1" eof="" idx=0 last=
|
||||
read -r -- "${ret?}" <<<"0"
|
||||
shift
|
||||
while [ "$1" ]; do
|
||||
last=$idx
|
||||
read -r -d '' -- "$1" || {
|
||||
## Put this last value in ${!ret}
|
||||
eof="$1"
|
||||
read -r -- "$ret" <<<"${!eof}"
|
||||
break
|
||||
}
|
||||
((idx++))
|
||||
shift
|
||||
done
|
||||
[ -z "$eof" ] || {
|
||||
if [ "$last" != 0 ]; then
|
||||
## Uhoh, we have no idea if the errorlevel of the internal
|
||||
## command was properly delimited with a NUL char, and
|
||||
## anyway something went really wrong at least about the
|
||||
## number of fields separated by NUL char and the one
|
||||
## expected.
|
||||
echo "Error: read-0-err couldn't fill all value $ret = '${!ret}', '$eof', '${!eof}'" >&2
|
||||
read -r -- "$ret" <<<"not-enough-values"
|
||||
else
|
||||
if ! [[ "${!ret}" =~ ^[0-9]+$ && "${!ret}" -ge 0 && "${!ret}" -le 127 ]]; then
|
||||
## This could happen if you don't use `p-err` wrapper,
|
||||
## or used stdout in unexpected ways in your inner
|
||||
## command.
|
||||
echo "Error: last value is not a number, did you finish with an errorlevel ?" >&2
|
||||
read -r -- "$ret" <<<"last-value-not-a-number"
|
||||
fi
|
||||
fi
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
## Simply runs command given as argument and adds errorlevel in the
|
||||
## standard output. Is expected to be used in tandem with
|
||||
## `read-0-err`.
|
||||
p-err() {
|
||||
local exp="$1"
|
||||
"$@"
|
||||
printf "%s" "$?"
|
||||
}
|
||||
|
||||
wyq-r() {
|
||||
local exp="$1"
|
||||
./yq e -0 -r=false "$1"
|
||||
printf "%s" "$?"
|
||||
}
|
||||
|
||||
testBasicUsageRaw() {
|
||||
cat >test.yml <<EOL
|
||||
a: foo
|
||||
b: bar
|
||||
EOL
|
||||
|
||||
printf "foo\0bar\0" > expected.out
|
||||
|
||||
## We need to compare binary content here. We have to filter the compared
|
||||
## content through a representation that gets rid of NUL chars but accurately
|
||||
## transcribe the content.
|
||||
## Also as it would be nice to have a pretty output in case the test fails,
|
||||
## we use here 'hd': a widely available shortcut to 'hexdump' that will
|
||||
## pretty-print any binary to it's hexadecimal representation.
|
||||
##
|
||||
## Note that the standard `assertEquals` compare its arguments
|
||||
## value, but they can't hold NUL characters (this comes from the
|
||||
## limitation of the C API of `exec*(..)` functions that requires
|
||||
## `const char *arv[]`). And these are NUL terminated strings. As a
|
||||
## consequence, the NUL characters gets removed in bash arguments.
|
||||
assertEquals "$(hd expected.out)" \
|
||||
"$(./yq e -0 '.a, .b' test.yml | hd)"
|
||||
|
||||
rm expected.out
|
||||
}
|
||||
|
||||
testBasicUsage() {
|
||||
local a b
|
||||
cat >test.yml <<EOL
|
||||
a: foo
|
||||
b: bar
|
||||
EOL
|
||||
|
||||
## We provide 2 values, and ask to fill 2 variables.
|
||||
read-0 a b < <(./yq e -0 '.a, .b' test.yml)
|
||||
assertEquals "$?" "0" ## Everything is fine
|
||||
assertEquals "foo" "$a" ## Values are correctly parsed
|
||||
assertEquals "bar" "$b"
|
||||
|
||||
a=YYY ; b=XXX
|
||||
## Not enough values provided to fill `a` and `b`.
|
||||
read-0 a b < <(./yq e -0 '.a' test.yml)
|
||||
assertEquals "$?" "1" ## An error was emitted
|
||||
assertEquals "foo" "$a" ## First value was correctly parsed
|
||||
assertEquals "" "$b" ## Second was still reset
|
||||
|
||||
## Error from inner command are not catchable !. Use
|
||||
## `read-0-err`/`p-err` for that.
|
||||
read-0 a < <(printf "\0"; ./yq e -0 'xxx' test.yml; )
|
||||
assertEquals "$?" "0"
|
||||
|
||||
}
|
||||
|
||||
testBasicUsageJson() {
|
||||
cat >test.yml <<EOL
|
||||
a:
|
||||
x: foo
|
||||
b: bar
|
||||
EOL
|
||||
|
||||
read-0 a b < <(./yq e -0 -o=json '.a, .b' test.yml)
|
||||
|
||||
assertEquals '{
|
||||
"x": "foo"
|
||||
}' "$a"
|
||||
assertEquals '"bar"' "$b"
|
||||
|
||||
}
|
||||
|
||||
testFailWithValueContainingNUL() {
|
||||
local a b c
|
||||
## Note that value of field 'a' actually contains a NUL char !
|
||||
cat >test.yml <<EOL
|
||||
a: "foo\u0000bar"
|
||||
b: 1
|
||||
c: |
|
||||
wiz
|
||||
boom
|
||||
EOL
|
||||
|
||||
## We are looking for trouble with asking to separated fields with NUL
|
||||
## char and requested value `.a` actually contains itself a NUL char !
|
||||
read-0 a b c < <(./yq e -0 '.a, .b, .c' test.yml)
|
||||
assertNotEquals "0" "$?" ## read-0 failed to fill all values
|
||||
|
||||
## But here, we can request for one value, even if `./yq` fails
|
||||
read-0 b < <(./yq e -0 '.b, .a' test.yml)
|
||||
assertEquals "0" "$?" ## read-0 succeeds at feeding the first value
|
||||
## Note: to catch the failure of `yq`, see in the next tests the usage
|
||||
## of `read-0-err`.
|
||||
|
||||
## using -r=false solves any NUL containing value issues, but keeps
|
||||
## all in YAML representation:
|
||||
read-0 a b c < <(./yq e -0 -r=false '.a, .b, .c' test.yml)
|
||||
assertEquals "0" "$?" ## All goes well despite asking for `a` value
|
||||
|
||||
assertEquals '"foo\0bar"' "$a" ## This is a YAML string representation
|
||||
assertEquals '1' "$b"
|
||||
assertEquals '|
|
||||
wiz
|
||||
boom' "$c"
|
||||
}
|
||||
|
||||
testStandardLoop() {
|
||||
local E a b res
|
||||
|
||||
## Here everything is normal: 4 values, that will be paired
|
||||
## in key/values.
|
||||
cat >test.yml <<EOL
|
||||
- yay
|
||||
- wiz
|
||||
- hop
|
||||
- pow
|
||||
EOL
|
||||
|
||||
res=""
|
||||
while read-0-err E a b; do
|
||||
res+="$a: $b;"
|
||||
done < <(p-err ./yq -0 '.[]' test.yml)
|
||||
|
||||
assertEquals "0" "$E" ## errorlevel of internal command
|
||||
assertEquals "yay: wiz;hop: pow;" "$res" ## expected result
|
||||
}
|
||||
|
||||
testStandardLoopWithoutEnoughValues() {
|
||||
local E a b res
|
||||
|
||||
## Here 5 values, there will be a missing value when reading
|
||||
## pairs of value.
|
||||
cat >test.yml <<EOL
|
||||
- yay
|
||||
- wiz
|
||||
- hop
|
||||
- pow
|
||||
- kwak
|
||||
EOL
|
||||
|
||||
res=""
|
||||
## The loop will succeed 2 times then fail
|
||||
while read-0-err E a b; do
|
||||
res+="$a: $b;"
|
||||
done < <(p-err ./yq -0 '.[]' test.yml)
|
||||
|
||||
assertEquals "not-enough-values" "$E" ## Not enough value error
|
||||
assertEquals "yay: wiz;hop: pow;" "$res" ## the 2 full key/value pairs
|
||||
|
||||
}
|
||||
|
||||
testStandardLoopWithInternalCmdError() {
|
||||
local E a b res
|
||||
|
||||
## Note the third value contains a NUL char !
|
||||
cat >test.yml <<EOL
|
||||
- yay
|
||||
- wiz
|
||||
- "foo\0bar"
|
||||
- hop
|
||||
- pow
|
||||
EOL
|
||||
|
||||
res=""
|
||||
## It should be only upon the second pass in the loop that
|
||||
## read-0-err will catch the fact that there is an error !
|
||||
while read-0-err E a b; do
|
||||
res+="$a: $b;"
|
||||
done < <(p-err ./yq -0 '.[]' test.yml)
|
||||
assertEquals "1" "$E" ## Internal command errorlevel (from `./yq`)
|
||||
assertEquals "yay: wiz;" "$res" ## first 2 values were ok at least
|
||||
|
||||
}
|
||||
|
||||
testStandardLoopNotEnoughErrorEatsCmdError() {
|
||||
local E a b res
|
||||
|
||||
## Because of possible edge cases where the internal errorlevel
|
||||
## reported by `p-err` in the standard output might be mangled
|
||||
## with the unfinished record, `read-0-err E ...` will NOT report
|
||||
## the internal command error in the variable E and instead will
|
||||
## store the value 'not-enough-values'. In real world, anyway, you
|
||||
## will want to react the same if the internal command failed
|
||||
## and/or you didn't get as much values as expected while
|
||||
## reading. Keep in mind also that standard error is not
|
||||
## swallowed, so you can read reports from the inner command AND
|
||||
## from `read-0-err`.
|
||||
|
||||
## Here, note that the fourth value contains a NUL char !
|
||||
cat >test.yml <<EOL
|
||||
- yay
|
||||
- wiz
|
||||
- hop
|
||||
- "foo\0bar"
|
||||
- pow
|
||||
EOL
|
||||
|
||||
res=""
|
||||
## It should be only upon the second loop that read-0-err will catch
|
||||
## the fact that there are not enough data to fill the requested variables
|
||||
while read-0-err E a b; do
|
||||
res+="$a: $b;"
|
||||
done < <(p-err ./yq -0 '.[]' test.yml)
|
||||
assertEquals "not-enough-values" "$E" ## Not enough values error eats internal error !
|
||||
assertEquals "yay: wiz;" "$res" ## first 2 values were ok at least
|
||||
}
|
||||
|
||||
|
||||
source ./scripts/shunit2
|
||||
+5
-2
@@ -6,8 +6,10 @@ var unwrapScalar = false
|
||||
|
||||
var writeInplace = false
|
||||
var outputToJSON = false
|
||||
var outputFormat = "yaml"
|
||||
var inputFormat = "yaml"
|
||||
|
||||
var outputFormat = ""
|
||||
|
||||
var inputFormat = ""
|
||||
|
||||
var exitStatus = false
|
||||
var forceColor = false
|
||||
@@ -16,6 +18,7 @@ var colorsEnabled = false
|
||||
var indent = 2
|
||||
var noDocSeparators = false
|
||||
var nullInput = false
|
||||
var nulSepOutput = false
|
||||
var verbose = false
|
||||
var version = false
|
||||
var prettyPrint = false
|
||||
|
||||
@@ -84,9 +84,15 @@ func evaluateAll(cmd *cobra.Command, args []string) (cmdError error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
encoder := configureEncoder(format)
|
||||
encoder, err := configureEncoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printer := yqlib.NewPrinter(encoder, printerWriter)
|
||||
if nulSepOutput {
|
||||
printer.SetNulSepOutput(true)
|
||||
}
|
||||
|
||||
if frontMatter != "" {
|
||||
frontMatterHandler := yqlib.NewFrontMatterHandler(args[0])
|
||||
|
||||
@@ -93,9 +93,15 @@ func evaluateSequence(cmd *cobra.Command, args []string) (cmdError error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
encoder := configureEncoder(format)
|
||||
encoder, err := configureEncoder()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
printer := yqlib.NewPrinter(encoder, printerWriter)
|
||||
if nulSepOutput {
|
||||
printer.SetNulSepOutput(true)
|
||||
}
|
||||
|
||||
decoder, err := configureDecoder(false)
|
||||
if err != nil {
|
||||
|
||||
+3
-21
@@ -53,25 +53,6 @@ yq -P sample.json
|
||||
logging.SetBackend(backend)
|
||||
yqlib.InitExpressionParser()
|
||||
|
||||
outputFormatType, err := yqlib.OutputFormatFromString(outputFormat)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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 nil
|
||||
},
|
||||
}
|
||||
@@ -84,8 +65,8 @@ yq -P sample.json
|
||||
panic(err)
|
||||
}
|
||||
|
||||
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().StringVarP(&outputFormat, "output-format", "o", "auto", "[auto|a|yaml|y|json|j|props|p|xml|x|tsv|t|csv|c] output format type.")
|
||||
rootCmd.PersistentFlags().StringVarP(&inputFormat, "input-format", "p", "auto", "[auto|a|yaml|y|props|p|xml|x|tsv|t|csv|c|toml] 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).")
|
||||
@@ -105,6 +86,7 @@ yq -P sample.json
|
||||
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(&nulSepOutput, "nul-output", "0", false, "Use NUL char to separate values. If unwrap scalar is also set, fail if unwrapped scalar contains NUL char.")
|
||||
|
||||
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")
|
||||
|
||||
+100
-12
@@ -10,6 +10,10 @@ import (
|
||||
"gopkg.in/op/go-logging.v1"
|
||||
)
|
||||
|
||||
func isAutomaticOutputFormat() bool {
|
||||
return outputFormat == "" || outputFormat == "auto" || outputFormat == "a"
|
||||
}
|
||||
|
||||
func initCommand(cmd *cobra.Command, args []string) (string, []string, error) {
|
||||
cmd.SilenceUsage = true
|
||||
|
||||
@@ -53,6 +57,62 @@ 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)
|
||||
|
||||
_, err := yqlib.InputFormatFromString(inputFormat)
|
||||
if err != nil {
|
||||
// unknown file type, default to yaml
|
||||
yqlib.GetLogger().Debug("Unknown file format extension '%v', defaulting to yaml", inputFormat)
|
||||
inputFormat = "yaml"
|
||||
if isAutomaticOutputFormat() {
|
||||
outputFormat = "yaml"
|
||||
}
|
||||
} else if isAutomaticOutputFormat() {
|
||||
// automatic input worked, we can do it for output too unless specified
|
||||
if inputFormat == "json" {
|
||||
yqlib.GetLogger().Warning("JSON file output is now JSON by default (instead of yaml). Use '-oy' or '--output-format=yaml' for yaml output")
|
||||
}
|
||||
outputFormat = inputFormat
|
||||
}
|
||||
} else if isAutomaticOutputFormat() {
|
||||
// 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 input format %v", inputFormat)
|
||||
yqlib.GetLogger().Debug("Using output format %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
|
||||
}
|
||||
|
||||
@@ -61,7 +121,15 @@ func configureDecoder(evaluateTogether bool) (yqlib.Decoder, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch yqlibInputFormat {
|
||||
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 {
|
||||
case yqlib.XMLInputFormat:
|
||||
return yqlib.NewXMLDecoder(yqlib.ConfiguredXMLPreferences), nil
|
||||
case yqlib.PropertiesInputFormat:
|
||||
@@ -72,10 +140,14 @@ func configureDecoder(evaluateTogether bool) (yqlib.Decoder, error) {
|
||||
return yqlib.NewCSVObjectDecoder(','), nil
|
||||
case yqlib.TSVObjectInputFormat:
|
||||
return yqlib.NewCSVObjectDecoder('\t'), nil
|
||||
case yqlib.TomlInputFormat:
|
||||
return yqlib.NewTomlDecoder(), nil
|
||||
case yqlib.YamlInputFormat:
|
||||
prefs := yqlib.ConfiguredYamlPreferences
|
||||
prefs.EvaluateTogether = evaluateTogether
|
||||
return yqlib.NewYamlDecoder(prefs), nil
|
||||
}
|
||||
prefs := yqlib.ConfiguredYamlPreferences
|
||||
prefs.EvaluateTogether = evaluateTogether
|
||||
return yqlib.NewYamlDecoder(prefs), nil
|
||||
return nil, fmt.Errorf("invalid decoder: %v", format)
|
||||
}
|
||||
|
||||
func configurePrinterWriter(format yqlib.PrinterOutputFormat, out io.Writer) (yqlib.PrinterWriter, error) {
|
||||
@@ -95,22 +167,38 @@ func configurePrinterWriter(format yqlib.PrinterOutputFormat, out io.Writer) (yq
|
||||
return printerWriter, nil
|
||||
}
|
||||
|
||||
func configureEncoder(format yqlib.PrinterOutputFormat) yqlib.Encoder {
|
||||
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) {
|
||||
switch format {
|
||||
case yqlib.JSONOutputFormat:
|
||||
return yqlib.NewJSONEncoder(indent, colorsEnabled, unwrapScalar)
|
||||
return yqlib.NewJSONEncoder(indent, colorsEnabled, unwrapScalar), nil
|
||||
case yqlib.PropsOutputFormat:
|
||||
return yqlib.NewPropertiesEncoder(unwrapScalar)
|
||||
return yqlib.NewPropertiesEncoder(unwrapScalar), nil
|
||||
case yqlib.CSVOutputFormat:
|
||||
return yqlib.NewCsvEncoder(',')
|
||||
return yqlib.NewCsvEncoder(','), nil
|
||||
case yqlib.TSVOutputFormat:
|
||||
return yqlib.NewCsvEncoder('\t')
|
||||
return yqlib.NewCsvEncoder('\t'), nil
|
||||
case yqlib.YamlOutputFormat:
|
||||
return yqlib.NewYamlEncoder(indent, colorsEnabled, yqlib.ConfiguredYamlPreferences)
|
||||
return yqlib.NewYamlEncoder(indent, colorsEnabled, yqlib.ConfiguredYamlPreferences), nil
|
||||
case yqlib.XMLOutputFormat:
|
||||
return yqlib.NewXMLEncoder(indent, yqlib.ConfiguredXMLPreferences)
|
||||
return yqlib.NewXMLEncoder(indent, yqlib.ConfiguredXMLPreferences), nil
|
||||
case yqlib.TomlOutputFormat:
|
||||
return yqlib.NewTomlEncoder(), nil
|
||||
case yqlib.ShellVariablesOutputFormat:
|
||||
return yqlib.NewShellVariablesEncoder(), nil
|
||||
}
|
||||
panic("invalid encoder")
|
||||
return nil, fmt.Errorf("invalid encoder: %v", format)
|
||||
}
|
||||
|
||||
// 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.31.1"
|
||||
Version = "v4.34.1"
|
||||
|
||||
// VersionPrerelease is a pre-release marker for the version. If this is "" (empty string)
|
||||
// then it means that it is a final release. Otherwise, this is a pre-release
|
||||
|
||||
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
|
||||
|
||||
|
||||
+1
-5
@@ -1,5 +1 @@
|
||||
date: "2022-11-25"
|
||||
raw:
|
||||
id: 1
|
||||
XML: text_outside_1<Tag1 />text_outside_2<Tag2Kling Klong</Tag2>text_outside_3
|
||||
time: 09:19:00
|
||||
<cat>3</cat>
|
||||
@@ -0,0 +1,26 @@
|
||||
|
||||
|
||||
# This is a TOML document
|
||||
|
||||
title = "TOML Example"
|
||||
|
||||
[owner]
|
||||
name = "Tom Preston-Werner"
|
||||
dob = 1979-05-27T07:32:00-08:00
|
||||
|
||||
[database]
|
||||
enabled = true
|
||||
ports = [ 8000, 8001, 8002 ]
|
||||
data = [ ["delta", "phi"], [3.14] ]
|
||||
temp_targets = { cpu = 79.5, case = 72.0 }
|
||||
|
||||
[servers]
|
||||
|
||||
[servers.alpha]
|
||||
ip = "10.0.0.1"
|
||||
role = "frontend"
|
||||
|
||||
[servers.beta]
|
||||
ip = "10.0.0.2"
|
||||
role = "backend"
|
||||
|
||||
@@ -1,30 +1,31 @@
|
||||
module github.com/mikefarah/yq/v4
|
||||
|
||||
require (
|
||||
github.com/a8m/envsubst v1.4.1
|
||||
github.com/alecthomas/participle/v2 v2.0.0-beta.5
|
||||
github.com/a8m/envsubst v1.4.2
|
||||
github.com/alecthomas/participle/v2 v2.0.0
|
||||
github.com/alecthomas/repr v0.2.0
|
||||
github.com/dimchansky/utfbom v1.1.1
|
||||
github.com/elliotchance/orderedmap v1.5.0
|
||||
github.com/fatih/color v1.14.1
|
||||
github.com/goccy/go-json v0.10.0
|
||||
github.com/goccy/go-yaml v1.9.8
|
||||
github.com/fatih/color v1.15.0
|
||||
github.com/goccy/go-json v0.10.2
|
||||
github.com/goccy/go-yaml v1.11.0
|
||||
github.com/jinzhu/copier v0.3.5
|
||||
github.com/magiconair/properties v1.8.7
|
||||
github.com/pelletier/go-toml/v2 v2.0.7
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/spf13/pflag v1.0.5
|
||||
golang.org/x/net v0.1.1-0.20221104162952-702349b0e862
|
||||
golang.org/x/net v0.10.0
|
||||
golang.org/x/text v0.9.0
|
||||
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/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
golang.org/x/sys v0.3.0 // indirect
|
||||
golang.org/x/text v0.4.0 // indirect
|
||||
golang.org/x/sys v0.8.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
|
||||
)
|
||||
|
||||
|
||||
@@ -1,82 +1,68 @@
|
||||
github.com/a8m/envsubst v1.4.1 h1:RShc2OKbQpIIp5qR2glVsBOJCImTVHFrDkJvmGh0Qi0=
|
||||
github.com/a8m/envsubst v1.4.1/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/a8m/envsubst v1.4.2 h1:4yWIHXOLEJHQEFd4UjrWDrYeYlV7ncFWJOCBRLOZHQg=
|
||||
github.com/a8m/envsubst v1.4.2/go.mod h1:MVUTQNGQ3tsjOOtKCNd+fl8RzhsXcDvvAEzkhGtlsbY=
|
||||
github.com/alecthomas/assert/v2 v2.2.2 h1:Z/iVC0xZfWTaFNE6bA3z07T86hd45Xe2eLt6WVy2bbk=
|
||||
github.com/alecthomas/participle/v2 v2.0.0 h1:Fgrq+MbuSsJwIkw3fEj9h75vDP0Er5JzepJ0/HNHv0g=
|
||||
github.com/alecthomas/participle/v2 v2.0.0/go.mod h1:rAKZdJldHu8084ojcWevWAL8KmEU+AT+Olodb+WoN2Y=
|
||||
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/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=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
|
||||
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
|
||||
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.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
|
||||
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
|
||||
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.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
|
||||
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-yaml v1.9.8 h1:5gMyLUeU1/6zl+WFfR1hN7D2kf+1/eRGa7DFtToiBvQ=
|
||||
github.com/goccy/go-yaml v1.9.8/go.mod h1:JubOolP3gh0HpiBc4BLRD4YmjEjHAmIIB2aaXKkTfoE=
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-yaml v1.11.0 h1:n7Z+zx8S9f9KgzG6KtQKf+kwqXZlLNR2F6018Dgau54=
|
||||
github.com/goccy/go-yaml v1.11.0/go.mod h1:H+mJrWtjPTJAHvRbV09MCK9xYwODM+wRTVFFTWckfng=
|
||||
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/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/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/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-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/pelletier/go-toml/v2 v2.0.7 h1:muncTPStnKRos5dpVKULv2FVd4bMOhNePj9CjgDb8Us=
|
||||
github.com/pelletier/go-toml/v2 v2.0.7/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
||||
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=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
|
||||
github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
|
||||
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
|
||||
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
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.1.1-0.20221104162952-702349b0e862 h1:KrLJ+iz8J6j6VVr/OCfULAcK+xozUmWE43fKpMR4MlI=
|
||||
golang.org/x/net v0.1.1-0.20221104162952-702349b0e862/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
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-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
||||
golang.org/x/sys v0.3.0/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.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
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/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0=
|
||||
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 h1:6D+BvnJ/j6e222UW8s2qTSe3wGBtvo0MbVQG/c5k8RE=
|
||||
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
@@ -126,6 +126,14 @@ var csvScenarios = []formatScenario{
|
||||
expected: expectedYamlFromCSV,
|
||||
scenarioType: "decode-csv-object",
|
||||
},
|
||||
{
|
||||
description: "Scalar roundtrip",
|
||||
skipDoc: true,
|
||||
input: "mike\ncat",
|
||||
expression: ".[0].mike",
|
||||
expected: "cat\n",
|
||||
scenarioType: "roundtrip-csv",
|
||||
},
|
||||
{
|
||||
description: "Parse TSV into an array of objects",
|
||||
subdescription: "First row is assumed to be the header row.",
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
logging "gopkg.in/op/go-logging.v1"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type DataTreeNavigator interface {
|
||||
@@ -11,6 +12,8 @@ type DataTreeNavigator interface {
|
||||
// this will process the against the given expressionNode and return
|
||||
// a new context of matching candidates
|
||||
GetMatchingNodes(context Context, expressionNode *ExpressionNode) (Context, error)
|
||||
|
||||
DeeplyAssign(context Context, path []interface{}, rhsNode *yaml.Node) error
|
||||
}
|
||||
|
||||
type dataTreeNavigator struct {
|
||||
@@ -20,6 +23,27 @@ func NewDataTreeNavigator() DataTreeNavigator {
|
||||
return &dataTreeNavigator{}
|
||||
}
|
||||
|
||||
func (d *dataTreeNavigator) DeeplyAssign(context Context, path []interface{}, rhsNode *yaml.Node) error {
|
||||
|
||||
rhsCandidateNode := &CandidateNode{
|
||||
Path: path,
|
||||
Node: rhsNode,
|
||||
}
|
||||
|
||||
assignmentOp := &Operation{OperationType: assignOpType, Preferences: assignPreferences{}}
|
||||
|
||||
rhsOp := &Operation{OperationType: valueOpType, CandidateNode: rhsCandidateNode}
|
||||
|
||||
assignmentOpNode := &ExpressionNode{
|
||||
Operation: assignmentOp,
|
||||
LHS: createTraversalTree(path, traversePreferences{}, false),
|
||||
RHS: &ExpressionNode{Operation: rhsOp},
|
||||
}
|
||||
|
||||
_, err := d.GetMatchingNodes(context, assignmentOpNode)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *dataTreeNavigator) GetMatchingNodes(context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
if expressionNode == nil {
|
||||
log.Debugf("getMatchingNodes - nothing to do")
|
||||
|
||||
+23
-3
@@ -3,6 +3,7 @@ package yqlib
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type InputFormat uint
|
||||
@@ -15,6 +16,7 @@ const (
|
||||
JsonInputFormat
|
||||
CSVObjectInputFormat
|
||||
TSVObjectInputFormat
|
||||
TomlInputFormat
|
||||
UriInputFormat
|
||||
)
|
||||
|
||||
@@ -25,11 +27,11 @@ type Decoder interface {
|
||||
|
||||
func InputFormatFromString(format string) (InputFormat, error) {
|
||||
switch format {
|
||||
case "yaml", "y":
|
||||
case "yaml", "yml", "y":
|
||||
return YamlInputFormat, nil
|
||||
case "xml", "x":
|
||||
return XMLInputFormat, nil
|
||||
case "props", "p":
|
||||
case "properties", "props", "p":
|
||||
return PropertiesInputFormat, nil
|
||||
case "json", "ndjson", "j":
|
||||
return JsonInputFormat, nil
|
||||
@@ -37,7 +39,25 @@ func InputFormatFromString(format string) (InputFormat, error) {
|
||||
return CSVObjectInputFormat, nil
|
||||
case "tsv", "t":
|
||||
return TSVObjectInputFormat, nil
|
||||
case "toml":
|
||||
return TomlInputFormat, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("unknown format '%v' please use [yaml|xml|props]", format)
|
||||
return 0, fmt.Errorf("unknown format '%v' please use [yaml|json|props|csv|tsv|xml|toml]", 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,10 +4,33 @@ 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
|
||||
@@ -20,7 +43,7 @@ func NewBase64Decoder() Decoder {
|
||||
}
|
||||
|
||||
func (dec *base64Decoder) Init(reader io.Reader) error {
|
||||
dec.reader = reader
|
||||
dec.reader = &base64Padder{Reader: reader}
|
||||
dec.readAnything = false
|
||||
dec.finished = false
|
||||
return nil
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build !yq_nojson
|
||||
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
@@ -44,6 +46,9 @@ func (dec *jsonDecoder) Decode() (*CandidateNode, error) {
|
||||
}
|
||||
|
||||
func (dec *jsonDecoder) convertToYamlNode(data *orderedMap) (*yaml.Node, error) {
|
||||
if data == nil {
|
||||
return createScalarNode(nil, "null"), nil
|
||||
}
|
||||
if data.kv == nil {
|
||||
switch rawData := data.altVal.(type) {
|
||||
case nil:
|
||||
|
||||
@@ -95,23 +95,7 @@ func (dec *propertiesDecoder) applyProperty(context Context, properties *propert
|
||||
|
||||
rhsNode.Tag = guessTagFromCustomType(rhsNode)
|
||||
|
||||
rhsCandidateNode := &CandidateNode{
|
||||
Path: path,
|
||||
Node: rhsNode,
|
||||
}
|
||||
|
||||
assignmentOp := &Operation{OperationType: assignOpType, Preferences: assignPreferences{}}
|
||||
|
||||
rhsOp := &Operation{OperationType: referenceOpType, CandidateNode: rhsCandidateNode}
|
||||
|
||||
assignmentOpNode := &ExpressionNode{
|
||||
Operation: assignmentOp,
|
||||
LHS: createTraversalTree(path, traversePreferences{}, false),
|
||||
RHS: &ExpressionNode{Operation: rhsOp},
|
||||
}
|
||||
|
||||
_, err := dec.d.GetMatchingNodes(context, assignmentOpNode)
|
||||
return err
|
||||
return dec.d.DeeplyAssign(context, path, rhsNode)
|
||||
}
|
||||
|
||||
func (dec *propertiesDecoder) Decode() (*CandidateNode, error) {
|
||||
|
||||
@@ -3,6 +3,7 @@ package yqlib
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -62,7 +63,7 @@ func mustProcessFormatScenario(s formatScenario, decoder Decoder, encoder Encode
|
||||
|
||||
result, err := processFormatScenario(s, decoder, encoder)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
panic(fmt.Errorf("Bad scenario %v: %w", s.description, err))
|
||||
}
|
||||
return result
|
||||
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
//go:build !yq_notoml
|
||||
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
toml "github.com/pelletier/go-toml/v2/unstable"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type tomlDecoder struct {
|
||||
parser toml.Parser
|
||||
finished bool
|
||||
d DataTreeNavigator
|
||||
rootMap *CandidateNode
|
||||
}
|
||||
|
||||
func NewTomlDecoder() Decoder {
|
||||
return &tomlDecoder{
|
||||
finished: false,
|
||||
d: NewDataTreeNavigator(),
|
||||
}
|
||||
}
|
||||
|
||||
func (dec *tomlDecoder) Init(reader io.Reader) error {
|
||||
dec.parser = toml.Parser{}
|
||||
buf := new(bytes.Buffer)
|
||||
_, err := buf.ReadFrom(reader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dec.parser.Reset(buf.Bytes())
|
||||
dec.rootMap = &CandidateNode{
|
||||
Node: &yaml.Node{
|
||||
Kind: yaml.MappingNode,
|
||||
Tag: "!!map",
|
||||
}}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dec *tomlDecoder) getFullPath(tomlNode *toml.Node) []interface{} {
|
||||
path := make([]interface{}, 0)
|
||||
for {
|
||||
path = append(path, string(tomlNode.Data))
|
||||
tomlNode = tomlNode.Next()
|
||||
if tomlNode == nil {
|
||||
return path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (dec *tomlDecoder) processKeyValueIntoMap(rootMap *CandidateNode, tomlNode *toml.Node) error {
|
||||
value := tomlNode.Value()
|
||||
path := dec.getFullPath(value.Next())
|
||||
log.Debug("!!!processKeyValueIntoMap: %v", path)
|
||||
|
||||
valueNode, err := dec.decodeNode(value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
context := Context{}
|
||||
context = context.SingleChildContext(rootMap)
|
||||
|
||||
return dec.d.DeeplyAssign(context, path, valueNode)
|
||||
}
|
||||
|
||||
func (dec *tomlDecoder) decodeKeyValuesIntoMap(rootMap *CandidateNode, tomlNode *toml.Node) (bool, error) {
|
||||
log.Debug("!! DECODE_KV_INTO_MAP -- processing first (current) entry")
|
||||
if err := dec.processKeyValueIntoMap(rootMap, tomlNode); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for dec.parser.NextExpression() {
|
||||
nextItem := dec.parser.Expression()
|
||||
log.Debug("!! DECODE_KV_INTO_MAP -- next exp, its a %v", nextItem.Kind)
|
||||
|
||||
if nextItem.Kind == toml.KeyValue {
|
||||
if err := dec.processKeyValueIntoMap(rootMap, nextItem); err != nil {
|
||||
return false, err
|
||||
}
|
||||
} else {
|
||||
// run out of key values
|
||||
log.Debug("! DECODE_KV_INTO_MAP - ok we are done in decodeKeyValuesIntoMap, gota a %v", nextItem.Kind)
|
||||
log.Debug("! DECODE_KV_INTO_MAP - processAgainstCurrentExp = true!")
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
log.Debug("! DECODE_KV_INTO_MAP - no more things to read in")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (dec *tomlDecoder) createInlineTableMap(tomlNode *toml.Node) (*yaml.Node, error) {
|
||||
content := make([]*yaml.Node, 0)
|
||||
log.Debug("!! createInlineTableMap")
|
||||
|
||||
iterator := tomlNode.Children()
|
||||
for iterator.Next() {
|
||||
child := iterator.Node()
|
||||
if child.Kind != toml.KeyValue {
|
||||
return nil, fmt.Errorf("only keyvalue pairs are supported in inlinetables, got %v instead", child.Kind)
|
||||
}
|
||||
|
||||
keyValues := &CandidateNode{
|
||||
Node: &yaml.Node{
|
||||
Kind: yaml.MappingNode,
|
||||
Tag: "!!map",
|
||||
},
|
||||
}
|
||||
|
||||
if err := dec.processKeyValueIntoMap(keyValues, child); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
content = append(content, keyValues.Node.Content...)
|
||||
}
|
||||
|
||||
return &yaml.Node{
|
||||
Kind: yaml.MappingNode,
|
||||
Tag: "!!map",
|
||||
Content: content,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (dec *tomlDecoder) createArray(tomlNode *toml.Node) (*yaml.Node, error) {
|
||||
content := make([]*yaml.Node, 0)
|
||||
iterator := tomlNode.Children()
|
||||
for iterator.Next() {
|
||||
child := iterator.Node()
|
||||
yamlNode, err := dec.decodeNode(child)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
content = append(content, yamlNode)
|
||||
}
|
||||
|
||||
return &yaml.Node{
|
||||
Kind: yaml.SequenceNode,
|
||||
Tag: "!!seq",
|
||||
Content: content,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func (dec *tomlDecoder) createStringScalar(tomlNode *toml.Node) (*yaml.Node, error) {
|
||||
content := string(tomlNode.Data)
|
||||
return createScalarNode(content, content), nil
|
||||
}
|
||||
|
||||
func (dec *tomlDecoder) createBoolScalar(tomlNode *toml.Node) (*yaml.Node, error) {
|
||||
content := string(tomlNode.Data)
|
||||
return createScalarNode(content == "true", content), nil
|
||||
}
|
||||
|
||||
func (dec *tomlDecoder) createIntegerScalar(tomlNode *toml.Node) (*yaml.Node, error) {
|
||||
content := string(tomlNode.Data)
|
||||
_, num, err := parseInt64(content)
|
||||
return createScalarNode(num, content), err
|
||||
}
|
||||
|
||||
func (dec *tomlDecoder) createDateTimeScalar(tomlNode *toml.Node) (*yaml.Node, error) {
|
||||
content := string(tomlNode.Data)
|
||||
val, err := parseDateTime(time.RFC3339, content)
|
||||
return createScalarNode(val, content), err
|
||||
}
|
||||
|
||||
func (dec *tomlDecoder) createFloatScalar(tomlNode *toml.Node) (*yaml.Node, error) {
|
||||
content := string(tomlNode.Data)
|
||||
num, err := strconv.ParseFloat(content, 64)
|
||||
return createScalarNode(num, content), err
|
||||
}
|
||||
|
||||
func (dec *tomlDecoder) decodeNode(tomlNode *toml.Node) (*yaml.Node, error) {
|
||||
switch tomlNode.Kind {
|
||||
case toml.Key, toml.String:
|
||||
return dec.createStringScalar(tomlNode)
|
||||
case toml.Bool:
|
||||
return dec.createBoolScalar(tomlNode)
|
||||
case toml.Integer:
|
||||
return dec.createIntegerScalar(tomlNode)
|
||||
case toml.DateTime:
|
||||
return dec.createDateTimeScalar(tomlNode)
|
||||
case toml.Float:
|
||||
return dec.createFloatScalar(tomlNode)
|
||||
case toml.Array:
|
||||
return dec.createArray(tomlNode)
|
||||
case toml.InlineTable:
|
||||
return dec.createInlineTableMap(tomlNode)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported type %v", tomlNode.Kind)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (dec *tomlDecoder) Decode() (*CandidateNode, error) {
|
||||
if dec.finished {
|
||||
return nil, io.EOF
|
||||
}
|
||||
//
|
||||
// toml library likes to panic
|
||||
var deferredError error
|
||||
defer func() { //catch or finally
|
||||
if r := recover(); r != nil {
|
||||
var ok bool
|
||||
deferredError, ok = r.(error)
|
||||
if !ok {
|
||||
deferredError = fmt.Errorf("pkg: %v", r)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
log.Debug("ok here we go")
|
||||
var runAgainstCurrentExp = false
|
||||
var err error
|
||||
for runAgainstCurrentExp || dec.parser.NextExpression() {
|
||||
|
||||
if runAgainstCurrentExp {
|
||||
log.Debug("running against current exp")
|
||||
}
|
||||
|
||||
currentNode := dec.parser.Expression()
|
||||
|
||||
log.Debug("currentNode: %v ", currentNode.Kind)
|
||||
runAgainstCurrentExp, err = dec.processTopLevelNode(currentNode)
|
||||
if err != nil {
|
||||
return dec.rootMap, err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
err = dec.parser.Error()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// must have finished
|
||||
dec.finished = true
|
||||
|
||||
if len(dec.rootMap.Node.Content) == 0 {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
return &CandidateNode{
|
||||
Node: &yaml.Node{
|
||||
Kind: yaml.DocumentNode,
|
||||
Content: []*yaml.Node{dec.rootMap.Node},
|
||||
},
|
||||
}, deferredError
|
||||
|
||||
}
|
||||
|
||||
func (dec *tomlDecoder) processTopLevelNode(currentNode *toml.Node) (bool, error) {
|
||||
var runAgainstCurrentExp bool
|
||||
var err error
|
||||
log.Debug("!!!!!!!!!!!!Going to process %v state is current %v", currentNode.Kind, NodeToString(dec.rootMap))
|
||||
if currentNode.Kind == toml.Table {
|
||||
runAgainstCurrentExp, err = dec.processTable((currentNode))
|
||||
} else if currentNode.Kind == toml.ArrayTable {
|
||||
runAgainstCurrentExp, err = dec.processArrayTable((currentNode))
|
||||
} else {
|
||||
runAgainstCurrentExp, err = dec.decodeKeyValuesIntoMap(dec.rootMap, currentNode)
|
||||
}
|
||||
|
||||
log.Debug("!!!!!!!!!!!!DONE Processing state is now %v", NodeToString(dec.rootMap))
|
||||
return runAgainstCurrentExp, err
|
||||
}
|
||||
|
||||
func (dec *tomlDecoder) processTable(currentNode *toml.Node) (bool, error) {
|
||||
log.Debug("!!! processing table")
|
||||
fullPath := dec.getFullPath(currentNode.Child())
|
||||
log.Debug("!!!fullpath: %v", fullPath)
|
||||
|
||||
hasValue := dec.parser.NextExpression()
|
||||
if !hasValue {
|
||||
return false, fmt.Errorf("error retrieving table %v value: %w", fullPath, dec.parser.Error())
|
||||
}
|
||||
|
||||
tableNodeValue := &CandidateNode{
|
||||
Node: &yaml.Node{
|
||||
Kind: yaml.MappingNode,
|
||||
Tag: "!!map",
|
||||
},
|
||||
}
|
||||
|
||||
tableValue := dec.parser.Expression()
|
||||
if tableValue.Kind != toml.KeyValue {
|
||||
log.Debug("got an empty table, returning")
|
||||
return true, nil
|
||||
}
|
||||
|
||||
runAgainstCurrentExp, err := dec.decodeKeyValuesIntoMap(tableNodeValue, tableValue)
|
||||
log.Debugf("table node err: %w", err)
|
||||
if err != nil && !errors.Is(io.EOF, err) {
|
||||
return false, err
|
||||
}
|
||||
c := Context{}
|
||||
|
||||
c = c.SingleChildContext(dec.rootMap)
|
||||
err = dec.d.DeeplyAssign(c, fullPath, tableNodeValue.Node)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return runAgainstCurrentExp, nil
|
||||
}
|
||||
|
||||
func (dec *tomlDecoder) arrayAppend(context Context, path []interface{}, rhsNode *yaml.Node) error {
|
||||
rhsCandidateNode := &CandidateNode{
|
||||
Path: path,
|
||||
Node: &yaml.Node{
|
||||
Kind: yaml.SequenceNode,
|
||||
Tag: "!!seq",
|
||||
Content: []*yaml.Node{rhsNode},
|
||||
},
|
||||
}
|
||||
|
||||
assignmentOp := &Operation{OperationType: addAssignOpType}
|
||||
|
||||
rhsOp := &Operation{OperationType: valueOpType, CandidateNode: rhsCandidateNode}
|
||||
|
||||
assignmentOpNode := &ExpressionNode{
|
||||
Operation: assignmentOp,
|
||||
LHS: createTraversalTree(path, traversePreferences{}, false),
|
||||
RHS: &ExpressionNode{Operation: rhsOp},
|
||||
}
|
||||
|
||||
_, err := dec.d.GetMatchingNodes(context, assignmentOpNode)
|
||||
return err
|
||||
}
|
||||
|
||||
func (dec *tomlDecoder) processArrayTable(currentNode *toml.Node) (bool, error) {
|
||||
log.Debug("!!! processing table")
|
||||
fullPath := dec.getFullPath(currentNode.Child())
|
||||
log.Debug("!!!fullpath: %v", fullPath)
|
||||
|
||||
// need to use the array append exp to add another entry to
|
||||
// this array: fullpath += [ thing ]
|
||||
|
||||
hasValue := dec.parser.NextExpression()
|
||||
if !hasValue {
|
||||
return false, fmt.Errorf("error retrieving table %v value: %w", fullPath, dec.parser.Error())
|
||||
}
|
||||
|
||||
tableNodeValue := &CandidateNode{
|
||||
Node: &yaml.Node{
|
||||
Kind: yaml.MappingNode,
|
||||
Tag: "!!map",
|
||||
},
|
||||
}
|
||||
|
||||
tableValue := dec.parser.Expression()
|
||||
runAgainstCurrentExp, err := dec.decodeKeyValuesIntoMap(tableNodeValue, tableValue)
|
||||
log.Debugf("table node err: %w", err)
|
||||
if err != nil && !errors.Is(io.EOF, err) {
|
||||
return false, err
|
||||
}
|
||||
c := Context{}
|
||||
|
||||
c = c.SingleChildContext(dec.rootMap)
|
||||
|
||||
// += function
|
||||
err = dec.arrayAppend(c, fullPath, tableNodeValue.Node)
|
||||
|
||||
return runAgainstCurrentExp, err
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build !yq_noxml
|
||||
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
@@ -5,6 +7,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
@@ -46,11 +49,19 @@ 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 ""
|
||||
}
|
||||
return "#" + strings.TrimRight(c, " ")
|
||||
//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
|
||||
}
|
||||
|
||||
func (dec *xmlDecoder) createMap(n *xmlNode) (*yaml.Node, error) {
|
||||
@@ -75,6 +86,7 @@ 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)
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
This operator is used to update node values. It can be used in either the:
|
||||
|
||||
### plain form: `=`
|
||||
Which will assign the LHS node values to the RHS node values. The RHS expression is run against the matching nodes in the pipeline.
|
||||
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.
|
||||
|
||||
### relative form: `|=`
|
||||
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.
|
||||
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.
|
||||
|
||||
|
||||
### Flags
|
||||
@@ -203,7 +203,7 @@ a:
|
||||
```
|
||||
|
||||
## Update node value that has an anchor
|
||||
Anchor will remaple
|
||||
Anchor will remain
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
@@ -250,7 +250,7 @@ a: !cat woof
|
||||
b: !dog woof
|
||||
```
|
||||
|
||||
## Custom types: clovver
|
||||
## Custom types: clobber
|
||||
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 assign the LHS nodes comments to the expression on the RHS. The RHS is run against the matching nodes in the pipeline
|
||||
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
|
||||
|
||||
### relative form: `|=`
|
||||
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.
|
||||
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.
|
||||
|
||||
## Set line comment
|
||||
Set the comment on the key node for more reliability (see below).
|
||||
|
||||
@@ -4,7 +4,7 @@ This returns `true` if the context contains the passed in parameter, and false o
|
||||
|
||||
{% 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 array like `contains(["cat"])` will return true for an array `["cats"]`.
|
||||
_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.
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Various operators for parsing and manipulating dates.
|
||||
|
||||
## Date time formattings
|
||||
This uses the golangs built in time library for parsing and formatting date times.
|
||||
This uses Golang's 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 golangs built in LoadLocation function to parse timezones strings. See the [library docs](https://pkg.go.dev/time#LoadLocation) for more details.
|
||||
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.
|
||||
|
||||
|
||||
## Durations
|
||||
Durations are parsed using golangs built in [ParseDuration](https://pkg.go.dev/time#ParseDuration) function.
|
||||
Durations are parsed using Golang's built in [ParseDuration](https://pkg.go.dev/time#ParseDuration) function.
|
||||
|
||||
You can durations to time using the `+` operator.
|
||||
You can add 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.
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
# 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
|
||||
```
|
||||
|
||||
@@ -309,7 +309,7 @@ cat,"thing1,thing2",true,3.40
|
||||
dog,thing3,false,12
|
||||
```
|
||||
|
||||
## Encode array of array scalars as tsv string
|
||||
## Encode array of arrays as tsv string
|
||||
Scalars are strings, numbers and booleans.
|
||||
|
||||
Given a sample.yml file of:
|
||||
|
||||
@@ -67,7 +67,7 @@ a: 1
|
||||
b: 2
|
||||
```
|
||||
|
||||
## from_entries with numeric key indexes
|
||||
## from_entries with numeric key indices
|
||||
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, beit 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, be it a path `.a.b.c | select(. == "cat")`, or an update `.a.b.c = "gogo"`.
|
||||
|
||||
Tip: This can be useful way parameterise complex scripts.
|
||||
Tip: This can be a useful way to parameterise complex scripts.
|
||||
|
||||
## Dynamically evaluate a path
|
||||
Given a sample.yml file of:
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
# 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 is operation that returns true if the key exists in a map (or index in an array), false otherwise.
|
||||
This operation 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:
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
This operator is used to update node values. It can be used in either the:
|
||||
|
||||
### plain form: `=`
|
||||
Which will assign the LHS node values to the RHS node values. The RHS expression is run against the matching nodes in the pipeline.
|
||||
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.
|
||||
|
||||
### relative form: `|=`
|
||||
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.
|
||||
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.
|
||||
|
||||
|
||||
### 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 assign the LHS nodes comments to the expression on the RHS. The RHS is run against the matching nodes in the pipeline
|
||||
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
|
||||
|
||||
### relative form: `|=`
|
||||
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.
|
||||
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.
|
||||
|
||||
@@ -4,7 +4,7 @@ This returns `true` if the context contains the passed in parameter, and false o
|
||||
|
||||
{% 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 array like `contains(["cat"])` will return true for an array `["cats"]`.
|
||||
_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.
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Various operators for parsing and manipulating dates.
|
||||
|
||||
## Date time formattings
|
||||
This uses the golangs built in time library for parsing and formatting date times.
|
||||
This uses Golang's 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 golangs built in LoadLocation function to parse timezones strings. See the [library docs](https://pkg.go.dev/time#LoadLocation) for more details.
|
||||
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.
|
||||
|
||||
|
||||
## Durations
|
||||
Durations are parsed using golangs built in [ParseDuration](https://pkg.go.dev/time#ParseDuration) function.
|
||||
Durations are parsed using Golang's built in [ParseDuration](https://pkg.go.dev/time#ParseDuration) function.
|
||||
|
||||
You can durations to time using the `+` operator.
|
||||
You can add durations to time using the `+` operator.
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Divide
|
||||
|
||||
Divide behaves differently according to the type of the LHS:
|
||||
* strings: split by the divider
|
||||
* number: arithmetic division
|
||||
@@ -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, beit 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, be it a path `.a.b.c | select(. == "cat")`, or an update `.a.b.c = "gogo"`.
|
||||
|
||||
Tip: This can be useful way parameterise complex scripts.
|
||||
Tip: This can be a useful way to parameterise complex scripts.
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# Filter
|
||||
|
||||
Filters an array (or map values) by the expression given. Equivalent to doing `map(select(exp))`.
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Has
|
||||
|
||||
This is operation that returns true if the key exists in a map (or index in an array), false otherwise.
|
||||
This operation returns true if the key exists in a map (or index in an array), false otherwise.
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# 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. It can be used in either the
|
||||
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.
|
||||
|
||||
## 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, tags and 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 and tags and also use anchors and aliases.
|
||||
|
||||
For instance to set the `style` of all nodes in a yaml doc, including the map keys:
|
||||
|
||||
|
||||
@@ -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 your are using merge anchors.
|
||||
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.
|
||||
|
||||
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 golangs native regex functions under the hood - See their [docs](https://github.com/google/re2/wiki/Syntax) for the supported syntax.
|
||||
This uses Golang's 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 is a 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 parameter specifies 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 new line.
|
||||
Using `$( exp )` wont work, as it will trim the trailing newline.
|
||||
|
||||
```
|
||||
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 new line:
|
||||
Similarly, if you're trying to set the content from a file, and want a trailing newline:
|
||||
|
||||
```
|
||||
IFS= read -rd '' output < <(cat my_file)
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Subtract
|
||||
|
||||
You can use subtract to subtract numbers, as well as removing elements from an array.
|
||||
You can use subtract to subtract numbers as well as remove 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
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
# 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 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.
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
@@ -123,7 +123,7 @@ a:
|
||||
```
|
||||
|
||||
## Set path to prune deep paths
|
||||
Like pick but recursive. This uses `ireduce` to deeply set the selected paths into an empty object,
|
||||
Like pick but recursive. This uses `ireduce` to deeply set the selected paths into an empty object.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
|
||||
@@ -27,7 +27,7 @@ myMap:
|
||||
```
|
||||
|
||||
## Pick indices from array
|
||||
Note that the order of the indexes matches the pick order and non existent indexes are skipped.
|
||||
Note that the order of the indices matches the pick order and non existent indices 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. It can be used in either the
|
||||
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.
|
||||
|
||||
## 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, tags and 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 and tags and also 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.
|
||||
See more regular expression examples under the [`string` operator docs](https://mikefarah.gitbook.io/yq/operators/string-operators).
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
|
||||
@@ -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 your are using merge anchors.
|
||||
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.
|
||||
|
||||
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 golangs native regex functions under the hood - See their [docs](https://github.com/google/re2/wiki/Syntax) for the supported syntax.
|
||||
This uses Golang's 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 is a 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 parameter specifies 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 new line.
|
||||
Using `$( exp )` wont work, as it will trim the trailing newline.
|
||||
|
||||
```
|
||||
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 new line:
|
||||
Similarly, if you're trying to set the content from a file, and want a trailing newline:
|
||||
|
||||
```
|
||||
IFS= read -rd '' output < <(cat my_file)
|
||||
@@ -298,7 +298,7 @@ false
|
||||
```
|
||||
|
||||
## Substitute / Replace string
|
||||
This uses golang regex, described [here](https://github.com/google/re2/wiki/Syntax)
|
||||
This uses Golang's 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 regex, described [here](https://github.com/google/re2/wiki/Syntax)
|
||||
This uses Golang's 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 removing elements from an array.
|
||||
You can use subtract to subtract numbers as well as remove elements from an array.
|
||||
|
||||
## Array subtraction
|
||||
Running
|
||||
@@ -59,24 +59,6 @@ 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 brackets around path elements with special characters
|
||||
Use quotes with square 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 brackets around path elements with special characters
|
||||
Use quotes with square brackets around path elements with special characters
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# TOML
|
||||
|
||||
Decode from TOML. Note that `yq` does not yet support outputting in TOML format (and therefore it cannot roundtrip)
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
|
||||
## Encode shell variables
|
||||
Note that comments are dropped and values will be enclosed in single quotes as needed.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
# comment
|
||||
name: Mike Wazowski
|
||||
eyes:
|
||||
color: turquoise
|
||||
number: 1
|
||||
friends:
|
||||
- James P. Sullivan
|
||||
- Celia Mae
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq -o=shell sample.yml
|
||||
```
|
||||
will output
|
||||
```sh
|
||||
name='Mike Wazowski'
|
||||
eyes_color=turquoise
|
||||
eyes_number=1
|
||||
friends_0='James P. Sullivan'
|
||||
friends_1='Celia Mae'
|
||||
```
|
||||
|
||||
## Encode shell variables: illegal variable names as key.
|
||||
Keys that would be illegal as variable keys are adapted.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
ascii_=_symbols: replaced with _
|
||||
"ascii_ _controls": dropped (this example uses \t)
|
||||
nonascii_א_characters: dropped
|
||||
effrot_expeñded_tò_preserve_accented_latin_letters: moderate (via unicode NFKD)
|
||||
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq -o=shell sample.yml
|
||||
```
|
||||
will output
|
||||
```sh
|
||||
ascii___symbols='replaced with _'
|
||||
ascii__controls='dropped (this example uses \t)'
|
||||
nonascii__characters=dropped
|
||||
effrot_expended_to_preserve_accented_latin_letters='moderate (via unicode NFKD)'
|
||||
```
|
||||
|
||||
## Encode shell variables: empty values, arrays and maps
|
||||
Empty values are encoded to empty variables, but empty arrays and maps are skipped.
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
empty:
|
||||
value:
|
||||
array: []
|
||||
map: {}
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq -o=shell sample.yml
|
||||
```
|
||||
will output
|
||||
```sh
|
||||
empty_value=
|
||||
```
|
||||
|
||||
## Encode shell variables: single quotes in values
|
||||
Single quotes in values are encoded as '"'"' (close single quote, double-quoted single quote, open single quote).
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
name: Miles O'Brien
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq -o=shell sample.yml
|
||||
```
|
||||
will output
|
||||
```sh
|
||||
name='Miles O'"'"'Brien'
|
||||
```
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
# TOML
|
||||
|
||||
Decode from TOML. Note that `yq` does not yet support outputting in TOML format (and therefore it cannot roundtrip)
|
||||
|
||||
|
||||
## Parse: Simple
|
||||
Given a sample.toml file of:
|
||||
```toml
|
||||
A = "hello"
|
||||
B = 12
|
||||
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq -oy '.' sample.toml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
A: hello
|
||||
B: 12
|
||||
```
|
||||
|
||||
## Parse: Deep paths
|
||||
Given a sample.toml file of:
|
||||
```toml
|
||||
person.name = "hello"
|
||||
person.address = "12 cat st"
|
||||
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq -oy '.' sample.toml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
person:
|
||||
name: hello
|
||||
address: 12 cat st
|
||||
```
|
||||
|
||||
## Encode: Scalar
|
||||
Given a sample.toml file of:
|
||||
```toml
|
||||
person.name = "hello"
|
||||
person.address = "12 cat st"
|
||||
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq '.person.name' sample.toml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
hello
|
||||
```
|
||||
|
||||
## Parse: inline table
|
||||
Given a sample.toml file of:
|
||||
```toml
|
||||
name = { first = "Tom", last = "Preston-Werner" }
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq -oy '.' sample.toml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
name:
|
||||
first: Tom
|
||||
last: Preston-Werner
|
||||
```
|
||||
|
||||
## Parse: Array Table
|
||||
Given a sample.toml file of:
|
||||
```toml
|
||||
|
||||
[owner.contact]
|
||||
name = "Tom Preston-Werner"
|
||||
age = 36
|
||||
|
||||
[[owner.addresses]]
|
||||
street = "first street"
|
||||
suburb = "ok"
|
||||
|
||||
[[owner.addresses]]
|
||||
street = "second street"
|
||||
suburb = "nice"
|
||||
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq -oy '.' sample.toml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
owner:
|
||||
contact:
|
||||
name: Tom Preston-Werner
|
||||
age: 36
|
||||
addresses:
|
||||
- street: first street
|
||||
suburb: ok
|
||||
- street: second street
|
||||
suburb: nice
|
||||
```
|
||||
|
||||
@@ -407,8 +407,10 @@ 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
|
||||
@@ -425,9 +427,11 @@ 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 -->
|
||||
@@ -489,7 +493,8 @@ 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 -->
|
||||
|
||||
+7
-156
@@ -1,10 +1,6 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
@@ -17,162 +13,17 @@ type Encoder interface {
|
||||
CanHandleAliases() bool
|
||||
}
|
||||
|
||||
// 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{}
|
||||
}
|
||||
func mapKeysToStrings(node *yaml.Node) {
|
||||
|
||||
type orderedMapKV struct {
|
||||
K string
|
||||
V orderedMap
|
||||
}
|
||||
|
||||
func (o *orderedMap) UnmarshalJSON(data []byte) error {
|
||||
switch data[0] {
|
||||
case '{':
|
||||
// initialise so that even if the object is empty it is not nil
|
||||
o.kv = []orderedMapKV{}
|
||||
|
||||
// create decoder
|
||||
dec := json.NewDecoder(bytes.NewReader(data))
|
||||
_, err := dec.Token() // open object
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// cycle through k/v
|
||||
var tok json.Token
|
||||
for tok, err = dec.Token(); err == nil; 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
|
||||
if node.Kind == yaml.MappingNode {
|
||||
for index, child := range node.Content {
|
||||
if index%2 == 0 { // its a map key
|
||||
child.Tag = "!!str"
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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")
|
||||
for _, child := range node.Content {
|
||||
mapKeysToStrings(child)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@@ -103,6 +103,10 @@ func (e *csvEncoder) encodeObjects(csvWriter *csv.Writer, content []*yaml.Node)
|
||||
}
|
||||
|
||||
func (e *csvEncoder) Encode(writer io.Writer, originalNode *yaml.Node) error {
|
||||
if originalNode.Kind == yaml.ScalarNode {
|
||||
return writeString(writer, originalNode.Value+"\n")
|
||||
}
|
||||
|
||||
csvWriter := csv.NewWriter(writer)
|
||||
csvWriter.Comma = e.separator
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build !yq_nojson
|
||||
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
@@ -14,21 +16,6 @@ type jsonEncoder struct {
|
||||
UnwrapScalar bool
|
||||
}
|
||||
|
||||
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, unwrapScalar bool) Encoder {
|
||||
var indentString = ""
|
||||
|
||||
|
||||
@@ -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 new line, put one in
|
||||
// the last comment we read didn't have a newline, put one in
|
||||
if err := writeString(writer, "\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -63,6 +63,11 @@ func (pe *propertiesEncoder) PrintLeadingContent(writer io.Writer, content strin
|
||||
}
|
||||
|
||||
func (pe *propertiesEncoder) Encode(writer io.Writer, node *yaml.Node) error {
|
||||
|
||||
if node.Kind == yaml.ScalarNode {
|
||||
return writeString(writer, node.Value+"\n")
|
||||
}
|
||||
|
||||
mapKeysToStrings(node)
|
||||
p := properties.NewProperties()
|
||||
err := pe.doEncode(p, node, "", nil)
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/text/unicode/norm"
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type shellVariablesEncoder struct {
|
||||
}
|
||||
|
||||
func NewShellVariablesEncoder() Encoder {
|
||||
return &shellVariablesEncoder{}
|
||||
}
|
||||
|
||||
func (pe *shellVariablesEncoder) CanHandleAliases() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (pe *shellVariablesEncoder) PrintDocumentSeparator(_ io.Writer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pe *shellVariablesEncoder) PrintLeadingContent(_ io.Writer, _ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pe *shellVariablesEncoder) Encode(writer io.Writer, node *yaml.Node) error {
|
||||
|
||||
mapKeysToStrings(node)
|
||||
err := pe.doEncode(&writer, node, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (pe *shellVariablesEncoder) doEncode(w *io.Writer, node *yaml.Node, path string) error {
|
||||
|
||||
// Note this drops all comments.
|
||||
|
||||
switch node.Kind {
|
||||
case yaml.ScalarNode:
|
||||
nonemptyPath := path
|
||||
if path == "" {
|
||||
// We can't assign an empty variable "=somevalue" because that would error out if sourced in a shell,
|
||||
// nor can we use "_" as a variable name ($_ is a special shell variable that can't be assigned)...
|
||||
// let's just pick a fallback key to use if we are encoding a single scalar
|
||||
nonemptyPath = "value"
|
||||
}
|
||||
_, err := io.WriteString(*w, nonemptyPath+"="+quoteValue(node.Value)+"\n")
|
||||
return err
|
||||
case yaml.DocumentNode:
|
||||
return pe.doEncode(w, node.Content[0], path)
|
||||
case yaml.SequenceNode:
|
||||
for index, child := range node.Content {
|
||||
err := pe.doEncode(w, child, appendPath(path, index))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case yaml.MappingNode:
|
||||
for index := 0; index < len(node.Content); index = index + 2 {
|
||||
key := node.Content[index]
|
||||
value := node.Content[index+1]
|
||||
err := pe.doEncode(w, value, appendPath(path, key.Value))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
case yaml.AliasNode:
|
||||
return pe.doEncode(w, node.Alias, path)
|
||||
default:
|
||||
return fmt.Errorf("Unsupported node %v", node.Tag)
|
||||
}
|
||||
}
|
||||
|
||||
func appendPath(cookedPath string, rawKey interface{}) string {
|
||||
|
||||
// Shell variable names must match
|
||||
// [a-zA-Z_]+[a-zA-Z0-9_]*
|
||||
//
|
||||
// While this is not mandated by POSIX, which is quite lenient, it is
|
||||
// what shells (for example busybox ash *) allow in practice.
|
||||
//
|
||||
// Since yaml names can contain basically any character, we will process them according to these steps:
|
||||
//
|
||||
// 1. apply unicode compatibility decomposition NFKD (this will convert accented
|
||||
// letters to letters followed by accents, split ligatures, replace exponents
|
||||
// with the corresponding digit, etc.
|
||||
//
|
||||
// 2. discard non-ASCII characters as well as ASCII control characters (ie. anything
|
||||
// with code point < 32 or > 126), this will eg. discard accents but keep the base
|
||||
// unaccented letter because of NFKD above
|
||||
//
|
||||
// 3. replace all non-alphanumeric characters with _
|
||||
//
|
||||
// Moreover, for the root key only, we will prepend an underscore if what results from the steps above
|
||||
// does not start with [a-zA-Z_] (ie. if the root key starts with a digit).
|
||||
//
|
||||
// Note this is NOT a 1:1 mapping.
|
||||
//
|
||||
// (*) see endofname.c from https://git.busybox.net/busybox/tag/?h=1_36_0
|
||||
|
||||
// XXX empty strings
|
||||
|
||||
key := strings.Map(func(r rune) rune {
|
||||
if isAlphaNumericOrUnderscore(r) {
|
||||
return r
|
||||
} else if r < 32 || 126 < r {
|
||||
return -1
|
||||
}
|
||||
return '_'
|
||||
}, norm.NFKD.String(fmt.Sprintf("%v", rawKey)))
|
||||
|
||||
if cookedPath == "" {
|
||||
firstRune, _ := utf8.DecodeRuneInString(key)
|
||||
if !isAlphaOrUnderscore(firstRune) {
|
||||
return "_" + key
|
||||
}
|
||||
return key
|
||||
}
|
||||
return cookedPath + "_" + key
|
||||
}
|
||||
|
||||
func quoteValue(value string) string {
|
||||
needsQuoting := false
|
||||
for _, r := range value {
|
||||
if !isAlphaNumericOrUnderscore(r) {
|
||||
needsQuoting = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if needsQuoting {
|
||||
return "'" + strings.ReplaceAll(value, "'", "'\"'\"'") + "'"
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func isAlphaOrUnderscore(r rune) bool {
|
||||
return ('a' <= r && r <= 'z') || ('A' <= r && r <= 'Z') || r == '_'
|
||||
}
|
||||
|
||||
func isAlphaNumericOrUnderscore(r rune) bool {
|
||||
return isAlphaOrUnderscore(r) || ('0' <= r && r <= '9')
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/mikefarah/yq/v4/test"
|
||||
)
|
||||
|
||||
func assertEncodesTo(t *testing.T, yaml string, shellvars string) {
|
||||
var output bytes.Buffer
|
||||
writer := bufio.NewWriter(&output)
|
||||
|
||||
var encoder = NewShellVariablesEncoder()
|
||||
inputs, err := readDocuments(strings.NewReader(yaml), "test.yml", 0, NewYamlDecoder(ConfiguredYamlPreferences))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
node := inputs.Front().Value.(*CandidateNode).Node
|
||||
err = encoder.Encode(writer, node)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
writer.Flush()
|
||||
|
||||
test.AssertResult(t, shellvars, strings.TrimSuffix(output.String(), "\n"))
|
||||
}
|
||||
|
||||
func TestShellVariablesEncoderNonquoting(t *testing.T) {
|
||||
assertEncodesTo(t, "a: alice", "a=alice")
|
||||
}
|
||||
|
||||
func TestShellVariablesEncoderQuoting(t *testing.T) {
|
||||
assertEncodesTo(t, "a: Lewis Carroll", "a='Lewis Carroll'")
|
||||
}
|
||||
|
||||
func TestShellVariablesEncoderQuotesQuoting(t *testing.T) {
|
||||
assertEncodesTo(t, "a: Lewis Carroll's Alice", "a='Lewis Carroll'\"'\"'s Alice'")
|
||||
}
|
||||
|
||||
func TestShellVariablesEncoderStripComments(t *testing.T) {
|
||||
assertEncodesTo(t, "a: Alice # comment", "a=Alice")
|
||||
}
|
||||
|
||||
func TestShellVariablesEncoderMap(t *testing.T) {
|
||||
assertEncodesTo(t, "a:\n b: Lewis\n c: Carroll", "a_b=Lewis\na_c=Carroll")
|
||||
}
|
||||
|
||||
func TestShellVariablesEncoderArray_Unwrapped(t *testing.T) {
|
||||
assertEncodesTo(t, "a: [{n: Alice}, {n: Bob}]", "a_0_n=Alice\na_1_n=Bob")
|
||||
}
|
||||
|
||||
func TestShellVariablesEncoderKeyNonPrintable(t *testing.T) {
|
||||
assertEncodesTo(t, `"be\all": ring!`, "bell='ring!'")
|
||||
}
|
||||
|
||||
func TestShellVariablesEncoderKeyPrintableNonAlphaNumeric(t *testing.T) {
|
||||
assertEncodesTo(t, `"b-e l=l": ring!`, "b_e_l_l='ring!'")
|
||||
}
|
||||
|
||||
func TestShellVariablesEncoderKeyPrintableNonAscii(t *testing.T) {
|
||||
assertEncodesTo(t, `"b\u00e9ll": ring!`, "bell='ring!'")
|
||||
}
|
||||
|
||||
func TestShellVariablesEncoderRootKeyStartingWithDigit(t *testing.T) {
|
||||
assertEncodesTo(t, "1a: onea", "_1a=onea")
|
||||
}
|
||||
|
||||
func TestShellVariablesEncoderRootKeyStartingWithUnderscore(t *testing.T) {
|
||||
assertEncodesTo(t, "_key: value", "_key=value")
|
||||
}
|
||||
|
||||
func TestShellVariablesEncoderChildStartingWithUnderscore(t *testing.T) {
|
||||
assertEncodesTo(t, "root:\n _child: value", "root__child=value")
|
||||
}
|
||||
|
||||
func TestShellVariablesEncoderEmptyValue(t *testing.T) {
|
||||
assertEncodesTo(t, "empty:", "empty=")
|
||||
}
|
||||
|
||||
func TestShellVariablesEncoderEmptyArray(t *testing.T) {
|
||||
assertEncodesTo(t, "empty: []", "")
|
||||
}
|
||||
|
||||
func TestShellVariablesEncoderEmptyMap(t *testing.T) {
|
||||
assertEncodesTo(t, "empty: {}", "")
|
||||
}
|
||||
|
||||
func TestShellVariablesEncoderScalarNode(t *testing.T) {
|
||||
assertEncodesTo(t, "some string", "value='some string'")
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build !yq_nojson
|
||||
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type tomlEncoder struct {
|
||||
}
|
||||
|
||||
func NewTomlEncoder() Encoder {
|
||||
return &tomlEncoder{}
|
||||
}
|
||||
|
||||
func (te *tomlEncoder) Encode(writer io.Writer, node *yaml.Node) error {
|
||||
if node.Kind == yaml.ScalarNode {
|
||||
return writeString(writer, node.Value+"\n")
|
||||
}
|
||||
return fmt.Errorf("only scalars (e.g. strings, numbers, booleans) are supported for TOML output at the moment. Please use yaml output format (-oy) until the encoder has been fully implemented")
|
||||
}
|
||||
|
||||
func (te *tomlEncoder) PrintDocumentSeparator(writer io.Writer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (te *tomlEncoder) PrintLeadingContent(writer io.Writer, content string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (te *tomlEncoder) CanHandleAliases() bool {
|
||||
return false
|
||||
}
|
||||
+63
-25
@@ -1,3 +1,5 @@
|
||||
//go:build !yq_noxml
|
||||
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
@@ -17,8 +19,6 @@ 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 = commentPrefix.ReplaceAllString(content, "\n")
|
||||
e.leadingContent = content
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -46,12 +46,39 @@ 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 {
|
||||
@@ -87,29 +114,12 @@ func (e *xmlEncoder) Encode(writer io.Writer, node *yaml.Node) error {
|
||||
default:
|
||||
return fmt.Errorf("unsupported type %v", node.Tag)
|
||||
}
|
||||
var charData xml.CharData = []byte("\n")
|
||||
return encoder.EncodeToken(charData)
|
||||
|
||||
return encoder.EncodeToken(newLine)
|
||||
|
||||
}
|
||||
|
||||
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
|
||||
@@ -124,6 +134,13 @@ 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.
|
||||
@@ -204,13 +221,34 @@ 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("encoding comment %v", commentStr)
|
||||
if !strings.HasSuffix(commentStr, " ") {
|
||||
commentStr = 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") {
|
||||
commentStr = commentStr + " "
|
||||
log.Debugf("added suffix [%v]", commentStr)
|
||||
}
|
||||
log.Debugf("encoding comment [%v]", commentStr)
|
||||
|
||||
var comment xml.Comment = []byte(commentStr)
|
||||
err := encoder.EncodeToken(comment)
|
||||
if err != nil {
|
||||
|
||||
@@ -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 new line, put one in
|
||||
// the last comment we read didn't have a newline, put one in
|
||||
if err := writeString(writer, "\n"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
//go:build !yq_nojson
|
||||
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
@@ -78,6 +80,13 @@ const roundTripMultiLineJson = `{
|
||||
`
|
||||
|
||||
var jsonScenarios = []formatScenario{
|
||||
{
|
||||
description: "array null",
|
||||
skipDoc: true,
|
||||
input: "[null]",
|
||||
scenarioType: "roundtrip-ndjson",
|
||||
expected: "[null]\n",
|
||||
},
|
||||
{
|
||||
description: "set tags",
|
||||
skipDoc: true,
|
||||
|
||||
@@ -37,6 +37,7 @@ var participleYqRules = []*participleYqRule{
|
||||
|
||||
{"MapValues", `map_?values`, opToken(mapValuesOpType), 0},
|
||||
simpleOp("map", mapOpType),
|
||||
simpleOp("filter", filterOpType),
|
||||
simpleOp("pick", pickOpType),
|
||||
|
||||
{"FlattenWithDepth", `flatten\([0-9]+\)`, flattenWithDepth(), 0},
|
||||
@@ -209,6 +210,10 @@ 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},
|
||||
|
||||
|
||||
+6
-1
@@ -62,6 +62,10 @@ 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,6 +88,7 @@ var expressionOpType = &operationType{Type: "EXP", NumArgs: 0, Precedence: 50, H
|
||||
|
||||
var collectOpType = &operationType{Type: "COLLECT", NumArgs: 1, Precedence: 50, Handler: collectOperator}
|
||||
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}
|
||||
@@ -338,7 +343,7 @@ func deepCloneWithOptions(node *yaml.Node, cloneContent bool) *yaml.Node {
|
||||
Tag: node.Tag,
|
||||
Value: node.Value,
|
||||
Anchor: node.Anchor,
|
||||
Alias: deepClone(node.Alias),
|
||||
Alias: node.Alias,
|
||||
HeadComment: node.HeadComment,
|
||||
LineComment: node.LineComment,
|
||||
FootComment: node.FootComment,
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
//go:build yq_nojson
|
||||
|
||||
package yqlib
|
||||
|
||||
func NewJSONDecoder() Decoder {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewJSONEncoder(indent int, colorise bool, unwrapScalar bool) Encoder {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
//go:build yq_notoml
|
||||
|
||||
package yqlib
|
||||
|
||||
func NewTomlDecoder() Decoder {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
//go:build yq_noxml
|
||||
|
||||
package yqlib
|
||||
|
||||
func NewXMLDecoder(prefs XmlPreferences) Decoder {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewXMLEncoder(indent int, prefs XmlPreferences) Encoder {
|
||||
return nil
|
||||
}
|
||||
@@ -228,7 +228,7 @@ foobar:
|
||||
},
|
||||
{
|
||||
description: "Dereference and update a field",
|
||||
subdescription: "`Use explode with multiply to dereference an object",
|
||||
subdescription: "Use explode with multiply to dereference an object",
|
||||
document: simpleArrayRef,
|
||||
expression: `.thingOne |= explode(.) * {"value": false}`,
|
||||
expected: []string{expectedUpdatedArrayRef},
|
||||
|
||||
@@ -4,6 +4,11 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var mergeAnchorAssign = `a: &a
|
||||
x: OriginalValue
|
||||
b:
|
||||
<<: *a`
|
||||
|
||||
var assignOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Create yaml file",
|
||||
@@ -20,6 +25,14 @@ var assignOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (doc)::a: null\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: mergeAnchorAssign,
|
||||
expression: `.c = .b | .a.x = "ModifiedValue" | explode(.)`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::a:\n x: ModifiedValue\nb:\n x: ModifiedValue\nc:\n x: ModifiedValue\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: "{}",
|
||||
@@ -183,7 +196,7 @@ var assignOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
{
|
||||
description: "Update node value that has an anchor",
|
||||
subdescription: "Anchor will remaple",
|
||||
subdescription: "Anchor will remain",
|
||||
dontFormatInputForDoc: true,
|
||||
document: `a: &cool cat`,
|
||||
expression: `.a = "dog"`,
|
||||
@@ -217,7 +230,7 @@ var assignOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Custom types: clovver",
|
||||
description: "Custom types: clobber",
|
||||
subdescription: "Use the `c` option to clobber custom tags",
|
||||
document: "a: !cat meow\nb: !dog woof",
|
||||
expression: `.a =c .b`,
|
||||
|
||||
@@ -27,6 +27,7 @@ func collectOperator(d *dataTreeNavigator, context Context, expressionNode *Expr
|
||||
log.Debugf("-- collectOperation")
|
||||
|
||||
if context.MatchingNodes.Len() == 0 {
|
||||
log.Debugf("nothing to collect")
|
||||
node := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq", Value: "[]"}
|
||||
candidate := &CandidateNode{Node: node}
|
||||
return context.SingleChildContext(candidate), nil
|
||||
@@ -41,6 +42,7 @@ func collectOperator(d *dataTreeNavigator, context Context, expressionNode *Expr
|
||||
}
|
||||
|
||||
if evaluateAllTogether {
|
||||
log.Debugf("collect together")
|
||||
collectedNode, err := collectTogether(d, context, expressionNode.RHS)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
@@ -56,6 +58,8 @@ func collectOperator(d *dataTreeNavigator, context Context, expressionNode *Expr
|
||||
collectedNode := &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
|
||||
collectCandidate := candidate.CreateReplacement(collectedNode)
|
||||
|
||||
log.Debugf("collect rhs: %v", expressionNode.RHS.Operation.toString())
|
||||
|
||||
collectExpResults, err := d.GetMatchingNodes(context.SingleChildContext(candidate), expressionNode.RHS)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
@@ -66,6 +70,7 @@ func collectOperator(d *dataTreeNavigator, context Context, expressionNode *Expr
|
||||
log.Debugf("found this: %v", NodeToString(resultC))
|
||||
collectedNode.Content = append(collectedNode.Content, unwrapDoc(resultC.Node))
|
||||
}
|
||||
log.Debugf("done collect rhs: %v", expressionNode.RHS.Operation.toString())
|
||||
|
||||
results.PushBack(collectCandidate)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,13 @@ var collectObjectOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (!!map)::dog: great\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
expression: `({} + {}) | (.b = 3)`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::b: 3\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: "a: []",
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func divideOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
log.Debugf("Divide operator")
|
||||
|
||||
return crossFunction(d, context.ReadOnlyClone(), expressionNode, divide, false)
|
||||
}
|
||||
|
||||
func divide(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
lhs.Node = unwrapDoc(lhs.Node)
|
||||
rhs.Node = unwrapDoc(rhs.Node)
|
||||
|
||||
lhsNode := lhs.Node
|
||||
|
||||
if lhsNode.Tag == "!!null" {
|
||||
return nil, fmt.Errorf("%v (%v) cannot be divided by %v (%v)", lhsNode.Tag, lhs.GetNicePath(), rhs.Node.Tag, rhs.GetNicePath())
|
||||
}
|
||||
|
||||
target := &yaml.Node{}
|
||||
|
||||
if lhsNode.Kind == yaml.ScalarNode && rhs.Node.Kind == yaml.ScalarNode {
|
||||
if err := divideScalars(target, lhsNode, rhs.Node); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("%v (%v) cannot be divided by %v (%v)", lhsNode.Tag, lhs.GetNicePath(), rhs.Node.Tag, rhs.GetNicePath())
|
||||
}
|
||||
|
||||
return lhs.CreateReplacement(target), nil
|
||||
}
|
||||
|
||||
func divideScalars(target *yaml.Node, lhs *yaml.Node, rhs *yaml.Node) error {
|
||||
lhsTag := lhs.Tag
|
||||
rhsTag := guessTagFromCustomType(rhs)
|
||||
lhsIsCustom := false
|
||||
if !strings.HasPrefix(lhsTag, "!!") {
|
||||
// custom tag - we have to have a guess
|
||||
lhsTag = guessTagFromCustomType(lhs)
|
||||
lhsIsCustom = true
|
||||
}
|
||||
|
||||
if lhsTag == "!!str" && rhsTag == "!!str" {
|
||||
res := split(lhs.Value, rhs.Value)
|
||||
target.Kind = res.Kind
|
||||
target.Tag = res.Tag
|
||||
target.Content = res.Content
|
||||
} else if (lhsTag == "!!int" || lhsTag == "!!float") && (rhsTag == "!!int" || rhsTag == "!!float") {
|
||||
target.Kind = yaml.ScalarNode
|
||||
target.Style = lhs.Style
|
||||
|
||||
lhsNum, err := strconv.ParseFloat(lhs.Value, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rhsNum, err := strconv.ParseFloat(rhs.Value, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
quotient := lhsNum / rhsNum
|
||||
if lhsIsCustom {
|
||||
target.Tag = lhs.Tag
|
||||
} else {
|
||||
target.Tag = "!!float"
|
||||
}
|
||||
target.Value = fmt.Sprintf("%v", quotient)
|
||||
} else {
|
||||
return fmt.Errorf("%v cannot be divided by %v", lhsTag, rhsTag)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var divideOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `[{a: foo_bar, b: _}, {a: 4, b: 2}]`,
|
||||
expression: ".[] | .a / .b",
|
||||
expected: []string{
|
||||
"D0, P[0 a], (!!seq)::- foo\n- bar\n",
|
||||
"D0, P[1 a], (!!float)::2\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{}`,
|
||||
expression: "(.a / .b) as $x | .",
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "String split",
|
||||
document: `{a: cat_meow, b: _}`,
|
||||
expression: `.c = .a / .b`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: cat_meow, b: _, c: [cat, meow]}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Number division",
|
||||
subdescription: "The result during division is calculated as a float",
|
||||
document: `{a: 12, b: 2.5}`,
|
||||
expression: `.a = .a / .b`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: 4.8, b: 2.5}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Number division by zero",
|
||||
subdescription: "Dividing by zero results in +Inf or -Inf",
|
||||
document: `{a: 1, b: -1}`,
|
||||
expression: `.a = .a / 0 | .b = .b / 0`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{a: !!float +Inf, b: !!float -Inf}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Custom types: that are really strings",
|
||||
document: "a: !horse cat_meow\nb: !goat _",
|
||||
expression: `.a = .a / .b`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::a: !horse\n - cat\n - meow\nb: !goat _\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Custom types: that are really numbers",
|
||||
document: "a: !horse 1.2\nb: !goat 2.3",
|
||||
expression: `.a = .a / .b`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::a: !horse 0.5217391304347826\nb: !goat 2.3\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: "a: 2\nb: !goat 2.3",
|
||||
expression: `.a = .a / .b`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::a: 0.8695652173913044\nb: !goat 2.3\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Custom types: that are really ints",
|
||||
document: "a: !horse 2\nb: !goat 3",
|
||||
expression: `.a = .a / .b`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::a: !horse 0.6666666666666666\nb: !goat 3\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Keep anchors",
|
||||
document: "a: &horse [1]",
|
||||
expression: `.a[1] = .a[0] / 2`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::a: &horse [1, 0.5]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Divide int by string",
|
||||
document: "a: 123\nb: '2'",
|
||||
expression: `.a / .b`,
|
||||
expectedError: "!!int cannot be divided by !!str",
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Divide string by int",
|
||||
document: "a: 2\nb: '123'",
|
||||
expression: `.b / .a`,
|
||||
expectedError: "!!str cannot be divided by !!int",
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Divide map by int",
|
||||
document: "a: {\"a\":1}\nb: 2",
|
||||
expression: `.a / .b`,
|
||||
expectedError: "!!map (a) cannot be divided by !!int (b)",
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Divide array by str",
|
||||
document: "a: [1,2]\nb: '2'",
|
||||
expression: `.a / .b`,
|
||||
expectedError: "!!seq (a) cannot be divided by !!str (b)",
|
||||
},
|
||||
}
|
||||
|
||||
func TestDivideOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range divideOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentOperatorScenarios(t, "divide", divideOperatorScenarios)
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"container/list"
|
||||
"errors"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -39,6 +40,9 @@ func encodeToString(candidate *CandidateNode, prefs encoderPreferences) (string,
|
||||
log.Debug("printing with indent: %v", prefs.indent)
|
||||
|
||||
encoder := configureEncoder(prefs.format, prefs.indent)
|
||||
if encoder == nil {
|
||||
return "", errors.New("no support for output format")
|
||||
}
|
||||
|
||||
printer := NewPrinter(encoder, NewSinglePrinterWriter(bufio.NewWriter(&output)))
|
||||
err := printer.PrintResults(candidate.AsList())
|
||||
@@ -75,13 +79,13 @@ func encodeOperator(d *dataTreeNavigator, context Context, expressionNode *Expre
|
||||
|
||||
original := originalList.Front().Value.(*CandidateNode)
|
||||
originalNode := unwrapDoc(original.Node)
|
||||
// original block did not have a new line at the end, get rid of this one too
|
||||
// original block did not have a newline at the end, get rid of this one too
|
||||
if !endWithNewLine.MatchString(originalNode.Value) {
|
||||
stringValue = chomper.ReplaceAllString(stringValue, "")
|
||||
}
|
||||
}
|
||||
|
||||
// dont print a new line when printing json on a single line.
|
||||
// dont print a newline when printing json on a single line.
|
||||
if (preferences.format == JSONOutputFormat && preferences.indent == 0) ||
|
||||
preferences.format == CSVOutputFormat ||
|
||||
preferences.format == TSVOutputFormat {
|
||||
@@ -98,13 +102,11 @@ type decoderPreferences struct {
|
||||
format InputFormat
|
||||
}
|
||||
|
||||
/* takes a string and decodes it back into an object */
|
||||
func decodeOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
|
||||
preferences := expressionNode.Operation.Preferences.(decoderPreferences)
|
||||
|
||||
func createDecoder(format InputFormat) Decoder {
|
||||
var decoder Decoder
|
||||
switch preferences.format {
|
||||
switch format {
|
||||
case JsonInputFormat:
|
||||
decoder = NewJSONDecoder()
|
||||
case YamlInputFormat:
|
||||
decoder = NewYamlDecoder(ConfiguredYamlPreferences)
|
||||
case XMLInputFormat:
|
||||
@@ -120,6 +122,18 @@ func decodeOperator(d *dataTreeNavigator, context Context, expressionNode *Expre
|
||||
case UriInputFormat:
|
||||
decoder = NewUriDecoder()
|
||||
}
|
||||
return decoder
|
||||
}
|
||||
|
||||
/* takes a string and decodes it back into an object */
|
||||
func decodeOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
|
||||
preferences := expressionNode.Operation.Preferences.(decoderPreferences)
|
||||
|
||||
decoder := createDecoder(preferences.format)
|
||||
if decoder == nil {
|
||||
return Context{}, errors.New("no support for input format")
|
||||
}
|
||||
|
||||
var results = list.New()
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
|
||||
@@ -8,15 +8,17 @@ var prefix = "D0, P[], (doc)::a:\n cool:\n bob: dylan\n"
|
||||
|
||||
var encoderDecoderOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Encode value as json string",
|
||||
document: `{a: {cool: "thing"}}`,
|
||||
expression: `.b = (.a | to_json)`,
|
||||
requiresFormat: "json",
|
||||
description: "Encode value as json string",
|
||||
document: `{a: {cool: "thing"}}`,
|
||||
expression: `.b = (.a | to_json)`,
|
||||
expected: []string{
|
||||
`D0, P[], (doc)::{a: {cool: "thing"}, b: "{\n \"cool\": \"thing\"\n}\n"}
|
||||
`,
|
||||
},
|
||||
},
|
||||
{
|
||||
requiresFormat: "json",
|
||||
description: "Encode value as json string, on one line",
|
||||
subdescription: "Pass in a 0 indent to print json on a single line.",
|
||||
document: `{a: {cool: "thing"}}`,
|
||||
@@ -27,6 +29,7 @@ var encoderDecoderOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
requiresFormat: "json",
|
||||
description: "Encode value as json string, on one line shorthand",
|
||||
subdescription: "Pass in a 0 indent to print json on a single line.",
|
||||
document: `{a: {cool: "thing"}}`,
|
||||
@@ -37,6 +40,7 @@ var encoderDecoderOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
requiresFormat: "json",
|
||||
description: "Decode a json encoded string",
|
||||
subdescription: "Keep in mind JSON is a subset of YAML. If you want idiomatic yaml, pipe through the style operator to clear out the JSON styling.",
|
||||
document: `a: '{"cool":"thing"}'`,
|
||||
@@ -175,7 +179,7 @@ var encoderDecoderOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Encode array of array scalars as tsv string",
|
||||
description: "Encode array of arrays as tsv string",
|
||||
subdescription: "Scalars are strings, numbers and booleans.",
|
||||
document: `[[cat, "thing1,thing2", true, 3.40], [dog, thing3, false, 12]]`,
|
||||
expression: `@tsv`,
|
||||
@@ -193,33 +197,37 @@ var encoderDecoderOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Encode value as xml string",
|
||||
document: `{a: {cool: {foo: "bar", +@id: hi}}}`,
|
||||
expression: `.a | to_xml`,
|
||||
requiresFormat: "xml",
|
||||
description: "Encode value as xml string",
|
||||
document: `{a: {cool: {foo: "bar", +@id: hi}}}`,
|
||||
expression: `.a | to_xml`,
|
||||
expected: []string{
|
||||
"D0, P[a], (!!str)::<cool id=\"hi\">\n <foo>bar</foo>\n</cool>\n\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Encode value as xml string on a single line",
|
||||
document: `{a: {cool: {foo: "bar", +@id: hi}}}`,
|
||||
expression: `.a | @xml`,
|
||||
requiresFormat: "xml",
|
||||
description: "Encode value as xml string on a single line",
|
||||
document: `{a: {cool: {foo: "bar", +@id: hi}}}`,
|
||||
expression: `.a | @xml`,
|
||||
expected: []string{
|
||||
"D0, P[a], (!!str)::<cool id=\"hi\"><foo>bar</foo></cool>\n\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Encode value as xml string with custom indentation",
|
||||
document: `{a: {cool: {foo: "bar", +@id: hi}}}`,
|
||||
expression: `{"cat": .a | to_xml(1)}`,
|
||||
requiresFormat: "xml",
|
||||
description: "Encode value as xml string with custom indentation",
|
||||
document: `{a: {cool: {foo: "bar", +@id: hi}}}`,
|
||||
expression: `{"cat": .a | to_xml(1)}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::cat: |\n <cool id=\"hi\">\n <foo>bar</foo>\n </cool>\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Decode a xml encoded string",
|
||||
document: `a: "<foo>bar</foo>"`,
|
||||
expression: `.b = (.a | from_xml)`,
|
||||
requiresFormat: "xml",
|
||||
description: "Decode a xml encoded string",
|
||||
document: `a: "<foo>bar</foo>"`,
|
||||
expression: `.b = (.a | from_xml)`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::a: \"<foo>bar</foo>\"\nb:\n foo: bar\n",
|
||||
},
|
||||
@@ -303,9 +311,26 @@ var encoderDecoderOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "empty xml decode",
|
||||
description: "base64 missing padding test",
|
||||
skipDoc: true,
|
||||
expression: `"" | @xmld`,
|
||||
expression: `"Y2F0cw" | @base64d`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!str)::cats\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "base64 missing padding test",
|
||||
skipDoc: true,
|
||||
expression: `"cats" | @base64 | @base64d`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!str)::cats\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
requiresFormat: "xml",
|
||||
description: "empty xml decode",
|
||||
skipDoc: true,
|
||||
expression: `"" | @xmld`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!null)::\n",
|
||||
},
|
||||
|
||||
@@ -36,7 +36,7 @@ var entriesOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "from_entries with numeric key indexes",
|
||||
description: "from_entries with numeric key indices",
|
||||
subdescription: "from_entries always creates a map, even for numeric keys",
|
||||
document: `[a,b]`,
|
||||
expression: `to_entries | from_entries`,
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
)
|
||||
|
||||
func filterOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
log.Debugf("-- filterOperation")
|
||||
var results = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
children := context.SingleChildContext(candidate)
|
||||
splatted, err := splat(children, traversePreferences{})
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
filtered, err := selectOperator(d, splatted, expressionNode)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
|
||||
selfExpression := &ExpressionNode{Operation: &Operation{OperationType: selfReferenceOpType}}
|
||||
collected, err := collectTogether(d, filtered, selfExpression)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
collected.Node.Style = unwrapDoc(candidate.Node).Style
|
||||
results.PushBack(collected)
|
||||
}
|
||||
return context.ChildContext(results), nil
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var filterOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Filter array",
|
||||
document: `[1,2,3]`,
|
||||
expression: `filter(. < 3)`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::[1, 2]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Filter map values",
|
||||
document: `{c: {things: cool, frog: yes}, d: {things: hot, frog: false}}`,
|
||||
expression: `filter(.things == "cool")`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::[{things: cool, frog: yes}]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `[1,2,3]`,
|
||||
expression: `filter(. > 1)`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::[2, 3]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Filter array to empty",
|
||||
document: `[1,2,3]`,
|
||||
expression: `filter(. > 4)`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::[]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Filter empty array",
|
||||
document: `[]`,
|
||||
expression: `filter(. > 1)`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::[]\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestFilterOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range filterOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentOperatorScenarios(t, "filter", filterOperatorScenarios)
|
||||
}
|
||||
@@ -17,7 +17,7 @@ var lineOperatorScenarios = []expressionScenario{
|
||||
description: "Returns line of _key_ node",
|
||||
subdescription: "Pipe through the key operator to get the line of the key",
|
||||
document: "a: cat\nb:\n c: cat",
|
||||
expression: `.b | key| line`,
|
||||
expression: `.b | key | line`,
|
||||
expected: []string{
|
||||
"D0, P[b], (!!int)::2\n",
|
||||
},
|
||||
|
||||
@@ -34,6 +34,9 @@ func loadString(filename string) (*CandidateNode, error) {
|
||||
}
|
||||
|
||||
func loadYaml(filename string, decoder Decoder) (*CandidateNode, error) {
|
||||
if decoder == nil {
|
||||
return nil, fmt.Errorf("could not load %s", filename)
|
||||
}
|
||||
|
||||
file, err := os.Open(filename) // #nosec
|
||||
if err != nil {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user