Compare commits

...
Author SHA1 Message Date
Mike Farah 8974bc7e96 formatting 2023-03-15 13:15:17 +11:00
Mike Farah 5a62b07d72 Use file extension to auto detect output format! 2023-03-15 13:14:40 +11:00
Mike Farah c1a236d720 Use file extension to auto detect output format! 2023-03-15 13:14:40 +11:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2c14c98408 Bump golang from 1.20.1 to 1.20.2 (#1589)
Bumps golang from 1.20.1 to 1.20.2.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-10 10:13:22 +11:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
eafcc3bafa Bump golang.org/x/net from 0.7.0 to 0.8.0 (#1590)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.7.0 to 0.8.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/compare/v0.7.0...v0.8.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-10 10:13:11 +11:00
Robert BrennanandGitHub 9539877ff6 Add filter operation (#1588)
* add filter operation

* add tests

* add tests

* revert debug

* simplify filter

* fix tests

* remove logs
2023-03-09 10:30:47 +11:00
ryenusandGitHub d30941b575 Detect input format based on file name extension (#1582)
* detect inputFormat from filename

* refactor and extract func InputFormatFromFilename

* detect inputFormat only when file is provided

* add test for automatic input format detection
2023-03-09 10:17:19 +11:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
fed96f67ea Bump github.com/goccy/go-yaml from 1.9.8 to 1.10.0 (#1581)
Bumps [github.com/goccy/go-yaml](https://github.com/goccy/go-yaml) from 1.9.8 to 1.10.0.
- [Release notes](https://github.com/goccy/go-yaml/releases)
- [Changelog](https://github.com/goccy/go-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/goccy/go-yaml/compare/v1.9.8...v1.10.0)

---
updated-dependencies:
- dependency-name: github.com/goccy/go-yaml
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-03 13:37:22 +11:00
Mike Farah 2340ce6a8c Release notes 2023-03-02 11:21:22 +11:00
Mike Farah e5260d855f Release notes 2023-03-02 10:59:10 +11:00
Mike Farah 47f4f8c793 Bumping version 2023-03-02 10:58:57 +11:00
Mike Farah 2195df9e7a Fixed xml encoding of ProcInst #1563, improved XML comment handling 2023-03-02 10:57:54 +11:00
Mike Farah fdb14875c0 Preparing release notes 2023-03-01 13:49:28 +11:00
Mike Farah 3f1f66a8ee Fixed merged anchor reference problem #1482 2023-03-01 13:45:35 +11:00
Anders BjörklundandGitHub cf8cfbd865 Allow build without json and xml support (#1556)
* Refactor ordered_map into separate files

Separate json and xml, from the regular yaml.

Makes it possible to compile, without those...

* Refactor encoder and decoder creation

Use more consistent parameters vs globals

Return errors instead of calling panic()

* Allow build without json and xml support
2023-03-01 13:19:06 +11:00
Mike FarahandGitHub 62d167c141 Variable loop - Fixes #1566 (#1577)
* Variable loop wip

* Variable loop wip

* Variable loop wip

* Variable loop wip

* Fixed variable operator to work like jq
2023-02-28 16:40:38 +11:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
cb27444e54 Bump golang.org/x/net from 0.1.1-0.20221104162952-702349b0e862 to 0.7.0 (#1576)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.1.1-0.20221104162952-702349b0e862 to 0.7.0.
- [Release notes](https://github.com/golang/net/releases)
- [Commits](https://github.com/golang/net/commits/v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-28 13:38:09 +11:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
ce3d8378e7 Bump github.com/a8m/envsubst from 1.4.1 to 1.4.2 (#1569)
Bumps [github.com/a8m/envsubst](https://github.com/a8m/envsubst) from 1.4.1 to 1.4.2.
- [Release notes](https://github.com/a8m/envsubst/releases)
- [Commits](https://github.com/a8m/envsubst/compare/v1.4.1...v1.4.2)

---
updated-dependencies:
- dependency-name: github.com/a8m/envsubst
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-28 13:28:21 +11:00
54 changed files with 1061 additions and 335 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.20.1 as builder
FROM golang:1.20.2 as builder
WORKDIR /go/src/mikefarah/yq
+1 -1
View File
@@ -1,4 +1,4 @@
FROM golang:1.20.1
FROM golang:1.20.2
COPY scripts/devtools.sh /opt/devtools.sh
+211
View File
@@ -0,0 +1,211 @@
#!/bin/bash
setUp() {
rm test*.yml 2>/dev/null || true
rm test*.json 2>/dev/null || true
rm test*.properties 2>/dev/null || true
rm test*.csv 2>/dev/null || true
rm test*.tsv 2>/dev/null || true
rm test*.xml 2>/dev/null || true
}
testInputJson() {
cat >test.json <<EOL
{ "mike" : { "things": "cool" } }
EOL
read -r -d '' expected << EOM
{
"mike": {
"things": "cool"
}
}
EOM
X=$(./yq test.json)
assertEquals "$expected" "$X"
X=$(./yq ea test.json)
assertEquals "$expected" "$X"
}
testInputJsonOutputYaml() {
cat >test.json <<EOL
{ "mike" : { "things": "cool" } }
EOL
read -r -d '' expected << EOM
mike:
things: cool
EOM
X=$(./yq test.json -oy)
assertEquals "$expected" "$X"
X=$(./yq ea test.json -oy)
assertEquals "$expected" "$X"
}
testInputProperties() {
cat >test.properties <<EOL
mike.things = hello
EOL
read -r -d '' expected << EOM
mike.things = hello
EOM
X=$(./yq e test.properties)
assertEquals "$expected" "$X"
X=$(./yq test.properties)
assertEquals "$expected" "$X"
X=$(./yq ea test.properties)
assertEquals "$expected" "$X"
}
testInputPropertiesGitHubAction() {
cat >test.properties <<EOL
mike.things = hello
EOL
read -r -d '' expected << EOM
mike.things = hello
EOM
X=$(cat /dev/null | ./yq e test.properties)
assertEquals "$expected" "$X"
X=$(cat /dev/null | ./yq ea test.properties)
assertEquals "$expected" "$X"
}
testInputCSV() {
cat >test.csv <<EOL
fruit,yumLevel
apple,5
banana,4
EOL
read -r -d '' expected << EOM
fruit,yumLevel
apple,5
banana,4
EOM
X=$(./yq e test.csv)
assertEquals "$expected" "$X"
X=$(./yq ea test.csv)
assertEquals "$expected" "$X"
}
testInputCSVUTF8() {
read -r -d '' expected << EOM
id,first,last
1,john,smith
1,jane,smith
EOM
X=$(./yq utf8.csv)
assertEquals "$expected" "$X"
}
testInputTSV() {
cat >test.tsv <<EOL
fruit yumLevel
apple 5
banana 4
EOL
read -r -d '' expected << EOM
fruit yumLevel
apple 5
banana 4
EOM
X=$(./yq e test.tsv)
assertEquals "$expected" "$X"
X=$(./yq ea test.tsv)
assertEquals "$expected" "$X"
}
testInputXml() {
cat >test.xml <<EOL
<cat legs="4">BiBi</cat>
EOL
read -r -d '' expected << EOM
<cat legs="4">BiBi</cat>
EOM
X=$(./yq e test.xml)
assertEquals "$expected" "$X"
X=$(./yq ea test.xml)
assertEquals "$expected" "$X"
}
testInputXmlNamespaces() {
cat >test.xml <<EOL
<?xml version="1.0"?>
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url">
</map>
EOL
read -r -d '' expected << EOM
<?xml version="1.0"?>
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url"></map>
EOM
X=$(./yq e test.xml)
assertEquals "$expected" "$X"
X=$(./yq ea test.xml)
assertEquals "$expected" "$X"
}
testInputXmlStrict() {
cat >test.xml <<EOL
<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY writer "Catherine.">
<!ENTITY copyright "(r) Great">
]>
<root>
<item>&writer;&copyright;</item>
</root>
EOL
X=$(./yq --xml-strict-mode test.xml 2>&1)
assertEquals 1 $?
assertEquals "Error: bad file 'test.xml': XML syntax error on line 7: invalid character entity &writer;" "$X"
X=$(./yq ea --xml-strict-mode test.xml 2>&1)
assertEquals "Error: bad file 'test.xml': XML syntax error on line 7: invalid character entity &writer;" "$X"
}
testInputXmlGithubAction() {
cat >test.xml <<EOL
<cat legs="4">BiBi</cat>
EOL
read -r -d '' expected << EOM
<cat legs="4">BiBi</cat>
EOM
X=$(cat /dev/null | ./yq e test.xml)
assertEquals "$expected" "$X"
X=$(cat /dev/null | ./yq ea test.xml)
assertEquals "$expected" "$X"
}
source ./scripts/shunit2
+3 -3
View File
@@ -120,7 +120,7 @@ EOM
}
testInputXmlNamespaces() {
cat >test.yml <<EOL
cat >test.xml <<EOL
<?xml version="1.0"?>
<map xmlns="some-namespace" xmlns:xsi="some-instance" xsi:schemaLocation="some-url">
</map>
@@ -134,10 +134,10 @@ map:
+@xsi:schemaLocation: some-url
EOM
X=$(./yq e -p=xml test.yml)
X=$(./yq e -p=xml test.xml)
assertEquals "$expected" "$X"
X=$(./yq ea -p=xml test.yml)
X=$(./yq ea -p=xml test.xml)
assertEquals "$expected" "$X"
}
+4 -2
View File
@@ -6,8 +6,10 @@ var unwrapScalar = false
var writeInplace = false
var outputToJSON = false
var outputFormat = "yaml"
var inputFormat = "yaml"
var outputFormat = ""
var inputFormat = ""
var exitStatus = false
var forceColor = false
+4 -1
View File
@@ -84,7 +84,10 @@ func evaluateAll(cmd *cobra.Command, args []string) (cmdError error) {
if err != nil {
return err
}
encoder := configureEncoder(format)
encoder, err := configureEncoder()
if err != nil {
return err
}
printer := yqlib.NewPrinter(encoder, printerWriter)
+4 -1
View File
@@ -93,7 +93,10 @@ func evaluateSequence(cmd *cobra.Command, args []string) (cmdError error) {
if err != nil {
return err
}
encoder := configureEncoder(format)
encoder, err := configureEncoder()
if err != nil {
return err
}
printer := yqlib.NewPrinter(encoder, printerWriter)
+2 -21
View File
@@ -53,25 +53,6 @@ yq -P sample.json
logging.SetBackend(backend)
yqlib.InitExpressionParser()
outputFormatType, err := yqlib.OutputFormatFromString(outputFormat)
if err != nil {
return err
}
if outputFormatType == yqlib.YamlOutputFormat ||
outputFormatType == yqlib.PropsOutputFormat {
unwrapScalar = true
}
if unwrapScalarFlag.IsExplicitySet() {
unwrapScalar = unwrapScalarFlag.IsSet()
}
//copy preference form global setting
yqlib.ConfiguredYamlPreferences.UnwrapScalar = unwrapScalar
yqlib.ConfiguredYamlPreferences.PrintDocSeparators = !noDocSeparators
return nil
},
}
@@ -84,8 +65,8 @@ yq -P sample.json
panic(err)
}
rootCmd.PersistentFlags().StringVarP(&outputFormat, "output-format", "o", "yaml", "[yaml|y|json|j|props|p|xml|x] output format type.")
rootCmd.PersistentFlags().StringVarP(&inputFormat, "input-format", "p", "yaml", "[yaml|y|props|p|xml|x] parse format for input. Note that json is a subset of yaml.")
rootCmd.PersistentFlags().StringVarP(&outputFormat, "output-format", "o", "auto", "[auto|a|yaml|y|json|j|props|p|xml|x] output format type.")
rootCmd.PersistentFlags().StringVarP(&inputFormat, "input-format", "p", "auto", "[auto|a|yaml|y|props|p|xml|x] parse format for input. Note that json is a subset of yaml.")
rootCmd.PersistentFlags().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).")
+76 -12
View File
@@ -53,6 +53,48 @@ func initCommand(cmd *cobra.Command, args []string) (string, []string, error) {
return "", nil, fmt.Errorf("cannot pass files in when using null-input flag")
}
inputFilename := ""
if len(args) > 0 {
inputFilename = args[0]
}
if inputFormat == "" || inputFormat == "auto" || inputFormat == "a" {
inputFormat = yqlib.FormatFromFilename(inputFilename)
if outputFormat == "" || outputFormat == "auto" || outputFormat == "a" {
outputFormat = yqlib.FormatFromFilename(inputFilename)
}
} else if outputFormat == "" || outputFormat == "auto" || outputFormat == "a" {
// backwards compatibility -
// before this was introduced, `yq -pcsv things.csv`
// would produce *yaml* output.
//
outputFormat = yqlib.FormatFromFilename(inputFilename)
if inputFilename != "-" {
yqlib.GetLogger().Warning("yq default output is now 'auto' (based on the filename extension). Normally yq would output '%v', but for backwards compatibility 'yaml' has been set. Please use -oy to specify yaml, or drop the -p flag.", outputFormat)
}
outputFormat = "yaml"
}
outputFormatType, err := yqlib.OutputFormatFromString(outputFormat)
if err != nil {
return "", nil, err
}
yqlib.GetLogger().Debug("Using outputformat %v", outputFormat)
if outputFormatType == yqlib.YamlOutputFormat ||
outputFormatType == yqlib.PropsOutputFormat {
unwrapScalar = true
}
if unwrapScalarFlag.IsExplicitySet() {
unwrapScalar = unwrapScalarFlag.IsSet()
}
//copy preference form global setting
yqlib.ConfiguredYamlPreferences.UnwrapScalar = unwrapScalar
yqlib.ConfiguredYamlPreferences.PrintDocSeparators = !noDocSeparators
return expression, args, nil
}
@@ -61,7 +103,15 @@ func configureDecoder(evaluateTogether bool) (yqlib.Decoder, error) {
if err != nil {
return nil, err
}
switch yqlibInputFormat {
yqlibDecoder, err := createDecoder(yqlibInputFormat, evaluateTogether)
if yqlibDecoder == nil {
return nil, fmt.Errorf("no support for %s input format", inputFormat)
}
return yqlibDecoder, err
}
func createDecoder(format yqlib.InputFormat, evaluateTogether bool) (yqlib.Decoder, error) {
switch format {
case yqlib.XMLInputFormat:
return yqlib.NewXMLDecoder(yqlib.ConfiguredXMLPreferences), nil
case yqlib.PropertiesInputFormat:
@@ -72,10 +122,12 @@ func configureDecoder(evaluateTogether bool) (yqlib.Decoder, error) {
return yqlib.NewCSVObjectDecoder(','), nil
case yqlib.TSVObjectInputFormat:
return yqlib.NewCSVObjectDecoder('\t'), nil
case yqlib.YamlInputFormat:
prefs := yqlib.ConfiguredYamlPreferences
prefs.EvaluateTogether = evaluateTogether
return yqlib.NewYamlDecoder(prefs), nil
}
prefs := yqlib.ConfiguredYamlPreferences
prefs.EvaluateTogether = evaluateTogether
return yqlib.NewYamlDecoder(prefs), nil
return nil, fmt.Errorf("invalid decoder: %v", format)
}
func configurePrinterWriter(format yqlib.PrinterOutputFormat, out io.Writer) (yqlib.PrinterWriter, error) {
@@ -95,22 +147,34 @@ func configurePrinterWriter(format yqlib.PrinterOutputFormat, out io.Writer) (yq
return printerWriter, nil
}
func configureEncoder(format yqlib.PrinterOutputFormat) yqlib.Encoder {
func configureEncoder() (yqlib.Encoder, error) {
yqlibOutputFormat, err := yqlib.OutputFormatFromString(outputFormat)
if err != nil {
return nil, err
}
yqlibEncoder, err := createEncoder(yqlibOutputFormat)
if yqlibEncoder == nil {
return nil, fmt.Errorf("no support for %s output format", outputFormat)
}
return yqlibEncoder, err
}
func createEncoder(format yqlib.PrinterOutputFormat) (yqlib.Encoder, error) {
switch format {
case yqlib.JSONOutputFormat:
return yqlib.NewJSONEncoder(indent, colorsEnabled, unwrapScalar)
return yqlib.NewJSONEncoder(indent, colorsEnabled, unwrapScalar), nil
case yqlib.PropsOutputFormat:
return yqlib.NewPropertiesEncoder(unwrapScalar)
return yqlib.NewPropertiesEncoder(unwrapScalar), nil
case yqlib.CSVOutputFormat:
return yqlib.NewCsvEncoder(',')
return yqlib.NewCsvEncoder(','), nil
case yqlib.TSVOutputFormat:
return yqlib.NewCsvEncoder('\t')
return yqlib.NewCsvEncoder('\t'), nil
case yqlib.YamlOutputFormat:
return yqlib.NewYamlEncoder(indent, colorsEnabled, yqlib.ConfiguredYamlPreferences)
return yqlib.NewYamlEncoder(indent, colorsEnabled, yqlib.ConfiguredYamlPreferences), nil
case yqlib.XMLOutputFormat:
return yqlib.NewXMLEncoder(indent, yqlib.ConfiguredXMLPreferences)
return yqlib.NewXMLEncoder(indent, yqlib.ConfiguredXMLPreferences), nil
}
panic("invalid encoder")
return nil, fmt.Errorf("invalid encoder: %v", format)
}
// this is a hack to enable backwards compatibility with githubactions (which pipe /dev/null into everything)
+1 -1
View File
@@ -11,7 +11,7 @@ var (
GitDescribe string
// Version is main version number that is being run at the moment.
Version = "v4.31.1"
Version = "v4.31.2"
// VersionPrerelease is a pre-release marker for the version. If this is "" (empty string)
// then it means that it is a final release. Otherwise, this is a pre-release
+1 -5
View File
@@ -1,5 +1 @@
date: "2022-11-25"
raw:
id: 1
XML: text_outside_1<Tag1 />text_outside_2<Tag2Kling Klong</Tag2>text_outside_3
time: 09:19:00
<cat>3</cat>
+5 -5
View File
@@ -1,20 +1,20 @@
module github.com/mikefarah/yq/v4
require (
github.com/a8m/envsubst v1.4.1
github.com/a8m/envsubst v1.4.2
github.com/alecthomas/participle/v2 v2.0.0-beta.5
github.com/alecthomas/repr v0.2.0
github.com/dimchansky/utfbom v1.1.1
github.com/elliotchance/orderedmap v1.5.0
github.com/fatih/color v1.14.1
github.com/goccy/go-json v0.10.0
github.com/goccy/go-yaml v1.9.8
github.com/goccy/go-yaml v1.10.0
github.com/jinzhu/copier v0.3.5
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.1.1-0.20221104162952-702349b0e862
golang.org/x/net v0.8.0
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
gopkg.in/yaml.v3 v3.0.1
)
@@ -23,8 +23,8 @@ require (
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
golang.org/x/sys v0.3.0 // indirect
golang.org/x/text v0.4.0 // indirect
golang.org/x/sys v0.6.0 // indirect
golang.org/x/text v0.8.0 // indirect
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
)
+11 -10
View File
@@ -1,5 +1,5 @@
github.com/a8m/envsubst v1.4.1 h1:RShc2OKbQpIIp5qR2glVsBOJCImTVHFrDkJvmGh0Qi0=
github.com/a8m/envsubst v1.4.1/go.mod h1:MVUTQNGQ3tsjOOtKCNd+fl8RzhsXcDvvAEzkhGtlsbY=
github.com/a8m/envsubst v1.4.2 h1:4yWIHXOLEJHQEFd4UjrWDrYeYlV7ncFWJOCBRLOZHQg=
github.com/a8m/envsubst v1.4.2/go.mod h1:MVUTQNGQ3tsjOOtKCNd+fl8RzhsXcDvvAEzkhGtlsbY=
github.com/alecthomas/assert/v2 v2.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=
@@ -21,8 +21,9 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-yaml v1.9.8 h1:5gMyLUeU1/6zl+WFfR1hN7D2kf+1/eRGa7DFtToiBvQ=
github.com/goccy/go-yaml v1.9.8/go.mod h1:JubOolP3gh0HpiBc4BLRD4YmjEjHAmIIB2aaXKkTfoE=
github.com/goccy/go-yaml v1.10.0 h1:rBi+5HGuznOxx0JZ+60LDY85gc0dyIJCIMvsMJTKSKQ=
github.com/goccy/go-yaml v1.10.0/go.mod h1:h/18Lr6oSQ3mvmqFoWmQ47KChOgpfHpTyIHl3yVmpiY=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/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=
@@ -54,20 +55,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.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/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
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=
+20 -3
View File
@@ -3,6 +3,7 @@ package yqlib
import (
"fmt"
"io"
"strings"
)
type InputFormat uint
@@ -25,11 +26,11 @@ type Decoder interface {
func InputFormatFromString(format string) (InputFormat, error) {
switch format {
case "yaml", "y":
case "yaml", "yml", "y":
return YamlInputFormat, nil
case "xml", "x":
return XMLInputFormat, nil
case "props", "p":
case "properties", "props", "p":
return PropertiesInputFormat, nil
case "json", "ndjson", "j":
return JsonInputFormat, nil
@@ -38,6 +39,22 @@ func InputFormatFromString(format string) (InputFormat, error) {
case "tsv", "t":
return TSVObjectInputFormat, nil
default:
return 0, fmt.Errorf("unknown format '%v' please use [yaml|xml|props]", format)
return 0, fmt.Errorf("unknown format '%v' please use [yaml|json|props|csv|tsv|xml]", format)
}
}
func FormatFromFilename(filename string) string {
if filename != "" {
GetLogger().Debugf("checking file extension '%s' for auto format detection", filename)
nPos := strings.LastIndex(filename, ".")
if nPos > -1 {
format := filename[nPos+1:]
GetLogger().Debugf("detected format '%s'", format)
return format
}
}
GetLogger().Debugf("using default inputFormat 'yaml'")
return "yaml"
}
+2
View File
@@ -1,3 +1,5 @@
//go:build !yq_nojson
package yqlib
import (
+13 -1
View File
@@ -1,3 +1,5 @@
//go:build !yq_noxml
package yqlib
import (
@@ -5,6 +7,7 @@ import (
"errors"
"fmt"
"io"
"regexp"
"strings"
"unicode"
@@ -46,11 +49,19 @@ func (dec *xmlDecoder) createSequence(nodes []*xmlNode) (*yaml.Node, error) {
return yamlNode, nil
}
var decoderCommentPrefix = regexp.MustCompile(`(^|\n)([[:alpha:]])`)
func (dec *xmlDecoder) processComment(c string) string {
if c == "" {
return ""
}
return "#" + strings.TrimRight(c, " ")
//need to replace "cat " with "# cat"
// "\ncat\n" with "\n cat\n"
// ensure non-empty comments starting with newline have a space in front
replacement := decoderCommentPrefix.ReplaceAllString(c, "$1 $2")
replacement = "#" + strings.ReplaceAll(strings.TrimRight(replacement, " "), "\n", "\n#")
return replacement
}
func (dec *xmlDecoder) createMap(n *xmlNode) (*yaml.Node, error) {
@@ -75,6 +86,7 @@ func (dec *xmlDecoder) createMap(n *xmlNode) (*yaml.Node, error) {
var err error
if i == 0 {
log.Debugf("head comment here")
labelNode.HeadComment = dec.processComment(n.HeadComment)
}
+18
View File
@@ -0,0 +1,18 @@
## Filter array
Given a sample.yml file of:
```yaml
- 1
- 2
- 3
```
then
```bash
yq 'filter(. < 3)' sample.yml
```
will output
```yaml
- 1
- 2
```
+9 -4
View File
@@ -407,8 +407,10 @@ A best attempt is made to copy comments to xml.
Given a sample.yml file of:
```yaml
#
# header comment
# above_cat
#
cat: # inline_cat
# above_array
array: # inline_array
@@ -425,9 +427,11 @@ yq -o=xml '.' sample.yml
will output
```xml
<!--
header comment
above_cat
--><!-- inline_cat --><cat><!-- above_array inline_array -->
header comment
above_cat
-->
<!-- inline_cat -->
<cat><!-- above_array inline_array -->
<array>val1<!-- inline_val1 --></array>
<array><!-- above_val2 -->val2<!-- inline_val2 --></array>
</cat><!-- below_cat -->
@@ -489,7 +493,8 @@ yq -p=xml -o=xml '.' sample.xml
```
will output
```xml
<!-- before cat --><cat><!-- in cat before -->
<!-- before cat -->
<cat><!-- in cat before -->
<x>3<!-- multi
line comment
for x --></x><!-- before y -->
+7 -156
View File
@@ -1,10 +1,6 @@
package yqlib
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
yaml "gopkg.in/yaml.v3"
@@ -17,162 +13,17 @@ type Encoder interface {
CanHandleAliases() bool
}
// orderedMap allows to marshal and unmarshal JSON and YAML values keeping the
// order of keys and values in a map or an object.
type orderedMap struct {
// if this is an object, kv != nil. If this is not an object, kv == nil.
kv []orderedMapKV
altVal interface{}
}
func mapKeysToStrings(node *yaml.Node) {
type orderedMapKV struct {
K string
V orderedMap
}
func (o *orderedMap) UnmarshalJSON(data []byte) error {
switch data[0] {
case '{':
// initialise so that even if the object is empty it is not nil
o.kv = []orderedMapKV{}
// create decoder
dec := json.NewDecoder(bytes.NewReader(data))
_, err := dec.Token() // open object
if err != nil {
return err
}
// cycle through k/v
var tok json.Token
for tok, err = dec.Token(); err == nil; tok, err = dec.Token() {
// we can expect two types: string or Delim. Delim automatically means
// that it is the closing bracket of the object, whereas string means
// that there is another key.
if _, ok := tok.(json.Delim); ok {
break
if node.Kind == yaml.MappingNode {
for index, child := range node.Content {
if index%2 == 0 { // its a map key
child.Tag = "!!str"
}
kv := orderedMapKV{
K: tok.(string),
}
if err := dec.Decode(&kv.V); err != nil {
return err
}
o.kv = append(o.kv, kv)
}
// unexpected error
if err != nil && !errors.Is(err, io.EOF) {
return err
}
return nil
case '[':
var res []*orderedMap
if err := json.Unmarshal(data, &res); err != nil {
return err
}
o.altVal = res
o.kv = nil
return nil
}
return json.Unmarshal(data, &o.altVal)
}
func (o orderedMap) MarshalJSON() ([]byte, error) {
buf := new(bytes.Buffer)
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false) // do not escape html chars e.g. &, <, >
if o.kv == nil {
if err := enc.Encode(o.altVal); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
buf.WriteByte('{')
for idx, el := range o.kv {
if err := enc.Encode(el.K); err != nil {
return nil, err
}
buf.WriteByte(':')
if err := enc.Encode(el.V); err != nil {
return nil, err
}
if idx != len(o.kv)-1 {
buf.WriteByte(',')
}
}
buf.WriteByte('}')
return buf.Bytes(), nil
}
func (o *orderedMap) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {
case yaml.DocumentNode:
if len(node.Content) == 0 {
return nil
}
return o.UnmarshalYAML(node.Content[0])
case yaml.AliasNode:
return o.UnmarshalYAML(node.Alias)
case yaml.ScalarNode:
return node.Decode(&o.altVal)
case yaml.MappingNode:
// set kv to non-nil
o.kv = []orderedMapKV{}
for i := 0; i < len(node.Content); i += 2 {
var key string
var val orderedMap
if err := node.Content[i].Decode(&key); err != nil {
return err
}
if err := node.Content[i+1].Decode(&val); err != nil {
return err
}
o.kv = append(o.kv, orderedMapKV{
K: key,
V: val,
})
}
return nil
case yaml.SequenceNode:
// note that this has to be a pointer, so that nulls can be represented.
var res []*orderedMap
if err := node.Decode(&res); err != nil {
return err
}
o.altVal = res
o.kv = nil
return nil
case 0:
// null
o.kv = nil
o.altVal = nil
return nil
default:
return fmt.Errorf("orderedMap: invalid yaml node")
for _, child := range node.Content {
mapKeysToStrings(child)
}
}
func (o *orderedMap) MarshalYAML() (interface{}, error) {
// fast path: kv is nil, use altVal
if o.kv == nil {
return o.altVal, nil
}
content := make([]*yaml.Node, 0, len(o.kv)*2)
for _, val := range o.kv {
n := new(yaml.Node)
if err := n.Encode(val.V); err != nil {
return nil, err
}
content = append(content, &yaml.Node{
Kind: yaml.ScalarNode,
Tag: "!!str",
Value: val.K,
}, n)
}
return &yaml.Node{
Kind: yaml.MappingNode,
Tag: "!!map",
Content: content,
}, nil
}
+2 -15
View File
@@ -1,3 +1,5 @@
//go:build !yq_nojson
package yqlib
import (
@@ -14,21 +16,6 @@ type jsonEncoder struct {
UnwrapScalar bool
}
func mapKeysToStrings(node *yaml.Node) {
if node.Kind == yaml.MappingNode {
for index, child := range node.Content {
if index%2 == 0 { // its a map key
child.Tag = "!!str"
}
}
}
for _, child := range node.Content {
mapKeysToStrings(child)
}
}
func NewJSONEncoder(indent int, colorise bool, unwrapScalar bool) Encoder {
var indentString = ""
+2
View File
@@ -1,3 +1,5 @@
//go:build !yq_nojson
package yqlib
import (
+63 -25
View File
@@ -1,3 +1,5 @@
//go:build !yq_noxml
package yqlib
import (
@@ -17,8 +19,6 @@ type xmlEncoder struct {
leadingContent string
}
var commentPrefix = regexp.MustCompile(`(^|\n)\s*#`)
func NewXMLEncoder(indent int, prefs XmlPreferences) Encoder {
var indentString = ""
@@ -37,7 +37,7 @@ func (e *xmlEncoder) PrintDocumentSeparator(writer io.Writer) error {
}
func (e *xmlEncoder) PrintLeadingContent(writer io.Writer, content string) error {
e.leadingContent = commentPrefix.ReplaceAllString(content, "\n")
e.leadingContent = content
return nil
}
@@ -46,12 +46,39 @@ func (e *xmlEncoder) Encode(writer io.Writer, node *yaml.Node) error {
// hack so we can manually add newlines to procInst and directives
e.writer = writer
encoder.Indent("", e.indentString)
var newLine xml.CharData = []byte("\n")
mapNode := unwrapDoc(node)
if mapNode.Tag == "!!map" {
// make sure <?xml .. ?> processing instructions are encoded first
for i := 0; i < len(mapNode.Content); i += 2 {
key := mapNode.Content[i]
value := mapNode.Content[i+1]
if key.Value == (e.prefs.ProcInstPrefix + "xml") {
name := strings.Replace(key.Value, e.prefs.ProcInstPrefix, "", 1)
procInst := xml.ProcInst{Target: name, Inst: []byte(value.Value)}
if err := encoder.EncodeToken(procInst); err != nil {
return err
}
if _, err := e.writer.Write([]byte("\n")); err != nil {
log.Warning("Unable to write newline, skipping: %w", err)
}
}
}
}
if e.leadingContent != "" {
// remove first and last newlines if present
err := e.encodeComment(encoder, e.leadingContent)
if err != nil {
return err
}
err = encoder.EncodeToken(newLine)
if err != nil {
return err
}
}
switch node.Kind {
@@ -87,29 +114,12 @@ func (e *xmlEncoder) Encode(writer io.Writer, node *yaml.Node) error {
default:
return fmt.Errorf("unsupported type %v", node.Tag)
}
var charData xml.CharData = []byte("\n")
return encoder.EncodeToken(charData)
return encoder.EncodeToken(newLine)
}
func (e *xmlEncoder) encodeTopLevelMap(encoder *xml.Encoder, node *yaml.Node) error {
// make sure <?xml .. ?> processing instructions are encoded first
for i := 0; i < len(node.Content); i += 2 {
key := node.Content[i]
value := node.Content[i+1]
if key.Value == (e.prefs.ProcInstPrefix + "xml") {
name := strings.Replace(key.Value, e.prefs.ProcInstPrefix, "", 1)
procInst := xml.ProcInst{Target: name, Inst: []byte(value.Value)}
if err := encoder.EncodeToken(procInst); err != nil {
return err
}
if _, err := e.writer.Write([]byte("\n")); err != nil {
log.Warning("Unable to write newline, skipping: %w", err)
}
}
}
err := e.encodeComment(encoder, headAndLineComment(node))
if err != nil {
return err
@@ -124,6 +134,13 @@ func (e *xmlEncoder) encodeTopLevelMap(encoder *xml.Encoder, node *yaml.Node) er
if err != nil {
return err
}
if headAndLineComment(key) != "" {
var newLine xml.CharData = []byte("\n")
err = encoder.EncodeToken(newLine)
if err != nil {
return err
}
}
if key.Value == (e.prefs.ProcInstPrefix + "xml") {
// dont double process these.
@@ -204,13 +221,34 @@ func (e *xmlEncoder) doEncode(encoder *xml.Encoder, node *yaml.Node, start xml.S
return fmt.Errorf("unsupported type %v", node.Tag)
}
var xmlEncodeMultilineCommentRegex = regexp.MustCompile(`(^|\n) *# ?(.*)`)
var xmlEncodeSingleLineCommentRegex = regexp.MustCompile(`^\s*#(.*)\n?`)
var chompRegexp = regexp.MustCompile(`\n$`)
func (e *xmlEncoder) encodeComment(encoder *xml.Encoder, commentStr string) error {
if commentStr != "" {
log.Debugf("encoding comment %v", commentStr)
if !strings.HasSuffix(commentStr, " ") {
commentStr = commentStr + " "
log.Debugf("got comment [%v]", commentStr)
// multi line string
if len(commentStr) > 2 && strings.Contains(commentStr[1:len(commentStr)-1], "\n") {
commentStr = chompRegexp.ReplaceAllString(commentStr, "")
log.Debugf("chompRegexp [%v]", commentStr)
commentStr = xmlEncodeMultilineCommentRegex.ReplaceAllString(commentStr, "$1$2")
log.Debugf("processed multine [%v]", commentStr)
// if the first line is non blank, add a space
if commentStr[0] != '\n' && commentStr[0] != ' ' {
commentStr = " " + commentStr
}
} else {
commentStr = xmlEncodeSingleLineCommentRegex.ReplaceAllString(commentStr, "$1")
}
if !strings.HasSuffix(commentStr, " ") && !strings.HasSuffix(commentStr, "\n") {
commentStr = commentStr + " "
log.Debugf("added suffix [%v]", commentStr)
}
log.Debugf("encoding comment [%v]", commentStr)
var comment xml.Comment = []byte(commentStr)
err := encoder.EncodeToken(comment)
if err != nil {
+2
View File
@@ -1,3 +1,5 @@
//go:build !yq_nojson
package yqlib
import (
+1
View File
@@ -37,6 +37,7 @@ var participleYqRules = []*participleYqRule{
{"MapValues", `map_?values`, opToken(mapValuesOpType), 0},
simpleOp("map", mapOpType),
simpleOp("filter", filterOpType),
simpleOp("pick", pickOpType),
{"FlattenWithDepth", `flatten\([0-9]+\)`, flattenWithDepth(), 0},
+3 -2
View File
@@ -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}
@@ -84,6 +84,7 @@ var expressionOpType = &operationType{Type: "EXP", NumArgs: 0, Precedence: 50, H
var collectOpType = &operationType{Type: "COLLECT", NumArgs: 1, Precedence: 50, Handler: collectOperator}
var mapOpType = &operationType{Type: "MAP", NumArgs: 1, Precedence: 50, Handler: mapOperator}
var filterOpType = &operationType{Type: "FILTER", NumArgs: 1, Precedence: 50, Handler: filterOperator}
var errorOpType = &operationType{Type: "ERROR", NumArgs: 1, Precedence: 50, Handler: errorOperator}
var pickOpType = &operationType{Type: "PICK", NumArgs: 1, Precedence: 50, Handler: pickOperator}
var evalOpType = &operationType{Type: "EVAL", NumArgs: 1, Precedence: 50, Handler: evalOperator}
@@ -338,7 +339,7 @@ func deepCloneWithOptions(node *yaml.Node, cloneContent bool) *yaml.Node {
Tag: node.Tag,
Value: node.Value,
Anchor: node.Anchor,
Alias: deepClone(node.Alias),
Alias: node.Alias,
HeadComment: node.HeadComment,
LineComment: node.LineComment,
FootComment: node.FootComment,
+11
View File
@@ -0,0 +1,11 @@
//go:build yq_nojson
package yqlib
func NewJSONDecoder() Decoder {
return nil
}
func NewJSONEncoder(indent int, colorise bool, unwrapScalar bool) Encoder {
return nil
}
+11
View File
@@ -0,0 +1,11 @@
//go:build yq_noxml
package yqlib
func NewXMLDecoder(prefs XmlPreferences) Decoder {
return nil
}
func NewXMLEncoder(indent int, prefs XmlPreferences) Encoder {
return nil
}
+1 -1
View File
@@ -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",
},
+1 -1
View File
@@ -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",
+13
View File
@@ -4,6 +4,11 @@ import (
"testing"
)
var mergeAnchorAssign = `a: &a
x: OriginalValue
b:
<<: *a`
var assignOperatorScenarios = []expressionScenario{
{
description: "Create yaml file",
@@ -20,6 +25,14 @@ var assignOperatorScenarios = []expressionScenario{
"D0, P[], (doc)::a: null\n",
},
},
{
skipDoc: true,
document: mergeAnchorAssign,
expression: `.c = .b | .a.x = "ModifiedValue" | explode(.)`,
expected: []string{
"D0, P[], (doc)::a:\n x: ModifiedValue\nb:\n x: ModifiedValue\nc:\n x: ModifiedValue\n",
},
},
{
skipDoc: true,
document: "{}",
+13 -5
View File
@@ -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",
},
+20 -6
View File
@@ -4,6 +4,7 @@ import (
"bufio"
"bytes"
"container/list"
"errors"
"regexp"
"strings"
@@ -39,6 +40,9 @@ func encodeToString(candidate *CandidateNode, prefs encoderPreferences) (string,
log.Debug("printing with indent: %v", prefs.indent)
encoder := configureEncoder(prefs.format, prefs.indent)
if encoder == nil {
return "", errors.New("no support for output format")
}
printer := NewPrinter(encoder, NewSinglePrinterWriter(bufio.NewWriter(&output)))
err := printer.PrintResults(candidate.AsList())
@@ -98,13 +102,11 @@ type decoderPreferences struct {
format InputFormat
}
/* takes a string and decodes it back into an object */
func decodeOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
preferences := expressionNode.Operation.Preferences.(decoderPreferences)
func createDecoder(format InputFormat) Decoder {
var decoder Decoder
switch preferences.format {
switch format {
case JsonInputFormat:
decoder = NewJSONDecoder()
case YamlInputFormat:
decoder = NewYamlDecoder(ConfiguredYamlPreferences)
case XMLInputFormat:
@@ -120,6 +122,18 @@ func decodeOperator(d *dataTreeNavigator, context Context, expressionNode *Expre
case UriInputFormat:
decoder = NewUriDecoder()
}
return decoder
}
/* takes a string and decodes it back into an object */
func decodeOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
preferences := expressionNode.Operation.Preferences.(decoderPreferences)
decoder := createDecoder(preferences.format)
if decoder == nil {
return Context{}, errors.New("no support for input format")
}
var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
+27 -18
View File
@@ -8,15 +8,17 @@ var prefix = "D0, P[], (doc)::a:\n cool:\n bob: dylan\n"
var encoderDecoderOperatorScenarios = []expressionScenario{
{
description: "Encode value as json string",
document: `{a: {cool: "thing"}}`,
expression: `.b = (.a | to_json)`,
requiresFormat: "json",
description: "Encode value as json string",
document: `{a: {cool: "thing"}}`,
expression: `.b = (.a | to_json)`,
expected: []string{
`D0, P[], (doc)::{a: {cool: "thing"}, b: "{\n \"cool\": \"thing\"\n}\n"}
`,
},
},
{
requiresFormat: "json",
description: "Encode value as json string, on one line",
subdescription: "Pass in a 0 indent to print json on a single line.",
document: `{a: {cool: "thing"}}`,
@@ -27,6 +29,7 @@ var encoderDecoderOperatorScenarios = []expressionScenario{
},
},
{
requiresFormat: "json",
description: "Encode value as json string, on one line shorthand",
subdescription: "Pass in a 0 indent to print json on a single line.",
document: `{a: {cool: "thing"}}`,
@@ -37,6 +40,7 @@ var encoderDecoderOperatorScenarios = []expressionScenario{
},
},
{
requiresFormat: "json",
description: "Decode a json encoded string",
subdescription: "Keep in mind JSON is a subset of YAML. If you want idiomatic yaml, pipe through the style operator to clear out the JSON styling.",
document: `a: '{"cool":"thing"}'`,
@@ -193,33 +197,37 @@ var encoderDecoderOperatorScenarios = []expressionScenario{
},
},
{
description: "Encode value as xml string",
document: `{a: {cool: {foo: "bar", +@id: hi}}}`,
expression: `.a | to_xml`,
requiresFormat: "xml",
description: "Encode value as xml string",
document: `{a: {cool: {foo: "bar", +@id: hi}}}`,
expression: `.a | to_xml`,
expected: []string{
"D0, P[a], (!!str)::<cool id=\"hi\">\n <foo>bar</foo>\n</cool>\n\n",
},
},
{
description: "Encode value as xml string on a single line",
document: `{a: {cool: {foo: "bar", +@id: hi}}}`,
expression: `.a | @xml`,
requiresFormat: "xml",
description: "Encode value as xml string on a single line",
document: `{a: {cool: {foo: "bar", +@id: hi}}}`,
expression: `.a | @xml`,
expected: []string{
"D0, P[a], (!!str)::<cool id=\"hi\"><foo>bar</foo></cool>\n\n",
},
},
{
description: "Encode value as xml string with custom indentation",
document: `{a: {cool: {foo: "bar", +@id: hi}}}`,
expression: `{"cat": .a | to_xml(1)}`,
requiresFormat: "xml",
description: "Encode value as xml string with custom indentation",
document: `{a: {cool: {foo: "bar", +@id: hi}}}`,
expression: `{"cat": .a | to_xml(1)}`,
expected: []string{
"D0, P[], (!!map)::cat: |\n <cool id=\"hi\">\n <foo>bar</foo>\n </cool>\n",
},
},
{
description: "Decode a xml encoded string",
document: `a: "<foo>bar</foo>"`,
expression: `.b = (.a | from_xml)`,
requiresFormat: "xml",
description: "Decode a xml encoded string",
document: `a: "<foo>bar</foo>"`,
expression: `.b = (.a | from_xml)`,
expected: []string{
"D0, P[], (doc)::a: \"<foo>bar</foo>\"\nb:\n foo: bar\n",
},
@@ -303,9 +311,10 @@ var encoderDecoderOperatorScenarios = []expressionScenario{
},
},
{
description: "empty xml decode",
skipDoc: true,
expression: `"" | @xmld`,
requiresFormat: "xml",
description: "empty xml decode",
skipDoc: true,
expression: `"" | @xmld`,
expected: []string{
"D0, P[], (!!null)::\n",
},
+2 -2
View File
@@ -47,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",
},
@@ -63,7 +63,7 @@ var equalsOperatorScenarios = []expressionScenario{
{
skipDoc: true,
document: "{}",
expression: "(.a != .b) as $x",
expression: "(.a != .b) as $x | .",
expected: []string{
"D0, P[], (doc)::{}\n",
},
+32
View File
@@ -0,0 +1,32 @@
package yqlib
import (
"container/list"
)
func filterOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debugf("-- filterOperation")
var results = list.New()
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
candidate := el.Value.(*CandidateNode)
children := context.SingleChildContext(candidate)
splatted, err := splat(children, traversePreferences{})
if err != nil {
return Context{}, err
}
filtered, err := selectOperator(d, splatted, expressionNode)
if err != nil {
return Context{}, err
}
selfExpression := &ExpressionNode{Operation: &Operation{OperationType: selfReferenceOpType}}
collected, err := collectTogether(d, filtered, selfExpression)
if err != nil {
return Context{}, err
}
collected.Node.Style = unwrapDoc(candidate.Node).Style
results.PushBack(collected)
}
return context.ChildContext(results), nil
}
+49
View File
@@ -0,0 +1,49 @@
package yqlib
import (
"testing"
)
var filterOperatorScenarios = []expressionScenario{
{
description: "Filter array",
document: `[1,2,3]`,
expression: `filter(. < 3)`,
expected: []string{
"D0, P[], (!!seq)::[1, 2]\n",
},
},
{
skipDoc: true,
document: `[1,2,3]`,
expression: `filter(. > 1)`,
expected: []string{
"D0, P[], (!!seq)::[2, 3]\n",
},
},
{
skipDoc: true,
description: "Filter array to empty",
document: `[1,2,3]`,
expression: `filter(. > 4)`,
expected: []string{
"D0, P[], (!!seq)::[]\n",
},
},
{
skipDoc: true,
description: "Filter empty array",
document: `[]`,
expression: `filter(. > 1)`,
expected: []string{
"D0, P[], (!!seq)::[]\n",
},
},
}
func TestFilterOperatorScenarios(t *testing.T) {
for _, tt := range filterOperatorScenarios {
testScenario(t, &tt)
}
documentOperatorScenarios(t, "filter", filterOperatorScenarios)
}
+1 -1
View File
@@ -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",
},
+3
View File
@@ -34,6 +34,9 @@ func loadString(filename string) (*CandidateNode, error) {
}
func loadYaml(filename string, decoder Decoder) (*CandidateNode, error) {
if decoder == nil {
return nil, fmt.Errorf("could not load %s", filename)
}
file, err := os.Open(filename) // #nosec
if err != nil {
+4 -3
View File
@@ -74,9 +74,10 @@ var loadScenarios = []expressionScenario{
},
},
{
description: "Load from XML",
document: "cool: things",
expression: `.more_stuff = load_xml("../../examples/small.xml")`,
requiresFormat: "xml",
description: "Load from XML",
document: "cool: things",
expression: `.more_stuff = load_xml("../../examples/small.xml")`,
expected: []string{
"D0, P[], (doc)::cool: things\nmore_stuff:\n this: is some xml\n",
},
+3 -3
View File
@@ -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 {
+1 -1
View File
@@ -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",
},
+1 -1
View File
@@ -151,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",
},
+1 -1
View File
@@ -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",
},
+69 -12
View File
@@ -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
}
+18 -2
View File
@@ -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|...)",
+18
View File
@@ -28,6 +28,7 @@ type expressionScenario struct {
skipDoc bool
expectedError string
dontFormatInputForDoc bool // dont format input doc for documentation generation
requiresFormat string
}
func TestMain(m *testing.M) {
@@ -103,6 +104,23 @@ func testScenario(t *testing.T, s *expressionScenario) {
return
}
if s.requiresFormat != "" {
format := s.requiresFormat
inputFormat, err := InputFormatFromString(format)
if err != nil {
t.Error(err)
}
if decoder := createDecoder(inputFormat); decoder == nil {
t.Skipf("no support for %s input format", format)
}
outputFormat, err := OutputFormatFromString(format)
if err != nil {
t.Error(err)
}
if encoder := configureEncoder(outputFormat, 4); encoder == nil {
t.Skipf("no support for %s output format", format)
}
}
if err != nil {
t.Error(fmt.Errorf("%w: %v: %v", err, s.description, s.expression))
return
+14
View File
@@ -0,0 +1,14 @@
package yqlib
// orderedMap allows to marshal and unmarshal JSON and YAML values keeping the
// order of keys and values in a map or an object.
type orderedMap struct {
// if this is an object, kv != nil. If this is not an object, kv == nil.
kv []orderedMapKV
altVal interface{}
}
type orderedMapKV struct {
K string
V orderedMap
}
+83
View File
@@ -0,0 +1,83 @@
package yqlib
import (
"bytes"
"encoding/json"
"errors"
"io"
)
func (o *orderedMap) UnmarshalJSON(data []byte) error {
switch data[0] {
case '{':
// initialise so that even if the object is empty it is not nil
o.kv = []orderedMapKV{}
// create decoder
dec := json.NewDecoder(bytes.NewReader(data))
_, err := dec.Token() // open object
if err != nil {
return err
}
// cycle through k/v
var tok json.Token
for tok, err = dec.Token(); err == nil; tok, err = dec.Token() {
// we can expect two types: string or Delim. Delim automatically means
// that it is the closing bracket of the object, whereas string means
// that there is another key.
if _, ok := tok.(json.Delim); ok {
break
}
kv := orderedMapKV{
K: tok.(string),
}
if err := dec.Decode(&kv.V); err != nil {
return err
}
o.kv = append(o.kv, kv)
}
// unexpected error
if err != nil && !errors.Is(err, io.EOF) {
return err
}
return nil
case '[':
var res []*orderedMap
if err := json.Unmarshal(data, &res); err != nil {
return err
}
o.altVal = res
o.kv = nil
return nil
}
return json.Unmarshal(data, &o.altVal)
}
func (o orderedMap) MarshalJSON() ([]byte, error) {
buf := new(bytes.Buffer)
enc := json.NewEncoder(buf)
enc.SetEscapeHTML(false) // do not escape html chars e.g. &, <, >
if o.kv == nil {
if err := enc.Encode(o.altVal); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
buf.WriteByte('{')
for idx, el := range o.kv {
if err := enc.Encode(el.K); err != nil {
return nil, err
}
buf.WriteByte(':')
if err := enc.Encode(el.V); err != nil {
return nil, err
}
if idx != len(o.kv)-1 {
buf.WriteByte(',')
}
}
buf.WriteByte('}')
return buf.Bytes(), nil
}
+79
View File
@@ -0,0 +1,79 @@
package yqlib
import (
"fmt"
yaml "gopkg.in/yaml.v3"
)
func (o *orderedMap) UnmarshalYAML(node *yaml.Node) error {
switch node.Kind {
case yaml.DocumentNode:
if len(node.Content) == 0 {
return nil
}
return o.UnmarshalYAML(node.Content[0])
case yaml.AliasNode:
return o.UnmarshalYAML(node.Alias)
case yaml.ScalarNode:
return node.Decode(&o.altVal)
case yaml.MappingNode:
// set kv to non-nil
o.kv = []orderedMapKV{}
for i := 0; i < len(node.Content); i += 2 {
var key string
var val orderedMap
if err := node.Content[i].Decode(&key); err != nil {
return err
}
if err := node.Content[i+1].Decode(&val); err != nil {
return err
}
o.kv = append(o.kv, orderedMapKV{
K: key,
V: val,
})
}
return nil
case yaml.SequenceNode:
// note that this has to be a pointer, so that nulls can be represented.
var res []*orderedMap
if err := node.Decode(&res); err != nil {
return err
}
o.altVal = res
o.kv = nil
return nil
case 0:
// null
o.kv = nil
o.altVal = nil
return nil
default:
return fmt.Errorf("orderedMap: invalid yaml node")
}
}
func (o *orderedMap) MarshalYAML() (interface{}, error) {
// fast path: kv is nil, use altVal
if o.kv == nil {
return o.altVal, nil
}
content := make([]*yaml.Node, 0, len(o.kv)*2)
for _, val := range o.kv {
n := new(yaml.Node)
if err := n.Encode(val.V); err != nil {
return nil, err
}
content = append(content, &yaml.Node{
Kind: yaml.ScalarNode,
Tag: "!!str",
Value: val.K,
}, n)
}
return &yaml.Node{
Kind: yaml.MappingNode,
Tag: "!!map",
Content: content,
}, nil
}
+2 -2
View File
@@ -33,11 +33,11 @@ const (
func OutputFormatFromString(format string) (PrinterOutputFormat, error) {
switch format {
case "yaml", "y":
case "yaml", "y", "yml":
return YamlOutputFormat, nil
case "json", "j":
return JSONOutputFormat, nil
case "props", "p":
case "props", "p", "properties":
return PropsOutputFormat, nil
case "csv", "c":
return CSVOutputFormat, nil
+5 -1
View File
@@ -314,7 +314,11 @@ func TestPrinterMultipleDocsJson(t *testing.T) {
var writer = bufio.NewWriter(&output)
// note printDocSeparators is true, it should still not print document separators
// when outputing JSON.
printer := NewPrinter(NewJSONEncoder(0, false, false), NewSinglePrinterWriter(writer))
encoder := NewJSONEncoder(0, false, false)
if encoder == nil {
t.Skipf("no support for %s output format", "json")
}
printer := NewPrinter(encoder, NewSinglePrinterWriter(writer))
inputs, err := readDocuments(strings.NewReader(multiDocSample), "sample.yml", 0, NewYamlDecoder(ConfiguredYamlPreferences))
if err != nil {
+105 -5
View File
@@ -1,3 +1,5 @@
//go:build !yq_noxml
package yqlib
import (
@@ -8,6 +10,29 @@ import (
"github.com/mikefarah/yq/v4/test"
)
const yamlInputWithProcInstAndHeadComment = `# cats
+p_xml: version="1.0"
this: is some xml`
const expectedXmlProcInstAndHeadComment = `<?xml version="1.0"?>
<!-- cats -->
<this>is some xml</this>
`
const xmlProcInstAndHeadCommentBlock = `<?xml version="1.0"?>
<!--
cats
-->
<this>is some xml</this>
`
const expectedYamlProcInstAndHeadCommentBlock = `#
# cats
#
+p_xml: version="1.0"
this: is some xml
`
const inputXMLWithComments = `
<!-- before cat -->
<cat>
@@ -126,7 +151,8 @@ cat:
# after cat
`
const expectedRoundtripXMLWithComments = `<!-- before cat --><cat><!-- in cat before -->
const expectedRoundtripXMLWithComments = `<!-- before cat -->
<cat><!-- in cat before -->
<x>3<!-- multi
line comment
for x --></x><!-- before y -->
@@ -137,8 +163,10 @@ in d before -->
</cat><!-- after cat -->
`
const yamlWithComments = `# header comment
const yamlWithComments = `#
# header comment
# above_cat
#
cat: # inline_cat
# above_array
array: # inline_array
@@ -149,9 +177,11 @@ cat: # inline_cat
`
const expectedXMLWithComments = `<!--
header comment
above_cat
--><!-- inline_cat --><cat><!-- above_array inline_array -->
header comment
above_cat
-->
<!-- inline_cat -->
<cat><!-- above_array inline_array -->
<array>val1<!-- inline_val1 --></array>
<array><!-- above_val2 -->val2<!-- inline_val2 --></array>
</cat><!-- below_cat -->
@@ -264,6 +294,41 @@ var xmlScenarios = []formatScenario{
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: "ProcInst with head comment",
skipDoc: true,
input: yamlInputWithProcInstAndHeadComment,
expected: expectedXmlProcInstAndHeadComment,
scenarioType: "encode",
},
{
description: "ProcInst with head comment round trip",
skipDoc: true,
input: expectedXmlProcInstAndHeadComment,
expected: expectedXmlProcInstAndHeadComment,
scenarioType: "roundtrip",
},
{
description: "ProcInst with block head comment to yaml",
skipDoc: true,
input: xmlProcInstAndHeadCommentBlock,
expected: expectedYamlProcInstAndHeadCommentBlock,
scenarioType: "decode",
},
{
description: "ProcInst with block head comment from yaml",
skipDoc: true,
input: expectedYamlProcInstAndHeadCommentBlock,
expected: xmlProcInstAndHeadCommentBlock,
scenarioType: "encode",
},
{
description: "ProcInst with head comment round trip block",
skipDoc: true,
input: xmlProcInstAndHeadCommentBlock,
expected: xmlProcInstAndHeadCommentBlock,
scenarioType: "roundtrip",
},
{
description: "Parse xml: simple",
subdescription: "Notice how all the values are strings, see the next example on how you can fix that.",
@@ -464,6 +529,41 @@ var xmlScenarios = []formatScenario{
expected: "<cat name=\"tiger\">cool</cat>\n",
scenarioType: "encode",
},
{
description: "round trip multiline 1",
skipDoc: true,
input: "<x><!-- cats --></x>\n",
expected: "<x><!-- cats --></x>\n",
scenarioType: "roundtrip",
},
{
description: "round trip multiline 2",
skipDoc: true,
input: "<x><!--\n cats\n --></x>\n",
expected: "<x><!--\ncats\n--></x>\n",
scenarioType: "roundtrip",
},
{
description: "round trip multiline 3",
skipDoc: true,
input: "<x><!--\n\tcats\n --></x>\n",
expected: "<x><!--\n\tcats\n--></x>\n",
scenarioType: "roundtrip",
},
{
description: "round trip multiline 4",
skipDoc: true,
input: "<x><!--\n\tcats\n\tdogs\n--></x>\n",
expected: "<x><!--\n\tcats\n\tdogs\n--></x>\n",
scenarioType: "roundtrip",
},
{
description: "round trip multiline 5",
skipDoc: true, // pity spaces aren't kept atm.
input: "<x><!--\ncats\ndogs\n--></x>\n",
expected: "<x><!--\ncats\ndogs\n--></x>\n",
scenarioType: "roundtrip",
},
{
description: "Encode xml: comments",
subdescription: "A best attempt is made to copy comments to xml.",
+7
View File
@@ -1,3 +1,10 @@
4.31.2:
- Fixed variable handling #1458, #1566
- Fixed merged anchor reference problem #1482
- Fixed xml encoding of ProcInst #1563, improved XML comment handling
- Allow build without json and xml support (#1556) Thanks @afbjorklund
- Bumped dependencies
4.31.1:
- Added shuffle command #1503
- Added ability to sort by multiple fields #1541
+1 -1
View File
@@ -1,5 +1,5 @@
name: yq
version: 'v4.31.1'
version: 'v4.31.2'
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.