mirror of
https://github.com/mikefarah/yq.git
synced 2026-08-24 08:22:13 +08:00
Compare commits
78
Commits
v4.30.1
...
variable-loop
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb9181670e | ||
|
|
0cd15867d0 | ||
|
|
7ade7a97a5 | ||
|
|
668e5600c3 | ||
|
|
ae228c4b2a | ||
|
|
9949a23724 | ||
|
|
b86fb0aea0 | ||
|
|
b369de6662 | ||
|
|
75483fe908 | ||
|
|
f4e7203a55 | ||
|
|
d17fd9424e | ||
|
|
a1698b740a | ||
|
|
5cb3c876fc | ||
|
|
0f2a84d270 | ||
|
|
bbb149b31e | ||
|
|
18a51ca5b8 | ||
|
|
93b7c999be | ||
|
|
f64f73a0ce | ||
|
|
88a6b20ba5 | ||
|
|
915ab69922 | ||
|
|
f9f340b6bf | ||
|
|
75920481b1 | ||
|
|
d21bb920d6 | ||
|
|
d119dbc239 | ||
|
|
7eda4a5100 | ||
|
|
3b1bcac5b3 | ||
|
|
6d7d76a3f1 | ||
|
|
dd6cf3df14 | ||
|
|
473be23153 | ||
|
|
ec40a1a08a | ||
|
|
729ac285da | ||
|
|
3b84c03131 | ||
|
|
d7da0cca3c | ||
|
|
fcda053d73 | ||
|
|
e23c989b78 | ||
|
|
95d14cb0af | ||
|
|
00c2be541d | ||
|
|
9af55d555b | ||
|
|
ef2064c753 | ||
|
|
18cdea3f88 | ||
|
|
36e0194e42 | ||
|
|
87cba2ecbe | ||
|
|
8d8e7c3b2c | ||
|
|
48d00f807e | ||
|
|
481ea64ccc | ||
|
|
f6a48b6899 | ||
|
|
8e5290d7c5 | ||
|
|
83c5e1bc83 | ||
|
|
8df8d89c6d | ||
|
|
226f1c1a9b | ||
|
|
bb3f3e541d | ||
|
|
42e7c3cdf0 | ||
|
|
f9a8ba91e4 | ||
|
|
1617e5de19 | ||
|
|
77998d1bb3 | ||
|
|
68f47c02c8 | ||
|
|
02be2b2918 | ||
|
|
43233ce62e | ||
|
|
51043770bf | ||
|
|
4478bd14c9 | ||
|
|
91dc315fdb | ||
|
|
a6d1a52e33 | ||
|
|
fd35530f35 | ||
|
|
cb609a1886 | ||
|
|
b202ccc5dc | ||
|
|
ee226b47fc | ||
|
|
9edff1f22c | ||
|
|
67864ffdab | ||
|
|
b55381f34e | ||
|
|
b6ad314dbb | ||
|
|
762f46ed54 | ||
|
|
63db5de4e2 | ||
|
|
9606957a0e | ||
|
|
e02bb71948 | ||
|
|
1fd96e168e | ||
|
|
ffc20f7e2c | ||
|
|
3e795d020d | ||
|
|
88ce6ffcbe |
@@ -10,10 +10,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Set up Go 1.19
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: 1.19
|
||||
go-version: '^1.20'
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
|
||||
@@ -12,17 +12,16 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '^1.19'
|
||||
go-version: '^1.20'
|
||||
check-latest: true
|
||||
- name: Compile man page markup
|
||||
id: gen-man-page-md
|
||||
run: |
|
||||
./scripts/generate-man-page-md.sh
|
||||
echo "::set-output name=man-page-md::man.md"
|
||||
|
||||
- name: Get the version
|
||||
id: get_version
|
||||
run: echo ::set-output name=VERSION::${GITHUB_REF##*/}
|
||||
run: echo "VERSION=${GITHUB_REF##*/}" >> "${GITHUB_OUTPUT}"
|
||||
|
||||
- name: Generate man page
|
||||
uses: docker://pandoc/core:2.14.2
|
||||
@@ -36,7 +35,7 @@ jobs:
|
||||
--variable=header:"yq (https://github.com/mikefarah/yq/) version ${{ steps.get_version.outputs.VERSION }}"
|
||||
--variable=author:"Mike Farah"
|
||||
--output=yq.1
|
||||
${{ steps.gen-man-page-md.outputs.man-page-md }}
|
||||
man.md
|
||||
|
||||
- name: Cross compile
|
||||
run: |
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
name: Release Snap
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
buildSnap:
|
||||
environment: snap
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: snapcore/action-build@v1
|
||||
id: build
|
||||
- uses: snapcore/action-publish@v1
|
||||
env:
|
||||
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.STORE_LOGIN }}
|
||||
with:
|
||||
snap: ${{ steps.build.outputs.snap }}
|
||||
release: stable
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.19.3 as builder
|
||||
FROM golang:1.20.1 as builder
|
||||
|
||||
WORKDIR /go/src/mikefarah/yq
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.19.3
|
||||
FROM golang:1.20.1
|
||||
|
||||
COPY scripts/devtools.sh /opt/devtools.sh
|
||||
|
||||
|
||||
@@ -109,7 +109,7 @@ EOL
|
||||
read -r -d '' expected << EOM
|
||||
cat:
|
||||
+content: BiBi
|
||||
+legs: "4"
|
||||
+@legs: "4"
|
||||
EOM
|
||||
|
||||
X=$(./yq e -p=xml test.yml)
|
||||
@@ -129,9 +129,9 @@ EOL
|
||||
read -r -d '' expected << EOM
|
||||
+p_xml: version="1.0"
|
||||
map:
|
||||
+xmlns: some-namespace
|
||||
+xmlns:xsi: some-instance
|
||||
+xsi:schemaLocation: some-url
|
||||
+@xmlns: some-namespace
|
||||
+@xmlns:xsi: some-instance
|
||||
+@xsi:schemaLocation: some-url
|
||||
EOM
|
||||
|
||||
X=$(./yq e -p=xml test.yml)
|
||||
@@ -190,7 +190,7 @@ EOL
|
||||
read -r -d '' expected << EOM
|
||||
cat:
|
||||
+content: BiBi
|
||||
+legs: "4"
|
||||
+@legs: "4"
|
||||
EOM
|
||||
|
||||
X=$(cat /dev/null | ./yq e -p=xml test.yml)
|
||||
|
||||
@@ -34,6 +34,82 @@ EOM
|
||||
assertEquals "$expected" "$X"
|
||||
}
|
||||
|
||||
|
||||
testLeadingSeperatorWithNewlinesNewDoc() {
|
||||
cat >test.yml <<EOL
|
||||
# hi peeps
|
||||
# cool
|
||||
|
||||
|
||||
---
|
||||
a: test
|
||||
---
|
||||
b: cool
|
||||
EOL
|
||||
|
||||
read -r -d '' expected << EOM
|
||||
# hi peeps
|
||||
# cool
|
||||
|
||||
|
||||
---
|
||||
a: thing
|
||||
---
|
||||
b: cool
|
||||
EOM
|
||||
|
||||
X=$(./yq e '(select(di == 0) | .a) = "thing"' - < test.yml)
|
||||
assertEquals "$expected" "$X"
|
||||
}
|
||||
|
||||
testLeadingSeperatorWithNewlinesMoreComments() {
|
||||
cat >test.yml <<EOL
|
||||
# hi peeps
|
||||
# cool
|
||||
|
||||
---
|
||||
# great
|
||||
|
||||
a: test
|
||||
---
|
||||
b: cool
|
||||
EOL
|
||||
|
||||
read -r -d '' expected << EOM
|
||||
# hi peeps
|
||||
# cool
|
||||
|
||||
---
|
||||
# great
|
||||
|
||||
a: thing
|
||||
---
|
||||
b: cool
|
||||
EOM
|
||||
|
||||
X=$(./yq e '(select(di == 0) | .a) = "thing"' - < test.yml)
|
||||
assertEquals "$expected" "$X"
|
||||
}
|
||||
|
||||
|
||||
testLeadingSeperatorWithDirective() {
|
||||
cat >test.yml <<EOL
|
||||
%YAML 1.1
|
||||
---
|
||||
this: should really work
|
||||
EOL
|
||||
|
||||
read -r -d '' expected << EOM
|
||||
%YAML 1.1
|
||||
---
|
||||
this: should really work
|
||||
EOM
|
||||
|
||||
X=$(./yq < test.yml)
|
||||
assertEquals "$expected" "$X"
|
||||
}
|
||||
|
||||
|
||||
testLeadingSeperatorPipeIntoEvalSeq() {
|
||||
X=$(./yq e - < test.yml)
|
||||
expected=$(cat test.yml)
|
||||
|
||||
@@ -252,7 +252,7 @@ EOM
|
||||
|
||||
testOutputXmComplex() {
|
||||
cat >test.yml <<EOL
|
||||
a: {b: {c: ["cat", "dog"], +f: meow}}
|
||||
a: {b: {c: ["cat", "dog"], +@f: meow}}
|
||||
EOL
|
||||
|
||||
read -r -d '' expected << EOM
|
||||
|
||||
+9
-25
@@ -59,22 +59,6 @@ yq -P sample.json
|
||||
return err
|
||||
}
|
||||
|
||||
inputFormatType, err := yqlib.InputFormatFromString(inputFormat)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if (inputFormatType == yqlib.XMLInputFormat &&
|
||||
outputFormatType != yqlib.XMLOutputFormat ||
|
||||
inputFormatType != yqlib.XMLInputFormat &&
|
||||
outputFormatType == yqlib.XMLOutputFormat) &&
|
||||
yqlib.ConfiguredXMLPreferences.AttributePrefix == "+@" {
|
||||
yqlib.GetLogger().Warning("The default xml-attribute-prefix has changed in the v4.30 to `+@` to avoid " +
|
||||
"naming conflicts with the default content name, directive name and proc inst prefix. If you need to keep " +
|
||||
"`+` please set that value explicityly with --xml-attribute-prefix.")
|
||||
}
|
||||
|
||||
if outputFormatType == yqlib.YamlOutputFormat ||
|
||||
outputFormatType == yqlib.PropsOutputFormat {
|
||||
unwrapScalar = true
|
||||
@@ -103,15 +87,15 @@ yq -P sample.json
|
||||
rootCmd.PersistentFlags().StringVarP(&outputFormat, "output-format", "o", "yaml", "[yaml|y|json|j|props|p|xml|x] output format type.")
|
||||
rootCmd.PersistentFlags().StringVarP(&inputFormat, "input-format", "p", "yaml", "[yaml|y|props|p|xml|x] parse format for input. Note that json is a subset of yaml.")
|
||||
|
||||
rootCmd.PersistentFlags().StringVar(&yqlib.ConfiguredXMLPreferences.AttributePrefix, "xml-attribute-prefix", "+", "prefix for xml attributes")
|
||||
rootCmd.PersistentFlags().StringVar(&yqlib.ConfiguredXMLPreferences.ContentName, "xml-content-name", "+content", "name for xml content (if no attribute name is present).")
|
||||
rootCmd.PersistentFlags().BoolVar(&yqlib.ConfiguredXMLPreferences.StrictMode, "xml-strict-mode", false, "enables strict parsing of XML. See https://pkg.go.dev/encoding/xml for more details.")
|
||||
rootCmd.PersistentFlags().BoolVar(&yqlib.ConfiguredXMLPreferences.KeepNamespace, "xml-keep-namespace", true, "enables keeping namespace after parsing attributes")
|
||||
rootCmd.PersistentFlags().BoolVar(&yqlib.ConfiguredXMLPreferences.UseRawToken, "xml-raw-token", true, "enables using RawToken method instead Token. Commonly disables namespace translations. See https://pkg.go.dev/encoding/xml#Decoder.RawToken for details.")
|
||||
rootCmd.PersistentFlags().StringVar(&yqlib.ConfiguredXMLPreferences.ProcInstPrefix, "xml-proc-inst-prefix", "+p_", "prefix for xml processing instructions (e.g. <?xml version=\"1\"?>)")
|
||||
rootCmd.PersistentFlags().StringVar(&yqlib.ConfiguredXMLPreferences.DirectiveName, "xml-directive-name", "+directive", "name for xml directives (e.g. <!DOCTYPE thing cat>)")
|
||||
rootCmd.PersistentFlags().BoolVar(&yqlib.ConfiguredXMLPreferences.SkipProcInst, "xml-skip-proc-inst", false, "skip over process instructions (e.g. <?xml version=\"1\"?>)")
|
||||
rootCmd.PersistentFlags().BoolVar(&yqlib.ConfiguredXMLPreferences.SkipDirectives, "xml-skip-directives", false, "skip over directives (e.g. <!DOCTYPE thing cat>)")
|
||||
rootCmd.PersistentFlags().StringVar(&yqlib.ConfiguredXMLPreferences.AttributePrefix, "xml-attribute-prefix", yqlib.ConfiguredXMLPreferences.AttributePrefix, "prefix for xml attributes")
|
||||
rootCmd.PersistentFlags().StringVar(&yqlib.ConfiguredXMLPreferences.ContentName, "xml-content-name", yqlib.ConfiguredXMLPreferences.ContentName, "name for xml content (if no attribute name is present).")
|
||||
rootCmd.PersistentFlags().BoolVar(&yqlib.ConfiguredXMLPreferences.StrictMode, "xml-strict-mode", yqlib.ConfiguredXMLPreferences.StrictMode, "enables strict parsing of XML. See https://pkg.go.dev/encoding/xml for more details.")
|
||||
rootCmd.PersistentFlags().BoolVar(&yqlib.ConfiguredXMLPreferences.KeepNamespace, "xml-keep-namespace", yqlib.ConfiguredXMLPreferences.KeepNamespace, "enables keeping namespace after parsing attributes")
|
||||
rootCmd.PersistentFlags().BoolVar(&yqlib.ConfiguredXMLPreferences.UseRawToken, "xml-raw-token", yqlib.ConfiguredXMLPreferences.UseRawToken, "enables using RawToken method instead Token. Commonly disables namespace translations. See https://pkg.go.dev/encoding/xml#Decoder.RawToken for details.")
|
||||
rootCmd.PersistentFlags().StringVar(&yqlib.ConfiguredXMLPreferences.ProcInstPrefix, "xml-proc-inst-prefix", yqlib.ConfiguredXMLPreferences.ProcInstPrefix, "prefix for xml processing instructions (e.g. <?xml version=\"1\"?>)")
|
||||
rootCmd.PersistentFlags().StringVar(&yqlib.ConfiguredXMLPreferences.DirectiveName, "xml-directive-name", yqlib.ConfiguredXMLPreferences.DirectiveName, "name for xml directives (e.g. <!DOCTYPE thing cat>)")
|
||||
rootCmd.PersistentFlags().BoolVar(&yqlib.ConfiguredXMLPreferences.SkipProcInst, "xml-skip-proc-inst", yqlib.ConfiguredXMLPreferences.SkipProcInst, "skip over process instructions (e.g. <?xml version=\"1\"?>)")
|
||||
rootCmd.PersistentFlags().BoolVar(&yqlib.ConfiguredXMLPreferences.SkipDirectives, "xml-skip-directives", yqlib.ConfiguredXMLPreferences.SkipDirectives, "skip over directives (e.g. <!DOCTYPE thing cat>)")
|
||||
|
||||
rootCmd.PersistentFlags().BoolVarP(&nullInput, "null-input", "n", false, "Don't read input, simply evaluate the expression given. Useful for creating docs from scratch.")
|
||||
rootCmd.PersistentFlags().BoolVarP(&noDocSeparators, "no-doc", "N", false, "Don't print document separators (---)")
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ var (
|
||||
GitDescribe string
|
||||
|
||||
// Version is main version number that is being run at the moment.
|
||||
Version = "4.30.1"
|
||||
Version = "v4.31.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
|
||||
|
||||
+5
-7
@@ -1,7 +1,5 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE config SYSTEM "/etc/iwatch/iwatch.dtd" >
|
||||
<apple>
|
||||
<?coolioo version="1.0"?>
|
||||
<!DOCTYPE config SYSTEM "/etc/iwatch/iwatch.dtd" >
|
||||
<b>things</b>
|
||||
</apple>
|
||||
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
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
# comment
|
||||
# about things
|
||||
a: cat
|
||||
@@ -1,31 +1,31 @@
|
||||
module github.com/mikefarah/yq/v4
|
||||
|
||||
require (
|
||||
github.com/a8m/envsubst v1.3.0
|
||||
github.com/a8m/envsubst v1.4.1
|
||||
github.com/alecthomas/participle/v2 v2.0.0-beta.5
|
||||
github.com/alecthomas/repr v0.1.1
|
||||
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.13.0
|
||||
github.com/goccy/go-json v0.9.11
|
||||
github.com/goccy/go-yaml v1.9.6
|
||||
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/jinzhu/copier v0.3.5
|
||||
github.com/magiconair/properties v1.8.6
|
||||
github.com/magiconair/properties v1.8.7
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e
|
||||
github.com/spf13/cobra v1.6.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
golang.org/x/net v0.0.0-20220708220712-1185a9018129
|
||||
golang.org/x/net v0.1.1-0.20221104162952-702349b0e862
|
||||
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/inconshreveable/mousetrap v1.0.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e // indirect
|
||||
golang.org/x/text v0.3.7 // 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/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
|
||||
)
|
||||
|
||||
go 1.19
|
||||
go 1.20
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
github.com/a8m/envsubst v1.3.0 h1:GmXKmVssap0YtlU3E230W98RWtWCyIZzjtf1apWWyAg=
|
||||
github.com/a8m/envsubst v1.3.0/go.mod h1:MVUTQNGQ3tsjOOtKCNd+fl8RzhsXcDvvAEzkhGtlsbY=
|
||||
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/alecthomas/repr v0.1.1 h1:87P60cSmareLAxMc4Hro0r2RBY4ROm0dYwkJNpS4pPs=
|
||||
github.com/alecthomas/repr v0.1.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
|
||||
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=
|
||||
@@ -13,31 +13,31 @@ github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/
|
||||
github.com/elliotchance/orderedmap v1.5.0 h1:1IsExUsjv5XNBD3ZdC7jkAAqLWOOKdbPTmkHx63OsBg=
|
||||
github.com/elliotchance/orderedmap v1.5.0/go.mod h1:wsDwEaX5jEoyhbs7x93zk2H/qv0zwuhg4inXhDkYqys=
|
||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
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.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk=
|
||||
github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-yaml v1.9.6 h1:KhAu1zf9JXnm3vbG49aDE0E5uEBUsM4uwD31/58ZWyI=
|
||||
github.com/goccy/go-yaml v1.9.6/go.mod h1:JubOolP3gh0HpiBc4BLRD4YmjEjHAmIIB2aaXKkTfoE=
|
||||
github.com/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/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
|
||||
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
|
||||
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
|
||||
github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
|
||||
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
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.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-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.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
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/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=
|
||||
@@ -54,21 +54,20 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20220708220712-1185a9018129 h1:vucSRfWwTsoXro7P+3Cjlr6flUMtzCwzlvkxEQtHHB0=
|
||||
golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e h1:NHvCuwuS43lGnYhten69ZWqi2QOj/CiDNcKbVqwVoew=
|
||||
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
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/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0=
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
//go:build linux
|
||||
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func changeOwner(info fs.FileInfo, file *os.File) error {
|
||||
if stat, ok := info.Sys().(*syscall.Stat_t); ok {
|
||||
uid := int(stat.Uid)
|
||||
gid := int(stat.Gid)
|
||||
|
||||
err := os.Chown(file.Name(), uid, gid)
|
||||
if err != nil {
|
||||
// this happens with snap confinement
|
||||
// not really a big issue as users can chown
|
||||
// the file themselves if required.
|
||||
log.Info("Skipping chown: %v", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
//go:build !linux
|
||||
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
)
|
||||
|
||||
func changeOwner(info fs.FileInfo, file *os.File) error {
|
||||
return nil
|
||||
}
|
||||
@@ -15,6 +15,7 @@ const (
|
||||
JsonInputFormat
|
||||
CSVObjectInputFormat
|
||||
TSVObjectInputFormat
|
||||
UriInputFormat
|
||||
)
|
||||
|
||||
type Decoder interface {
|
||||
|
||||
@@ -60,7 +60,7 @@ func (dec *jsonDecoder) convertToYamlNode(data *orderedMap) (*yaml.Node, error)
|
||||
}
|
||||
}
|
||||
|
||||
var yamlMap = &yaml.Node{Kind: yaml.MappingNode}
|
||||
var yamlMap = &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}
|
||||
for _, keyValuePair := range data.kv {
|
||||
yamlValue, err := dec.convertToYamlNode(&keyValuePair.V)
|
||||
if err != nil {
|
||||
@@ -74,7 +74,7 @@ func (dec *jsonDecoder) convertToYamlNode(data *orderedMap) (*yaml.Node, error)
|
||||
|
||||
func (dec *jsonDecoder) parseArray(dataArray []*orderedMap) (*yaml.Node, error) {
|
||||
|
||||
var yamlMap = &yaml.Node{Kind: yaml.SequenceNode}
|
||||
var yamlMap = &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq"}
|
||||
|
||||
for _, value := range dataArray {
|
||||
yamlValue, err := dec.convertToYamlNode(value)
|
||||
|
||||
@@ -63,7 +63,7 @@ func (dec *propertiesDecoder) applyPropertyComments(context Context, path []inte
|
||||
|
||||
rhsCandidateNode.Node.Tag = guessTagFromCustomType(rhsCandidateNode.Node)
|
||||
|
||||
rhsOp := &Operation{OperationType: valueOpType, CandidateNode: rhsCandidateNode}
|
||||
rhsOp := &Operation{OperationType: referenceOpType, CandidateNode: rhsCandidateNode}
|
||||
|
||||
assignmentOpNode := &ExpressionNode{
|
||||
Operation: assignmentOp,
|
||||
@@ -102,7 +102,7 @@ func (dec *propertiesDecoder) applyProperty(context Context, properties *propert
|
||||
|
||||
assignmentOp := &Operation{OperationType: assignOpType, Preferences: assignPreferences{}}
|
||||
|
||||
rhsOp := &Operation{OperationType: valueOpType, CandidateNode: rhsCandidateNode}
|
||||
rhsOp := &Operation{OperationType: referenceOpType, CandidateNode: rhsCandidateNode}
|
||||
|
||||
assignmentOpNode := &ExpressionNode{
|
||||
Operation: assignmentOp,
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"net/url"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type uriDecoder struct {
|
||||
reader io.Reader
|
||||
finished bool
|
||||
readAnything bool
|
||||
}
|
||||
|
||||
func NewUriDecoder() Decoder {
|
||||
return &uriDecoder{finished: false}
|
||||
}
|
||||
|
||||
func (dec *uriDecoder) Init(reader io.Reader) error {
|
||||
dec.reader = reader
|
||||
dec.readAnything = false
|
||||
dec.finished = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dec *uriDecoder) Decode() (*CandidateNode, error) {
|
||||
if dec.finished {
|
||||
return nil, io.EOF
|
||||
}
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
|
||||
if _, err := buf.ReadFrom(dec.reader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if buf.Len() == 0 {
|
||||
dec.finished = true
|
||||
|
||||
// if we've read _only_ an empty string, lets return that
|
||||
// otherwise if we've already read some bytes, and now we get
|
||||
// an empty string, then we are done.
|
||||
if dec.readAnything {
|
||||
return nil, io.EOF
|
||||
}
|
||||
}
|
||||
newValue, err := url.QueryUnescape(buf.String())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dec.readAnything = true
|
||||
return &CandidateNode{
|
||||
Node: &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: "!!str",
|
||||
Value: newValue,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
+72
-23
@@ -3,6 +3,7 @@ package yqlib
|
||||
import (
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"unicode"
|
||||
@@ -53,15 +54,17 @@ func (dec *xmlDecoder) processComment(c string) string {
|
||||
}
|
||||
|
||||
func (dec *xmlDecoder) createMap(n *xmlNode) (*yaml.Node, error) {
|
||||
log.Debug("createMap: headC: %v, footC: %v", n.HeadComment, n.FootComment)
|
||||
log.Debug("createMap: headC: %v, lineC: %v, footC: %v", n.HeadComment, n.LineComment, n.FootComment)
|
||||
yamlNode := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}
|
||||
|
||||
if len(n.Data) > 0 {
|
||||
log.Debugf("creating content node for map: %v", dec.prefs.ContentName)
|
||||
label := dec.prefs.ContentName
|
||||
labelNode := createScalarNode(label, label)
|
||||
labelNode.HeadComment = dec.processComment(n.HeadComment)
|
||||
labelNode.LineComment = dec.processComment(n.LineComment)
|
||||
labelNode.FootComment = dec.processComment(n.FootComment)
|
||||
yamlNode.Content = append(yamlNode.Content, labelNode, createScalarNode(n.Data, n.Data))
|
||||
yamlNode.Content = append(yamlNode.Content, labelNode, dec.createValueNodeFromData(n.Data))
|
||||
}
|
||||
|
||||
for i, keyValuePair := range n.Children {
|
||||
@@ -75,7 +78,7 @@ func (dec *xmlDecoder) createMap(n *xmlNode) (*yaml.Node, error) {
|
||||
labelNode.HeadComment = dec.processComment(n.HeadComment)
|
||||
|
||||
}
|
||||
|
||||
log.Debugf("label=%v, i=%v, keyValuePair.FootComment: %v", label, i, keyValuePair.FootComment)
|
||||
labelNode.FootComment = dec.processComment(keyValuePair.FootComment)
|
||||
|
||||
log.Debug("len of children in %v is %v", label, len(children))
|
||||
@@ -89,8 +92,16 @@ func (dec *xmlDecoder) createMap(n *xmlNode) (*yaml.Node, error) {
|
||||
// if the value is a scalar, the head comment of the scalar needs to go on the key?
|
||||
// add tests for <z/> as well as multiple <ds> of inputXmlWithComments > yaml
|
||||
if len(children[0].Children) == 0 && children[0].HeadComment != "" {
|
||||
labelNode.HeadComment = labelNode.HeadComment + "\n" + strings.TrimSpace(children[0].HeadComment)
|
||||
children[0].HeadComment = ""
|
||||
if len(children[0].Data) > 0 {
|
||||
|
||||
log.Debug("scalar comment hack, currentlabel [%v]", labelNode.HeadComment)
|
||||
labelNode.HeadComment = joinComments([]string{labelNode.HeadComment, strings.TrimSpace(children[0].HeadComment)}, "\n")
|
||||
children[0].HeadComment = ""
|
||||
} else {
|
||||
// child is null, put the headComment as a linecomment for reasons
|
||||
children[0].LineComment = children[0].HeadComment
|
||||
children[0].HeadComment = ""
|
||||
}
|
||||
}
|
||||
valueNode, err = dec.convertToYamlNode(children[0])
|
||||
if err != nil {
|
||||
@@ -103,17 +114,40 @@ func (dec *xmlDecoder) createMap(n *xmlNode) (*yaml.Node, error) {
|
||||
return yamlNode, nil
|
||||
}
|
||||
|
||||
func (dec *xmlDecoder) createValueNodeFromData(values []string) *yaml.Node {
|
||||
switch len(values) {
|
||||
case 0:
|
||||
return createScalarNode(nil, "")
|
||||
case 1:
|
||||
return createScalarNode(values[0], values[0])
|
||||
default:
|
||||
content := make([]*yaml.Node, 0)
|
||||
for _, value := range values {
|
||||
content = append(content, createScalarNode(value, value))
|
||||
}
|
||||
return &yaml.Node{
|
||||
Kind: yaml.SequenceNode,
|
||||
Tag: "!!seq",
|
||||
Content: content,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (dec *xmlDecoder) convertToYamlNode(n *xmlNode) (*yaml.Node, error) {
|
||||
if len(n.Children) > 0 {
|
||||
return dec.createMap(n)
|
||||
}
|
||||
scalar := createScalarNode(n.Data, n.Data)
|
||||
if n.Data == "" {
|
||||
scalar = createScalarNode(nil, "")
|
||||
}
|
||||
log.Debug("scalar headC: %v, footC: %v", n.HeadComment, n.FootComment)
|
||||
|
||||
scalar := dec.createValueNodeFromData(n.Data)
|
||||
|
||||
log.Debug("scalar (%v), headC: %v, lineC: %v, footC: %v", scalar.Tag, n.HeadComment, n.LineComment, n.FootComment)
|
||||
scalar.HeadComment = dec.processComment(n.HeadComment)
|
||||
scalar.LineComment = dec.processComment(n.LineComment)
|
||||
if scalar.Tag == "!!seq" {
|
||||
scalar.Content[0].HeadComment = scalar.LineComment
|
||||
scalar.LineComment = ""
|
||||
}
|
||||
|
||||
scalar.FootComment = dec.processComment(n.FootComment)
|
||||
|
||||
return scalar, nil
|
||||
@@ -156,7 +190,7 @@ type xmlNode struct {
|
||||
HeadComment string
|
||||
FootComment string
|
||||
LineComment string
|
||||
Data string
|
||||
Data []string
|
||||
}
|
||||
|
||||
type xmlChildrenKv struct {
|
||||
@@ -201,6 +235,8 @@ func (dec *xmlDecoder) decodeXML(root *xmlNode) error {
|
||||
// That will convert the charset if the provided XML is non-UTF-8
|
||||
xmlDec.CharsetReader = charset.NewReaderLabel
|
||||
|
||||
started := false
|
||||
|
||||
// Create first element from the root node
|
||||
elem := &element{
|
||||
parent: nil,
|
||||
@@ -241,12 +277,18 @@ func (dec *xmlDecoder) decodeXML(root *xmlNode) error {
|
||||
a.Name.Local = a.Name.Space + ":" + a.Name.Local
|
||||
}
|
||||
}
|
||||
elem.n.AddChild(dec.prefs.AttributePrefix+a.Name.Local, &xmlNode{Data: a.Value})
|
||||
elem.n.AddChild(dec.prefs.AttributePrefix+a.Name.Local, &xmlNode{Data: []string{a.Value}})
|
||||
}
|
||||
case xml.CharData:
|
||||
|
||||
// Extract XML data (if any)
|
||||
elem.n.Data = trimNonGraphic(string(se))
|
||||
if elem.n.Data != "" {
|
||||
newBit := trimNonGraphic(string(se))
|
||||
if !started && len(newBit) > 0 {
|
||||
return fmt.Errorf("invalid XML: Encountered chardata [%v] outside of XML node", newBit)
|
||||
}
|
||||
|
||||
if len(newBit) > 0 {
|
||||
elem.n.Data = append(elem.n.Data, newBit)
|
||||
elem.state = "chardata"
|
||||
log.Debug("chardata [%v] for %v", elem.n.Data, elem.label)
|
||||
}
|
||||
@@ -268,21 +310,22 @@ func (dec *xmlDecoder) decodeXML(root *xmlNode) error {
|
||||
|
||||
} else if elem.state == "chardata" {
|
||||
log.Debug("got a line comment for (%v) %v: [%v]", elem.state, elem.label, commentStr)
|
||||
elem.n.LineComment = joinFilter([]string{elem.n.LineComment, commentStr})
|
||||
elem.n.LineComment = joinComments([]string{elem.n.LineComment, commentStr}, " ")
|
||||
} else {
|
||||
log.Debug("got a head comment for (%v) %v: [%v]", elem.state, elem.label, commentStr)
|
||||
elem.n.HeadComment = joinFilter([]string{elem.n.HeadComment, commentStr})
|
||||
elem.n.HeadComment = joinComments([]string{elem.n.HeadComment, commentStr}, " ")
|
||||
}
|
||||
|
||||
case xml.ProcInst:
|
||||
if !dec.prefs.SkipProcInst {
|
||||
elem.n.AddChild(dec.prefs.ProcInstPrefix+se.Target, &xmlNode{Data: string(se.Inst)})
|
||||
elem.n.AddChild(dec.prefs.ProcInstPrefix+se.Target, &xmlNode{Data: []string{string(se.Inst)}})
|
||||
}
|
||||
case xml.Directive:
|
||||
if !dec.prefs.SkipDirectives {
|
||||
elem.n.AddChild(dec.prefs.DirectiveName, &xmlNode{Data: string(se)})
|
||||
elem.n.AddChild(dec.prefs.DirectiveName, &xmlNode{Data: []string{string(se)}})
|
||||
}
|
||||
}
|
||||
started = true
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -294,22 +337,28 @@ func applyFootComment(elem *element, commentStr string) {
|
||||
if len(elem.n.Children) > 0 {
|
||||
lastChildIndex := len(elem.n.Children) - 1
|
||||
childKv := elem.n.Children[lastChildIndex]
|
||||
log.Debug("got a foot comment for %v: [%v]", childKv.K, commentStr)
|
||||
childKv.FootComment = joinFilter([]string{elem.n.FootComment, commentStr})
|
||||
log.Debug("got a foot comment, putting on last child for %v: [%v]", childKv.K, commentStr)
|
||||
// if it's an array of scalars, put the foot comment on the scalar itself
|
||||
if len(childKv.V) > 0 && len(childKv.V[0].Children) == 0 {
|
||||
nodeToUpdate := childKv.V[len(childKv.V)-1]
|
||||
nodeToUpdate.FootComment = joinComments([]string{nodeToUpdate.FootComment, commentStr}, " ")
|
||||
} else {
|
||||
childKv.FootComment = joinComments([]string{elem.n.FootComment, commentStr}, " ")
|
||||
}
|
||||
} else {
|
||||
log.Debug("got a foot comment for %v: [%v]", elem.label, commentStr)
|
||||
elem.n.FootComment = joinFilter([]string{elem.n.FootComment, commentStr})
|
||||
elem.n.FootComment = joinComments([]string{elem.n.FootComment, commentStr}, " ")
|
||||
}
|
||||
}
|
||||
|
||||
func joinFilter(rawStrings []string) string {
|
||||
func joinComments(rawStrings []string, joinStr string) string {
|
||||
stringsToJoin := make([]string, 0)
|
||||
for _, str := range rawStrings {
|
||||
if str != "" {
|
||||
stringsToJoin = append(stringsToJoin, str)
|
||||
}
|
||||
}
|
||||
return strings.Join(stringsToJoin, " ")
|
||||
return strings.Join(stringsToJoin, joinStr)
|
||||
}
|
||||
|
||||
// trimNonGraphic returns a slice of the string s, with all leading and trailing
|
||||
|
||||
@@ -2,6 +2,7 @@ package yqlib
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
"regexp"
|
||||
@@ -12,11 +13,15 @@ import (
|
||||
|
||||
type yamlDecoder struct {
|
||||
decoder yaml.Decoder
|
||||
|
||||
prefs YamlPreferences
|
||||
|
||||
// work around of various parsing issues by yaml.v3 with document headers
|
||||
prefs YamlPreferences
|
||||
leadingContent string
|
||||
readAnything bool
|
||||
firstFile bool
|
||||
bufferRead bytes.Buffer
|
||||
|
||||
readAnything bool
|
||||
firstFile bool
|
||||
}
|
||||
|
||||
func NewYamlDecoder(prefs YamlPreferences) Decoder {
|
||||
@@ -25,6 +30,7 @@ func NewYamlDecoder(prefs YamlPreferences) Decoder {
|
||||
|
||||
func (dec *yamlDecoder) processReadStream(reader *bufio.Reader) (io.Reader, string, error) {
|
||||
var commentLineRegEx = regexp.MustCompile(`^\s*#`)
|
||||
var yamlDirectiveLineRegEx = regexp.MustCompile(`^\s*%YA`)
|
||||
var sb strings.Builder
|
||||
for {
|
||||
peekBytes, err := reader.Peek(3)
|
||||
@@ -33,6 +39,14 @@ func (dec *yamlDecoder) processReadStream(reader *bufio.Reader) (io.Reader, stri
|
||||
return reader, sb.String(), nil
|
||||
} else if err != nil {
|
||||
return reader, sb.String(), err
|
||||
} else if string(peekBytes[0]) == "\n" {
|
||||
_, err := reader.ReadString('\n')
|
||||
sb.WriteString("\n")
|
||||
if errors.Is(err, io.EOF) {
|
||||
return reader, sb.String(), nil
|
||||
} else if err != nil {
|
||||
return reader, sb.String(), err
|
||||
}
|
||||
} else if string(peekBytes) == "---" {
|
||||
_, err := reader.ReadString('\n')
|
||||
sb.WriteString("$yqDocSeperator$\n")
|
||||
@@ -41,7 +55,7 @@ func (dec *yamlDecoder) processReadStream(reader *bufio.Reader) (io.Reader, stri
|
||||
} else if err != nil {
|
||||
return reader, sb.String(), err
|
||||
}
|
||||
} else if commentLineRegEx.MatchString(string(peekBytes)) {
|
||||
} else if commentLineRegEx.MatchString(string(peekBytes)) || yamlDirectiveLineRegEx.MatchString(string(peekBytes)) {
|
||||
line, err := reader.ReadString('\n')
|
||||
sb.WriteString(line)
|
||||
if errors.Is(err, io.EOF) {
|
||||
@@ -58,6 +72,7 @@ func (dec *yamlDecoder) processReadStream(reader *bufio.Reader) (io.Reader, stri
|
||||
func (dec *yamlDecoder) Init(reader io.Reader) error {
|
||||
readerToUse := reader
|
||||
leadingContent := ""
|
||||
dec.bufferRead = bytes.Buffer{}
|
||||
var err error
|
||||
// if we 'evaluating together' - we only process the leading content
|
||||
// of the first file - this ensures comments from subsequent files are
|
||||
@@ -67,6 +82,12 @@ func (dec *yamlDecoder) Init(reader io.Reader) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if !dec.prefs.LeadingContentPreProcessing {
|
||||
// if we're not process the leading content
|
||||
// keep a copy of what we've read. This is incase its a
|
||||
// doc with only comments - the decoder will return nothing
|
||||
// then we can read the comments from bufferRead
|
||||
readerToUse = io.TeeReader(reader, &dec.bufferRead)
|
||||
}
|
||||
dec.leadingContent = leadingContent
|
||||
dec.readAnything = false
|
||||
@@ -77,13 +98,20 @@ func (dec *yamlDecoder) Init(reader io.Reader) error {
|
||||
|
||||
func (dec *yamlDecoder) Decode() (*CandidateNode, error) {
|
||||
var dataBucket yaml.Node
|
||||
|
||||
err := dec.decoder.Decode(&dataBucket)
|
||||
if errors.Is(err, io.EOF) && dec.leadingContent != "" && !dec.readAnything {
|
||||
// force returning an empty node with a comment.
|
||||
dec.readAnything = true
|
||||
return dec.blankNodeWithComment(), nil
|
||||
|
||||
} else if errors.Is(err, io.EOF) && !dec.prefs.LeadingContentPreProcessing && !dec.readAnything {
|
||||
// didn't find any yaml,
|
||||
// check the tee buffer, maybe there were comments
|
||||
dec.readAnything = true
|
||||
dec.leadingContent = dec.bufferRead.String()
|
||||
if dec.leadingContent != "" {
|
||||
return dec.blankNodeWithComment(), nil
|
||||
}
|
||||
return nil, err
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -96,6 +124,7 @@ func (dec *yamlDecoder) Decode() (*CandidateNode, error) {
|
||||
candidateNode.LeadingContent = dec.leadingContent
|
||||
dec.leadingContent = ""
|
||||
}
|
||||
dec.readAnything = true
|
||||
// move document comments into candidate node
|
||||
// otherwise unwrap drops them.
|
||||
candidateNode.TrailingContent = dataBucket.FootComment
|
||||
|
||||
@@ -86,6 +86,23 @@ will output
|
||||
a: ['dog', 'cat']
|
||||
```
|
||||
|
||||
## Prepend to existing array
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
a:
|
||||
- dog
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq '.a = ["cat"] + .a' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
a:
|
||||
- cat
|
||||
- dog
|
||||
```
|
||||
|
||||
## Add new object to array
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Array to Map
|
||||
|
||||
Use this operator to convert an array to..a map. Skips over null values.
|
||||
Use this operator to convert an array to..a map. The indices are used as map keys, null values in the array are skipped over.
|
||||
|
||||
Behind the scenes, this is implemented using reduce:
|
||||
|
||||
|
||||
@@ -247,6 +247,7 @@ Note the use of `...` to ensure key nodes are included.
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
# hi
|
||||
|
||||
a: cat # comment
|
||||
# great
|
||||
b: # key comment
|
||||
@@ -265,6 +266,7 @@ b:
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
# welcome!
|
||||
|
||||
a: cat # meow
|
||||
# have a great day
|
||||
```
|
||||
@@ -293,6 +295,7 @@ yq '. | head_comment' sample.yml
|
||||
will output
|
||||
```yaml
|
||||
welcome!
|
||||
|
||||
```
|
||||
|
||||
## Head comment with document split
|
||||
|
||||
@@ -86,6 +86,30 @@ a: cool
|
||||
updated: 2021-05-19T01:02:03Z
|
||||
```
|
||||
|
||||
## From Unix
|
||||
Converts from unix time. Note, you don't have to pipe through the tz operator :)
|
||||
|
||||
Running
|
||||
```bash
|
||||
yq --null-input '1675301929 | from_unix | tz("UTC")'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
2023-02-02T01:38:49Z
|
||||
```
|
||||
|
||||
## To Unix
|
||||
Converts to unix time
|
||||
|
||||
Running
|
||||
```bash
|
||||
yq --null-input 'now | to_unix'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
1621386123
|
||||
```
|
||||
|
||||
## Timezone: from standard RFC3339 format
|
||||
Returns a new datetime in the specified timezone. Specify standard IANA Time Zone format or 'utc', 'local'. When given a single parameter, this assumes the datetime is in RFC3339 format.
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ These operators are useful to process yaml documents that have stringified embed
|
||||
| TSV | from_tsv/@tsvd | to_tsv/@tsv |
|
||||
| XML | from_xml/@xmld | to_xml(i)/@xml |
|
||||
| Base64 | @base64d | @base64 |
|
||||
| URI | @urid | @uri |
|
||||
| Shell | | @sh |
|
||||
|
||||
|
||||
See CSV and TSV [documentation](https://mikefarah.gitbook.io/yq/usage/csv-tsv) for accepted formats.
|
||||
@@ -435,6 +437,50 @@ will output
|
||||
YTogYXBwbGUK
|
||||
```
|
||||
|
||||
## Encode a string to uri
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
coolData: this has & special () characters *
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq '.coolData | @uri' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
this+has+%26+special+%28%29+characters+%2A
|
||||
```
|
||||
|
||||
## Decode a URI to a string
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
this+has+%26+special+%28%29+characters+%2A
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq '@urid' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
this has & special () characters *
|
||||
```
|
||||
|
||||
## Encode a string to sh
|
||||
Sh/Bash friendly string
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
coolData: strings with spaces and a 'quote'
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq '.coolData | @sh' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
strings' with spaces and a '\'quote\'
|
||||
```
|
||||
|
||||
## Decode a base64 encoded string
|
||||
Decoded data is assumed to be a string.
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Array to Map
|
||||
|
||||
Use this operator to convert an array to..a map. Skips over null values.
|
||||
Use this operator to convert an array to..a map. The indices are used as map keys, null values in the array are skipped over.
|
||||
|
||||
Behind the scenes, this is implemented using reduce:
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ These operators are useful to process yaml documents that have stringified embed
|
||||
| TSV | from_tsv/@tsvd | to_tsv/@tsv |
|
||||
| XML | from_xml/@xmld | to_xml(i)/@xml |
|
||||
| Base64 | @base64d | @base64 |
|
||||
| URI | @urid | @uri |
|
||||
| Shell | | @sh |
|
||||
|
||||
|
||||
See CSV and TSV [documentation](https://mikefarah.gitbook.io/yq/usage/csv-tsv) for accepted formats.
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# Shuffle
|
||||
|
||||
Shuffles an array. Note that this command does _not_ use a cryptographically secure random number generator to randomise the array order.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Slice Array
|
||||
# Slice/Splice Array
|
||||
|
||||
The slice array operator takes an array as input and returns a subarray. Like the `jq` equivalent, `.[10:15]` will return an array of length 5, starting from index 10 inclusive, up to index 15 exclusive. Negative numbers count backwards from the end of the array.
|
||||
|
||||
|
||||
@@ -499,3 +499,43 @@ b: !goat
|
||||
dog: woof
|
||||
```
|
||||
|
||||
## Merging a null with a map
|
||||
Running
|
||||
```bash
|
||||
yq --null-input 'null * {"some": "thing"}'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
some: thing
|
||||
```
|
||||
|
||||
## Merging a map with null
|
||||
Running
|
||||
```bash
|
||||
yq --null-input '{"some": "thing"} * null'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
some: thing
|
||||
```
|
||||
|
||||
## Merging an null with an array
|
||||
Running
|
||||
```bash
|
||||
yq --null-input 'null * ["some"]'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- some
|
||||
```
|
||||
|
||||
## Merging an array with null
|
||||
Running
|
||||
```bash
|
||||
yq --null-input '["some"] * null'
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- some
|
||||
```
|
||||
|
||||
|
||||
@@ -127,6 +127,7 @@ Like pick but recursive. This uses `ireduce` to deeply set the selected paths in
|
||||
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
|
||||
parentA: bob
|
||||
parentB:
|
||||
child1: i am child1
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# Shuffle
|
||||
|
||||
Shuffles an array. Note that this command does _not_ use a cryptographically secure random number generator to randomise the array order.
|
||||
|
||||
|
||||
## Shuffle array
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
- 5
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq 'shuffle' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- 5
|
||||
- 2
|
||||
- 4
|
||||
- 1
|
||||
- 3
|
||||
```
|
||||
|
||||
## Shuffle array in place
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
cool:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
- 4
|
||||
- 5
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq '.cool |= shuffle' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
cool:
|
||||
- 5
|
||||
- 2
|
||||
- 4
|
||||
- 1
|
||||
- 3
|
||||
```
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Slice Array
|
||||
# Slice/Splice Array
|
||||
|
||||
The slice array operator takes an array as input and returns a subarray. Like the `jq` equivalent, `.[10:15]` will return an array of length 5, starting from index 10 inclusive, up to index 15 exclusive. Negative numbers count backwards from the end of the array.
|
||||
|
||||
|
||||
@@ -25,6 +25,28 @@ will output
|
||||
- a: cat
|
||||
```
|
||||
|
||||
## Sort by multiple fields
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
- a: dog
|
||||
- a: cat
|
||||
b: banana
|
||||
- a: cat
|
||||
b: apple
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq 'sort_by(.a, .b)' sample.yml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
- a: cat
|
||||
b: apple
|
||||
- a: cat
|
||||
b: banana
|
||||
- a: dog
|
||||
```
|
||||
|
||||
## Sort descending by string field
|
||||
Use sort with reverse to sort in descending order.
|
||||
|
||||
|
||||
+56
-35
@@ -152,6 +152,27 @@ cat:
|
||||
+@legs: "4"
|
||||
```
|
||||
|
||||
## Parse xml: content split between comments/children
|
||||
Multiple content texts are collected into a sequence.
|
||||
|
||||
Given a sample.xml file of:
|
||||
```xml
|
||||
<root> value <!-- comment-->anotherValue <a>frog</a> cool!</root>
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq -p=xml '.' sample.xml
|
||||
```
|
||||
will output
|
||||
```yaml
|
||||
root:
|
||||
+content: # comment
|
||||
- value
|
||||
- anotherValue
|
||||
- cool!
|
||||
a: frog
|
||||
```
|
||||
|
||||
## Parse xml: custom dtd
|
||||
DTD entities are processed as directives.
|
||||
|
||||
@@ -258,55 +279,55 @@ cat:
|
||||
```
|
||||
|
||||
## Parse xml: keep attribute namespace
|
||||
Defaults to true
|
||||
|
||||
Given a sample.xml file of:
|
||||
```xml
|
||||
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url">
|
||||
</map>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url"></map>
|
||||
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq -p=xml -o=xml --xml-keep-namespace '.' sample.xml
|
||||
yq -p=xml -o=xml --xml-keep-namespace=false '.' sample.xml
|
||||
```
|
||||
will output
|
||||
```xml
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" some-instance:schemaLocation="some-url"></map>
|
||||
```
|
||||
|
||||
instead of
|
||||
```xml
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" some-instance:schemaLocation="some-url"></map>
|
||||
```
|
||||
|
||||
## Parse xml: keep raw attribute namespace
|
||||
Given a sample.xml file of:
|
||||
```xml
|
||||
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url">
|
||||
</map>
|
||||
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq -p=xml -o=xml --xml-keep-namespace --xml-raw-token '.' sample.xml
|
||||
```
|
||||
will output
|
||||
```xml
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" some-instance:schemaLocation="some-url"></map>
|
||||
```
|
||||
|
||||
instead of
|
||||
```xml
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xsi="some-instance" schemaLocation="some-url"></map>
|
||||
```
|
||||
|
||||
instead of
|
||||
```xml
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url"></map>
|
||||
```
|
||||
|
||||
## Parse xml: keep raw attribute namespace
|
||||
Defaults to true
|
||||
|
||||
Given a sample.xml file of:
|
||||
```xml
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url"></map>
|
||||
|
||||
```
|
||||
then
|
||||
```bash
|
||||
yq -p=xml -o=xml --xml-raw-token=false '.' sample.xml
|
||||
```
|
||||
will output
|
||||
```xml
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" some-instance:schemaLocation="some-url"></map>
|
||||
```
|
||||
|
||||
instead of
|
||||
```xml
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url"></map>
|
||||
```
|
||||
|
||||
## Encode xml: simple
|
||||
Given a sample.yml file of:
|
||||
```yaml
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var unsafeChars = regexp.MustCompile(`[^\w@%+=:,./-]`)
|
||||
|
||||
type shEncoder struct {
|
||||
quoteAll bool
|
||||
}
|
||||
|
||||
func NewShEncoder() Encoder {
|
||||
return &shEncoder{false}
|
||||
}
|
||||
|
||||
func (e *shEncoder) CanHandleAliases() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (e *shEncoder) PrintDocumentSeparator(writer io.Writer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *shEncoder) PrintLeadingContent(writer io.Writer, content string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *shEncoder) Encode(writer io.Writer, originalNode *yaml.Node) error {
|
||||
node := unwrapDoc(originalNode)
|
||||
if guessTagFromCustomType(node) != "!!str" {
|
||||
return fmt.Errorf("cannot encode %v as URI, can only operate on strings. Please first pipe through another encoding operator to convert the value to a string", node.Tag)
|
||||
}
|
||||
|
||||
return writeString(writer, e.encode(originalNode.Value))
|
||||
}
|
||||
|
||||
// put any (shell-unsafe) characters into a single-quoted block, close the block lazily
|
||||
func (e *shEncoder) encode(input string) string {
|
||||
const quote = '\''
|
||||
var inQuoteBlock = false
|
||||
var encoded strings.Builder
|
||||
encoded.Grow(len(input))
|
||||
|
||||
for _, ir := range input {
|
||||
// open or close a single-quote block
|
||||
if ir == quote {
|
||||
if inQuoteBlock {
|
||||
// get out of a quote block for an input quote
|
||||
encoded.WriteRune(quote)
|
||||
inQuoteBlock = !inQuoteBlock
|
||||
}
|
||||
// escape the quote with a backslash
|
||||
encoded.WriteRune('\\')
|
||||
} else {
|
||||
if e.shouldQuote(ir) && !inQuoteBlock {
|
||||
// start a quote block for any (unsafe) characters
|
||||
encoded.WriteRune(quote)
|
||||
inQuoteBlock = !inQuoteBlock
|
||||
}
|
||||
}
|
||||
// pass on the input character
|
||||
encoded.WriteRune(ir)
|
||||
}
|
||||
// close any pending quote block
|
||||
if inQuoteBlock {
|
||||
encoded.WriteRune(quote)
|
||||
}
|
||||
return encoded.String()
|
||||
}
|
||||
|
||||
func (e *shEncoder) shouldQuote(ir rune) bool {
|
||||
return e.quoteAll || unsafeChars.MatchString(string(ir))
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type uriEncoder struct {
|
||||
}
|
||||
|
||||
func NewUriEncoder() Encoder {
|
||||
return &uriEncoder{}
|
||||
}
|
||||
|
||||
func (e *uriEncoder) CanHandleAliases() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (e *uriEncoder) PrintDocumentSeparator(writer io.Writer) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *uriEncoder) PrintLeadingContent(writer io.Writer, content string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *uriEncoder) Encode(writer io.Writer, originalNode *yaml.Node) error {
|
||||
node := unwrapDoc(originalNode)
|
||||
if guessTagFromCustomType(node) != "!!str" {
|
||||
return fmt.Errorf("cannot encode %v as URI, can only operate on strings. Please first pipe through another encoding operator to convert the value to a string", node.Tag)
|
||||
}
|
||||
_, err := writer.Write([]byte(url.QueryEscape(originalNode.Value)))
|
||||
return err
|
||||
}
|
||||
@@ -78,6 +78,14 @@ const roundTripMultiLineJson = `{
|
||||
`
|
||||
|
||||
var jsonScenarios = []formatScenario{
|
||||
{
|
||||
description: "set tags",
|
||||
skipDoc: true,
|
||||
input: "[{}]",
|
||||
expression: `[.. | type]`,
|
||||
scenarioType: "roundtrip-ndjson",
|
||||
expected: "[\"!!seq\",\"!!map\"]\n",
|
||||
},
|
||||
{
|
||||
description: "Parse json: simple",
|
||||
subdescription: "JSON is a subset of yaml, so all you need to do is prettify the output",
|
||||
|
||||
@@ -45,8 +45,11 @@ var participleYqRules = []*participleYqRule{
|
||||
simpleOp("format_datetime", formatDateTimeOpType),
|
||||
simpleOp("now", nowOpType),
|
||||
simpleOp("tz", tzOpType),
|
||||
simpleOp("from_?unix", fromUnixOpType),
|
||||
simpleOp("to_?unix", toUnixOpType),
|
||||
simpleOp("with_dtf", withDtFormatOpType),
|
||||
simpleOp("error", errorOpType),
|
||||
simpleOp("shuffle", shuffleOpType),
|
||||
simpleOp("sortKeys", sortKeysOpType),
|
||||
simpleOp("sort_?keys", sortKeysOpType),
|
||||
|
||||
@@ -78,6 +81,10 @@ var participleYqRules = []*participleYqRule{
|
||||
{"Base64d", `@base64d`, decodeOp(Base64InputFormat), 0},
|
||||
{"Base64", `@base64`, encodeWithIndent(Base64OutputFormat, 0), 0},
|
||||
|
||||
{"Urid", `@urid`, decodeOp(UriInputFormat), 0},
|
||||
{"Uri", `@uri`, encodeWithIndent(UriOutputFormat, 0), 0},
|
||||
{"SH", `@sh`, encodeWithIndent(ShOutputFormat, 0), 0},
|
||||
|
||||
{"LoadXML", `load_?xml|xml_?load`, loadOp(NewXMLDecoder(ConfiguredXMLPreferences), false), 0},
|
||||
|
||||
{"LoadBase64", `load_?base64`, loadOp(NewBase64Decoder(), false), 0},
|
||||
@@ -86,7 +93,7 @@ var participleYqRules = []*participleYqRule{
|
||||
|
||||
{"LoadString", `load_?str|str_?load`, loadOp(nil, true), 0},
|
||||
|
||||
{"LoadYaml", `load`, loadOp(NewYamlDecoder(ConfiguredYamlPreferences), false), 0},
|
||||
{"LoadYaml", `load`, loadOp(NewYamlDecoder(LoadYamlPreferences), false), 0},
|
||||
|
||||
{"SplitDocument", `splitDoc|split_?doc`, opToken(splitDocumentOpType), 0},
|
||||
|
||||
@@ -352,8 +359,12 @@ func nullValue() yqAction {
|
||||
|
||||
func stringValue() yqAction {
|
||||
return func(rawToken lexer.Token) (*token, error) {
|
||||
log.Debug("rawTokenvalue: %v", rawToken.Value)
|
||||
value := unwrap(rawToken.Value)
|
||||
log.Debug("unwrapped: %v", value)
|
||||
value = strings.ReplaceAll(value, "\\\"", "\"")
|
||||
value = strings.ReplaceAll(value, "\\n", "\n")
|
||||
log.Debug("replaced: %v", value)
|
||||
return &token{TokenType: operationToken, Operation: createValueOperation(value, value)}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ var participleLexerScenarios = []participleLexerScenario{
|
||||
TokenType: operationToken,
|
||||
Operation: &Operation{
|
||||
OperationType: valueOpType,
|
||||
Value: 3,
|
||||
Value: int64(3),
|
||||
StringValue: "3",
|
||||
CandidateNode: &CandidateNode{
|
||||
Node: &yaml.Node{
|
||||
@@ -103,7 +103,7 @@ var participleLexerScenarios = []participleLexerScenario{
|
||||
TokenType: operationToken,
|
||||
Operation: &Operation{
|
||||
OperationType: valueOpType,
|
||||
Value: -2,
|
||||
Value: int64(-2),
|
||||
StringValue: "-2",
|
||||
CandidateNode: &CandidateNode{
|
||||
Node: &yaml.Node{
|
||||
@@ -620,6 +620,27 @@ var participleLexerScenarios = []participleLexerScenario{
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
expression: `"string with a\n"`,
|
||||
tokens: []*token{
|
||||
{
|
||||
TokenType: operationToken,
|
||||
Operation: &Operation{
|
||||
OperationType: valueOpType,
|
||||
Value: "string with a\n",
|
||||
StringValue: "string with a\n",
|
||||
Preferences: nil,
|
||||
CandidateNode: &CandidateNode{
|
||||
Node: &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: "!!str",
|
||||
Value: "string with a\n",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
expression: `"string with a \""`,
|
||||
tokens: []*token{
|
||||
|
||||
+12
-4
@@ -53,7 +53,7 @@ var subtractAssignOpType = &operationType{Type: "SUBTRACT_ASSIGN", NumArgs: 2, P
|
||||
|
||||
var assignAttributesOpType = &operationType{Type: "ASSIGN_ATTRIBUTES", NumArgs: 2, Precedence: 40, Handler: assignAttributesOperator}
|
||||
var assignStyleOpType = &operationType{Type: "ASSIGN_STYLE", NumArgs: 2, Precedence: 40, Handler: assignStyleOperator}
|
||||
var assignVariableOpType = &operationType{Type: "ASSIGN_VARIABLE", NumArgs: 2, Precedence: 40, Handler: assignVariableOperator}
|
||||
var assignVariableOpType = &operationType{Type: "ASSIGN_VARIABLE", NumArgs: 2, Precedence: 40, Handler: useWithPipe}
|
||||
var assignTagOpType = &operationType{Type: "ASSIGN_TAG", NumArgs: 2, Precedence: 40, Handler: assignTagOperator}
|
||||
var assignCommentOpType = &operationType{Type: "ASSIGN_COMMENT", NumArgs: 2, Precedence: 40, Handler: assignCommentsOperator}
|
||||
var assignAnchorOpType = &operationType{Type: "ASSIGN_ANCHOR", NumArgs: 2, Precedence: 40, Handler: assignAnchorOperator}
|
||||
@@ -93,6 +93,8 @@ var formatDateTimeOpType = &operationType{Type: "FORMAT_DATE_TIME", NumArgs: 1,
|
||||
var withDtFormatOpType = &operationType{Type: "WITH_DATE_TIME_FORMAT", NumArgs: 1, Precedence: 50, Handler: withDateTimeFormat}
|
||||
var nowOpType = &operationType{Type: "NOW", NumArgs: 0, Precedence: 50, Handler: nowOp}
|
||||
var tzOpType = &operationType{Type: "TIMEZONE", NumArgs: 1, Precedence: 50, Handler: tzOp}
|
||||
var fromUnixOpType = &operationType{Type: "FROM_UNIX", NumArgs: 0, Precedence: 50, Handler: fromUnixOp}
|
||||
var toUnixOpType = &operationType{Type: "TO_UNIX", NumArgs: 0, Precedence: 50, Handler: toUnixOp}
|
||||
|
||||
var encodeOpType = &operationType{Type: "ENCODE", NumArgs: 0, Precedence: 50, Handler: encodeOperator}
|
||||
var decodeOpType = &operationType{Type: "DECODE", NumArgs: 0, Precedence: 50, Handler: decodeOperator}
|
||||
@@ -133,6 +135,7 @@ var explodeOpType = &operationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50,
|
||||
var sortByOpType = &operationType{Type: "SORT_BY", NumArgs: 1, Precedence: 50, Handler: sortByOperator}
|
||||
var reverseOpType = &operationType{Type: "REVERSE", NumArgs: 0, Precedence: 50, Handler: reverseOperator}
|
||||
var sortOpType = &operationType{Type: "SORT", NumArgs: 0, Precedence: 50, Handler: sortOperator}
|
||||
var shuffleOpType = &operationType{Type: "SHUFFLE", NumArgs: 0, Precedence: 50, Handler: shuffleOperator}
|
||||
|
||||
var sortKeysOpType = &operationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: sortKeysOperator}
|
||||
|
||||
@@ -155,6 +158,7 @@ var traverseArrayOpType = &operationType{Type: "TRAVERSE_ARRAY", NumArgs: 2, Pre
|
||||
|
||||
var selfReferenceOpType = &operationType{Type: "SELF", NumArgs: 0, Precedence: 55, Handler: selfOperator}
|
||||
var valueOpType = &operationType{Type: "VALUE", NumArgs: 0, Precedence: 50, Handler: valueOperator}
|
||||
var referenceOpType = &operationType{Type: "REF", NumArgs: 0, Precedence: 50, Handler: referenceOperator}
|
||||
var envOpType = &operationType{Type: "ENV", NumArgs: 0, Precedence: 50, Handler: envOperator}
|
||||
var notOpType = &operationType{Type: "NOT", NumArgs: 0, Precedence: 50, Handler: notOperator}
|
||||
var emptyOpType = &operationType{Type: "EMPTY", Precedence: 50, Handler: emptyOperator}
|
||||
@@ -235,13 +239,13 @@ func guessTagFromCustomType(node *yaml.Node) string {
|
||||
if strings.HasPrefix(node.Tag, "!!") {
|
||||
return node.Tag
|
||||
} else if node.Value == "" {
|
||||
log.Warning("node has no value to guess the type with")
|
||||
log.Debug("guessTagFromCustomType: node has no value to guess the type with")
|
||||
return node.Tag
|
||||
}
|
||||
dataBucket, errorReading := parseSnippet(node.Value)
|
||||
|
||||
if errorReading != nil {
|
||||
log.Warning("could not guess underlying tag type %v", errorReading)
|
||||
log.Debug("guessTagFromCustomType: could not guess underlying tag type %v", errorReading)
|
||||
return node.Tag
|
||||
}
|
||||
guessedTag := unwrapDoc(dataBucket).Tag
|
||||
@@ -268,7 +272,10 @@ func parseSnippet(value string) (*yaml.Node, error) {
|
||||
if len(parsedNode.Node.Content) == 0 {
|
||||
return nil, fmt.Errorf("bad data")
|
||||
}
|
||||
return unwrapDoc(parsedNode.Node), err
|
||||
result := unwrapDoc(parsedNode.Node)
|
||||
result.Line = 0
|
||||
result.Column = 0
|
||||
return result, err
|
||||
}
|
||||
|
||||
func recursiveNodeEqual(lhs *yaml.Node, rhs *yaml.Node) bool {
|
||||
@@ -413,6 +420,7 @@ func footComment(node *yaml.Node) string {
|
||||
}
|
||||
|
||||
func createValueOperation(value interface{}, stringValue string) *Operation {
|
||||
log.Debug("creating value op for string %v", stringValue)
|
||||
var node = createScalarNode(value, stringValue)
|
||||
|
||||
return &Operation{
|
||||
|
||||
+10
-10
@@ -38,8 +38,8 @@ var parseSnippetScenarios = []parseSnippetScenario{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: "!!null",
|
||||
Value: "null",
|
||||
Line: 1,
|
||||
Column: 1,
|
||||
Line: 0,
|
||||
Column: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -48,8 +48,8 @@ var parseSnippetScenarios = []parseSnippetScenario{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: "!!int",
|
||||
Value: "3",
|
||||
Line: 1,
|
||||
Column: 1,
|
||||
Line: 0,
|
||||
Column: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -58,8 +58,8 @@ var parseSnippetScenarios = []parseSnippetScenario{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: "!!str",
|
||||
Value: "cat",
|
||||
Line: 1,
|
||||
Column: 1,
|
||||
Line: 0,
|
||||
Column: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -68,8 +68,8 @@ var parseSnippetScenarios = []parseSnippetScenario{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: "!!float",
|
||||
Value: "3.1",
|
||||
Line: 1,
|
||||
Column: 1,
|
||||
Line: 0,
|
||||
Column: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -78,8 +78,8 @@ var parseSnippetScenarios = []parseSnippetScenario{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: "!!bool",
|
||||
Value: "true",
|
||||
Line: 1,
|
||||
Column: 1,
|
||||
Line: 0,
|
||||
Column: 0,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ var addOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{}`,
|
||||
expression: "(.a + .b) as $x",
|
||||
expression: "(.a + .b) as $x | .",
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{}\n",
|
||||
},
|
||||
@@ -101,6 +101,14 @@ var addOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (doc)::a: ['dog', 'cat']\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Prepend to existing array",
|
||||
document: `a: [dog]`,
|
||||
expression: `.a = ["cat"] + .a`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::a: [cat, dog]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Concatenate to existing array",
|
||||
|
||||
@@ -16,7 +16,7 @@ var alternativeOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
expression: `(.b // "hello") as $x`,
|
||||
expression: `(.b // "hello") as $x | .`,
|
||||
document: `a: bridge`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::a: bridge\n",
|
||||
|
||||
@@ -12,8 +12,7 @@ var specDocument = `- &CENTER { x: 1, y: 2 }
|
||||
|
||||
var expectedSpecResult = "D0, P[4], (!!map)::x: 1\ny: 2\nr: 10\n"
|
||||
|
||||
var simpleArrayRef = `
|
||||
item_value: &item_value
|
||||
var simpleArrayRef = `item_value: &item_value
|
||||
value: true
|
||||
|
||||
thingOne:
|
||||
|
||||
@@ -102,7 +102,7 @@ var booleanOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `[{pet: cat}]`,
|
||||
expression: `any_c(.name == "harry") as $c`,
|
||||
expression: `any_c(.name == "harry") as $c | .`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::[{pet: cat}]\n",
|
||||
},
|
||||
@@ -110,9 +110,17 @@ var booleanOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `[{pet: cat}]`,
|
||||
expression: `all_c(.name == "harry") as $c`,
|
||||
expression: `any_c(.name == "harry") as $c | $c`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::[{pet: cat}]\n",
|
||||
"D0, P[], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `[{pet: cat}]`,
|
||||
expression: `all_c(.name == "harry") as $c | $c`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -185,7 +193,7 @@ var booleanOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{}`,
|
||||
expression: `(.a.b or .c) as $x`,
|
||||
expression: `(.a.b or .c) as $x | .`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{}\n",
|
||||
},
|
||||
@@ -193,7 +201,7 @@ var booleanOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{}`,
|
||||
expression: `(.a.b and .c) as $x`,
|
||||
expression: `(.a.b and .c) as $x | .`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{}\n",
|
||||
},
|
||||
|
||||
@@ -221,7 +221,7 @@ var commentOperatorScenarios = []expressionScenario{
|
||||
document: "# welcome!\n\na: cat # meow\n\n# have a great day",
|
||||
expression: `. | head_comment`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!str)::welcome!\n",
|
||||
"D0, P[], (!!str)::welcome!\n\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -3,7 +3,6 @@ package yqlib
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
@@ -16,7 +15,7 @@ type compareTypePref struct {
|
||||
func compareOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
log.Debugf("-- compareOperator")
|
||||
prefs := expressionNode.Operation.Preferences.(compareTypePref)
|
||||
return crossFunction(d, context.ReadOnlyClone(), expressionNode, compare(prefs), true)
|
||||
return crossFunction(d, context, expressionNode, compare(prefs), true)
|
||||
}
|
||||
|
||||
func compare(prefs compareTypePref) func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
@@ -80,7 +79,7 @@ func compareScalars(context Context, prefs compareTypePref, lhs *yaml.Node, rhs
|
||||
|
||||
isDateTime := lhs.Tag == "!!timestamp"
|
||||
// if the lhs is a string, it might be a timestamp in a custom format.
|
||||
if lhsTag == "!!str" && context.GetDateTimeLayout() != time.RFC3339 {
|
||||
if lhsTag == "!!str" {
|
||||
_, err := parseDateTime(context.GetDateTimeLayout(), lhs.Value)
|
||||
isDateTime = err == nil
|
||||
}
|
||||
@@ -127,6 +126,10 @@ func compareScalars(context Context, prefs compareTypePref, lhs *yaml.Node, rhs
|
||||
return lhs.Value > rhs.Value, nil
|
||||
}
|
||||
return lhs.Value < rhs.Value, nil
|
||||
} else if lhsTag == "!!null" && rhsTag == "!!null" && prefs.OrEqual {
|
||||
return true, nil
|
||||
} else if lhsTag == "!!null" || rhsTag == "!!null" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return false, fmt.Errorf("%v not yet supported for comparison", lhs.Tag)
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"container/list"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
@@ -129,3 +130,69 @@ func tzOp(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode)
|
||||
|
||||
return context.ChildContext(results), nil
|
||||
}
|
||||
|
||||
func parseUnixTime(unixTime string) (time.Time, error) {
|
||||
seconds, err := strconv.ParseFloat(unixTime, 64)
|
||||
|
||||
if err != nil {
|
||||
return time.Now(), err
|
||||
}
|
||||
|
||||
return time.UnixMilli(int64(seconds * 1000)), nil
|
||||
}
|
||||
|
||||
func fromUnixOp(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
|
||||
var results = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
actualTag := guessTagFromCustomType(candidate.Node)
|
||||
|
||||
if actualTag != "!!int" && guessTagFromCustomType(candidate.Node) != "!!float" {
|
||||
return Context{}, fmt.Errorf("from_unix only works on numbers, found %v instead", candidate.Node.Tag)
|
||||
}
|
||||
|
||||
parsedTime, err := parseUnixTime(candidate.Node.Value)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
|
||||
node := &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: "!!timestamp",
|
||||
Value: parsedTime.Format(time.RFC3339),
|
||||
}
|
||||
|
||||
results.PushBack(candidate.CreateReplacement(node))
|
||||
}
|
||||
|
||||
return context.ChildContext(results), nil
|
||||
}
|
||||
|
||||
func toUnixOp(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
|
||||
layout := context.GetDateTimeLayout()
|
||||
|
||||
var results = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
parsedTime, err := parseDateTime(layout, candidate.Node.Value)
|
||||
if err != nil {
|
||||
return Context{}, fmt.Errorf("could not parse datetime of [%v] using layout [%v]: %w", candidate.GetNicePath(), layout, err)
|
||||
}
|
||||
|
||||
node := &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Tag: "!!int",
|
||||
Value: fmt.Sprintf("%v", parsedTime.Unix()),
|
||||
}
|
||||
|
||||
results.PushBack(candidate.CreateReplacement(node))
|
||||
}
|
||||
|
||||
return context.ChildContext(results), nil
|
||||
}
|
||||
|
||||
@@ -39,6 +39,22 @@ var dateTimeOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (doc)::a: cool\nupdated: 2021-05-19T01:02:03Z\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "From Unix",
|
||||
subdescription: "Converts from unix time. Note, you don't have to pipe through the tz operator :)",
|
||||
expression: `1675301929 | from_unix | tz("UTC")`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!timestamp)::2023-02-02T01:38:49Z\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "To Unix",
|
||||
subdescription: "Converts to unix time",
|
||||
expression: `now | to_unix`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!int)::1621386123\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Timezone: from standard RFC3339 format",
|
||||
subdescription: "Returns a new datetime in the specified timezone. Specify standard IANA Time Zone format or 'utc', 'local'. When given a single parameter, this assumes the datetime is in RFC3339 format.",
|
||||
|
||||
@@ -26,6 +26,10 @@ func configureEncoder(format PrinterOutputFormat, indent int) Encoder {
|
||||
return NewXMLEncoder(indent, ConfiguredXMLPreferences)
|
||||
case Base64OutputFormat:
|
||||
return NewBase64Encoder()
|
||||
case UriOutputFormat:
|
||||
return NewUriEncoder()
|
||||
case ShOutputFormat:
|
||||
return NewShEncoder()
|
||||
}
|
||||
panic("invalid encoder")
|
||||
}
|
||||
@@ -113,6 +117,8 @@ func decodeOperator(d *dataTreeNavigator, context Context, expressionNode *Expre
|
||||
decoder = NewCSVObjectDecoder(',')
|
||||
case TSVObjectInputFormat:
|
||||
decoder = NewCSVObjectDecoder('\t')
|
||||
case UriInputFormat:
|
||||
decoder = NewUriDecoder()
|
||||
}
|
||||
|
||||
var results = list.New()
|
||||
|
||||
@@ -241,6 +241,41 @@ var encoderDecoderOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (!!str)::YTogYXBwbGUK\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Encode a string to uri",
|
||||
document: "coolData: this has & special () characters *",
|
||||
expression: ".coolData | @uri",
|
||||
expected: []string{
|
||||
"D0, P[coolData], (!!str)::this+has+%26+special+%28%29+characters+%2A\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Decode a URI to a string",
|
||||
document: "this+has+%26+special+%28%29+characters+%2A",
|
||||
expression: "@urid",
|
||||
expected: []string{
|
||||
"D0, P[], (!!str)::this has & special () characters *\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Encode a string to sh",
|
||||
subdescription: "Sh/Bash friendly string",
|
||||
document: "coolData: strings with spaces and a 'quote'",
|
||||
expression: ".coolData | @sh",
|
||||
expected: []string{
|
||||
"D0, P[coolData], (!!str)::strings' with spaces and a '\\'quote\\'\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Encode a string to sh",
|
||||
subdescription: "Watch out for stray '' (empty strings)",
|
||||
document: "coolData: \"'starts, contains more '' and ends with a quote'\"",
|
||||
expression: ".coolData | @sh",
|
||||
expected: []string{
|
||||
"D0, P[coolData], (!!str)::\\'starts,' contains more '\\'\\'' and ends with a quote'\\'\n",
|
||||
},
|
||||
skipDoc: true,
|
||||
},
|
||||
{
|
||||
description: "Decode a base64 encoded string",
|
||||
subdescription: "Decoded data is assumed to be a string.",
|
||||
|
||||
@@ -4,7 +4,7 @@ import "gopkg.in/yaml.v3"
|
||||
|
||||
func equalsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
log.Debugf("-- equalsOperation")
|
||||
return crossFunction(d, context.ReadOnlyClone(), expressionNode, isEquals(false), true)
|
||||
return crossFunction(d, context, expressionNode, isEquals(false), true)
|
||||
}
|
||||
|
||||
func isEquals(flip bool) func(d *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
||||
|
||||
@@ -9,7 +9,14 @@ var equalsOperatorScenarios = []expressionScenario{
|
||||
skipDoc: true,
|
||||
expression: ".a == .b",
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::true\n",
|
||||
"D0, P[a], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
expression: `(.k | length) == 0`,
|
||||
skipDoc: true,
|
||||
expected: []string{
|
||||
"D0, P[k], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -25,7 +32,7 @@ var equalsOperatorScenarios = []expressionScenario{
|
||||
document: `a: cat`,
|
||||
expression: ".b == .a",
|
||||
expected: []string{
|
||||
"D0, P[a], (!!bool)::false\n",
|
||||
"D0, P[b], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -40,7 +47,7 @@ var equalsOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: "{}",
|
||||
expression: "(.a == .b) as $x",
|
||||
expression: "(.a == .b) as $x | .",
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{}\n",
|
||||
},
|
||||
@@ -50,13 +57,13 @@ var equalsOperatorScenarios = []expressionScenario{
|
||||
document: "{}",
|
||||
expression: ".a == .b",
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::true\n",
|
||||
"D0, P[a], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: "{}",
|
||||
expression: "(.a != .b) as $x",
|
||||
expression: "(.a != .b) as $x | .",
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{}\n",
|
||||
},
|
||||
|
||||
@@ -16,7 +16,7 @@ var hasOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `a: hello`,
|
||||
expression: `has(.b) as $c`,
|
||||
expression: `has(.b) as $c | .`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::a: hello\n",
|
||||
},
|
||||
|
||||
@@ -9,6 +9,13 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var LoadYamlPreferences = YamlPreferences{
|
||||
LeadingContentPreProcessing: false,
|
||||
PrintDocSeparators: true,
|
||||
UnwrapScalar: true,
|
||||
EvaluateTogether: false,
|
||||
}
|
||||
|
||||
type loadPrefs struct {
|
||||
loadAsString bool
|
||||
decoder Decoder
|
||||
@@ -43,7 +50,8 @@ func loadYaml(filename string, decoder Decoder) (*CandidateNode, error) {
|
||||
// return null candidate
|
||||
return &CandidateNode{Node: &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!null"}}, nil
|
||||
} else if documents.Len() == 1 {
|
||||
return documents.Front().Value.(*CandidateNode), nil
|
||||
candidate := documents.Front().Value.(*CandidateNode)
|
||||
return candidate, nil
|
||||
|
||||
} else {
|
||||
sequenceNode := &CandidateNode{Node: &yaml.Node{Kind: yaml.SequenceNode}}
|
||||
|
||||
@@ -13,6 +13,15 @@ var loadScenarios = []expressionScenario{
|
||||
"D0, P[], (doc)::# comment\n\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Load file with a header comment into an array",
|
||||
document: `- "../../examples/small.yaml"`,
|
||||
expression: `.[] |= load(.)`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::- # comment\n # about things\n a: cat\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "Load empty file with no comment",
|
||||
|
||||
@@ -60,8 +60,14 @@ func multiply(preferences multiplyPreferences) func(d *dataTreeNavigator, contex
|
||||
log.Debugf("Multiplying LHS: %v", lhs.Node.Tag)
|
||||
log.Debugf("- RHS: %v", rhs.Node.Tag)
|
||||
|
||||
if lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode ||
|
||||
(lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) {
|
||||
if rhs.Node.Tag == "!!null" {
|
||||
return lhs.Copy()
|
||||
}
|
||||
|
||||
if (lhs.Node.Kind == yaml.MappingNode && rhs.Node.Kind == yaml.MappingNode) ||
|
||||
(lhs.Node.Tag == "!!null" && rhs.Node.Kind == yaml.MappingNode) ||
|
||||
(lhs.Node.Kind == yaml.SequenceNode && rhs.Node.Kind == yaml.SequenceNode) ||
|
||||
(lhs.Node.Tag == "!!null" && rhs.Node.Kind == yaml.SequenceNode) {
|
||||
var newBlank = CandidateNode{}
|
||||
err := copier.CopyWithOption(&newBlank, lhs, copier.Option{IgnoreEmpty: true, DeepCopy: true})
|
||||
if err != nil {
|
||||
@@ -189,7 +195,7 @@ func applyAssignment(d *dataTreeNavigator, context Context, pathIndexToStartFrom
|
||||
} else {
|
||||
log.Debugf("merge - assignmentOp := &Operation{OperationType: assignAttributesOpType}")
|
||||
}
|
||||
rhsOp := &Operation{OperationType: valueOpType, CandidateNode: rhs}
|
||||
rhsOp := &Operation{OperationType: referenceOpType, CandidateNode: rhs}
|
||||
|
||||
assignmentOpNode := &ExpressionNode{
|
||||
Operation: assignmentOp,
|
||||
|
||||
@@ -108,7 +108,7 @@ var multiplyOperatorScenarios = []expressionScenario{
|
||||
document2: docNoComments,
|
||||
expression: `select(fi == 0) * select(fi == 1)`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::# here\na: apple\nb: banana\n",
|
||||
"D0, P[], (!!map)::# here\n\na: apple\nb: banana\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -126,7 +126,7 @@ var multiplyOperatorScenarios = []expressionScenario{
|
||||
document2: docWithHeader,
|
||||
expression: `select(fi == 0) * select(fi == 1)`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::# here\nb: banana\na: apple\n",
|
||||
"D0, P[], (!!map)::# here\n\nb: banana\na: apple\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -579,6 +579,41 @@ var multiplyOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (doc)::a: {a: apple is included, b: cool.}\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Merging a null with a map",
|
||||
expression: `null * {"some": "thing"}`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::some: thing\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Merging a map with null",
|
||||
expression: `{"some": "thing"} * null`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!map)::some: thing\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Merging an null with an array",
|
||||
expression: `null * ["some"]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- some\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Merging an array with null",
|
||||
expression: `["some"] * null`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- some\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
expression: `null * null`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!null)::null\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestMultiplyOperatorScenarios(t *testing.T) {
|
||||
|
||||
@@ -81,7 +81,7 @@ func setPathOperator(d *dataTreeNavigator, context Context, expressionNode *Expr
|
||||
return Context{}, fmt.Errorf("SETPATH: expected single value on RHS but found %v", targetContextValue.MatchingNodes.Len())
|
||||
}
|
||||
|
||||
rhsOp := &Operation{OperationType: valueOpType, CandidateNode: targetContextValue.MatchingNodes.Front().Value.(*CandidateNode)}
|
||||
rhsOp := &Operation{OperationType: referenceOpType, CandidateNode: targetContextValue.MatchingNodes.Front().Value.(*CandidateNode)}
|
||||
|
||||
assignmentOpNode := &ExpressionNode{
|
||||
Operation: assignmentOp,
|
||||
|
||||
@@ -2,9 +2,9 @@ package yqlib
|
||||
|
||||
func pipeOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
|
||||
//lhs may update the variable context, we should pass that into the RHS
|
||||
// BUT we still return the original context back (see jq)
|
||||
// https://stedolan.github.io/jq/manual/#Variable/SymbolicBindingOperator:...as$identifier|...
|
||||
if expressionNode.LHS.Operation.OperationType == assignVariableOpType {
|
||||
return variableLoop(d, context, expressionNode)
|
||||
}
|
||||
|
||||
lhs, err := d.GetMatchingNodes(context, expressionNode.LHS)
|
||||
if err != nil {
|
||||
|
||||
@@ -76,9 +76,10 @@ var selectOperatorScenarios = []expressionScenario{
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `[{animal: cat, legs: {cool: true}}, {animal: fish}]`,
|
||||
expression: `(.[] | select(.legs.cool == true).canWalk) = true | (.[] | .alive.things) = "yes"`,
|
||||
description: "select does not update the map",
|
||||
skipDoc: true,
|
||||
document: `[{animal: cat, legs: {cool: true}}, {animal: fish}]`,
|
||||
expression: `(.[] | select(.legs.cool == true).canWalk) = true | (.[] | .alive.things) = "yes"`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::[{animal: cat, legs: {cool: true}, canWalk: true, alive: {things: yes}}, {animal: fish, alive: {things: yes}}]\n",
|
||||
},
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
package yqlib
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
|
||||
yaml "gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func shuffleOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
|
||||
// ignore CWE-338 gosec issue of not using crypto/rand
|
||||
// this is just to shuffle an array rather generating a
|
||||
// secret or something that needs proper rand.
|
||||
myRand := rand.New(rand.NewSource(Now().UnixNano())) // #nosec
|
||||
|
||||
results := list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
candidate := el.Value.(*CandidateNode)
|
||||
|
||||
candidateNode := unwrapDoc(candidate.Node)
|
||||
|
||||
if candidateNode.Kind != yaml.SequenceNode {
|
||||
return context, fmt.Errorf("node at path [%v] is not an array (it's a %v)", candidate.GetNicePath(), candidate.GetNiceTag())
|
||||
}
|
||||
|
||||
result := deepClone(candidateNode)
|
||||
|
||||
a := result.Content
|
||||
|
||||
myRand.Shuffle(len(a), func(i, j int) { a[i], a[j] = a[j], a[i] })
|
||||
results.PushBack(candidate.CreateReplacement(result))
|
||||
}
|
||||
return context.ChildContext(results), nil
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package yqlib
|
||||
|
||||
import "testing"
|
||||
|
||||
var shuffleOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
description: "Shuffle array",
|
||||
document: "[1, 2, 3, 4, 5]",
|
||||
expression: `shuffle`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::[5, 2, 4, 1, 3]\n",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
description: "Shuffle array in place",
|
||||
document: "cool: [1, 2, 3, 4, 5]",
|
||||
expression: `.cool |= shuffle`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::cool: [5, 2, 4, 1, 3]\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestShuffleByOperatorScenarios(t *testing.T) {
|
||||
for _, tt := range shuffleOperatorScenarios {
|
||||
testScenario(t, &tt)
|
||||
}
|
||||
documentOperatorScenarios(t, "shuffle", shuffleOperatorScenarios)
|
||||
}
|
||||
@@ -48,6 +48,8 @@ func sliceArrayOperator(d *dataTreeNavigator, context Context, expressionNode *E
|
||||
relativeSecondNumber := secondNumber
|
||||
if relativeSecondNumber < 0 {
|
||||
relativeSecondNumber = len(original.Content) + secondNumber
|
||||
} else if relativeSecondNumber > len(original.Content) {
|
||||
relativeSecondNumber = len(original.Content)
|
||||
}
|
||||
|
||||
log.Debug("calculateIndicesToTraverse: slice from %v to %v", relativeFirstNumber, relativeSecondNumber)
|
||||
|
||||
@@ -55,6 +55,24 @@ var sliceArrayScenarios = []expressionScenario{
|
||||
"D0, P[1], (!!seq)::- banana\n- grape\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "second index beyond array clamps",
|
||||
document: `[cat]`,
|
||||
expression: `.[:3]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- cat\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
description: "first index beyond array returns nothing",
|
||||
document: `[cat]`,
|
||||
expression: `.[3:]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::[]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `[[cat, dog, frog, cow], [apple, banana, grape, mango]]`,
|
||||
|
||||
+52
-28
@@ -42,18 +42,7 @@ func sortByOperator(d *dataTreeNavigator, context Context, expressionNode *Expre
|
||||
return Context{}, err
|
||||
}
|
||||
|
||||
nodeToCompare := &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!null"}
|
||||
if compareContext.MatchingNodes.Len() > 0 {
|
||||
nodeToCompare = compareContext.MatchingNodes.Front().Value.(*CandidateNode).Node
|
||||
}
|
||||
|
||||
log.Debug("going to compare %v by %v", NodeToString(candidate.CreateReplacement(originalNode)), NodeToString(candidate.CreateReplacement(nodeToCompare)))
|
||||
|
||||
sortableArray[i] = sortableNode{Node: originalNode, NodeToCompare: nodeToCompare, dateTimeLayout: context.GetDateTimeLayout()}
|
||||
|
||||
if nodeToCompare.Kind != yaml.ScalarNode {
|
||||
return Context{}, fmt.Errorf("sort only works for scalars, got %v", nodeToCompare.Tag)
|
||||
}
|
||||
sortableArray[i] = sortableNode{Node: originalNode, CompareContext: compareContext, dateTimeLayout: context.GetDateTimeLayout()}
|
||||
|
||||
}
|
||||
|
||||
@@ -72,7 +61,7 @@ func sortByOperator(d *dataTreeNavigator, context Context, expressionNode *Expre
|
||||
|
||||
type sortableNode struct {
|
||||
Node *yaml.Node
|
||||
NodeToCompare *yaml.Node
|
||||
CompareContext Context
|
||||
dateTimeLayout string
|
||||
}
|
||||
|
||||
@@ -82,9 +71,28 @@ func (a sortableNodeArray) Len() int { return len(a) }
|
||||
func (a sortableNodeArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
func (a sortableNodeArray) Less(i, j int) bool {
|
||||
lhs := a[i].NodeToCompare
|
||||
rhs := a[j].NodeToCompare
|
||||
lhsContext := a[i].CompareContext
|
||||
rhsContext := a[j].CompareContext
|
||||
|
||||
rhsEl := rhsContext.MatchingNodes.Front()
|
||||
for lhsEl := lhsContext.MatchingNodes.Front(); lhsEl != nil && rhsEl != nil; lhsEl = lhsEl.Next() {
|
||||
lhs := lhsEl.Value.(*CandidateNode)
|
||||
rhs := rhsEl.Value.(*CandidateNode)
|
||||
|
||||
result := a.compare(lhs.Node, rhs.Node, a[i].dateTimeLayout)
|
||||
|
||||
if result < 0 {
|
||||
return true
|
||||
} else if result > 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
rhsEl = rhsEl.Next()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (a sortableNodeArray) compare(lhs *yaml.Node, rhs *yaml.Node, dateTimeLayout string) int {
|
||||
lhsTag := lhs.Tag
|
||||
rhsTag := rhs.Tag
|
||||
|
||||
@@ -99,7 +107,7 @@ func (a sortableNodeArray) Less(i, j int) bool {
|
||||
}
|
||||
|
||||
isDateTime := lhsTag == "!!timestamp" && rhsTag == "!!timestamp"
|
||||
layout := a[i].dateTimeLayout
|
||||
layout := dateTimeLayout
|
||||
// if the lhs is a string, it might be a timestamp in a custom format.
|
||||
if lhsTag == "!!str" && layout != time.RFC3339 {
|
||||
_, errLhs := parseDateTime(layout, lhs.Value)
|
||||
@@ -108,13 +116,13 @@ func (a sortableNodeArray) Less(i, j int) bool {
|
||||
}
|
||||
|
||||
if lhsTag == "!!null" && rhsTag != "!!null" {
|
||||
return true
|
||||
return -1
|
||||
} else if lhsTag != "!!null" && rhsTag == "!!null" {
|
||||
return false
|
||||
return 1
|
||||
} else if lhsTag == "!!bool" && rhsTag != "!!bool" {
|
||||
return true
|
||||
return -1
|
||||
} else if lhsTag != "!!bool" && rhsTag == "!!bool" {
|
||||
return false
|
||||
return 1
|
||||
} else if lhsTag == "!!bool" && rhsTag == "!!bool" {
|
||||
lhsTruthy, err := isTruthyNode(lhs)
|
||||
if err != nil {
|
||||
@@ -125,20 +133,30 @@ func (a sortableNodeArray) Less(i, j int) bool {
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("could not parse %v as boolean: %w", rhs.Value, err))
|
||||
}
|
||||
|
||||
return !lhsTruthy && rhsTruthy
|
||||
if lhsTruthy == rhsTruthy {
|
||||
return 0
|
||||
} else if lhsTruthy {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
} else if isDateTime {
|
||||
lhsTime, err := parseDateTime(layout, lhs.Value)
|
||||
if err != nil {
|
||||
log.Warningf("Could not parse time %v with layout %v for sort, sorting by string instead: %w", lhs.Value, layout, err)
|
||||
return strings.Compare(lhs.Value, rhs.Value) < 0
|
||||
return strings.Compare(lhs.Value, rhs.Value)
|
||||
}
|
||||
rhsTime, err := parseDateTime(layout, rhs.Value)
|
||||
if err != nil {
|
||||
log.Warningf("Could not parse time %v with layout %v for sort, sorting by string instead: %w", rhs.Value, layout, err)
|
||||
return strings.Compare(lhs.Value, rhs.Value) < 0
|
||||
return strings.Compare(lhs.Value, rhs.Value)
|
||||
}
|
||||
return lhsTime.Before(rhsTime)
|
||||
if lhsTime.Equal(rhsTime) {
|
||||
return 0
|
||||
} else if lhsTime.Before(rhsTime) {
|
||||
return -1
|
||||
}
|
||||
|
||||
return 1
|
||||
} else if lhsTag == "!!int" && rhsTag == "!!int" {
|
||||
_, lhsNum, err := parseInt64(lhs.Value)
|
||||
if err != nil {
|
||||
@@ -148,7 +166,7 @@ func (a sortableNodeArray) Less(i, j int) bool {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return lhsNum < rhsNum
|
||||
return int(lhsNum - rhsNum)
|
||||
} else if (lhsTag == "!!int" || lhsTag == "!!float") && (rhsTag == "!!int" || rhsTag == "!!float") {
|
||||
lhsNum, err := strconv.ParseFloat(lhs.Value, 64)
|
||||
if err != nil {
|
||||
@@ -158,8 +176,14 @@ func (a sortableNodeArray) Less(i, j int) bool {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return lhsNum < rhsNum
|
||||
if lhsNum == rhsNum {
|
||||
return 0
|
||||
} else if lhsNum < rhsNum {
|
||||
return -1
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
return strings.Compare(lhs.Value, rhs.Value) < 0
|
||||
return strings.Compare(lhs.Value, rhs.Value)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,32 @@ var sortByOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (!!seq)::[{a: apple}, {a: banana}, {a: cat}]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Sort by multiple fields",
|
||||
document: "[{a: dog},{a: cat, b: banana},{a: cat, b: apple}]",
|
||||
expression: `sort_by(.a, .b)`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::[{a: cat, b: apple}, {a: cat, b: banana}, {a: dog}]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Sort by multiple fields",
|
||||
skipDoc: true,
|
||||
document: "[{a: dog, b: good},{a: cat, c: things},{a: cat, b: apple}]",
|
||||
expression: `sort_by(.a, .b)`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::[{a: cat, c: things}, {a: cat, b: apple}, {a: dog, b: good}]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Sort by multiple fields",
|
||||
skipDoc: true,
|
||||
document: "[{a: dog, b: 0.1},{a: cat, b: 0.01},{a: cat, b: 0.001}]",
|
||||
expression: `sort_by(.a, .b)`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::[{a: cat, b: 0.001}, {a: cat, b: 0.01}, {a: dog, b: 0.1}]\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Sort descending by string field",
|
||||
subdescription: "Use sort with reverse to sort in descending order.",
|
||||
|
||||
@@ -432,6 +432,7 @@ func split(value string, spltStr string) *yaml.Node {
|
||||
var contents []*yaml.Node
|
||||
|
||||
if value != "" {
|
||||
log.Debug("going to spltStr[%v]", spltStr)
|
||||
var newStrings = strings.Split(value, spltStr)
|
||||
contents = make([]*yaml.Node, len(newStrings))
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ var subtractOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{}`,
|
||||
expression: "(.a - .b) as $x",
|
||||
expression: "(.a - .b) as $x | .",
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{}\n",
|
||||
},
|
||||
|
||||
@@ -39,7 +39,7 @@ func traverse(context Context, matchingNode *CandidateNode, operation *Operation
|
||||
log.Debug("Traversing %v", NodeToString(matchingNode))
|
||||
value := matchingNode.Node
|
||||
|
||||
if value.Tag == "!!null" && operation.Value != "[]" {
|
||||
if value.Tag == "!!null" && operation.Value != "[]" && !context.DontAutoCreate {
|
||||
log.Debugf("Guessing kind")
|
||||
// we must have added this automatically, lets guess what it should be now
|
||||
switch operation.Value.(type) {
|
||||
|
||||
@@ -4,8 +4,7 @@ import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
var mergeDocSample = `
|
||||
foo: &foo
|
||||
var mergeDocSample = `foo: &foo
|
||||
a: foo_a
|
||||
thing: foo_thing
|
||||
c: foo_c
|
||||
@@ -152,7 +151,7 @@ var traversePathOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `c: dog`,
|
||||
expression: `.[.a.b] as $x`,
|
||||
expression: `.[.a.b] as $x | .`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::c: dog\n",
|
||||
},
|
||||
|
||||
@@ -8,7 +8,7 @@ var unionOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: "{}",
|
||||
expression: `(.a, .b.c) as $x`,
|
||||
expression: `(.a, .b.c) as $x | .`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{}\n",
|
||||
},
|
||||
|
||||
@@ -1,6 +1,30 @@
|
||||
package yqlib
|
||||
|
||||
import "container/list"
|
||||
|
||||
func referenceOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
return context.SingleChildContext(expressionNode.Operation.CandidateNode), nil
|
||||
}
|
||||
|
||||
func valueOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
log.Debug("value = %v", expressionNode.Operation.CandidateNode.Node.Value)
|
||||
return context.SingleChildContext(expressionNode.Operation.CandidateNode), nil
|
||||
if context.MatchingNodes.Len() == 0 {
|
||||
clone, err := expressionNode.Operation.CandidateNode.Copy()
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
return context.SingleChildContext(clone), nil
|
||||
}
|
||||
|
||||
var results = list.New()
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
clone, err := expressionNode.Operation.CandidateNode.Copy()
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
results.PushBack(clone)
|
||||
}
|
||||
|
||||
return context.ChildContext(results), nil
|
||||
}
|
||||
|
||||
@@ -12,6 +12,28 @@ var valueOperatorScenarios = []expressionScenario{
|
||||
"D0, P[], (!!int)::1\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `[1,2,3]`,
|
||||
expression: `.[] | "foo"`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!str)::foo\n",
|
||||
"D0, P[], (!!str)::foo\n",
|
||||
"D0, P[], (!!str)::foo\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: `[1,2,3]`,
|
||||
expression: `[.[] | "foo"] | .[0] = "cat"`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- cat\n- foo\n- foo\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
expression: `"foo"`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!str)::foo\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
document: ``,
|
||||
expression: `0x9f`,
|
||||
|
||||
@@ -19,24 +19,81 @@ type assignVarPreferences struct {
|
||||
IsReference bool
|
||||
}
|
||||
|
||||
func assignVariableOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
||||
lhs, err := d.GetMatchingNodes(context.ReadOnlyClone(), expressionNode.LHS)
|
||||
func useWithPipe(d *dataTreeNavigator, context Context, originalExp *ExpressionNode) (Context, error) {
|
||||
return Context{}, fmt.Errorf("must use variable with a pipe, e.g. `exp as $x | ...`")
|
||||
}
|
||||
|
||||
// variables are like loops in jq
|
||||
// https://stedolan.github.io/jq/manual/#Variable
|
||||
func variableLoop(d *dataTreeNavigator, context Context, originalExp *ExpressionNode) (Context, error) {
|
||||
log.Debug("variable loop!")
|
||||
results := list.New()
|
||||
var evaluateAllTogether = true
|
||||
for matchEl := context.MatchingNodes.Front(); matchEl != nil; matchEl = matchEl.Next() {
|
||||
evaluateAllTogether = evaluateAllTogether && matchEl.Value.(*CandidateNode).EvaluateTogether
|
||||
if !evaluateAllTogether {
|
||||
break
|
||||
}
|
||||
}
|
||||
if evaluateAllTogether {
|
||||
return variableLoopSingleChild(d, context, originalExp)
|
||||
}
|
||||
|
||||
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
result, err := variableLoopSingleChild(d, context.SingleChildContext(el.Value.(*CandidateNode)), originalExp)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
results.PushBackList(result.MatchingNodes)
|
||||
}
|
||||
return context.ChildContext(results), nil
|
||||
}
|
||||
|
||||
func variableLoopSingleChild(d *dataTreeNavigator, context Context, originalExp *ExpressionNode) (Context, error) {
|
||||
|
||||
variableExp := originalExp.LHS
|
||||
lhs, err := d.GetMatchingNodes(context.ReadOnlyClone(), variableExp.LHS)
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
if expressionNode.RHS.Operation.OperationType.Type != "GET_VARIABLE" {
|
||||
if variableExp.RHS.Operation.OperationType.Type != "GET_VARIABLE" {
|
||||
return Context{}, fmt.Errorf("RHS of 'as' operator must be a variable name e.g. $foo")
|
||||
}
|
||||
variableName := expressionNode.RHS.Operation.StringValue
|
||||
variableName := variableExp.RHS.Operation.StringValue
|
||||
|
||||
prefs := expressionNode.Operation.Preferences.(assignVarPreferences)
|
||||
prefs := variableExp.Operation.Preferences.(assignVarPreferences)
|
||||
|
||||
var variableValue *list.List
|
||||
if prefs.IsReference {
|
||||
variableValue = lhs.MatchingNodes
|
||||
} else {
|
||||
variableValue = lhs.DeepClone().MatchingNodes
|
||||
results := list.New()
|
||||
|
||||
// now we loop over lhs, set variable to each result and calculate originalExp.Rhs
|
||||
for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||||
log.Debug("PROCESSING VARIABLE: ", NodeToString(el.Value.(*CandidateNode)))
|
||||
var variableValue = list.New()
|
||||
if prefs.IsReference {
|
||||
variableValue.PushBack(el.Value)
|
||||
} else {
|
||||
candidateCopy, err := el.Value.(*CandidateNode).Copy()
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
variableValue.PushBack(candidateCopy)
|
||||
}
|
||||
newContext := context.ChildContext(context.MatchingNodes)
|
||||
newContext.SetVariable(variableName, variableValue)
|
||||
|
||||
rhs, err := d.GetMatchingNodes(newContext, originalExp.RHS)
|
||||
log.Debug("PROCESSING VARIABLE DONE, got back: ", rhs.MatchingNodes.Len())
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
results.PushBackList(rhs.MatchingNodes)
|
||||
}
|
||||
context.SetVariable(variableName, variableValue)
|
||||
return context, nil
|
||||
|
||||
// if there is no LHS - then I guess we just calculate originalExp.Rhs
|
||||
if lhs.MatchingNodes.Len() == 0 {
|
||||
return d.GetMatchingNodes(context, originalExp.RHS)
|
||||
}
|
||||
|
||||
return context.ChildContext(results), nil
|
||||
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ var variableOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `{}`,
|
||||
expression: `.a.b as $foo`,
|
||||
expression: `.a.b as $foo | .`,
|
||||
expected: []string{
|
||||
"D0, P[], (doc)::{}\n",
|
||||
},
|
||||
@@ -16,7 +16,7 @@ var variableOperatorScenarios = []expressionScenario{
|
||||
{
|
||||
document: "a: [cat]",
|
||||
skipDoc: true,
|
||||
expression: "(.[] | {.name: .}) as $item",
|
||||
expression: "(.[] | {.name: .}) as $item | .",
|
||||
expectedError: `cannot index array with 'name' (strconv.ParseInt: parsing "name": invalid syntax)`,
|
||||
},
|
||||
{
|
||||
@@ -36,6 +36,22 @@ var variableOperatorScenarios = []expressionScenario{
|
||||
"D0, P[1], (!!str)::dog\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `[1, 2]`,
|
||||
expression: `.[] | . as $f | select($f == 2)`,
|
||||
expected: []string{
|
||||
"D0, P[1], (!!int)::2\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: `[1, 2]`,
|
||||
expression: `[.[] | . as $f | $f + 1]`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!seq)::- 2\n- 3\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Using variables as a lookup",
|
||||
subdescription: "Example taken from [jq](https://stedolan.github.io/jq/manual/#Variable/SymbolicBindingOperator:...as$identifier|...)",
|
||||
|
||||
@@ -41,9 +41,9 @@ func compoundAssignFunction(d *dataTreeNavigator, context Context, expressionNod
|
||||
if err != nil {
|
||||
return Context{}, err
|
||||
}
|
||||
valueCopyExp := &ExpressionNode{Operation: &Operation{OperationType: valueOpType, CandidateNode: clone}}
|
||||
valueCopyExp := &ExpressionNode{Operation: &Operation{OperationType: referenceOpType, CandidateNode: clone}}
|
||||
|
||||
valueExpression := &ExpressionNode{Operation: &Operation{OperationType: valueOpType, CandidateNode: candidate}}
|
||||
valueExpression := &ExpressionNode{Operation: &Operation{OperationType: referenceOpType, CandidateNode: candidate}}
|
||||
|
||||
assignmentOpNode := &ExpressionNode{Operation: assignmentOp, LHS: valueExpression, RHS: calculation(valueCopyExp, expressionNode.RHS)}
|
||||
|
||||
|
||||
@@ -14,6 +14,20 @@ var compareOperatorScenarios = []expressionScenario{
|
||||
"D0, P[a], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
expression: "(.k | length) >= 0",
|
||||
expected: []string{
|
||||
"D0, P[k], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
expression: `"2022-01-30T15:53:09Z" > "2020-01-30T15:53:09Z"`,
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
document: "a: 5\nb: 4",
|
||||
@@ -285,28 +299,28 @@ var compareOperatorScenarios = []expressionScenario{
|
||||
description: "Both sides are null: > is false",
|
||||
expression: ".a > .b",
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::false\n",
|
||||
"D0, P[a], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
expression: ".a < .b",
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::false\n",
|
||||
"D0, P[a], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Both sides are null: >= is true",
|
||||
expression: ".a >= .b",
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::true\n",
|
||||
"D0, P[a], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
expression: ".a <= .b",
|
||||
expected: []string{
|
||||
"D0, P[], (!!bool)::true\n",
|
||||
"D0, P[a], (!!bool)::true\n",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -350,7 +364,7 @@ var compareOperatorScenarios = []expressionScenario{
|
||||
document: `a: 5`,
|
||||
expression: ".b <= .a",
|
||||
expected: []string{
|
||||
"D0, P[a], (!!bool)::false\n",
|
||||
"D0, P[b], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -358,7 +372,7 @@ var compareOperatorScenarios = []expressionScenario{
|
||||
document: `a: 5`,
|
||||
expression: ".b < .a",
|
||||
expected: []string{
|
||||
"D0, P[a], (!!bool)::false\n",
|
||||
"D0, P[b], (!!bool)::false\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -27,6 +27,8 @@ const (
|
||||
TSVOutputFormat
|
||||
XMLOutputFormat
|
||||
Base64OutputFormat
|
||||
UriOutputFormat
|
||||
ShOutputFormat
|
||||
)
|
||||
|
||||
func OutputFormatFromString(format string) (PrinterOutputFormat, error) {
|
||||
|
||||
@@ -34,6 +34,10 @@ func (w *writeInPlaceHandlerImpl) CreateTempFile() (*os.File, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = changeOwner(info, file); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debug("WriteInPlaceHandler: writing to tempfile: %v", file.Name())
|
||||
w.tempFile = file
|
||||
return file, err
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ func NewDefaultXmlPreferences() XmlPreferences {
|
||||
ContentName: "+content",
|
||||
StrictMode: false,
|
||||
KeepNamespace: true,
|
||||
UseRawToken: false,
|
||||
UseRawToken: true,
|
||||
ProcInstPrefix: "+p_",
|
||||
DirectiveName: "+directive",
|
||||
SkipProcInst: false,
|
||||
|
||||
+87
-18
@@ -157,17 +157,15 @@ const expectedXMLWithComments = `<!--
|
||||
</cat><!-- below_cat -->
|
||||
`
|
||||
|
||||
const inputXMLWithNamespacedAttr = `
|
||||
<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url">
|
||||
</map>
|
||||
const inputXMLWithNamespacedAttr = `<?xml version="1.0"?>
|
||||
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url"></map>
|
||||
`
|
||||
|
||||
const expectedYAMLWithNamespacedAttr = `+p_xml: version="1.0"
|
||||
map:
|
||||
+@xmlns: some-namespace
|
||||
+@xmlns:xsi: some-instance
|
||||
+@some-instance:schemaLocation: some-url
|
||||
+@xsi:schemaLocation: some-url
|
||||
`
|
||||
|
||||
const expectedYAMLWithRawNamespacedAttr = `+p_xml: version="1.0"
|
||||
@@ -177,6 +175,13 @@ map:
|
||||
+@xsi:schemaLocation: some-url
|
||||
`
|
||||
|
||||
const expectedYAMLWithoutRawNamespacedAttr = `+p_xml: version="1.0"
|
||||
map:
|
||||
+@xmlns: some-namespace
|
||||
+@xmlns:xsi: some-instance
|
||||
+@some-instance:schemaLocation: some-url
|
||||
`
|
||||
|
||||
const xmlWithCustomDtd = `
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE root [
|
||||
@@ -228,6 +233,37 @@ const expectedXmlWithProcInstAndDirectives = `<?xml version="1.0"?>
|
||||
`
|
||||
|
||||
var xmlScenarios = []formatScenario{
|
||||
{
|
||||
skipDoc: true,
|
||||
input: " <root>value<!-- comment--> </root>",
|
||||
expected: "root: value # comment\n",
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
input: "value<root>value</root>",
|
||||
expectedError: "bad file 'sample.yml': invalid XML: Encountered chardata [value] outside of XML node",
|
||||
scenarioType: "decode-error",
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
input: "<root><!-- comment-->value</root>",
|
||||
expected: "# comment\nroot: value\n",
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
input: "<root> <!-- comment--></root>",
|
||||
expected: "root: # comment\n",
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
input: "<root>value<!-- comment-->anotherValue </root>",
|
||||
expected: "root:\n # comment\n - value\n - anotherValue\n",
|
||||
},
|
||||
{
|
||||
skipDoc: true,
|
||||
input: "<root><cats><cat>quick</cat><cat>soft</cat><!-- kitty_comment--><cat>squishy</cat></cats></root>",
|
||||
expected: "root:\n cats:\n cat:\n - quick\n - soft\n # kitty_comment\n\n - squishy\n",
|
||||
},
|
||||
{
|
||||
description: "Parse xml: simple",
|
||||
subdescription: "Notice how all the values are strings, see the next example on how you can fix that.",
|
||||
@@ -259,6 +295,12 @@ var xmlScenarios = []formatScenario{
|
||||
input: "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<cat legs=\"4\">meow</cat>",
|
||||
expected: "+p_xml: version=\"1.0\" encoding=\"UTF-8\"\ncat:\n +content: meow\n +@legs: \"4\"\n",
|
||||
},
|
||||
{
|
||||
description: "Parse xml: content split between comments/children",
|
||||
subdescription: "Multiple content texts are collected into a sequence.",
|
||||
input: "<root> value <!-- comment-->anotherValue <a>frog</a> cool!</root>",
|
||||
expected: "root:\n +content: # comment\n - value\n - anotherValue\n - cool!\n a: frog\n",
|
||||
},
|
||||
{
|
||||
description: "Parse xml: custom dtd",
|
||||
subdescription: "DTD entities are processed as directives.",
|
||||
@@ -266,6 +308,13 @@ var xmlScenarios = []formatScenario{
|
||||
expected: expectedDtd,
|
||||
scenarioType: "roundtrip",
|
||||
},
|
||||
{
|
||||
description: "Roundtrip with name spaced attributes",
|
||||
skipDoc: true,
|
||||
input: inputXMLWithNamespacedAttr,
|
||||
expected: inputXMLWithNamespacedAttr,
|
||||
scenarioType: "roundtrip",
|
||||
},
|
||||
{
|
||||
description: "Parse xml: skip custom dtd",
|
||||
subdescription: "DTDs are directives, skip over directives to skip DTDs.",
|
||||
@@ -323,19 +372,28 @@ var xmlScenarios = []formatScenario{
|
||||
scenarioType: "decode",
|
||||
},
|
||||
{
|
||||
description: "Parse xml: keep attribute namespace",
|
||||
skipDoc: false,
|
||||
input: inputXMLWithNamespacedAttr,
|
||||
expected: expectedYAMLWithNamespacedAttr,
|
||||
scenarioType: "decode-keep-ns",
|
||||
description: "Parse xml: keep attribute namespace",
|
||||
subdescription: fmt.Sprintf(`Defaults to %v`, ConfiguredXMLPreferences.KeepNamespace),
|
||||
skipDoc: false,
|
||||
input: inputXMLWithNamespacedAttr,
|
||||
expected: expectedYAMLWithNamespacedAttr,
|
||||
scenarioType: "decode-keep-ns",
|
||||
},
|
||||
{
|
||||
description: "Parse xml: keep raw attribute namespace",
|
||||
skipDoc: false,
|
||||
skipDoc: true,
|
||||
input: inputXMLWithNamespacedAttr,
|
||||
expected: expectedYAMLWithRawNamespacedAttr,
|
||||
scenarioType: "decode-raw-token",
|
||||
},
|
||||
{
|
||||
description: "Parse xml: keep raw attribute namespace",
|
||||
subdescription: fmt.Sprintf(`Defaults to %v`, ConfiguredXMLPreferences.UseRawToken),
|
||||
skipDoc: false,
|
||||
input: inputXMLWithNamespacedAttr,
|
||||
expected: expectedYAMLWithoutRawNamespacedAttr,
|
||||
scenarioType: "decode-raw-token-off",
|
||||
},
|
||||
{
|
||||
description: "Encode xml: simple",
|
||||
input: "cat: purrs",
|
||||
@@ -452,10 +510,21 @@ func testXMLScenario(t *testing.T, s formatScenario) {
|
||||
prefs := NewDefaultXmlPreferences()
|
||||
prefs.UseRawToken = true
|
||||
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewXMLDecoder(prefs), NewYamlEncoder(2, false, ConfiguredYamlPreferences)), s.description)
|
||||
case "decode-raw-token-off":
|
||||
prefs := NewDefaultXmlPreferences()
|
||||
prefs.UseRawToken = false
|
||||
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewXMLDecoder(prefs), NewYamlEncoder(2, false, ConfiguredYamlPreferences)), s.description)
|
||||
case "roundtrip-skip-directives":
|
||||
prefs := NewDefaultXmlPreferences()
|
||||
prefs.SkipDirectives = true
|
||||
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewXMLDecoder(prefs), NewXMLEncoder(2, prefs)), s.description)
|
||||
case "decode-error":
|
||||
result, err := processFormatScenario(s, NewXMLDecoder(NewDefaultXmlPreferences()), NewYamlEncoder(2, false, ConfiguredYamlPreferences))
|
||||
if err == nil {
|
||||
t.Errorf("Expected error '%v' but it worked: %v", s.expectedError, result)
|
||||
} else {
|
||||
test.AssertResultComplexWithContext(t, s.expectedError, err.Error(), s.description)
|
||||
}
|
||||
case "encode-error":
|
||||
result, err := processFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewXMLEncoder(2, NewDefaultXmlPreferences()))
|
||||
if err == nil {
|
||||
@@ -484,7 +553,7 @@ func documentXMLScenario(t *testing.T, w *bufio.Writer, i interface{}) {
|
||||
documentXMLRoundTripScenario(w, s)
|
||||
case "decode-keep-ns":
|
||||
documentXMLDecodeKeepNsScenario(w, s)
|
||||
case "decode-raw-token":
|
||||
case "decode-raw-token-off":
|
||||
documentXMLDecodeKeepNsRawTokenScenario(w, s)
|
||||
case "roundtrip-skip-directives":
|
||||
documentXMLSkipDirectrivesScenario(w, s)
|
||||
@@ -528,14 +597,14 @@ func documentXMLDecodeKeepNsScenario(w *bufio.Writer, s formatScenario) {
|
||||
writeOrPanic(w, fmt.Sprintf("```xml\n%v\n```\n", s.input))
|
||||
|
||||
writeOrPanic(w, "then\n")
|
||||
writeOrPanic(w, "```bash\nyq -p=xml -o=xml --xml-keep-namespace '.' sample.xml\n```\n")
|
||||
writeOrPanic(w, "```bash\nyq -p=xml -o=xml --xml-keep-namespace=false '.' sample.xml\n```\n")
|
||||
writeOrPanic(w, "will output\n")
|
||||
prefs := NewDefaultXmlPreferences()
|
||||
prefs.KeepNamespace = true
|
||||
prefs.KeepNamespace = false
|
||||
writeOrPanic(w, fmt.Sprintf("```xml\n%v```\n\n", mustProcessFormatScenario(s, NewXMLDecoder(prefs), NewXMLEncoder(2, prefs))))
|
||||
|
||||
prefsWithout := NewDefaultXmlPreferences()
|
||||
prefs.KeepNamespace = false
|
||||
prefs.KeepNamespace = true
|
||||
writeOrPanic(w, "instead of\n")
|
||||
writeOrPanic(w, fmt.Sprintf("```xml\n%v```\n\n", mustProcessFormatScenario(s, NewXMLDecoder(prefsWithout), NewXMLEncoder(2, prefsWithout))))
|
||||
}
|
||||
@@ -552,16 +621,16 @@ func documentXMLDecodeKeepNsRawTokenScenario(w *bufio.Writer, s formatScenario)
|
||||
writeOrPanic(w, fmt.Sprintf("```xml\n%v\n```\n", s.input))
|
||||
|
||||
writeOrPanic(w, "then\n")
|
||||
writeOrPanic(w, "```bash\nyq -p=xml -o=xml --xml-keep-namespace --xml-raw-token '.' sample.xml\n```\n")
|
||||
writeOrPanic(w, "```bash\nyq -p=xml -o=xml --xml-raw-token=false '.' sample.xml\n```\n")
|
||||
writeOrPanic(w, "will output\n")
|
||||
|
||||
prefs := NewDefaultXmlPreferences()
|
||||
prefs.KeepNamespace = true
|
||||
prefs.UseRawToken = false
|
||||
|
||||
writeOrPanic(w, fmt.Sprintf("```xml\n%v```\n\n", mustProcessFormatScenario(s, NewXMLDecoder(prefs), NewXMLEncoder(2, prefs))))
|
||||
|
||||
prefsWithout := NewDefaultXmlPreferences()
|
||||
prefsWithout.KeepNamespace = false
|
||||
prefsWithout.UseRawToken = true
|
||||
|
||||
writeOrPanic(w, "instead of\n")
|
||||
writeOrPanic(w, fmt.Sprintf("```xml\n%v```\n\n", mustProcessFormatScenario(s, NewXMLDecoder(prefsWithout), NewXMLEncoder(2, prefsWithout))))
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
- update release notes
|
||||
- increment version in version.go
|
||||
- increment version in snapcraft.yaml
|
||||
- make sure local build passes
|
||||
- run ./scripts/copy-docs.sh (and commit the changed in the yq-book branch)
|
||||
- commit version update changes
|
||||
- tag git with same version number
|
||||
- commit vX tag - this will trigger github actions
|
||||
- run ./scripts/bump-version.sh
|
||||
- git push
|
||||
- git push --tags
|
||||
- use github actions to publish docker and make github release
|
||||
- check github updated yq action in marketplace
|
||||
|
||||
|
||||
- snapcraft
|
||||
- update snapcraft version
|
||||
- https://snapcraft.io/yq/builds
|
||||
- will auto create a candidate, test it works then promote
|
||||
- !! need to update v4/stable as well as latest
|
||||
|
||||
|
||||
@@ -1,3 +1,51 @@
|
||||
4.31.1:
|
||||
- Added shuffle command #1503
|
||||
- Added ability to sort by multiple fields #1541
|
||||
- Added @sh encoder #1526
|
||||
- Added @uri/@urid encoder/decoder #1529
|
||||
- Fixed date comparison with string date #1537
|
||||
- Added from_unix/to_unix Operators
|
||||
- Bumped dependency versions
|
||||
|
||||
4.30.8:
|
||||
- Log info message when unable to chown file in linux (e.g. snap confinement) #1521
|
||||
|
||||
|
||||
4.30.7:
|
||||
- Fixed bug in splice operator #1511
|
||||
- Fixed value operator bug #1515
|
||||
- Fixed handling of merging null #1501
|
||||
- Ownership of file now maintained in linux (thanks @vaguecoder) #1473
|
||||
- Bumped dependency versions
|
||||
|
||||
4.30.6:
|
||||
- Fixed xml comment in array of scalars #1465
|
||||
- Include blank new lines in leading header preprocessing #1462
|
||||
- Added aarch64 build (#1261)
|
||||
- Bumped dependency versions (#1453)
|
||||
|
||||
4.30.5:
|
||||
- XML Decoder: Comment parsing tweak
|
||||
- XML Decoder: Fixed processing comments in empty XML #1446
|
||||
- XML Decoder: Checking for invalid content outside of a root node #1448
|
||||
- XML Decoder: Fixed issue where content surrounding tags are lost #1447
|
||||
- XML Decoder: Fixed xml decode bug when there is content after a comment
|
||||
- Fixed loading yaml with header issue #1445
|
||||
- guessTagFromCustomType warning log is now a debug.
|
||||
- Special thanks to @Kopfbremse for reporting XML issues!
|
||||
|
||||
4.30.4:
|
||||
- Fixed bug in automated versioning (snap/brew)
|
||||
|
||||
4.30.3:
|
||||
- Updated release process (automated versioning)
|
||||
- Fixed handling of yaml directives (#1424)
|
||||
- Fixed parsing of newline character in string expression #1430
|
||||
- Fixed length compares to null instead of 0 issue #1427
|
||||
|
||||
4.30.2:
|
||||
- Actually updated the default xml prefix :facepalm:
|
||||
|
||||
4.30.1:
|
||||
- XML users note: the default attribute prefix has change to `+@` to avoid naming conflicts!
|
||||
- Can use expressions in slice #1419
|
||||
|
||||
Executable
+43
@@ -0,0 +1,43 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
if [ "$1" == "" ]; then
|
||||
echo "Please specify at a version"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
version=$1
|
||||
|
||||
# validate version is in the right format
|
||||
echo $version | sed -r '/v4\.[0-9][0-9]\.[0-9][0-9]?$/!{q1}'
|
||||
|
||||
previousVersion=$(cat cmd/version.go| sed -n 's/.*Version = "\([^"]*\)"/\1/p')
|
||||
|
||||
echo "Updating from $previousVersion to $version"
|
||||
|
||||
sed -i "s/\(.*Version =\).*/\1 \"$version\"/" cmd/version.go
|
||||
|
||||
go build .
|
||||
actualVersion=$(./yq --version)
|
||||
|
||||
if [ "$actualVersion" != "yq (https://github.com/mikefarah/yq/) version $version" ]; then
|
||||
echo "Failed to update version.go"
|
||||
exit 1
|
||||
else
|
||||
echo "version.go updated"
|
||||
fi
|
||||
|
||||
version=$version ./yq -i '.version=strenv(version)' snap/snapcraft.yaml
|
||||
|
||||
actualSnapVersion=$(./yq '.version' snap/snapcraft.yaml)
|
||||
|
||||
if [ "$actualSnapVersion" != "$version" ]; then
|
||||
echo "Failed to update snapcraft"
|
||||
exit 1
|
||||
else
|
||||
echo "snapcraft updated"
|
||||
fi
|
||||
|
||||
git add cmd/version.go snap/snapcraft.yaml
|
||||
git commit -m 'Bumping version'
|
||||
git tag $version
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
set -ex
|
||||
go mod download golang.org/x/tools@latest
|
||||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.49.0
|
||||
wget -O- -nv https://raw.githubusercontent.com/securego/gosec/master/install.sh | sh -s v2.13.1
|
||||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.51.1
|
||||
wget -O- -nv https://raw.githubusercontent.com/securego/gosec/master/install.sh | sh -s
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
# you may need to go install github.com/mitchellh/gox@v1.0.1 first
|
||||
|
||||
echo $VERSION
|
||||
CGO_ENABLED=0 gox -ldflags "${LDFLAGS}" -output="build/yq_{{.OS}}_{{.Arch}}" --osarch="darwin/amd64 darwin/arm64 freebsd/386 freebsd/amd64 freebsd/arm linux/386 linux/amd64 linux/arm linux/arm64 linux/mips linux/mips64 linux/mips64le linux/mipsle linux/ppc64 linux/ppc64le linux/s390x netbsd/386 netbsd/amd64 netbsd/arm openbsd/386 openbsd/amd64 windows/386 windows/amd64"
|
||||
|
||||
cd build
|
||||
@@ -21,4 +21,4 @@ rhash -r -a . -o checksums
|
||||
|
||||
rhash --list-hashes > checksums_hashes_order
|
||||
|
||||
cp ../scripts/extract-checksum.sh .
|
||||
cp ../scripts/extract-checksum.sh .
|
||||
|
||||
+2
-5
@@ -1,22 +1,19 @@
|
||||
name: yq
|
||||
version: '4.30.1'
|
||||
version: 'v4.31.1'
|
||||
summary: A lightweight and portable command-line YAML processor
|
||||
description: |
|
||||
The aim of the project is to be the jq or sed of yaml files.
|
||||
|
||||
base: core18
|
||||
grade: stable # devel|stable. must be 'stable' to release into candidate/stable channels
|
||||
confinement: strict
|
||||
|
||||
apps:
|
||||
yq:
|
||||
command: yq
|
||||
plugs: [home]
|
||||
|
||||
parts:
|
||||
yq:
|
||||
plugin: go
|
||||
go-channel: 1.19/stable
|
||||
go-channel: 1.20/stable
|
||||
source: .
|
||||
source-type: git
|
||||
go-importpath: github.com/mikefarah/yq
|
||||
|
||||
Reference in New Issue
Block a user