Compare commits

..

No commits in common. "76c817009f99310f7719fc006e833b00036e8108" and "64bee0fbe34e87e4342b92d0fc6a18de27780bd4" have entirely different histories.

123 changed files with 653 additions and 5036 deletions

51
.github/ISSUE_TEMPLATE/bug_report_v3.md vendored Normal file
View File

@ -0,0 +1,51 @@
---
name: Bug report - V3
about: Create a report to help us improve
title: ''
labels: bug, v3
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
Note that any how to questions should be posted in the discussion board and not raised as an issue.
Version of yq: 3.X.X
Operating system: mac/linux/windows/....
Installed via: docker/binary release/homebrew/snap/...
**Input Yaml**
Concise yaml document(s) (as simple as possible to show the bug, please keep it to 10 lines or less)
data1.yml:
```yaml
this: should really work
```
data2.yml:
```yaml
but: it strangely didn't
```
**Command**
The command you ran:
```
yq merge data1.yml data2.yml
```
**Actual behavior**
```yaml
cat: meow
```
**Expected behavior**
```yaml
this: should really work
but: it strangely didn't
```
**Additional context**
Add any other context about the problem here.

View File

@ -51,7 +51,7 @@ jobs:
IMAGE_VERSION=${VERSION:1}
echo "IMAGE_VERSION: ${IMAGE_VERSION}"
PLATFORMS="linux/amd64,linux/ppc64le,linux/arm64,linux/arm/v7,linux/s390x"
PLATFORMS="linux/amd64,linux/ppc64le,linux/arm64,linux/arm/v7"
echo "Building and pushing version ${IMAGE_VERSION} of image ${IMAGE_NAME}"
docker buildx build \
@ -95,6 +95,6 @@ jobs:
-t "${IMAGE_NAME}:4-githubaction" \
-t "${IMAGE_NAME}:latest-githubaction" \
-t "ghcr.io/${IMAGE_NAME}:${IMAGE_VERSION}-githubaction" \
-t "ghcr.io/${IMAGE_NAME}:4-githubaction" \
-t "ghrc.io/${IMAGE_NAME}:4-githubaction" \
-t "ghcr.io/${IMAGE_NAME}:latest-githubaction" \
.

View File

@ -1,38 +0,0 @@
run:
timeout: 5m
linters:
enable:
- asciicheck
- depguard
- errorlint
- gci
- gochecknoinits
- gofmt
- goimports
- gosec
- gosimple
- staticcheck
- unused
- misspell
- nakedret
- nolintlint
- predeclared
- revive
- unconvert
- unparam
linters-settings:
depguard:
rules:
prevent_unmaintained_packages:
list-mode: lax
files:
- $all
- "!$test"
deny:
- pkg: io/ioutil
desc: "replaced by io and os packages since Go 1.16: https://tip.golang.org/doc/go1.16#ioutil"
issues:
exclude-rules:
- linters:
- revive
text: "var-naming"

View File

@ -1,11 +1,18 @@
version: "2"
run:
timeout: 5m
linters:
enable:
- asciicheck
- depguard
- errorlint
- gci
- gochecknoinits
- gofmt
- goimports
- gosec
- gosimple
- staticcheck
- unused
- misspell
- nakedret
- nolintlint
@ -13,40 +20,19 @@ linters:
- revive
- unconvert
- unparam
settings:
depguard:
rules:
prevent_unmaintained_packages:
list-mode: lax
files:
- $all
- '!$test'
deny:
- pkg: io/ioutil
desc: 'replaced by io and os packages since Go 1.16: https://tip.golang.org/doc/go1.16#ioutil'
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
linters-settings:
depguard:
rules:
- linters:
- revive
text: var-naming
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gci
- gofmt
- goimports
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
prevent_unmaintained_packages:
list-mode: lax
files:
- $all
- "!$test"
deny:
- pkg: io/ioutil
desc: "replaced by io and os packages since Go 1.16: https://tip.golang.org/doc/go1.16#ioutil"
issues:
exclude-rules:
- linters:
- revive
text: "var-naming"

View File

@ -38,8 +38,6 @@ builds:
- openbsd_amd64
- windows_386
- windows_amd64
- windows_arm
- windows_arm64
no_unique_dist_dir: true

View File

@ -1,4 +1,4 @@
FROM golang:1.24.5 AS builder
FROM golang:1.24.1 AS builder
WORKDIR /go/src/mikefarah/yq

View File

@ -1,4 +1,4 @@
FROM golang:1.24.5
FROM golang:1.24.1
RUN apt-get update && \
apt-get install -y npm && \

View File

@ -3,7 +3,7 @@
![Build](https://github.com/mikefarah/yq/workflows/Build/badge.svg) ![Docker Pulls](https://img.shields.io/docker/pulls/mikefarah/yq.svg) ![Github Releases (by Release)](https://img.shields.io/github/downloads/mikefarah/yq/total.svg) ![Go Report](https://goreportcard.com/badge/github.com/mikefarah/yq) ![CodeQL](https://github.com/mikefarah/yq/workflows/CodeQL/badge.svg)
a lightweight and portable command-line YAML, JSON, INI and XML processor. `yq` uses [jq](https://github.com/stedolan/jq) like syntax but works with yaml files as well as json, xml, ini, properties, csv and tsv. It doesn't yet support everything `jq` does - but it does support the most common operations and functions, and more is being added continuously.
a lightweight and portable command-line YAML, JSON and XML processor. `yq` uses [jq](https://github.com/stedolan/jq) like syntax but works with yaml files as well as json, xml, properties, csv and tsv. It doesn't yet support everything `jq` does - but it does support the most common operations and functions, and more is being added continuously.
yq is written in go - so you can download a dependency free binary for your platform and you are good to go! If you prefer there are a variety of package managers that can be used as well as Docker and Podman, all listed below.
@ -76,21 +76,21 @@ For instance, VERSION=v4.2.0 and BINARY=yq_linux_amd64
#### Compressed via tar.gz
```bash
wget https://github.com/mikefarah/yq/releases/download/${VERSION}/${BINARY}.tar.gz -O - |\
tar xz && mv ${BINARY} /usr/local/bin/yq
tar xz && mv ${BINARY} /usr/bin/yq
```
#### Plain binary
```bash
wget https://github.com/mikefarah/yq/releases/download/${VERSION}/${BINARY} -O /usr/local/bin/yq &&\
chmod +x /usr/local/bin/yq
wget https://github.com/mikefarah/yq/releases/download/${VERSION}/${BINARY} -O /usr/bin/yq &&\
chmod +x /usr/bin/yq
```
#### Latest version
```bash
wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/local/bin/yq &&\
chmod +x /usr/local/bin/yq
wget https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64 -O /usr/bin/yq &&\
chmod +x /usr/bin/yq
```
### MacOS / Linux via Homebrew:
@ -327,7 +327,7 @@ flox install yq
## Features
- [Detailed documentation with many examples](https://mikefarah.gitbook.io/yq/)
- Written in portable go, so you can download a lovely dependency free binary
- Uses similar syntax as `jq` but works with YAML, INI, [JSON](https://mikefarah.gitbook.io/yq/usage/convert) and [XML](https://mikefarah.gitbook.io/yq/usage/xml) files
- Uses similar syntax as `jq` but works with YAML, [JSON](https://mikefarah.gitbook.io/yq/usage/convert) and [XML](https://mikefarah.gitbook.io/yq/usage/xml) files
- Fully supports multi document yaml files
- Supports yaml [front matter](https://mikefarah.gitbook.io/yq/usage/front-matter) blocks (e.g. jekyll/assemble)
- Colorized yaml output
@ -366,52 +366,31 @@ yq -i '.stuff = "foo"' myfile.yml # update myfile.yml in place
Available Commands:
completion Generate the autocompletion script for the specified shell
eval (default) Apply the expression to each document in each yaml file in sequence
eval-all Loads _all_ yaml documents of _all_ yaml files and runs expression once
help Help about any command
completion Generate the autocompletion script for the specified shell
eval (default) Apply the expression to each document in each yaml file in sequence
eval-all Loads _all_ yaml documents of _all_ yaml files and runs expression once
help Help about any command
Flags:
-C, --colors force print with colors
--csv-auto-parse parse CSV YAML/JSON values (default true)
--csv-separator char CSV Separator character (default ,)
-e, --exit-status set exit status if there are no matches or null or false is returned
--expression string forcibly set the expression argument. Useful when yq argument detection thinks your expression is a file.
--from-file string Load expression from specified file.
-f, --front-matter string (extract|process) first input as yaml front-matter. Extract will pull out the yaml content, process will run the expression against the yaml content, leaving the remaining data intact
--header-preprocess Slurp any header comments and separators before processing expression. (default true)
-h, --help help for yq
-I, --indent int sets indent level for output (default 2)
-i, --inplace update the file in place of first file given.
-p, --input-format string [auto|a|yaml|y|json|j|props|p|csv|c|tsv|t|xml|x|base64|uri|toml|lua|l|ini|i] parse format for input. (default "auto")
--lua-globals output keys as top-level global variables
--lua-prefix string prefix (default "return ")
--lua-suffix string suffix (default ";\n")
--lua-unquoted output unquoted string keys (e.g. {foo="bar"})
-p, --input-format string [yaml|y|xml|x] parse format for input. Note that json is a subset of yaml. (default "yaml")
-M, --no-colors force print with no colors
-N, --no-doc Don't print document separators (---)
-0, --nul-output Use NUL char to separate values. If unwrap scalar is also set, fail if unwrapped scalar contains NUL char.
-n, --null-input Don't read input, simply evaluate the expression given. Useful for creating docs from scratch.
-o, --output-format string [auto|a|yaml|y|json|j|props|p|csv|c|tsv|t|xml|x|base64|uri|toml|shell|s|lua|l|ini|i] output format type. (default "auto")
-o, --output-format string [yaml|y|json|j|props|p|xml|x] output format type. (default "yaml")
-P, --prettyPrint pretty print, shorthand for '... style = ""'
--properties-array-brackets use [x] in array paths (e.g. for SpringBoot)
--properties-separator string separator to use between keys and values (default " = ")
-s, --split-exp string print each result (or doc) into a file named (exp). [exp] argument must return a string. You can use $index in the expression as the result counter. The necessary directories will be created.
--split-exp-file string Use a file to specify the split-exp expression.
--string-interpolation Toggles strings interpolation of \(exp) (default true)
--tsv-auto-parse parse TSV YAML/JSON values (default true)
-r, --unwrapScalar unwrap scalar, print the value with no quotes, colors or comments. Defaults to true for yaml (default true)
-s, --split-exp string print each result (or doc) into a file named (exp). [exp] argument must return a string. You can use $index in the expression as the result counter.
--unwrapScalar unwrap scalar, print the value with no quotes, colors or comments (default true)
-v, --verbose verbose mode
-V, --version Print version information and quit
--xml-attribute-prefix string prefix for xml attributes (default "+@")
--xml-attribute-prefix string prefix for xml attributes (default "+")
--xml-content-name string name for xml content (if no attribute name is present). (default "+content")
--xml-directive-name string name for xml directives (e.g. <!DOCTYPE thing cat>) (default "+directive")
--xml-keep-namespace enables keeping namespace after parsing attributes (default true)
--xml-proc-inst-prefix string prefix for xml processing instructions (e.g. <?xml version="1"?>) (default "+p_")
--xml-raw-token enables using RawToken method instead Token. Commonly disables namespace translations. See https://pkg.go.dev/encoding/xml#Decoder.RawToken for details. (default true)
--xml-skip-directives skip over directives (e.g. <!DOCTYPE thing cat>)
--xml-skip-proc-inst skip over process instructions (e.g. <?xml version="1"?>)
--xml-strict-mode enables strict parsing of XML. See https://pkg.go.dev/encoding/xml for more details.
Use "yq [command] --help" for more information about a command.
```

View File

@ -3,7 +3,7 @@
testLoadFileNotExist() {
result=$(./yq e -n 'load("cat.yml")' 2>&1)
assertEquals 1 $?
assertEquals "Error: failed to load cat.yml: open cat.yml: no such file or directory" "$result"
assertEquals "Error: Failed to load cat.yml: open cat.yml: no such file or directory" "$result"
}
testLoadFileExpNotExist() {
@ -15,7 +15,7 @@ testLoadFileExpNotExist() {
testStrLoadFileNotExist() {
result=$(./yq e -n 'strload("cat.yml")' 2>&1)
assertEquals 1 $?
assertEquals "Error: failed to load cat.yml: open cat.yml: no such file or directory" "$result"
assertEquals "Error: Failed to load cat.yml: open cat.yml: no such file or directory" "$result"
}
testStrLoadFileExpNotExist() {

View File

@ -2,8 +2,6 @@ package cmd
var unwrapScalarFlag = newUnwrapFlag()
var printNodeInfo = false
var unwrapScalar = false
var writeInplace = false

View File

@ -105,11 +105,6 @@ func evaluateSequence(cmd *cobra.Command, args []string) (cmdError error) {
}
printer := yqlib.NewPrinter(encoder, printerWriter)
if printNodeInfo {
printer = yqlib.NewNodeInfoPrinter(printerWriter)
}
if nulSepOutput {
printer.SetNulSepOutput(true)
}

View File

@ -99,7 +99,6 @@ yq -P -oy sample.json
}
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "verbose mode")
rootCmd.PersistentFlags().BoolVarP(&printNodeInfo, "debug-node-info", "", false, "debug node info")
rootCmd.PersistentFlags().BoolVarP(&outputToJSON, "tojson", "j", false, "(deprecated) output as json. Set indent to 0 to print json in one line.")
err := rootCmd.PersistentFlags().MarkDeprecated("tojson", "please use -o=json instead")
@ -197,7 +196,6 @@ yq -P -oy sample.json
panic(err)
}
rootCmd.PersistentFlags().BoolVarP(&yqlib.ConfiguredYamlPreferences.LeadingContentPreProcessing, "header-preprocess", "", true, "Slurp any header comments and separators before processing expression.")
rootCmd.PersistentFlags().BoolVarP(&yqlib.ConfiguredYamlPreferences.FixMergeAnchorToSpec, "yaml-fix-merge-anchor-to-spec", "", false, "Fix merge anchor to match YAML spec. Will default to true in late 2025")
rootCmd.PersistentFlags().StringVarP(&splitFileExp, "split-exp", "s", "", "print each result (or doc) into a file named (exp). [exp] argument must return a string. You can use $index in the expression as the result counter. The necessary directories will be created.")
if err = rootCmd.RegisterFlagCompletionFunc("split-exp", cobra.NoFileCompletions); err != nil {

View File

@ -18,100 +18,52 @@ func isAutomaticOutputFormat() bool {
func initCommand(cmd *cobra.Command, args []string) (string, []string, error) {
cmd.SilenceUsage = true
setupColors()
fileInfo, _ := os.Stdout.Stat()
if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) {
colorsEnabled = true
}
expression, args, err := processArgs(args)
if err != nil {
return "", nil, err
}
if err := loadSplitFileExpression(); err != nil {
return "", nil, err
}
handleBackwardsCompatibility()
if err := validateCommandFlags(args); err != nil {
return "", nil, err
}
if err := configureFormats(args); err != nil {
return "", nil, err
}
configureUnwrapScalar()
return expression, args, nil
}
func setupColors() {
fileInfo, _ := os.Stdout.Stat()
if forceColor || (!forceNoColor && (fileInfo.Mode()&os.ModeCharDevice) != 0) {
colorsEnabled = true
}
}
func loadSplitFileExpression() error {
if splitFileExpFile != "" {
splitExpressionBytes, err := os.ReadFile(splitFileExpFile)
if err != nil {
return err
return "", nil, err
}
splitFileExp = string(splitExpressionBytes)
}
return nil
}
func handleBackwardsCompatibility() {
// backwards compatibility
if outputToJSON {
outputFormat = "json"
}
}
func validateCommandFlags(args []string) error {
if writeInplace && (len(args) == 0 || args[0] == "-") {
return fmt.Errorf("write in place flag only applicable when giving an expression and at least one file")
return "", nil, fmt.Errorf("write in place flag only applicable when giving an expression and at least one file")
}
if frontMatter != "" && len(args) == 0 {
return fmt.Errorf("front matter flag only applicable when giving an expression and at least one file")
return "", nil, fmt.Errorf("front matter flag only applicable when giving an expression and at least one file")
}
if writeInplace && splitFileExp != "" {
return fmt.Errorf("write in place cannot be used with split file")
return "", nil, fmt.Errorf("write in place cannot be used with split file")
}
if nullInput && len(args) > 0 {
return fmt.Errorf("cannot pass files in when using null-input flag")
return "", nil, fmt.Errorf("cannot pass files in when using null-input flag")
}
return nil
}
func configureFormats(args []string) error {
inputFilename := ""
if len(args) > 0 {
inputFilename = args[0]
}
if err := configureInputFormat(inputFilename); err != nil {
return err
}
if err := configureOutputFormat(); err != nil {
return err
}
yqlib.GetLogger().Debug("Using input format %v", inputFormat)
yqlib.GetLogger().Debug("Using output format %v", outputFormat)
return nil
}
func configureInputFormat(inputFilename string) error {
if inputFormat == "" || inputFormat == "auto" || inputFormat == "a" {
inputFormat = yqlib.FormatStringFromFilename(inputFilename)
_, err := yqlib.FormatFromString(inputFormat)
@ -136,27 +88,24 @@ func configureInputFormat(inputFilename string) error {
}
outputFormat = "yaml"
}
return nil
}
func configureOutputFormat() error {
outputFormatType, err := yqlib.FormatFromString(outputFormat)
if err != nil {
return err
return "", nil, err
}
yqlib.GetLogger().Debug("Using input format %v", inputFormat)
yqlib.GetLogger().Debug("Using output format %v", outputFormat)
if outputFormatType == yqlib.YamlFormat ||
outputFormatType == yqlib.PropertiesFormat {
unwrapScalar = true
}
return nil
}
func configureUnwrapScalar() {
if unwrapScalarFlag.IsExplicitlySet() {
unwrapScalar = unwrapScalarFlag.IsSet()
}
return expression, args, nil
}
func configureDecoder(evaluateTogether bool) (yqlib.Decoder, error) {

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,7 @@ var (
GitDescribe string
// Version is main version number that is being run at the moment.
Version = "v4.47.1"
Version = "v4.45.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
@ -45,5 +45,5 @@ func getHumanVersion() string {
}
// Strip off any single quotes added by the git information.
return strings.ReplaceAll(version, "'", "")
return strings.Replace(version, "'", "", -1)
}

View File

@ -1,8 +1,3 @@
# 001
---
abc: # 001
- 1 # one
- 2 # two
---
def # 002
Foo: 3
apple: 1
bar: 2

View File

@ -1,6 +1,6 @@
---
a: apple
b: banana
b: bannana
---
hello there
apples: great

View File

@ -1,14 +0,0 @@
; This is a INI document
[owner]
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00-08:00
[database]
db_host = "localhost"
db_port = 5432
db_user = "postgres"
db_password = ""
db_name = "postgres"

View File

@ -1,8 +0,0 @@
; This is a INI document
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00-08:00
enabled = true
ip = "10.0.0.1"
role = "frontend"
treads = 4

31
go.mod
View File

@ -1,35 +1,32 @@
module github.com/mikefarah/yq/v4
require (
github.com/a8m/envsubst v1.4.3
github.com/alecthomas/participle/v2 v2.1.4
github.com/alecthomas/repr v0.5.1
github.com/a8m/envsubst v1.4.2
github.com/alecthomas/participle/v2 v2.1.1
github.com/alecthomas/repr v0.4.0
github.com/dimchansky/utfbom v1.1.1
github.com/elliotchance/orderedmap v1.8.0
github.com/fatih/color v1.18.0
github.com/go-ini/ini v1.67.0
github.com/goccy/go-json v0.10.5
github.com/goccy/go-yaml v1.18.0
github.com/goccy/go-yaml v1.13.3
github.com/jinzhu/copier v0.4.0
github.com/magiconair/properties v1.8.10
github.com/pelletier/go-toml/v2 v2.2.4
github.com/magiconair/properties v1.8.9
github.com/pelletier/go-toml/v2 v2.2.3
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e
github.com/spf13/cobra v1.9.1
github.com/spf13/pflag v1.0.7
github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.6
github.com/yuin/gopher-lua v1.1.1
go.yaml.in/yaml/v3 v3.0.4
golang.org/x/net v0.42.0
golang.org/x/text v0.27.0
golang.org/x/net v0.34.0
golang.org/x/text v0.23.0
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/sys v0.29.0 // indirect
)
go 1.24
toolchain go1.24.1
go 1.23.0

80
go.sum
View File

@ -1,64 +1,78 @@
github.com/a8m/envsubst v1.4.3 h1:kDF7paGK8QACWYaQo6KtyYBozY2jhQrTuNNuUxQkhJY=
github.com/a8m/envsubst v1.4.3/go.mod h1:4jjHWQlZoaXPoLQUb7H2qT4iLkZDdmEQiOUogdUmqVU=
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
github.com/alecthomas/participle/v2 v2.1.4 h1:W/H79S8Sat/krZ3el6sQMvMaahJ+XcM9WSI2naI7w2U=
github.com/alecthomas/participle/v2 v2.1.4/go.mod h1:8tqVbpTX20Ru4NfYQgZf4mP18eXPTBViyMWiArNEgGI=
github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg=
github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
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.3.0 h1:mAsH2wmvjsuvyBvAmCtm7zFsBlb8mIHx5ySLVdDZXL0=
github.com/alecthomas/assert/v2 v2.3.0/go.mod h1:pXcQ2Asjp247dahGEmsZ6ru0UVwnkhktn7S0bBDLxvQ=
github.com/alecthomas/participle/v2 v2.1.1 h1:hrjKESvSqGHzRb4yW1ciisFJ4p3MGYih6icjJvbsmV8=
github.com/alecthomas/participle/v2 v2.1.1/go.mod h1:Y1+hAs8DHPmc3YUFzqllV+eSQ9ljPTk0ZkPMtEdAx2c=
github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc=
github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/elliotchance/orderedmap v1.8.0 h1:TrOREecvh3JbS+NCgwposXG5ZTFHtEsQiCGOhPElnMw=
github.com/elliotchance/orderedmap v1.8.0/go.mod h1:wsDwEaX5jEoyhbs7x93zk2H/qv0zwuhg4inXhDkYqys=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/goccy/go-yaml v1.13.3 h1:IXRULR8mAa0MXQobzzp0VOfMUJ8EnaQ4x3jhf7S0/nI=
github.com/goccy/go-yaml v1.13.3/go.mod h1:IjYwxUiJDoqpx2RmbdjMUceGHZwYLon3sfOGl5Hi9lc=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
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.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473 h1:6D+BvnJ/j6e222UW8s2qTSe3wGBtvo0MbVQG/c5k8RE=

View File

@ -53,20 +53,6 @@ func createScalarNode(value interface{}, stringValue string) *CandidateNode {
return node
}
type NodeInfo struct {
Kind string `yaml:"kind"`
Style string `yaml:"style,omitempty"`
Anchor string `yaml:"anchor,omitempty"`
Tag string `yaml:"tag,omitempty"`
HeadComment string `yaml:"headComment,omitempty"`
LineComment string `yaml:"lineComment,omitempty"`
FootComment string `yaml:"footComment,omitempty"`
Value string `yaml:"value,omitempty"`
Line int `yaml:"line,omitempty"`
Column int `yaml:"column,omitempty"`
Content []*NodeInfo `yaml:"content,omitempty"`
}
type CandidateNode struct {
Kind Kind
Style Style
@ -169,18 +155,6 @@ func (n *CandidateNode) getParsedKey() interface{} {
}
func (n *CandidateNode) FilterMapContentByKey(keyPredicate func(*CandidateNode) bool) []*CandidateNode {
var result []*CandidateNode
for index := 0; index < len(n.Content); index = index + 2 {
keyNode := n.Content[index]
valueNode := n.Content[index+1]
if keyPredicate(keyNode) {
result = append(result, keyNode, valueNode)
}
}
return result
}
func (n *CandidateNode) GetPath() []interface{} {
key := n.getParsedKey()
if n.Parent != nil && key != nil {
@ -227,14 +201,13 @@ func (n *CandidateNode) SetParent(parent *CandidateNode) {
type ValueVisitor func(*CandidateNode) error
func (n *CandidateNode) VisitValues(visitor ValueVisitor) error {
switch n.Kind {
case MappingNode:
if n.Kind == MappingNode {
for i := 1; i < len(n.Content); i = i + 2 {
if err := visitor(n.Content[i]); err != nil {
return err
}
}
case SequenceNode:
} else if n.Kind == SequenceNode {
for i := 0; i < len(n.Content); i = i + 1 {
if err := visitor(n.Content[i]); err != nil {
return err
@ -265,13 +238,14 @@ func (n *CandidateNode) AddKeyValueChild(rawKey *CandidateNode, rawValue *Candid
func (n *CandidateNode) AddChild(rawChild *CandidateNode) {
value := rawChild.Copy()
value.SetParent(n)
value.IsMapKey = false
index := len(n.Content)
keyNode := createScalarNode(index, fmt.Sprintf("%v", index))
keyNode.SetParent(n)
value.Key = keyNode
if value.Key != nil {
value.Key.SetParent(n)
} else {
index := len(n.Content)
keyNode := createScalarNode(index, fmt.Sprintf("%v", index))
keyNode.SetParent(n)
value.Key = keyNode
}
n.Content = append(n.Content, value)
}
@ -477,64 +451,3 @@ func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode, prefs assignP
n.LineComment = other.LineComment
}
}
func (n *CandidateNode) ConvertToNodeInfo() *NodeInfo {
info := &NodeInfo{
Kind: kindToString(n.Kind),
Style: styleToString(n.Style),
Anchor: n.Anchor,
Tag: n.Tag,
HeadComment: n.HeadComment,
LineComment: n.LineComment,
FootComment: n.FootComment,
Value: n.Value,
Line: n.Line,
Column: n.Column,
}
if len(n.Content) > 0 {
info.Content = make([]*NodeInfo, len(n.Content))
for i, child := range n.Content {
info.Content[i] = child.ConvertToNodeInfo()
}
}
return info
}
// Helper functions to convert Kind and Style to string for NodeInfo
func kindToString(k Kind) string {
switch k {
case SequenceNode:
return "SequenceNode"
case MappingNode:
return "MappingNode"
case ScalarNode:
return "ScalarNode"
case AliasNode:
return "AliasNode"
default:
return "Unknown"
}
}
func styleToString(s Style) string {
var styles []string
if s&TaggedStyle != 0 {
styles = append(styles, "TaggedStyle")
}
if s&DoubleQuotedStyle != 0 {
styles = append(styles, "DoubleQuotedStyle")
}
if s&SingleQuotedStyle != 0 {
styles = append(styles, "SingleQuotedStyle")
}
if s&LiteralStyle != 0 {
styles = append(styles, "LiteralStyle")
}
if s&FoldedStyle != 0 {
styles = append(styles, "FoldedStyle")
}
if s&FlowStyle != 0 {
styles = append(styles, "FlowStyle")
}
return strings.Join(styles, ",")
}

View File

@ -9,14 +9,14 @@ import (
goccyToken "github.com/goccy/go-yaml/token"
)
func (o *CandidateNode) goccyDecodeIntoChild(childNode ast.Node, cm yaml.CommentMap, anchorMap map[string]*CandidateNode) (*CandidateNode, error) {
func (o *CandidateNode) goccyDecodeIntoChild(childNode ast.Node, cm yaml.CommentMap) (*CandidateNode, error) {
newChild := o.CreateChild()
err := newChild.UnmarshalGoccyYAML(childNode, cm, anchorMap)
err := newChild.UnmarshalGoccyYAML(childNode, cm)
return newChild, err
}
func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap, anchorMap map[string]*CandidateNode) error {
func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap) error {
log.Debugf("UnmarshalYAML %v", node)
log.Debugf("UnmarshalYAML %v", node.Type().String())
log.Debugf("UnmarshalYAML Node Value: %v", node.String())
@ -51,9 +51,6 @@ func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap, an
}
o.Value = node.String()
o.Line = node.GetToken().Position.Line
o.Column = node.GetToken().Position.Column
switch node.Type() {
case ast.IntegerType:
o.Kind = ScalarNode
@ -65,13 +62,9 @@ func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap, an
o.Kind = ScalarNode
o.Tag = "!!bool"
case ast.NullType:
log.Debugf("its a null type with value %v", node.GetToken().Value)
o.Kind = ScalarNode
o.Tag = "!!null"
o.Value = node.GetToken().Value
if node.GetToken().Type == goccyToken.ImplicitNullType {
o.Value = ""
}
case ast.StringType:
o.Kind = ScalarNode
o.Tag = "!!str"
@ -99,7 +92,7 @@ func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap, an
// to solve the multiline > problem
o.Value = astLiteral.Value.Value
case ast.TagType:
if err := o.UnmarshalGoccyYAML(node.(*ast.TagNode).Value, cm, anchorMap); err != nil {
if err := o.UnmarshalGoccyYAML(node.(*ast.TagNode).Value, cm); err != nil {
return err
}
o.Tag = node.(*ast.TagNode).Start.Value
@ -113,9 +106,9 @@ func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap, an
o.Style = FlowStyle
}
for _, mappingValueNode := range mappingNode.Values {
err := o.goccyProcessMappingValueNode(mappingValueNode, cm, anchorMap)
err := o.goccyProcessMappingValueNode(mappingValueNode, cm)
if err != nil {
return err
return ast.ErrInvalidAnchorName
}
}
if mappingNode.FootComment != nil {
@ -127,9 +120,9 @@ func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap, an
o.Kind = MappingNode
o.Tag = "!!map"
mappingValueNode := node.(*ast.MappingValueNode)
err := o.goccyProcessMappingValueNode(mappingValueNode, cm, anchorMap)
err := o.goccyProcessMappingValueNode(mappingValueNode, cm)
if err != nil {
return err
return ast.ErrInvalidAnchorName
}
case ast.SequenceType:
log.Debugf("UnmarshalYAML - a sequence node")
@ -148,7 +141,7 @@ func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap, an
keyNode.Kind = ScalarNode
keyNode.Value = fmt.Sprintf("%v", i)
valueNode, err := o.goccyDecodeIntoChild(astSeq[i], cm, anchorMap)
valueNode, err := o.goccyDecodeIntoChild(astSeq[i], cm)
if err != nil {
return err
}
@ -156,55 +149,32 @@ func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap, an
valueNode.Key = keyNode
o.Content[i] = valueNode
}
case ast.AnchorType:
log.Debugf("UnmarshalYAML - an anchor node")
anchorNode := node.(*ast.AnchorNode)
err := o.UnmarshalGoccyYAML(anchorNode.Value, cm, anchorMap)
if err != nil {
return err
}
o.Anchor = anchorNode.Name.String()
anchorMap[o.Anchor] = o
case ast.AliasType:
log.Debugf("UnmarshalYAML - an alias node")
aliasNode := node.(*ast.AliasNode)
o.Kind = AliasNode
o.Value = aliasNode.Value.String()
o.Alias = anchorMap[o.Value]
case ast.MergeKeyType:
log.Debugf("UnmarshalYAML - a merge key")
o.Kind = ScalarNode
o.Tag = "!!merge" // note - I should be able to get rid of this.
o.Value = "<<"
default:
log.Debugf("UnmarshalYAML - no idea of the type!!\n%v: %v", node.Type(), node.String())
log.Debugf("UnmarshalYAML - node idea of the type!!")
}
log.Debugf("KIND: %v", o.Kind)
return nil
}
func (o *CandidateNode) goccyProcessMappingValueNode(mappingEntry *ast.MappingValueNode, cm yaml.CommentMap, anchorMap map[string]*CandidateNode) error {
func (o *CandidateNode) goccyProcessMappingValueNode(mappingEntry *ast.MappingValueNode, cm yaml.CommentMap) error {
log.Debug("UnmarshalYAML MAP KEY entry %v", mappingEntry.Key)
// AddKeyValueFirst because it clones the nodes, and we want to have the real refs when Unmarshalling
// particularly for the anchorMap
keyNode, valueNode := o.AddKeyValueChild(&CandidateNode{}, &CandidateNode{})
if err := keyNode.UnmarshalGoccyYAML(mappingEntry.Key, cm, anchorMap); err != nil {
keyNode, err := o.goccyDecodeIntoChild(mappingEntry.Key, cm)
if err != nil {
return err
}
keyNode.IsMapKey = true
log.Debug("UnmarshalYAML MAP VALUE entry %v", mappingEntry.Value)
if err := valueNode.UnmarshalGoccyYAML(mappingEntry.Value, cm, anchorMap); err != nil {
valueNode, err := o.goccyDecodeIntoChild(mappingEntry.Value, cm)
if err != nil {
return err
}
if mappingEntry.FootComment != nil {
valueNode.FootComment = mappingEntry.FootComment.String()
}
o.AddKeyValueChild(keyNode, valueNode)
return nil
}

View File

@ -160,47 +160,3 @@ func TestCandidateNodeAddKeyValueChild(t *testing.T) {
test.AssertResult(t, key.IsMapKey, true)
}
func TestConvertToNodeInfo(t *testing.T) {
child := &CandidateNode{
Kind: ScalarNode,
Style: DoubleQuotedStyle,
Tag: "!!str",
Value: "childValue",
Line: 2,
Column: 3,
}
parent := &CandidateNode{
Kind: MappingNode,
Style: TaggedStyle,
Tag: "!!map",
Value: "",
Line: 1,
Column: 1,
Content: []*CandidateNode{child},
HeadComment: "head",
LineComment: "line",
FootComment: "foot",
Anchor: "anchor",
}
info := parent.ConvertToNodeInfo()
test.AssertResult(t, "MappingNode", info.Kind)
test.AssertResult(t, "TaggedStyle", info.Style)
test.AssertResult(t, "!!map", info.Tag)
test.AssertResult(t, "head", info.HeadComment)
test.AssertResult(t, "line", info.LineComment)
test.AssertResult(t, "foot", info.FootComment)
test.AssertResult(t, "anchor", info.Anchor)
test.AssertResult(t, 1, info.Line)
test.AssertResult(t, 1, info.Column)
if len(info.Content) != 1 {
t.Fatalf("Expected 1 child, got %d", len(info.Content))
}
childInfo := info.Content[0]
test.AssertResult(t, "ScalarNode", childInfo.Kind)
test.AssertResult(t, "DoubleQuotedStyle", childInfo.Style)
test.AssertResult(t, "!!str", childInfo.Tag)
test.AssertResult(t, "childValue", childInfo.Value)
test.AssertResult(t, 2, childInfo.Line)
test.AssertResult(t, 3, childInfo.Column)
}

View File

@ -3,7 +3,7 @@ package yqlib
import (
"fmt"
yaml "go.yaml.in/yaml/v3"
yaml "gopkg.in/yaml.v3"
)
func MapYamlStyle(original yaml.Style) Style {
@ -78,6 +78,9 @@ func (o *CandidateNode) copyToYamlNode(node *yaml.Node) {
node.Value = o.Value
node.Anchor = o.Anchor
// node.Alias = TODO - find Alias in our own structure
// might need to be a post process thing
node.HeadComment = o.HeadComment
node.LineComment = o.LineComment

View File

@ -1,5 +1,3 @@
//go:build !yq_nojson
package yqlib
import (

View File

@ -2,11 +2,9 @@ package yqlib
import (
"container/list"
"strings"
"testing"
"github.com/mikefarah/yq/v4/test"
logging "gopkg.in/op/go-logging.v1"
)
func TestChildContext(t *testing.T) {
@ -51,211 +49,3 @@ func TestChildContextNoVariables(t *testing.T) {
test.AssertResultComplex(t, make(map[string]*list.List), clone.Variables)
}
func TestSingleReadonlyChildContext(t *testing.T) {
original := Context{
DontAutoCreate: false,
datetimeLayout: "2006-01-02",
}
candidate := &CandidateNode{Value: "test"}
clone := original.SingleReadonlyChildContext(candidate)
// Should have DontAutoCreate set to true
test.AssertResultComplex(t, true, clone.DontAutoCreate)
// Should have the candidate node in MatchingNodes
test.AssertResultComplex(t, 1, clone.MatchingNodes.Len())
test.AssertResultComplex(t, candidate, clone.MatchingNodes.Front().Value)
}
func TestSingleChildContext(t *testing.T) {
original := Context{
DontAutoCreate: true,
datetimeLayout: "2006-01-02",
}
candidate := &CandidateNode{Value: "test"}
clone := original.SingleChildContext(candidate)
// Should preserve DontAutoCreate
test.AssertResultComplex(t, true, clone.DontAutoCreate)
// Should have the candidate node in MatchingNodes
test.AssertResultComplex(t, 1, clone.MatchingNodes.Len())
test.AssertResultComplex(t, candidate, clone.MatchingNodes.Front().Value)
}
func TestSetDateTimeLayout(t *testing.T) {
context := Context{}
// Test setting datetime layout
context.SetDateTimeLayout("2006-01-02T15:04:05Z07:00")
test.AssertResultComplex(t, "2006-01-02T15:04:05Z07:00", context.datetimeLayout)
}
func TestGetDateTimeLayout(t *testing.T) {
// Test with custom layout
context := Context{datetimeLayout: "2006-01-02"}
result := context.GetDateTimeLayout()
test.AssertResultComplex(t, "2006-01-02", result)
// Test with empty layout (should return default)
context = Context{}
result = context.GetDateTimeLayout()
test.AssertResultComplex(t, "2006-01-02T15:04:05Z07:00", result)
}
func TestGetVariable(t *testing.T) {
// Test with nil Variables
context := Context{}
result := context.GetVariable("nonexistent")
test.AssertResultComplex(t, (*list.List)(nil), result)
// Test with existing variable
variables := make(map[string]*list.List)
variables["test"] = list.New()
variables["test"].PushBack(&CandidateNode{Value: "value"})
context = Context{Variables: variables}
result = context.GetVariable("test")
test.AssertResultComplex(t, variables["test"], result)
// Test with non-existent variable
result = context.GetVariable("nonexistent")
test.AssertResultComplex(t, (*list.List)(nil), result)
}
func TestSetVariable(t *testing.T) {
// Test setting variable when Variables is nil
context := Context{}
value := list.New()
value.PushBack(&CandidateNode{Value: "test"})
context.SetVariable("key", value)
test.AssertResultComplex(t, value, context.Variables["key"])
// Test setting variable when Variables already exists
context.SetVariable("key2", value)
test.AssertResultComplex(t, value, context.Variables["key2"])
}
func TestToString(t *testing.T) {
context := Context{
DontAutoCreate: true,
MatchingNodes: list.New(),
}
// Add a node to test the full string representation
node := &CandidateNode{Value: "test"}
context.MatchingNodes.PushBack(node)
// Test with debug logging disabled (default)
result := context.ToString()
test.AssertResultComplex(t, "", result)
// Test with debug logging enabled
logging.SetLevel(logging.DEBUG, "")
defer logging.SetLevel(logging.INFO, "") // Reset to default
result2 := context.ToString()
test.AssertResultComplex(t, true, len(result2) > 0)
test.AssertResultComplex(t, true, strings.Contains(result2, "Context"))
test.AssertResultComplex(t, true, strings.Contains(result2, "DontAutoCreate: true"))
}
func TestDeepClone(t *testing.T) {
// Create original context with variables and matching nodes
originalVariables := make(map[string]*list.List)
originalVariables["test"] = list.New()
originalVariables["test"].PushBack(&CandidateNode{Value: "original"})
original := Context{
DontAutoCreate: true,
datetimeLayout: "2006-01-02",
Variables: originalVariables,
MatchingNodes: list.New(),
}
// Add a node to MatchingNodes
node := &CandidateNode{Value: "test"}
original.MatchingNodes.PushBack(node)
clone := original.DeepClone()
// Should preserve DontAutoCreate and datetimeLayout
test.AssertResultComplex(t, original.DontAutoCreate, clone.DontAutoCreate)
test.AssertResultComplex(t, original.datetimeLayout, clone.datetimeLayout)
// Should have copied variables
test.AssertResultComplex(t, 1, len(clone.Variables))
test.AssertResultComplex(t, "original", clone.Variables["test"].Front().Value.(*CandidateNode).Value)
// Should have deep copied MatchingNodes
test.AssertResultComplex(t, 1, clone.MatchingNodes.Len())
// Verify it's a deep copy by modifying the original
original.MatchingNodes.Front().Value.(*CandidateNode).Value = "modified"
test.AssertResultComplex(t, "test", clone.MatchingNodes.Front().Value.(*CandidateNode).Value)
}
func TestClone(t *testing.T) {
// Create original context
original := Context{
DontAutoCreate: true,
datetimeLayout: "2006-01-02",
MatchingNodes: list.New(),
}
node := &CandidateNode{Value: "test"}
original.MatchingNodes.PushBack(node)
clone := original.Clone()
// Should preserve DontAutoCreate and datetimeLayout
test.AssertResultComplex(t, original.DontAutoCreate, clone.DontAutoCreate)
test.AssertResultComplex(t, original.datetimeLayout, clone.datetimeLayout)
// Should have the same MatchingNodes reference
test.AssertResultComplex(t, original.MatchingNodes, clone.MatchingNodes)
}
func TestReadOnlyClone(t *testing.T) {
original := Context{
DontAutoCreate: false,
datetimeLayout: "2006-01-02",
MatchingNodes: list.New(),
}
node := &CandidateNode{Value: "test"}
original.MatchingNodes.PushBack(node)
clone := original.ReadOnlyClone()
// Should set DontAutoCreate to true
test.AssertResultComplex(t, true, clone.DontAutoCreate)
// Should preserve other fields
test.AssertResultComplex(t, original.datetimeLayout, clone.datetimeLayout)
test.AssertResultComplex(t, original.MatchingNodes, clone.MatchingNodes)
}
func TestWritableClone(t *testing.T) {
original := Context{
DontAutoCreate: true,
datetimeLayout: "2006-01-02",
MatchingNodes: list.New(),
}
node := &CandidateNode{Value: "test"}
original.MatchingNodes.PushBack(node)
clone := original.WritableClone()
// Should set DontAutoCreate to false
test.AssertResultComplex(t, false, clone.DontAutoCreate)
// Should preserve other fields
test.AssertResultComplex(t, original.datetimeLayout, clone.datetimeLayout)
test.AssertResultComplex(t, original.MatchingNodes, clone.MatchingNodes)
}

View File

@ -64,6 +64,6 @@ func (d *dataTreeNavigator) GetMatchingNodes(context Context, expressionNode *Ex
if handler != nil {
return handler(d, context, expressionNode)
}
return Context{}, fmt.Errorf("unknown operator %v", expressionNode.Operation.OperationType.Type)
return Context{}, fmt.Errorf("Unknown operator %v", expressionNode.Operation.OperationType)
}

View File

@ -1,437 +0,0 @@
package yqlib
import (
"container/list"
"testing"
"github.com/mikefarah/yq/v4/test"
)
func TestGetMatchingNodes_NilExpressionNode(t *testing.T) {
navigator := NewDataTreeNavigator()
context := Context{
MatchingNodes: list.New(),
}
result, err := navigator.GetMatchingNodes(context, nil)
test.AssertResult(t, nil, err)
test.AssertResultComplex(t, context, result)
}
func TestGetMatchingNodes_UnknownOperator(t *testing.T) {
navigator := NewDataTreeNavigator()
context := Context{
MatchingNodes: list.New(),
}
// Create an expression node with an unknown operation type
unknownOpType := &operationType{Type: "UNKNOWN", Handler: nil}
expressionNode := &ExpressionNode{
Operation: &Operation{OperationType: unknownOpType},
}
result, err := navigator.GetMatchingNodes(context, expressionNode)
test.AssertResult(t, "unknown operator UNKNOWN", err.Error())
test.AssertResultComplex(t, Context{}, result)
}
func TestGetMatchingNodes_ValidOperator(t *testing.T) {
navigator := NewDataTreeNavigator()
// Create a simple context with a scalar node
scalarNode := &CandidateNode{
Kind: ScalarNode,
Tag: "!!str",
Value: "test",
}
context := Context{
MatchingNodes: list.New(),
}
context.MatchingNodes.PushBack(scalarNode)
// Create an expression node with a valid operation (self reference)
expressionNode := &ExpressionNode{
Operation: &Operation{OperationType: selfReferenceOpType},
}
result, err := navigator.GetMatchingNodes(context, expressionNode)
test.AssertResult(t, nil, err)
test.AssertResult(t, 1, result.MatchingNodes.Len())
// Verify the result contains the same node
resultNode := result.MatchingNodes.Front().Value.(*CandidateNode)
test.AssertResult(t, scalarNode, resultNode)
}
func TestDeeplyAssign_ScalarNode(t *testing.T) {
navigator := NewDataTreeNavigator()
// Create a context with a root mapping node
rootNode := &CandidateNode{
Kind: MappingNode,
Tag: "!!map",
Content: []*CandidateNode{
{Kind: ScalarNode, Tag: "!!str", Value: "existing", IsMapKey: true},
{Kind: ScalarNode, Tag: "!!str", Value: "old_value"},
},
}
context := Context{
MatchingNodes: list.New(),
}
context.MatchingNodes.PushBack(rootNode)
// Create a scalar node to assign
scalarNode := &CandidateNode{
Kind: ScalarNode,
Tag: "!!str",
Value: "new_value",
}
// Assign to path ["new_key"]
path := []interface{}{"new_key"}
err := navigator.DeeplyAssign(context, path, scalarNode)
test.AssertResult(t, nil, err)
// Verify the assignment was made
// The root node should now have the new key-value pair
test.AssertResult(t, 4, len(rootNode.Content)) // 2 original + 2 new
// Find the new key-value pair
found := false
for i := 0; i < len(rootNode.Content)-1; i += 2 {
key := rootNode.Content[i]
value := rootNode.Content[i+1]
if key.Value == "new_key" && value.Value == "new_value" {
found = true
break
}
}
test.AssertResult(t, true, found)
}
func TestDeeplyAssign_MappingNode(t *testing.T) {
navigator := NewDataTreeNavigator()
// Create a context with a root mapping node
rootNode := &CandidateNode{
Kind: MappingNode,
Tag: "!!map",
Content: []*CandidateNode{
{Kind: ScalarNode, Tag: "!!str", Value: "existing", IsMapKey: true},
{Kind: ScalarNode, Tag: "!!str", Value: "old_value"},
},
}
context := Context{
MatchingNodes: list.New(),
}
context.MatchingNodes.PushBack(rootNode)
// Create a mapping node to assign (this should trigger deep merge)
mappingNode := &CandidateNode{
Kind: MappingNode,
Tag: "!!map",
Content: []*CandidateNode{
{Kind: ScalarNode, Tag: "!!str", Value: "nested_key", IsMapKey: true},
{Kind: ScalarNode, Tag: "!!str", Value: "nested_value"},
},
}
// Assign to path ["new_map"]
path := []interface{}{"new_map"}
err := navigator.DeeplyAssign(context, path, mappingNode)
test.AssertResult(t, nil, err)
// Verify the assignment was made
// The root node should now have the new mapping
test.AssertResult(t, 4, len(rootNode.Content)) // 2 original + 2 new
// Find the new mapping
found := false
for i := 0; i < len(rootNode.Content); i += 2 {
if i+1 < len(rootNode.Content) {
key := rootNode.Content[i]
value := rootNode.Content[i+1]
if key.Value == "new_map" && value.Kind == MappingNode {
found = true
// Verify the nested content
test.AssertResult(t, 2, len(value.Content))
test.AssertResult(t, "nested_key", value.Content[0].Value)
test.AssertResult(t, "nested_value", value.Content[1].Value)
break
}
}
}
test.AssertResult(t, true, found)
}
func TestDeeplyAssign_DeepPath(t *testing.T) {
navigator := NewDataTreeNavigator()
// Create a context with a root mapping node
rootNode := &CandidateNode{
Kind: MappingNode,
Tag: "!!map",
Content: []*CandidateNode{
{Kind: ScalarNode, Tag: "!!str", Value: "level1", IsMapKey: true},
{Kind: MappingNode, Tag: "!!map", Content: []*CandidateNode{}},
},
}
context := Context{
MatchingNodes: list.New(),
}
context.MatchingNodes.PushBack(rootNode)
// Create a scalar node to assign
scalarNode := &CandidateNode{
Kind: ScalarNode,
Tag: "!!str",
Value: "deep_value",
}
// Assign to deep path ["level1", "level2", "level3"]
path := []interface{}{"level1", "level2", "level3"}
err := navigator.DeeplyAssign(context, path, scalarNode)
test.AssertResult(t, nil, err)
// Verify the deep assignment was made
level1Node := rootNode.Content[1] // The mapping node
test.AssertResult(t, 2, len(level1Node.Content)) // Should have level2 key-value
level2Key := level1Node.Content[0]
level2Value := level1Node.Content[1]
test.AssertResult(t, "level2", level2Key.Value)
test.AssertResult(t, MappingNode, level2Value.Kind)
level3Key := level2Value.Content[0]
level3Value := level2Value.Content[1]
test.AssertResult(t, "level3", level3Key.Value)
test.AssertResult(t, "deep_value", level3Value.Value)
}
func TestDeeplyAssign_ArrayPath(t *testing.T) {
navigator := NewDataTreeNavigator()
// Create a context with a root mapping node containing an array
rootNode := &CandidateNode{
Kind: MappingNode,
Tag: "!!map",
Content: []*CandidateNode{
{Kind: ScalarNode, Tag: "!!str", Value: "array", IsMapKey: true},
{Kind: SequenceNode, Tag: "!!seq", Content: []*CandidateNode{}},
},
}
context := Context{
MatchingNodes: list.New(),
}
context.MatchingNodes.PushBack(rootNode)
// Create a scalar node to assign
scalarNode := &CandidateNode{
Kind: ScalarNode,
Tag: "!!str",
Value: "array_value",
}
// Assign to array path ["array", 0]
path := []interface{}{"array", 0}
err := navigator.DeeplyAssign(context, path, scalarNode)
test.AssertResult(t, nil, err)
// Verify the array assignment was made
arrayNode := rootNode.Content[1] // The sequence node
test.AssertResult(t, 1, len(arrayNode.Content)) // Should have one element
arrayElement := arrayNode.Content[0]
test.AssertResult(t, "array_value", arrayElement.Value)
}
func TestDeeplyAssign_OverwriteExisting(t *testing.T) {
navigator := NewDataTreeNavigator()
// Create a context with a root mapping node
rootNode := &CandidateNode{
Kind: MappingNode,
Tag: "!!map",
Content: []*CandidateNode{
{Kind: ScalarNode, Tag: "!!str", Value: "key", IsMapKey: true},
{Kind: ScalarNode, Tag: "!!str", Value: "old_value"},
},
}
context := Context{
MatchingNodes: list.New(),
}
context.MatchingNodes.PushBack(rootNode)
// Create a scalar node to assign
scalarNode := &CandidateNode{
Kind: ScalarNode,
Tag: "!!str",
Value: "new_value",
}
// Assign to existing path ["key"]
path := []interface{}{"key"}
err := navigator.DeeplyAssign(context, path, scalarNode)
test.AssertResult(t, nil, err)
// Verify the value was overwritten
test.AssertResult(t, 2, len(rootNode.Content)) // Should still have 2 elements
key := rootNode.Content[0]
value := rootNode.Content[1]
test.AssertResult(t, "key", key.Value)
test.AssertResult(t, "new_value", value.Value) // Should be overwritten
}
func TestDeeplyAssign_ErrorHandling(t *testing.T) {
navigator := NewDataTreeNavigator()
// Create a context with a scalar node (not a mapping)
scalarNode := &CandidateNode{
Kind: ScalarNode,
Tag: "!!str",
Value: "not_a_map",
}
context := Context{
MatchingNodes: list.New(),
}
context.MatchingNodes.PushBack(scalarNode)
// Create a scalar node to assign
assignNode := &CandidateNode{
Kind: ScalarNode,
Tag: "!!str",
Value: "value",
}
// Try to assign to a path on a scalar (should fail)
path := []interface{}{"key"}
err := navigator.DeeplyAssign(context, path, assignNode)
// Print the actual error for debugging
if err != nil {
t.Logf("Actual error: %v", err)
}
// This should fail because we can't assign to a scalar
test.AssertResult(t, nil, err)
}
func TestGetMatchingNodes_WithVariables(t *testing.T) {
navigator := NewDataTreeNavigator()
// Create a context with variables
variables := make(map[string]*list.List)
varList := list.New()
varList.PushBack(&CandidateNode{Kind: ScalarNode, Tag: "!!str", Value: "var_value"})
variables["test_var"] = varList
context := Context{
MatchingNodes: list.New(),
Variables: variables,
}
// Create an expression node that gets a variable
expressionNode := &ExpressionNode{
Operation: &Operation{OperationType: getVariableOpType, StringValue: "test_var"},
}
result, err := navigator.GetMatchingNodes(context, expressionNode)
test.AssertResult(t, nil, err)
test.AssertResult(t, 1, result.MatchingNodes.Len())
// Verify the variable was retrieved
resultNode := result.MatchingNodes.Front().Value.(*CandidateNode)
test.AssertResult(t, "var_value", resultNode.Value)
}
func TestGetMatchingNodes_EmptyContext(t *testing.T) {
navigator := NewDataTreeNavigator()
// Create an empty context
context := Context{
MatchingNodes: list.New(),
}
// Create an expression node with self reference
expressionNode := &ExpressionNode{
Operation: &Operation{OperationType: selfReferenceOpType},
}
result, err := navigator.GetMatchingNodes(context, expressionNode)
test.AssertResult(t, nil, err)
test.AssertResult(t, 0, result.MatchingNodes.Len())
}
func TestDeeplyAssign_ComplexMappingMerge(t *testing.T) {
navigator := NewDataTreeNavigator()
// Create a context with a root mapping node containing nested data
rootNode := &CandidateNode{
Kind: MappingNode,
Tag: "!!map",
Content: []*CandidateNode{
{Kind: ScalarNode, Tag: "!!str", Value: "config", IsMapKey: true},
{Kind: MappingNode, Tag: "!!map", Content: []*CandidateNode{
{Kind: ScalarNode, Tag: "!!str", Value: "existing_key", IsMapKey: true},
{Kind: ScalarNode, Tag: "!!str", Value: "existing_value"},
}},
},
}
context := Context{
MatchingNodes: list.New(),
}
context.MatchingNodes.PushBack(rootNode)
// Create a mapping node to merge
mappingNode := &CandidateNode{
Kind: MappingNode,
Tag: "!!map",
Content: []*CandidateNode{
{Kind: ScalarNode, Tag: "!!str", Value: "new_key", IsMapKey: true},
{Kind: ScalarNode, Tag: "!!str", Value: "new_value"},
{Kind: ScalarNode, Tag: "!!str", Value: "existing_key", IsMapKey: true},
{Kind: ScalarNode, Tag: "!!str", Value: "updated_value"},
},
}
// Assign to path ["config"] (should merge with existing mapping)
path := []interface{}{"config"}
err := navigator.DeeplyAssign(context, path, mappingNode)
test.AssertResult(t, nil, err)
// Verify the merge was successful
configNode := rootNode.Content[1] // The config mapping node
test.AssertResult(t, 4, len(configNode.Content)) // Should have 2 key-value pairs
// Check that both existing and new keys are present
foundExisting := false
foundNew := false
for i := 0; i < len(configNode.Content); i += 2 {
if i+1 < len(configNode.Content) {
key := configNode.Content[i]
value := configNode.Content[i+1]
switch key.Value {
case "existing_key":
foundExisting = true
test.AssertResult(t, "updated_value", value.Value) // Should be updated
case "new_key":
foundNew = true
test.AssertResult(t, "new_value", value.Value)
}
}
}
test.AssertResult(t, true, foundExisting)
test.AssertResult(t, true, foundNew)
}

View File

@ -1,5 +1,3 @@
//go:build !yq_nobase64
package yqlib
import (

View File

@ -1,5 +1,3 @@
//go:build !yq_nocsv
package yqlib
import (

View File

@ -16,8 +16,6 @@ import (
type goccyYamlDecoder struct {
decoder yaml.Decoder
cm yaml.CommentMap
// anchor map persists over multiple documents for convenience.
anchorMap map[string]*CandidateNode
}
func NewGoccyYAMLDecoder() Decoder {
@ -27,7 +25,6 @@ func NewGoccyYAMLDecoder() Decoder {
func (dec *goccyYamlDecoder) Init(reader io.Reader) error {
dec.cm = yaml.CommentMap{}
dec.decoder = *yaml.NewDecoder(reader, yaml.CommentToMap(dec.cm), yaml.UseOrderedMap())
dec.anchorMap = make(map[string]*CandidateNode)
return nil
}
@ -41,7 +38,7 @@ func (dec *goccyYamlDecoder) Decode() (*CandidateNode, error) {
}
candidateNode := &CandidateNode{}
if err := candidateNode.UnmarshalGoccyYAML(ast, dec.cm, dec.anchorMap); err != nil {
if err := candidateNode.UnmarshalGoccyYAML(ast, dec.cm); err != nil {
return nil, err
}

View File

@ -1,106 +0,0 @@
//go:build !yq_noini
package yqlib
import (
"fmt"
"io"
"github.com/go-ini/ini"
)
type iniDecoder struct {
reader io.Reader
finished bool // Flag to signal completion of processing
}
func NewINIDecoder() Decoder {
return &iniDecoder{
finished: false, // Initialize the flag as false
}
}
func (dec *iniDecoder) Init(reader io.Reader) error {
// Store the reader for use in Decode
dec.reader = reader
return nil
}
func (dec *iniDecoder) Decode() (*CandidateNode, error) {
// If processing is already finished, return io.EOF
if dec.finished {
return nil, io.EOF
}
// Read all content from the stored reader
content, err := io.ReadAll(dec.reader)
if err != nil {
return nil, fmt.Errorf("failed to read INI content: %w", err)
}
// Parse the INI content
cfg, err := ini.Load(content)
if err != nil {
return nil, fmt.Errorf("failed to parse INI content: %w", err)
}
// Create a root CandidateNode as a MappingNode (since INI is key-value based)
root := &CandidateNode{
Kind: MappingNode,
Tag: "!!map",
Value: "",
}
// Process each section in the INI file
for _, section := range cfg.Sections() {
sectionName := section.Name()
if sectionName == ini.DefaultSection {
// For the default section, add key-value pairs directly to the root node
for _, key := range section.Keys() {
keyName := key.Name()
keyValue := key.String()
// Create a key node (scalar for the key name)
keyNode := createStringScalarNode(keyName)
// Create a value node (scalar for the value)
valueNode := createStringScalarNode(keyValue)
// Add key-value pair to the root node
root.AddKeyValueChild(keyNode, valueNode)
}
} else {
// For named sections, create a nested map
sectionNode := &CandidateNode{
Kind: MappingNode,
Tag: "!!map",
Value: "",
}
// Add key-value pairs to the section node
for _, key := range section.Keys() {
keyName := key.Name()
keyValue := key.String()
// Create a key node (scalar for the key name)
keyNode := createStringScalarNode(keyName)
// Create a value node (scalar for the value)
valueNode := createStringScalarNode(keyValue)
// Add key-value pair to the section node
sectionNode.AddKeyValueChild(keyNode, valueNode)
}
// Create a key node for the section name
sectionKeyNode := createStringScalarNode(sectionName)
// Add the section as a nested map to the root node
root.AddKeyValueChild(sectionKeyNode, sectionNode)
}
}
// Set the finished flag to true to prevent further Decode calls
dec.finished = true
// Return the root node
return root, nil
}

View File

@ -1,5 +1,3 @@
//go:build !yq_noprops
package yqlib
import (

View File

@ -68,8 +68,7 @@ func mustProcessFormatScenario(s formatScenario, decoder Decoder, encoder Encode
result, err := processFormatScenario(s, decoder, encoder)
if err != nil {
log.Error("Bad scenario %v: %w", s.description, err)
return fmt.Sprintf("Bad scenario %v: %v", s.description, err.Error())
panic(fmt.Errorf("Bad scenario %v: %w", s.description, err))
}
return result

View File

@ -249,12 +249,11 @@ func (dec *tomlDecoder) processTopLevelNode(currentNode *toml.Node) (bool, error
var runAgainstCurrentExp bool
var err error
log.Debug("processTopLevelNode: Going to process %v state is current %v", currentNode.Kind, NodeToString(dec.rootMap))
switch currentNode.Kind {
case toml.Table:
if currentNode.Kind == toml.Table {
runAgainstCurrentExp, err = dec.processTable(currentNode)
case toml.ArrayTable:
} else if currentNode.Kind == toml.ArrayTable {
runAgainstCurrentExp, err = dec.processArrayTable(currentNode)
default:
} else {
runAgainstCurrentExp, err = dec.decodeKeyValuesIntoMap(dec.rootMap, currentNode)
}

View File

@ -1,5 +1,3 @@
//go:build !yq_nouri
package yqlib
import (

View File

@ -315,14 +315,13 @@ func (dec *xmlDecoder) decodeXML(root *xmlNode) error {
case xml.Comment:
commentStr := string(xml.CharData(se))
switch elem.state {
case "started":
if elem.state == "started" {
applyFootComment(elem, commentStr)
case "chardata":
} else if elem.state == "chardata" {
log.Debug("got a line comment for (%v) %v: [%v]", elem.state, elem.label, commentStr)
elem.n.LineComment = joinComments([]string{elem.n.LineComment, commentStr}, " ")
default:
} else {
log.Debug("got a head comment for (%v) %v: [%v]", elem.state, elem.label, commentStr)
elem.n.HeadComment = joinComments([]string{elem.n.HeadComment, commentStr}, " ")
}

View File

@ -8,7 +8,7 @@ import (
"regexp"
"strings"
yaml "go.yaml.in/yaml/v3"
yaml "gopkg.in/yaml.v3"
)
type yamlDecoder struct {

View File

@ -5,18 +5,6 @@ Use the `alias` and `anchor` operators to read and write yaml aliases and anchor
`yq` supports merge aliases (like `<<: *blah`) however this is no longer in the standard yaml spec (1.2) and so `yq` will automatically add the `!!merge` tag to these nodes as it is effectively a custom tag.
## NOTE --yaml-fix-merge-anchor-to-spec flag
`yq` doesn't merge anchors `<<:` to spec, in some circumstances it incorrectly overrides existing keys when the spec documents not to do that.
To minimise disruption while still fixing the issue, a flag has been added to toggle this behaviour. This will first default to false; and log warnings to users. Then it will default to true (and still allow users to specify false if needed).
This flag also enables advanced merging, like inline maps, as well as fixes to ensure when exploding a particular path, neighbours are not affect ed.
Long story short, you should be setting this flag to true.
See examples of the flag differences below, where LEGACY is with the flag off; and FIXED is with the flag on.
## Merge one map
see https://yaml.org/type/merge.html
@ -46,6 +34,68 @@ y: 2
r: 10
```
## Merge multiple maps
see https://yaml.org/type/merge.html
Given a sample.yml file of:
```yaml
- &CENTER
x: 1
y: 2
- &LEFT
x: 0
y: 2
- &BIG
r: 10
- &SMALL
r: 1
- !!merge <<:
- *CENTER
- *BIG
```
then
```bash
yq '.[4] | explode(.)' sample.yml
```
will output
```yaml
r: 10
x: 1
y: 2
```
## Override
see https://yaml.org/type/merge.html
Given a sample.yml file of:
```yaml
- &CENTER
x: 1
y: 2
- &LEFT
x: 0
y: 2
- &BIG
r: 10
- &SMALL
r: 1
- !!merge <<:
- *BIG
- *LEFT
- *SMALL
x: 1
```
then
```bash
yq '.[4] | explode(.)' sample.yml
```
will output
```yaml
r: 10
x: 1
y: 2
```
## Get anchor
Given a sample.yml file of:
```yaml
@ -204,6 +254,53 @@ f:
cat: b
```
## Explode with merge anchors
Given a sample.yml file of:
```yaml
foo: &foo
a: foo_a
thing: foo_thing
c: foo_c
bar: &bar
b: bar_b
thing: bar_thing
c: bar_c
foobarList:
b: foobarList_b
!!merge <<:
- *foo
- *bar
c: foobarList_c
foobar:
c: foobar_c
!!merge <<: *foo
thing: foobar_thing
```
then
```bash
yq 'explode(.)' sample.yml
```
will output
```yaml
foo:
a: foo_a
thing: foo_thing
c: foo_c
bar:
b: bar_b
thing: bar_thing
c: bar_c
foobarList:
b: bar_b
thing: foo_thing
c: foobarList_c
a: foo_a
foobar:
c: foo_c
a: foo_a
thing: foobar_thing
```
## Dereference and update a field
Use explode with multiply to dereference an object
@ -220,7 +317,7 @@ thingTwo:
```
then
```bash
yq '.thingOne |= (explode(.) | sort_keys(.)) * {"value": false}' sample.yml
yq '.thingOne |= explode(.) * {"value": false}' sample.yml
```
will output
```yaml
@ -234,250 +331,3 @@ thingTwo:
!!merge <<: *item_value
```
## LEGACY: Explode with merge anchors
Caution: this is for when --yaml-fix-merge-anchor-to-spec=false; it's not to YAML spec because the merge anchors incorrectly override the object values (foobarList.b is set to bar_b when it should still be foobarList_b). Flag will default to true in late 2025
Given a sample.yml file of:
```yaml
foo: &foo
a: foo_a
thing: foo_thing
c: foo_c
bar: &bar
b: bar_b
thing: bar_thing
c: bar_c
foobarList:
b: foobarList_b
!!merge <<:
- *foo
- *bar
c: foobarList_c
foobar:
c: foobar_c
!!merge <<: *foo
thing: foobar_thing
```
then
```bash
yq 'explode(.)' sample.yml
```
will output
```yaml
foo:
a: foo_a
thing: foo_thing
c: foo_c
bar:
b: bar_b
thing: bar_thing
c: bar_c
foobarList:
b: bar_b
thing: foo_thing
c: foobarList_c
a: foo_a
foobar:
c: foo_c
a: foo_a
thing: foobar_thing
```
## LEGACY: Merge multiple maps
see https://yaml.org/type/merge.html. This has the correct data, but the wrong key order; set --yaml-fix-merge-anchor-to-spec=true to fix the key order.
Given a sample.yml file of:
```yaml
- &CENTER
x: 1
y: 2
- &LEFT
x: 0
y: 2
- &BIG
r: 10
- &SMALL
r: 1
- !!merge <<:
- *CENTER
- *BIG
```
then
```bash
yq '.[4] | explode(.)' sample.yml
```
will output
```yaml
r: 10
x: 1
y: 2
```
## LEGACY: Override
see https://yaml.org/type/merge.html. This has the correct data, but the wrong key order; set --yaml-fix-merge-anchor-to-spec=true to fix the key order.
Given a sample.yml file of:
```yaml
- &CENTER
x: 1
y: 2
- &LEFT
x: 0
y: 2
- &BIG
r: 10
- &SMALL
r: 1
- !!merge <<:
- *BIG
- *LEFT
- *SMALL
x: 1
```
then
```bash
yq '.[4] | explode(.)' sample.yml
```
will output
```yaml
r: 10
x: 1
y: 2
```
## FIXED: Explode with merge anchors
Set `--yaml-fix-merge-anchor-to-spec=true` to get this correct merge behaviour (flag will default to true in late 2025).
Observe that foobarList.b property is still foobarList_b.
Given a sample.yml file of:
```yaml
foo: &foo
a: foo_a
thing: foo_thing
c: foo_c
bar: &bar
b: bar_b
thing: bar_thing
c: bar_c
foobarList:
b: foobarList_b
!!merge <<:
- *foo
- *bar
c: foobarList_c
foobar:
c: foobar_c
!!merge <<: *foo
thing: foobar_thing
```
then
```bash
yq 'explode(.)' sample.yml
```
will output
```yaml
foo:
a: foo_a
thing: foo_thing
c: foo_c
bar:
b: bar_b
thing: bar_thing
c: bar_c
foobarList:
b: foobarList_b
a: foo_a
thing: foo_thing
c: foobarList_c
foobar:
c: foobar_c
a: foo_a
thing: foobar_thing
```
## FIXED: Merge multiple maps
Set `--yaml-fix-merge-anchor-to-spec=true` to get this correct merge behaviour (flag will default to true in late 2025).
Taken from https://yaml.org/type/merge.html. Same values as legacy, but with the correct key order.
Given a sample.yml file of:
```yaml
- &CENTER
x: 1
y: 2
- &LEFT
x: 0
y: 2
- &BIG
r: 10
- &SMALL
r: 1
- !!merge <<:
- *CENTER
- *BIG
```
then
```bash
yq '.[4] | explode(.)' sample.yml
```
will output
```yaml
x: 1
y: 2
r: 10
```
## FIXED: Override
Set `--yaml-fix-merge-anchor-to-spec=true` to get this correct merge behaviour (flag will default to true in late 2025).
Taken from https://yaml.org/type/merge.html. Same values as legacy, but with the correct key order.
Given a sample.yml file of:
```yaml
- &CENTER
x: 1
y: 2
- &LEFT
x: 0
y: 2
- &BIG
r: 10
- &SMALL
r: 1
- !!merge <<:
- *BIG
- *LEFT
- *SMALL
x: 1
```
then
```bash
yq '.[4] | explode(.)' sample.yml
```
will output
```yaml
r: 10
y: 2
x: 1
```
## Exploding inline merge anchor
Set `--yaml-fix-merge-anchor-to-spec=true` to get this correct merge behaviour (flag will default to true in late 2025).
Given a sample.yml file of:
```yaml
a:
b: &b 42
!!merge <<:
c: *b
```
then
```bash
yq 'explode(.) | sort_keys(.)' sample.yml
```
will output
```yaml
a:
b: 42
c: 42
```

View File

@ -2,7 +2,7 @@
Similar to the same named functions in `jq` these functions convert to/from an object and an array of key-value pairs. This is most useful for performing operations on keys of maps.
Use `with_entries(op)` as a syntactic sugar for doing `to_entries | op | from_entries`.
Use `with_entries(op)` as a syntatic sugar for doing `to_entries | op | from_entries`.
## to_entries Map
Given a sample.yml file of:

View File

@ -4,15 +4,3 @@ Use the `alias` and `anchor` operators to read and write yaml aliases and anchor
`yq` supports merge aliases (like `<<: *blah`) however this is no longer in the standard yaml spec (1.2) and so `yq` will automatically add the `!!merge` tag to these nodes as it is effectively a custom tag.
## NOTE --yaml-fix-merge-anchor-to-spec flag
`yq` doesn't merge anchors `<<:` to spec, in some circumstances it incorrectly overrides existing keys when the spec documents not to do that.
To minimise disruption while still fixing the issue, a flag has been added to toggle this behaviour. This will first default to false; and log warnings to users. Then it will default to true (and still allow users to specify false if needed).
This flag also enables advanced merging, like inline maps, as well as fixes to ensure when exploding a particular path, neighbours are not affect ed.
Long story short, you should be setting this flag to true.
See examples of the flag differences below, where LEGACY is with the flag off; and FIXED is with the flag on.

View File

@ -2,4 +2,4 @@
Similar to the same named functions in `jq` these functions convert to/from an object and an array of key-value pairs. This is most useful for performing operations on keys of maps.
Use `with_entries(op)` as a syntactic sugar for doing `to_entries | op | from_entries`.
Use `with_entries(op)` as a syntatic sugar for doing `to_entries | op | from_entries`.

View File

@ -3,7 +3,7 @@
## RegEx
This uses Golang's native regex functions under the hood - See their [docs](https://github.com/google/re2/wiki/Syntax) for the supported syntax.
Case insensitive tip: prefix the regex with `(?i)` - e.g. `test("(?i)cats")`.
Case insensitive tip: prefix the regex with `(?i)` - e.g. `test("(?i)cats)"`.
### match(regEx)
This operator returns the substring match details of the given regEx.

View File

@ -1,12 +1,3 @@
# Traverse (Read)
This is the simplest (and perhaps most used) operator. It is used to navigate deeply into yaml structures.
## NOTE --yaml-fix-merge-anchor-to-spec flag
`yq` doesn't merge anchors `<<:` to spec, in some circumstances it incorrectly overrides existing keys when the spec documents not to do that.
To minimise disruption while still fixing the issue, a flag has been added to toggle this behaviour. This will first default to false; and log warnings to users. Then it will default to true (and still allow users to specify false if needed)
See examples of the flag differences below, where LEGACY is the flag off; and FIXED is with the flag on.

View File

@ -3,7 +3,7 @@
## RegEx
This uses Golang's native regex functions under the hood - See their [docs](https://github.com/google/re2/wiki/Syntax) for the supported syntax.
Case insensitive tip: prefix the regex with `(?i)` - e.g. `test("(?i)cats")`.
Case insensitive tip: prefix the regex with `(?i)` - e.g. `test("(?i)cats)"`.
### match(regEx)
This operator returns the substring match details of the given regEx.

View File

@ -2,15 +2,6 @@
This is the simplest (and perhaps most used) operator. It is used to navigate deeply into yaml structures.
## NOTE --yaml-fix-merge-anchor-to-spec flag
`yq` doesn't merge anchors `<<:` to spec, in some circumstances it incorrectly overrides existing keys when the spec documents not to do that.
To minimise disruption while still fixing the issue, a flag has been added to toggle this behaviour. This will first default to false; and log warnings to users. Then it will default to true (and still allow users to specify false if needed)
See examples of the flag differences below, where LEGACY is the flag off; and FIXED is with the flag on.
## Simple map navigation
Given a sample.yml file of:
```yaml
@ -312,6 +303,37 @@ will output
foo_a
```
## Traversing merge anchors with override
Given a sample.yml file of:
```yaml
foo: &foo
a: foo_a
thing: foo_thing
c: foo_c
bar: &bar
b: bar_b
thing: bar_thing
c: bar_c
foobarList:
b: foobarList_b
!!merge <<:
- *foo
- *bar
c: foobarList_c
foobar:
c: foobar_c
!!merge <<: *foo
thing: foobar_thing
```
then
```bash
yq '.foobar.c' sample.yml
```
will output
```yaml
foo_c
```
## Traversing merge anchors with local override
Given a sample.yml file of:
```yaml
@ -343,93 +365,7 @@ will output
foobar_thing
```
## Select multiple indices
Given a sample.yml file of:
```yaml
a:
- a
- b
- c
```
then
```bash
yq '.a[0, 2]' sample.yml
```
will output
```yaml
a
c
```
## LEGACY: Traversing merge anchors with override
This is legacy behaviour, see --yaml-fix-merge-anchor-to-spec
Given a sample.yml file of:
```yaml
foo: &foo
a: foo_a
thing: foo_thing
c: foo_c
bar: &bar
b: bar_b
thing: bar_thing
c: bar_c
foobarList:
b: foobarList_b
!!merge <<:
- *foo
- *bar
c: foobarList_c
foobar:
c: foobar_c
!!merge <<: *foo
thing: foobar_thing
```
then
```bash
yq '.foobar.c' sample.yml
```
will output
```yaml
foo_c
```
## LEGACY: Traversing merge anchor lists
Note that the later merge anchors override previous, but this is legacy behaviour, see --yaml-fix-merge-anchor-to-spec
Given a sample.yml file of:
```yaml
foo: &foo
a: foo_a
thing: foo_thing
c: foo_c
bar: &bar
b: bar_b
thing: bar_thing
c: bar_c
foobarList:
b: foobarList_b
!!merge <<:
- *foo
- *bar
c: foobarList_c
foobar:
c: foobar_c
!!merge <<: *foo
thing: foobar_thing
```
then
```bash
yq '.foobarList.thing' sample.yml
```
will output
```yaml
bar_thing
```
## LEGACY: Splatting merge anchors
With legacy override behaviour, see --yaml-fix-merge-anchor-to-spec
## Splatting merge anchors
Given a sample.yml file of:
```yaml
foo: &foo
@ -462,9 +398,40 @@ foo_a
foobar_thing
```
## LEGACY: Splatting merge anchor lists
With legacy override behaviour, see --yaml-fix-merge-anchor-to-spec
## Traversing merge anchor lists
Note that the later merge anchors override previous
Given a sample.yml file of:
```yaml
foo: &foo
a: foo_a
thing: foo_thing
c: foo_c
bar: &bar
b: bar_b
thing: bar_thing
c: bar_c
foobarList:
b: foobarList_b
!!merge <<:
- *foo
- *bar
c: foobarList_c
foobar:
c: foobar_c
!!merge <<: *foo
thing: foobar_thing
```
then
```bash
yq '.foobarList.thing' sample.yml
```
will output
```yaml
bar_thing
```
## Splatting merge anchor lists
Given a sample.yml file of:
```yaml
foo: &foo
@ -498,140 +465,21 @@ bar_thing
foobarList_c
```
## FIXED: Traversing merge anchors with override
Set `--yaml-fix-merge-anchor-to-spec=true` to get this correct merge behaviour.
## Select multiple indices
Given a sample.yml file of:
```yaml
foo: &foo
a: foo_a
thing: foo_thing
c: foo_c
bar: &bar
b: bar_b
thing: bar_thing
c: bar_c
foobarList:
b: foobarList_b
!!merge <<:
- *foo
- *bar
c: foobarList_c
foobar:
c: foobar_c
!!merge <<: *foo
thing: foobar_thing
a:
- a
- b
- c
```
then
```bash
yq '.foobar.c' sample.yml
yq '.a[0, 2]' sample.yml
```
will output
```yaml
foobar_c
```
## FIXED: Traversing merge anchor lists
Set `--yaml-fix-merge-anchor-to-spec=true` to get this correct merge behaviour. Note that the keys earlier in the merge anchors sequence override later ones
Given a sample.yml file of:
```yaml
foo: &foo
a: foo_a
thing: foo_thing
c: foo_c
bar: &bar
b: bar_b
thing: bar_thing
c: bar_c
foobarList:
b: foobarList_b
!!merge <<:
- *foo
- *bar
c: foobarList_c
foobar:
c: foobar_c
!!merge <<: *foo
thing: foobar_thing
```
then
```bash
yq '.foobarList.thing' sample.yml
```
will output
```yaml
foo_thing
```
## FIXED: Splatting merge anchors
Set `--yaml-fix-merge-anchor-to-spec=true` to get this correct merge behaviour. Note that the keys earlier in the merge anchors sequence override later ones
Given a sample.yml file of:
```yaml
foo: &foo
a: foo_a
thing: foo_thing
c: foo_c
bar: &bar
b: bar_b
thing: bar_thing
c: bar_c
foobarList:
b: foobarList_b
!!merge <<:
- *foo
- *bar
c: foobarList_c
foobar:
c: foobar_c
!!merge <<: *foo
thing: foobar_thing
```
then
```bash
yq '.foobar[]' sample.yml
```
will output
```yaml
foo_a
foobar_thing
foobar_c
```
## FIXED: Splatting merge anchor lists
Set `--yaml-fix-merge-anchor-to-spec=true` to get this correct merge behaviour. Note that the keys earlier in the merge anchors sequence override later ones
Given a sample.yml file of:
```yaml
foo: &foo
a: foo_a
thing: foo_thing
c: foo_c
bar: &bar
b: bar_b
thing: bar_thing
c: bar_c
foobarList:
b: foobarList_b
!!merge <<:
- *foo
- *bar
c: foobarList_c
foobar:
c: foobar_c
!!merge <<: *foo
thing: foobar_thing
```
then
```bash
yq '.foobarList[]' sample.yml
```
will output
```yaml
foobarList_b
foo_thing
foobarList_c
foo_a
a
c
```

View File

@ -1,5 +1,3 @@
//go:build !yq_nobase64
package yqlib
import (

View File

@ -1,5 +1,3 @@
//go:build !yq_nocsv
package yqlib
import (

View File

@ -1,113 +0,0 @@
//go:build !yq_noini
package yqlib
import (
"bytes"
"fmt"
"io"
"github.com/go-ini/ini"
)
type iniEncoder struct {
indentString string
}
// NewINIEncoder creates a new INI encoder
func NewINIEncoder() Encoder {
// Hardcoded indent value of 0, meaning no additional spacing.
return &iniEncoder{""}
}
// CanHandleAliases indicates whether the encoder supports aliases. INI does not support aliases.
func (ie *iniEncoder) CanHandleAliases() bool {
return false
}
// PrintDocumentSeparator is a no-op since INI does not support multiple documents.
func (ie *iniEncoder) PrintDocumentSeparator(_ io.Writer) error {
return nil
}
// PrintLeadingContent is a no-op since INI does not support leading content or comments at the encoder level.
func (ie *iniEncoder) PrintLeadingContent(_ io.Writer, _ string) error {
return nil
}
// Encode converts a CandidateNode into INI format and writes it to the provided writer.
func (ie *iniEncoder) Encode(writer io.Writer, node *CandidateNode) error {
log.Debugf("I need to encode %v", NodeToString(node))
log.Debugf("kids %v", len(node.Content))
if node.Kind == ScalarNode {
return writeStringINI(writer, node.Value+"\n")
}
// Create a new INI configuration.
cfg := ini.Empty()
if node.Kind == MappingNode {
// Default section for key-value pairs at the root level.
defaultSection, err := cfg.NewSection(ini.DefaultSection)
if err != nil {
return err
}
// Process the node's content.
for i := 0; i < len(node.Content); i += 2 {
keyNode := node.Content[i]
valueNode := node.Content[i+1]
key := keyNode.Value
switch valueNode.Kind {
case ScalarNode:
// Add key-value pair to the default section.
_, err := defaultSection.NewKey(key, valueNode.Value)
if err != nil {
return err
}
case MappingNode:
// Create a new section for nested MappingNode.
section, err := cfg.NewSection(key)
if err != nil {
return err
}
// Process nested key-value pairs.
for j := 0; j < len(valueNode.Content); j += 2 {
nestedKeyNode := valueNode.Content[j]
nestedValueNode := valueNode.Content[j+1]
if nestedValueNode.Kind == ScalarNode {
_, err := section.NewKey(nestedKeyNode.Value, nestedValueNode.Value)
if err != nil {
return err
}
} else {
log.Debugf("Skipping nested non-scalar value for key %s: %v", nestedKeyNode.Value, nestedValueNode.Kind)
}
}
default:
log.Debugf("Skipping non-scalar value for key %s: %v", key, valueNode.Kind)
}
}
} else {
return fmt.Errorf("INI encoder supports only MappingNode at the root level, got %v", node.Kind)
}
// Use a buffer to store the INI output as the library doesn't support direct io.Writer with indent.
var buffer bytes.Buffer
_, err := cfg.WriteToIndent(&buffer, ie.indentString)
if err != nil {
return err
}
// Write the buffer content to the provided writer.
_, err = writer.Write(buffer.Bytes())
return err
}
// writeStringINI is a helper function to write a string to the provided writer for INI encoder.
func writeStringINI(writer io.Writer, content string) error {
_, err := writer.Write([]byte(content))
return err
}

View File

@ -167,14 +167,10 @@ func needsQuoting(s string) bool {
// [%a_][%w_]*
for i, c := range s {
if i == 0 {
// keeping for legacy reasons, upgraded linter
//nolint:staticcheck
if !((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_') {
return true
}
} else {
// keeping for legacy reasons, upgraded linter
//nolint:staticcheck
if !((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_') {
return true
}
@ -303,10 +299,10 @@ func (le *luaEncoder) encodeAny(writer io.Writer, node *CandidateNode) error {
return writeString(writer, node.Value)
}
default:
return fmt.Errorf("lua encoder NYI -- %s", node.Tag)
return fmt.Errorf("Lua encoder NYI -- %s", node.Tag)
}
default:
return fmt.Errorf("lua encoder NYI -- %s", node.Tag)
return fmt.Errorf("Lua encoder NYI -- %s", node.Tag)
}
}

View File

@ -1,5 +1,3 @@
//go:build !yq_noprops
package yqlib
import (
@ -109,7 +107,7 @@ func (pe *propertiesEncoder) doEncode(p *properties.Properties, node *CandidateN
case AliasNode:
return pe.doEncode(p, node.Alias, path, nil)
default:
return fmt.Errorf("unsupported node %v", node.Tag)
return fmt.Errorf("Unsupported node %v", node.Tag)
}
}

View File

@ -1,5 +1,3 @@
//go:build !yq_nosh
package yqlib
import (

View File

@ -1,5 +1,3 @@
//go:build !yq_noshell
package yqlib
import (
@ -77,7 +75,7 @@ func (pe *shellVariablesEncoder) doEncode(w *io.Writer, node *CandidateNode, pat
case AliasNode:
return pe.doEncode(w, node.Alias, path)
default:
return fmt.Errorf("unsupported node %v", node.Tag)
return fmt.Errorf("Unsupported node %v", node.Tag)
}
}

View File

@ -1,5 +1,3 @@
//go:build !yq_nouri
package yqlib
import (

View File

@ -9,7 +9,7 @@ import (
"strings"
"github.com/fatih/color"
"go.yaml.in/yaml/v3"
"gopkg.in/yaml.v3"
)
type yamlEncoder struct {

View File

@ -9,7 +9,6 @@ type ExpressionNode struct {
Operation *Operation
LHS *ExpressionNode
RHS *ExpressionNode
Parent *ExpressionNode
}
type ExpressionParserInterface interface {
@ -51,26 +50,20 @@ func (p *expressionParserImpl) createExpressionTree(postFixPath []*Operation) (*
log.Debugf("pathTree %v ", Operation.toString())
if Operation.OperationType.NumArgs > 0 {
numArgs := Operation.OperationType.NumArgs
switch numArgs {
case 1:
if numArgs == 1 {
if len(stack) < 1 {
return nil, fmt.Errorf("'%v' expects 1 arg but received none", strings.TrimSpace(Operation.StringValue))
}
remaining, rhs := stack[:len(stack)-1], stack[len(stack)-1]
newNode.RHS = rhs
rhs.Parent = &newNode
stack = remaining
case 2:
} else if numArgs == 2 {
if len(stack) < 2 {
return nil, fmt.Errorf("'%v' expects 2 args but there is %v", strings.TrimSpace(Operation.StringValue), len(stack))
}
remaining, lhs, rhs := stack[:len(stack)-2], stack[len(stack)-2], stack[len(stack)-1]
newNode.LHS = lhs
lhs.Parent = &newNode
newNode.RHS = rhs
rhs.Parent = &newNode
stack = remaining
}
}

View File

@ -26,12 +26,11 @@ func popOpToResult(opStack []*token, result []*Operation) ([]*token, []*Operatio
}
func validateNoOpenTokens(token *token) error {
switch token.TokenType {
case openCollect:
if token.TokenType == openCollect {
return fmt.Errorf(("bad expression, could not find matching `]`"))
case openCollectObject:
} else if token.TokenType == openCollectObject {
return fmt.Errorf(("bad expression, could not find matching `}`"))
case openBracket:
} else if token.TokenType == openBracket {
return fmt.Errorf(("bad expression, could not find matching `)`"))
}
return nil
@ -65,7 +64,7 @@ func (p *expressionPostFixerImpl) ConvertToPostfix(infixTokens []*token) ([]*Ope
opStack, result = popOpToResult(opStack, result)
}
if len(opStack) == 0 {
return nil, errors.New("bad path expression, got close collect brackets without matching opening bracket")
return nil, errors.New("Bad path expression, got close collect brackets without matching opening bracket")
}
// now we should have [ as the last element on the opStack, get rid of it
opStack = opStack[0 : len(opStack)-1]

View File

@ -77,11 +77,6 @@ var LuaFormat = &Format{"lua", []string{"l"},
func() Decoder { return NewLuaDecoder(ConfiguredLuaPreferences) },
}
var INIFormat = &Format{"ini", []string{"i"},
func() Encoder { return NewINIEncoder() },
func() Decoder { return NewINIDecoder() },
}
var Formats = []*Format{
YamlFormat,
JSONFormat,
@ -95,7 +90,6 @@ var Formats = []*Format{
TomlFormat,
ShellVariablesFormat,
LuaFormat,
INIFormat,
}
func (f *Format) MatchesName(name string) bool {
@ -113,7 +107,7 @@ func FormatStringFromFilename(filename string) string {
if filename != "" {
GetLogger().Debugf("checking filename '%s' for auto format detection", filename)
ext := filepath.Ext(filename)
if len(ext) >= 2 && ext[0] == '.' {
if ext != "" && ext[0] == '.' {
format := strings.ToLower(ext[1:])
GetLogger().Debugf("detected format '%s'", format)
return format

View File

@ -97,18 +97,12 @@ var goccyYamlFormatScenarios = []formatScenario{
input: "a: meow # line comment\n",
expected: "a: meow # line comment\n",
},
// {
// description: "basic - head comment",
// skipDoc: true,
// input: "# head comment\na: meow\n",
// expected: "# head comment\na: meow\n", // go-yaml does this
// },
// {
// description: "basic - head and line comment",
// skipDoc: true,
// input: "# head comment\na: #line comment\n meow\n",
// expected: "# head comment\na: meow #line comment\n", // go-yaml does this
// },
{
description: "basic - line comment",
skipDoc: true,
input: "# head comment\na: #line comment\n meow\n",
expected: "# head comment\na: meow #line comment\n", // go-yaml does this
},
{
description: "basic - foot comment",
skipDoc: true,
@ -139,136 +133,24 @@ var goccyYamlFormatScenarios = []formatScenario{
input: "a: ~\n",
expected: "a: ~\n",
},
{
description: "basic - ~",
skipDoc: true,
input: "null\n",
expected: "null\n",
},
{
skipDoc: true,
description: "blank value round trip",
input: "test:",
expected: "test:\n",
},
{
skipDoc: true,
description: "trailing comment",
input: "test: null\n# this comment will be removed",
expected: "test: null\n# this comment will be removed\n",
},
// {
// description: "doc separator",
// description: "basic - ~",
// skipDoc: true,
// input: "# hi\n---\na: cat\n---",
// expected: "---\na: cat\n",
// input: "null\n",
// expected: "null\n",
// },
// {
// description: "scalar with doc separator",
// skipDoc: true,
// input: "--- cat",
// expected: "---\ncat\n",
// description: "trailing comment",
// input: "test:",
// expected: "test:",
// },
// {
// skipDoc: true,
// description: "trailing comment",
// input: "test:\n# this comment will be removed",
// expected: "test:\n# this comment will be removed",
// },
{
description: "scalar with doc separator",
skipDoc: true,
input: "---cat",
expected: "---cat\n",
},
{
description: "basic - null",
skipDoc: true,
input: "null",
expected: "null\n",
},
{
description: "basic - ~",
skipDoc: true,
input: "~",
expected: "~\n",
},
{
description: "octal",
skipDoc: true,
input: "0o30",
expression: "tag",
expected: "!!int\n",
},
{
description: "basic - [null]",
skipDoc: true,
input: "[null]",
expected: "[null]\n",
},
{
description: "multi document",
skipDoc: true,
input: "a: mike\n---\nb: remember",
expected: "a: mike\n---\nb: remember\n",
},
{
description: "single doc anchor map",
skipDoc: true,
input: "a: &remember mike\nb: *remember",
expected: "a: &remember mike\nb: *remember\n",
},
{
description: "explode doc anchor map",
skipDoc: true,
input: "a: &remember mike\nb: *remember",
expression: "explode(.)",
expected: "a: mike\nb: mike\n",
},
{
description: "multi document anchor map",
skipDoc: true,
input: "a: &remember mike\n---\nb: *remember",
expression: "explode(.)",
expected: "a: mike\n---\nb: mike\n",
},
{
description: "merge anchor",
skipDoc: true,
input: "a: &remember\n c: mike\nb:\n <<: *remember",
// fine to have !!merge as that's what the current impl does
expected: "a: &remember\n c: mike\nb:\n !!merge <<: *remember\n",
},
{
description: "custom tag",
skipDoc: true,
input: "a: !cat mike",
expected: "a: !cat mike\n",
},
{
description: "basic - [~]",
skipDoc: true,
input: "[~]",
expected: "[~]\n",
},
{
description: "basic - null map value",
skipDoc: true,
input: "a: null",
expected: "a: null\n",
},
{
description: "basic - number",
skipDoc: true,
input: "3",
expected: "3\n",
},
{
description: "basic - float",
skipDoc: true,
input: "3.1",
expected: "3.1\n",
},
{
description: "basic - float",
skipDoc: true,
input: "[1, 2]",
expected: "[1, 2]\n",
},
}
func testGoccyYamlScenario(t *testing.T, s formatScenario) {

View File

@ -1,19 +0,0 @@
package yqlib
type INIPreferences struct {
ColorsEnabled bool
}
func NewDefaultINIPreferences() INIPreferences {
return INIPreferences{
ColorsEnabled: false,
}
}
func (p *INIPreferences) Copy() INIPreferences {
return INIPreferences{
ColorsEnabled: p.ColorsEnabled,
}
}
var ConfiguredINIPreferences = NewDefaultINIPreferences()

View File

@ -1,187 +0,0 @@
//go:build !yq_noini
package yqlib
import (
"bufio"
"fmt"
"testing"
"github.com/mikefarah/yq/v4/test"
)
const simpleINIInput = `[section]
key = value
`
const expectedSimpleINIOutput = `[section]
key = value
`
const expectedSimpleINIYaml = `section:
key: value
`
var iniScenarios = []formatScenario{
{
description: "Parse INI: simple",
input: simpleINIInput,
scenarioType: "decode",
expected: expectedSimpleINIYaml,
},
{
description: "Encode INI: simple",
input: `section: {key: value}`,
expected: expectedSimpleINIOutput,
scenarioType: "encode",
},
{
description: "Roundtrip INI: simple",
input: simpleINIInput,
expected: expectedSimpleINIOutput,
scenarioType: "roundtrip",
},
{
description: "bad ini",
input: `[section\nkey = value`,
expectedError: `bad file 'sample.yml': failed to parse INI content: unclosed section: [section\nkey = value`,
scenarioType: "decode-error",
},
}
func documentRoundtripINIScenario(w *bufio.Writer, s formatScenario) {
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
if s.subdescription != "" {
writeOrPanic(w, s.subdescription)
writeOrPanic(w, "\n\n")
}
writeOrPanic(w, "Given a sample.ini file of:\n")
writeOrPanic(w, fmt.Sprintf("```ini\n%v\n```\n", s.input))
writeOrPanic(w, "then\n")
expression := s.expression
if expression != "" {
writeOrPanic(w, fmt.Sprintf("```bash\nyq -p=ini -o=ini '%v' sample.ini\n```\n", expression))
} else {
writeOrPanic(w, "```bash\nyq -p=ini -o=ini sample.ini\n```\n")
}
writeOrPanic(w, "will output\n")
writeOrPanic(w, fmt.Sprintf("```ini\n%v```\n\n", mustProcessFormatScenario(s, NewINIDecoder(), NewINIEncoder())))
}
func documentDecodeINIScenario(w *bufio.Writer, s formatScenario) {
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
if s.subdescription != "" {
writeOrPanic(w, s.subdescription)
writeOrPanic(w, "\n\n")
}
writeOrPanic(w, "Given a sample.ini file of:\n")
writeOrPanic(w, fmt.Sprintf("```ini\n%v\n```\n", s.input))
writeOrPanic(w, "then\n")
expression := s.expression
if expression != "" {
writeOrPanic(w, fmt.Sprintf("```bash\nyq -p=ini '%v' sample.ini\n```\n", expression))
} else {
writeOrPanic(w, "```bash\nyq -p=ini sample.ini\n```\n")
}
writeOrPanic(w, "will output\n")
writeOrPanic(w, fmt.Sprintf("```yaml\n%v```\n\n", mustProcessFormatScenario(s, NewINIDecoder(), NewYamlEncoder(ConfiguredYamlPreferences))))
}
func testINIScenario(t *testing.T, s formatScenario) {
switch s.scenarioType {
case "encode":
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewINIEncoder()), s.description)
case "decode":
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewINIDecoder(), NewYamlEncoder(ConfiguredYamlPreferences)), s.description)
case "roundtrip":
test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewINIDecoder(), NewINIEncoder()), s.description)
case "decode-error":
result, err := processFormatScenario(s, NewINIDecoder(), NewINIEncoder())
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)
}
default:
panic(fmt.Sprintf("unhandled scenario type %q", s.scenarioType))
}
}
func documentINIScenario(_ *testing.T, w *bufio.Writer, i interface{}) {
s := i.(formatScenario)
if s.skipDoc {
return
}
switch s.scenarioType {
case "encode":
documentINIEncodeScenario(w, s)
case "decode":
documentDecodeINIScenario(w, s)
case "roundtrip":
documentRoundtripINIScenario(w, s)
case "decode-error":
documentDecodeErrorINIScenario(w, s)
default:
panic(fmt.Sprintf("unhandled scenario type %q", s.scenarioType))
}
}
func documentINIEncodeScenario(w *bufio.Writer, s formatScenario) {
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
if s.subdescription != "" {
writeOrPanic(w, s.subdescription)
writeOrPanic(w, "\n\n")
}
writeOrPanic(w, "Given a sample.yml file of:\n")
writeOrPanic(w, fmt.Sprintf("```yaml\n%v\n```\n", s.input))
writeOrPanic(w, "then\n")
expression := s.expression
if expression == "" {
expression = "."
}
writeOrPanic(w, fmt.Sprintf("```bash\nyq -o=ini '%v' sample.yml\n```\n", expression))
writeOrPanic(w, "will output\n")
writeOrPanic(w, fmt.Sprintf("```ini\n%v```\n\n", mustProcessFormatScenario(s, NewYamlDecoder(ConfiguredYamlPreferences), NewINIEncoder())))
}
func documentDecodeErrorINIScenario(w *bufio.Writer, s formatScenario) {
writeOrPanic(w, fmt.Sprintf("## %v\n", s.description))
if s.subdescription != "" {
writeOrPanic(w, s.subdescription)
writeOrPanic(w, "\n\n")
}
writeOrPanic(w, "Given a sample.ini file of:\n")
writeOrPanic(w, fmt.Sprintf("```ini\n%v\n```\n", s.input))
writeOrPanic(w, "then an error is expected:\n")
writeOrPanic(w, fmt.Sprintf("```\n%v\n```\n\n", s.expectedError))
}
func TestINIScenarios(t *testing.T) {
for _, tt := range iniScenarios {
testINIScenario(t, tt)
}
genericScenarios := make([]interface{}, len(iniScenarios))
for i, s := range iniScenarios {
genericScenarios[i] = s
}
documentScenarios(t, "usage", "convert", genericScenarios, documentINIScenario)
}

View File

@ -31,25 +31,24 @@ type token struct {
}
func (t *token) toString(detail bool) string {
switch t.TokenType {
case operationToken:
if t.TokenType == operationToken {
if detail {
return fmt.Sprintf("%v (%v)", t.Operation.toString(), t.Operation.OperationType.Precedence)
}
return t.Operation.toString()
case openBracket:
} else if t.TokenType == openBracket {
return "("
case closeBracket:
} else if t.TokenType == closeBracket {
return ")"
case openCollect:
} else if t.TokenType == openCollect {
return "["
case closeCollect:
} else if t.TokenType == closeCollect {
return "]"
case openCollectObject:
} else if t.TokenType == openCollectObject {
return "{"
case closeCollectObject:
} else if t.TokenType == closeCollectObject {
return "}"
case traverseArrayCollect:
} else if t.TokenType == traverseArrayCollect {
return ".["
}

View File

@ -28,17 +28,6 @@ func GetLogger() *logging.Logger {
return log
}
func getContentValueByKey(content []*CandidateNode, key string) *CandidateNode {
for index := 0; index < len(content); index = index + 2 {
keyNode := content[index]
valueNode := content[index+1]
if keyNode.Value == key {
return valueNode
}
}
return nil
}
func recurseNodeArrayEqual(lhs *CandidateNode, rhs *CandidateNode) bool {
if len(lhs.Content) != len(rhs.Content) {
return false

View File

@ -1,11 +0,0 @@
//go:build yq_nobase64
package yqlib
func NewBase64Decoder() Decoder {
return nil
}
func NewBase64Encoder() Encoder {
return nil
}

View File

@ -1,11 +0,0 @@
//go:build yq_nocsv
package yqlib
func NewCSVObjectDecoder(prefs CsvPreferences) Decoder {
return nil
}
func NewCsvEncoder(prefs CsvPreferences) Encoder {
return nil
}

View File

@ -1,11 +0,0 @@
//go:build yq_noini
package yqlib
func NewINIDecoder() Decoder {
return nil
}
func NewINIEncoder() Encoder {
return nil
}

View File

@ -6,6 +6,6 @@ func NewJSONDecoder() Decoder {
return nil
}
func NewJSONEncoder(prefs JsonPreferences) Encoder {
func NewJSONEncoder(indent int, colorise bool, unwrapScalar bool) Encoder {
return nil
}

View File

@ -1,11 +0,0 @@
//go:build yq_noprops
package yqlib
func NewPropertiesDecoder() Decoder {
return nil
}
func NewPropertiesEncoder(prefs PropertiesPreferences) Encoder {
return nil
}

View File

@ -1,7 +0,0 @@
//go:build yq_nosh
package yqlib
func NewShEncoder() Encoder {
return nil
}

View File

@ -1,7 +0,0 @@
//go:build yq_noshell
package yqlib
func NewShellVariablesEncoder() Encoder {
return nil
}

View File

@ -1,11 +0,0 @@
//go:build yq_nouri
package yqlib
func NewUriDecoder() Decoder {
return nil
}
func NewUriEncoder() Encoder {
return nil
}

View File

@ -6,6 +6,6 @@ func NewXMLDecoder(prefs XmlPreferences) Decoder {
return nil
}
func NewXMLEncoder(prefs XmlPreferences) Encoder {
func NewXMLEncoder(indent int, prefs XmlPreferences) Encoder {
return nil
}

View File

@ -38,11 +38,8 @@ func toNodes(candidate *CandidateNode, lhs *CandidateNode) []*CandidateNode {
func addOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debugf("Add operator")
// only calculate when empty IF we are the root expression; OR
// calcWhenEmpty := expressionNode.Parent == nil || expressionNode.Parent.LHS == expressionNode
calcWhenEmpty := context.MatchingNodes.Len() > 0
return crossFunction(d, context.ReadOnlyClone(), expressionNode, add, calcWhenEmpty)
return crossFunction(d, context.ReadOnlyClone(), expressionNode, add, true)
}
func add(_ *dataTreeNavigator, context Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {

View File

@ -5,107 +5,6 @@ import (
)
var addOperatorScenarios = []expressionScenario{
{
skipDoc: true,
expression: `"foo" + "bar"`,
expected: []string{
"D0, P[], (!!str)::foobar\n",
},
},
{
skipDoc: true,
expression: `[] | .[] | "foo" + .`,
expected: []string{},
},
{
skipDoc: true,
expression: `[] | .[] | . + "foo"`,
expected: []string{},
},
{
skipDoc: true,
expression: `select(.) | "foo" + "bar"`,
expected: []string{
"D0, P[], (!!str)::foobar\n", // jq does not do this :/ - but yq has for quite some time.
},
},
{
skipDoc: true,
document: "apples: 3",
expression: `.apples + 3`,
expected: []string{
"D0, P[apples], (!!int)::6\n",
},
},
{
skipDoc: true,
document: "apples: 3",
expression: `.bobo + 3`,
expected: []string{
"D0, P[], (!!int)::3\n",
},
},
{
skipDoc: true,
expression: `select(.) | "cat" + .`,
expected: []string{},
},
{
skipDoc: true,
document: `[]`,
expression: `.[] | (.a + "|" + .b)`,
expected: []string{},
},
{
skipDoc: true,
document: `[]`,
expression: `.[] | (.a + "|")`,
expected: []string{},
},
{
skipDoc: true,
document: `[]`,
expression: `.[] | ("|" + .a)`,
expected: []string{},
},
{
skipDoc: true,
document: `resources: [foo, bar, baz]`,
expression: `.missing + .resources | .[]`,
expected: []string{
"D0, P[resources 0], (!!str)::foo\n",
"D0, P[resources 1], (!!str)::bar\n",
"D0, P[resources 2], (!!str)::baz\n",
},
},
{
skipDoc: true,
document: `resources: [foo, bar, baz]`,
expression: `. | .missing + .resources | .[]`,
expected: []string{
"D0, P[resources 0], (!!str)::foo\n",
"D0, P[resources 1], (!!str)::bar\n",
"D0, P[resources 2], (!!str)::baz\n",
},
},
{
skipDoc: true,
document: `resources: [foo, bar, baz]`,
expression: `. | .missing + .resources`,
expected: []string{
"D0, P[resources], (!!seq)::[foo, bar, baz]\n",
},
},
{
skipDoc: true,
document: `resources: [foo, bar, baz]`,
expression: `. | .missing + .resources | .[]`,
expected: []string{
"D0, P[resources 0], (!!str)::foo\n",
"D0, P[resources 1], (!!str)::bar\n",
"D0, P[resources 2], (!!str)::baz\n",
},
},
{
skipDoc: true,
document: `[{a: foo, b: bar}, {a: 1, b: 2}]`,
@ -411,20 +310,6 @@ var addOperatorScenarios = []expressionScenario{
"D0, P[], (!!map)::a: !cat Saturday, 15-Dec-01 at 6:00AM GMT\n",
},
},
{
skipDoc: true,
description: "empty add shouldn't add",
document: `[]`,
expression: `.[] | (.a + "cat")`,
expected: []string{},
},
{
skipDoc: true,
description: "empty add shouldn't add",
document: `[]`,
expression: `.[] | (.a + "cat" + .b)`,
expected: []string{},
},
{
skipDoc: true,
description: "Add to empty",

View File

@ -5,8 +5,6 @@ import (
"fmt"
)
var showMergeAnchorToSpecWarning = true
func assignAliasOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
log.Debugf("AssignAlias operator!")
@ -140,59 +138,6 @@ func explodeOperator(d *dataTreeNavigator, context Context, expressionNode *Expr
return context, nil
}
func fixedReconstructAliasedMap(node *CandidateNode) error {
var newContent = []*CandidateNode{}
for index := 0; index < len(node.Content); index = index + 2 {
keyNode := node.Content[index]
valueNode := node.Content[index+1]
if keyNode.Tag != "!!merge" {
// always add in plain nodes
// explode both the key and value nodes
if err := explodeNode(keyNode, Context{}); err != nil {
return err
}
if err := explodeNode(valueNode, Context{}); err != nil {
return err
}
newContent = append(newContent, keyNode, valueNode)
} else {
sequence := valueNode
if sequence.Kind == AliasNode {
sequence = sequence.Alias
}
if sequence.Kind != SequenceNode {
sequence = &CandidateNode{Content: []*CandidateNode{sequence}}
}
for index := 0; index < len(sequence.Content); index = index + 1 {
// for merge anchors, we only set them if the key is not already in node or the newContent
mergeNodeSeq := sequence.Content[index]
if mergeNodeSeq.Kind == AliasNode {
mergeNodeSeq = mergeNodeSeq.Alias
}
if mergeNodeSeq.Kind != MappingNode {
return fmt.Errorf("can only use merge anchors with maps (!!map) or sequences (!!seq) of maps, but got sequence containing %v", mergeNodeSeq.Tag)
}
itemsToAdd := mergeNodeSeq.FilterMapContentByKey(func(keyNode *CandidateNode) bool {
return getContentValueByKey(node.Content, keyNode.Value) == nil &&
getContentValueByKey(newContent, keyNode.Value) == nil
})
for _, item := range itemsToAdd {
// copy to ensure exploding doesn't modify the original node
itemCopy := item.Copy()
if err := explodeNode(itemCopy, Context{}); err != nil {
return err
}
newContent = append(newContent, itemCopy)
}
}
}
}
node.Content = newContent
return nil
}
func reconstructAliasedMap(node *CandidateNode, context Context) error {
var newContent = list.New()
// can I short cut here by prechecking if there's an anchor in the map?
@ -270,13 +215,6 @@ func explodeNode(node *CandidateNode, context Context) error {
}
if hasAlias {
if ConfiguredYamlPreferences.FixMergeAnchorToSpec {
return fixedReconstructAliasedMap(node)
}
if showMergeAnchorToSpecWarning {
log.Warning("--yaml-fix-merge-anchor-to-spec is false; causing merge anchors to override the existing values which isn't to the yaml spec. This flag will default to true in late 2025.")
showMergeAnchorToSpecWarning = false
}
// this is a slow op, which is why we want to check before running it.
return reconstructAliasedMap(node, context)
}
@ -306,7 +244,7 @@ func applyAlias(node *CandidateNode, alias *CandidateNode, aliasIndex int, newCo
}
log.Debug("alias: %v", NodeToString(alias))
if alias.Kind != MappingNode {
return fmt.Errorf("can only use merge anchors with maps (!!map) or sequences (!!seq) of maps, but got sequence containing %v", alias.Tag)
return fmt.Errorf("merge anchor only supports maps, got %v instead", alias.Tag)
}
for index := 0; index < len(alias.Content); index = index + 2 {
keyNode := alias.Content[index]

View File

@ -34,25 +34,6 @@ thingTwo:
!!merge <<: *item_value
`
var explodeMergeAnchorsFixedExpected = `D0, P[], (!!map)::foo:
a: foo_a
thing: foo_thing
c: foo_c
bar:
b: bar_b
thing: bar_thing
c: bar_c
foobarList:
b: foobarList_b
a: foo_a
thing: foo_thing
c: foobarList_c
foobar:
c: foobar_c
a: foo_a
thing: foobar_thing
`
var explodeMergeAnchorsExpected = `D0, P[], (!!map)::foo:
a: foo_a
thing: foo_thing
@ -72,226 +53,12 @@ foobar:
thing: foobar_thing
`
var explodeWhenKeysExistDocument = `objects:
- &circle
name: circle
shape: round
- name: ellipse
!!merge <<: *circle
- !!merge <<: *circle
name: egg
`
var explodeWhenKeysExistLegacy = `D0, P[], (!!map)::objects:
- name: circle
shape: round
- name: circle
shape: round
- shape: round
name: egg
`
var explodeWhenKeysExistExpected = `D0, P[], (!!map)::objects:
- name: circle
shape: round
- name: ellipse
shape: round
- shape: round
name: egg
`
var fixedAnchorOperatorScenarios = []expressionScenario{
{
skipDoc: true,
description: "merge anchor after existing keys",
subdescription: "Does not override existing keys - note the name field in the second element is still ellipse.",
document: explodeWhenKeysExistDocument,
expression: "explode(.)",
expected: []string{explodeWhenKeysExistExpected},
},
{
description: "FIXED: Explode with merge anchors",
subdescription: "Observe that foobarList.b property is still foobarList_b.",
document: mergeDocSample,
expression: `explode(.)`,
expected: []string{explodeMergeAnchorsFixedExpected},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foo* | explode(.) | (. style="flow")`,
expected: []string{
"D0, P[foo], (!!map)::{a: foo_a, thing: foo_thing, c: foo_c}\n",
"D0, P[foobarList], (!!map)::{b: foobarList_b, a: foo_a, thing: foo_thing, c: foobarList_c}\n",
"D0, P[foobar], (!!map)::{c: foobar_c, a: foo_a, thing: foobar_thing}\n",
},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foo* | explode(explode(.)) | (. style="flow")`,
expected: []string{
"D0, P[foo], (!!map)::{a: foo_a, thing: foo_thing, c: foo_c}\n",
"D0, P[foobarList], (!!map)::{b: foobarList_b, a: foo_a, thing: foo_thing, c: foobarList_c}\n",
"D0, P[foobar], (!!map)::{c: foobar_c, a: foo_a, thing: foobar_thing}\n",
},
},
{
description: "FIXED: Merge multiple maps",
subdescription: "Taken from https://yaml.org/type/merge.html. Same values as legacy, but with the correct key order.",
document: specDocument + "- << : [ *CENTER, *BIG ]\n",
expression: ".[4] | explode(.)",
expected: []string{"D0, P[4], (!!map)::x: 1\ny: 2\nr: 10\n"},
},
{
description: "FIXED: Override",
subdescription: "Taken from https://yaml.org/type/merge.html. Same values as legacy, but with the correct key order.",
document: specDocument + "- << : [ *BIG, *LEFT, *SMALL ]\n x: 1\n",
expression: ".[4] | explode(.)",
expected: []string{"D0, P[4], (!!map)::r: 10\ny: 2\nx: 1\n"},
},
{
description: "Exploding inline merge anchor",
// subdescription: "`<<` map must be exploded, otherwise `c: *b` will become invalid",
document: `{a: {b: &b 42}, <<: {c: *b}}`,
expression: `explode(.) | sort_keys(.)`,
expected: []string{
"D0, P[], (!!map)::{a: {b: 42}, c: 42}\n",
},
},
{
skipDoc: true,
description: "Exploding inline merge anchor in sequence",
subdescription: "`<<` map must be exploded, otherwise `c: *b` will become invalid",
document: `{a: {b: &b 42}, <<: [{c: *b}]}`,
expression: `explode(.) | sort_keys(.)`,
expected: []string{
"D0, P[], (!!map)::{a: {b: 42}, c: 42}\n",
},
},
{
skipDoc: true,
description: "Exploding merge anchor should not explode neighbors",
subdescription: "b must not be exploded, as `r: *a` will become invalid",
document: `{b: &b {a: &a 42}, r: *a, c: {<<: *b}}`,
expression: `explode(.c)`,
expected: []string{
"D0, P[], (!!map)::{b: &b {a: &a 42}, r: *a, c: {a: 42}}\n",
},
},
{
skipDoc: true,
description: "Exploding sequence merge anchor should not explode neighbors",
subdescription: "b must not be exploded, as `r: *a` will become invalid",
document: `{b: &b {a: &a 42}, r: *a, c: {<<: [*b]}}`,
expression: `explode(.c)`,
expected: []string{
"D0, P[], (!!map)::{b: &b {a: &a 42}, r: *a, c: {a: 42}}\n",
},
},
{
skipDoc: true,
description: "Merge anchor with inline map",
document: `{<<: {a: 42}}`,
expression: `explode(.)`,
expected: []string{
"D0, P[], (!!map)::{a: 42}\n",
},
},
{
skipDoc: true,
description: "Merge anchor with sequence with inline map",
document: `{<<: [{a: 42}]}`,
expression: `explode(.)`,
expected: []string{
"D0, P[], (!!map)::{a: 42}\n",
},
},
{
skipDoc: true,
description: "Merge anchor with aliased sequence with inline map",
document: `{s: &s [{a: 42}], m: {<<: *s}}`,
expression: `.m | explode(.)`,
expected: []string{
"D0, P[m], (!!map)::{a: 42}\n",
},
},
{
skipDoc: true,
description: "deleting after explode",
document: "x: 37\na: &a\n b: 42\n<<: *a",
expression: `explode(.) | del(.x)`,
expected: []string{
"D0, P[], (!!map)::a:\n b: 42\nb: 42\n",
},
},
}
var badAnchorOperatorScenarios = []expressionScenario{
{
skipDoc: true, // incorrect overrides
description: "LEGACY: merge anchor after existing keys",
document: explodeWhenKeysExistDocument,
expression: "explode(.)",
expected: []string{explodeWhenKeysExistLegacy},
},
{
description: "LEGACY: Explode with merge anchors", // incorrect overrides
subdescription: "Caution: this is for when --yaml-fix-merge-anchor-to-spec=false; it's not to YAML spec because the merge anchors incorrectly override the object values (foobarList.b is set to bar_b when it should still be foobarList_b). Flag will default to true in late 2025",
document: mergeDocSample,
expression: `explode(.)`,
expected: []string{explodeMergeAnchorsExpected},
},
{
skipDoc: true,
document: mergeDocSample, // incorrect overrides
expression: `.foo* | explode(.) | (. style="flow")`,
expected: []string{
"D0, P[foo], (!!map)::{a: foo_a, thing: foo_thing, c: foo_c}\n",
"D0, P[foobarList], (!!map)::{b: bar_b, thing: foo_thing, c: foobarList_c, a: foo_a}\n",
"D0, P[foobar], (!!map)::{c: foo_c, a: foo_a, thing: foobar_thing}\n",
},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foo* | explode(explode(.)) | (. style="flow")`,
expected: []string{
"D0, P[foo], (!!map)::{a: foo_a, thing: foo_thing, c: foo_c}\n",
"D0, P[foobarList], (!!map)::{b: bar_b, thing: foo_thing, c: foobarList_c, a: foo_a}\n",
"D0, P[foobar], (!!map)::{c: foo_c, a: foo_a, thing: foobar_thing}\n",
},
},
{
description: "LEGACY: Merge multiple maps",
subdescription: "see https://yaml.org/type/merge.html. This has the correct data, but the wrong key order; set --yaml-fix-merge-anchor-to-spec=true to fix the key order.",
document: specDocument + "- << : [ *CENTER, *BIG ]\n",
expression: ".[4] | explode(.)",
expected: []string{"D0, P[4], (!!map)::r: 10\nx: 1\ny: 2\n"},
},
{
description: "LEGACY: Override",
subdescription: "see https://yaml.org/type/merge.html. This has the correct data, but the wrong key order; set --yaml-fix-merge-anchor-to-spec=true to fix the key order.",
document: specDocument + "- << : [ *BIG, *LEFT, *SMALL ]\n x: 1\n",
expression: ".[4] | explode(.)",
expected: []string{"D0, P[4], (!!map)::r: 10\nx: 1\ny: 2\n"},
},
}
var anchorOperatorScenarios = []expressionScenario{
{
skipDoc: true,
description: "merge anchor to alias alias",
document: "b: &b 10\na: &a { k: *b }\nc:\n <<: [*a]",
expression: "explode(.)",
expected: []string{"D0, P[], (!!map)::b: 10\na: {k: 10}\nc:\n k: 10\n"},
},
{
skipDoc: true,
description: "merge anchor not map",
document: "a: &a\n - 0\nc:\n <<: [*a]\n",
expectedError: "can only use merge anchors with maps (!!map) or sequences (!!seq) of maps, but got sequence containing !!seq",
expectedError: "merge anchor only supports maps, got !!seq instead",
expression: "explode(.)",
},
{
@ -301,7 +68,20 @@ var anchorOperatorScenarios = []expressionScenario{
expression: ".[4] | explode(.)",
expected: []string{expectedSpecResult},
},
{
description: "Merge multiple maps",
subdescription: "see https://yaml.org/type/merge.html",
document: specDocument + "- << : [ *CENTER, *BIG ]\n",
expression: ".[4] | explode(.)",
expected: []string{"D0, P[4], (!!map)::r: 10\nx: 1\ny: 2\n"},
},
{
description: "Override",
subdescription: "see https://yaml.org/type/merge.html",
document: specDocument + "- << : [ *BIG, *LEFT, *SMALL ]\n x: 1\n",
expression: ".[4] | explode(.)",
expected: []string{"D0, P[4], (!!map)::r: 10\nx: 1\ny: 2\n"},
},
{
description: "Get anchor",
document: `a: &billyBob cat`,
@ -443,25 +223,40 @@ var anchorOperatorScenarios = []expressionScenario{
"D0, P[], (!!map)::a: mike\n",
},
},
{
description: "Explode with alias keys",
subdescription: "No space between alias",
skipDoc: true,
document: `{f : {a: &a cat, *a: b}}`,
expression: `explode(.f)`,
expected: []string{
"D0, P[], (!!map)::{f: {a: cat, cat: b}}\n",
},
skipForGoccy: true, // can't handle no space between alias
},
{
description: "Explode with alias keys",
document: `{f : {a: &a cat, *a : b}}`,
document: `{f : {a: &a cat, *a: b}}`,
expression: `explode(.f)`,
expected: []string{
"D0, P[], (!!map)::{f: {a: cat, cat: b}}\n",
},
},
{
description: "Explode with merge anchors",
document: mergeDocSample,
expression: `explode(.)`,
expected: []string{explodeMergeAnchorsExpected},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foo* | explode(.) | (. style="flow")`,
expected: []string{
"D0, P[foo], (!!map)::{a: foo_a, thing: foo_thing, c: foo_c}\n",
"D0, P[foobarList], (!!map)::{b: bar_b, thing: foo_thing, c: foobarList_c, a: foo_a}\n",
"D0, P[foobar], (!!map)::{c: foo_c, a: foo_a, thing: foobar_thing}\n",
},
},
{
skipDoc: true,
document: mergeDocSample,
expression: `.foo* | explode(explode(.)) | (. style="flow")`,
expected: []string{
"D0, P[foo], (!!map)::{a: foo_a, thing: foo_thing, c: foo_c}\n",
"D0, P[foobarList], (!!map)::{b: bar_b, thing: foo_thing, c: foobarList_c, a: foo_a}\n",
"D0, P[foobar], (!!map)::{c: foo_c, a: foo_a, thing: foobar_thing}\n",
},
},
{
skipDoc: true,
document: `{f : {a: &a cat, b: &b {foo: *a}, *a: *b}}`,
@ -474,47 +269,14 @@ var anchorOperatorScenarios = []expressionScenario{
description: "Dereference and update a field",
subdescription: "Use explode with multiply to dereference an object",
document: simpleArrayRef,
expression: `.thingOne |= (explode(.) | sort_keys(.)) * {"value": false}`,
expression: `.thingOne |= explode(.) * {"value": false}`,
expected: []string{expectedUpdatedArrayRef},
},
{
skipDoc: true,
description: "Duplicate keys",
subdescription: "outside merge anchor",
document: `{a: 1, a: 2}`,
expression: `explode(.)`,
expected: []string{
// {a: 2} would also be fine
"D0, P[], (!!map)::{a: 1, a: 2}\n",
},
},
{
skipDoc: true,
description: "!!str << should not be treated as merge anchor",
document: `{!!str <<: {a: 37}}`,
expression: `explode(.).a`,
expected: []string{
"D0, P[a], (!!null)::null\n",
},
},
}
func TestAnchorAliasOperatorScenarios(t *testing.T) {
for _, tt := range append(anchorOperatorScenarios, badAnchorOperatorScenarios...) {
for _, tt := range anchorOperatorScenarios {
testScenario(t, &tt)
}
documentOperatorScenarios(t, "anchor-and-alias-operators", append(anchorOperatorScenarios, badAnchorOperatorScenarios...))
}
func TestAnchorAliasOperatorAlignedToSpecScenarios(t *testing.T) {
ConfiguredYamlPreferences.FixMergeAnchorToSpec = true
for _, tt := range append(fixedAnchorOperatorScenarios, anchorOperatorScenarios...) {
testScenario(t, &tt)
}
for i, tt := range fixedAnchorOperatorScenarios {
fixedAnchorOperatorScenarios[i].subdescription = "Set `--yaml-fix-merge-anchor-to-spec=true` to get this correct merge behaviour (flag will default to true in late 2025).\n" + tt.subdescription
}
appendOperatorDocumentScenario(t, "anchor-and-alias-operators", fixedAnchorOperatorScenarios)
ConfiguredYamlPreferences.FixMergeAnchorToSpec = false
documentOperatorScenarios(t, "anchor-and-alias-operators", anchorOperatorScenarios)
}

View File

@ -2,7 +2,6 @@ package yqlib
import (
"container/list"
"fmt"
)
/*
@ -35,9 +34,6 @@ func collectObjectOperator(d *dataTreeNavigator, originalContext Context, _ *Exp
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
candidateNode := el.Value.(*CandidateNode)
if len(candidateNode.Content) < len(first.Content) {
return Context{}, fmt.Errorf("CollectObject: mismatching node sizes; are you creating a map with mismatching key value pairs?")
}
for i := 0; i < len(first.Content); i++ {
log.Debugf("rotate[%v] = %v", i, NodeToString(candidateNode.Content[i]))

View File

@ -12,11 +12,6 @@ var collectObjectOperatorScenarios = []expressionScenario{
"D0, P[name], (!!str)::mike\n",
},
},
{
skipDoc: true,
expression: `{"c": "a", "b", "d"}`,
expectedError: "CollectObject: mismatching node sizes; are you creating a map with mismatching key value pairs?",
},
{
skipDoc: true,
expression: `{"person": {"names": ["mike"]}} | .person.names[0]`,

View File

@ -26,12 +26,11 @@ func deleteChildOperator(d *dataTreeNavigator, context Context, expressionNode *
candidatePath := candidate.GetPath()
childPath := candidatePath[len(candidatePath)-1]
switch parentNode.Kind {
case MappingNode:
if parentNode.Kind == MappingNode {
deleteFromMap(candidate.Parent, childPath)
case SequenceNode:
} else if parentNode.Kind == SequenceNode {
deleteFromArray(candidate.Parent, childPath)
default:
} else {
return Context{}, fmt.Errorf("cannot delete nodes from parent of tag %v", parentNode.Tag)
}
}

View File

@ -119,60 +119,6 @@ var deleteOperatorScenarios = []expressionScenario{
"D0, P[], (!!map)::a: []\n",
},
},
{
skipDoc: true,
description: "Delete entry appended to an array",
document: `[1,2]`,
expression: `. += [3] | del(.[2])`,
expected: []string{
"D0, P[], (!!seq)::[1, 2]\n",
},
},
{
skipDoc: true,
description: "Delete entry after sorting an array",
document: `[3,2,1]`,
expression: `sort | del(.[2])`,
expected: []string{
"D0, P[], (!!seq)::[1, 2]\n",
},
},
{
skipDoc: true,
description: "Delete entry after reversing an array",
document: `[1,2,3]`,
expression: `reverse | del(.[2])`,
expected: []string{
"D0, P[], (!!seq)::[3, 2]\n",
},
},
{
skipDoc: true,
description: "Delete entry after shuffling an array",
document: `[1,2,3]`,
expression: `shuffle | del(.[2])`,
expected: []string{
"D0, P[], (!!seq)::[3, 1]\n",
},
},
{
skipDoc: true,
description: "Delete entry from keys array",
document: `{"a": 1, "b": 2, "c": 3}`,
expression: `keys | del(.[] | select(.=="b"))`,
expected: []string{
"D0, P[], (!!seq)::- \"a\"\n- \"c\"\n",
},
},
{
skipDoc: true,
description: "Delete entry after flattening an array",
document: `[1,[2],[[3]]]`,
expression: `flatten | del(.[2])`,
expected: []string{
"D0, P[], (!!seq)::[1, 2]\n",
},
},
{
skipDoc: true,
document: `a: [10,x,10, 10, x, 10]`,

View File

@ -21,8 +21,8 @@ var flattenOperatorScenarios = []expressionScenario{
expression: `flatten[]`,
expected: []string{
"D0, P[0], (!!int)::1\n",
"D0, P[1], (!!int)::2\n",
"D0, P[2], (!!int)::3\n",
"D0, P[0], (!!int)::2\n",
"D0, P[0], (!!int)::3\n",
},
},
{
@ -40,8 +40,8 @@ var flattenOperatorScenarios = []expressionScenario{
expression: `flatten(1)[]`,
expected: []string{
"D0, P[0], (!!int)::1\n",
"D0, P[1], (!!int)::2\n",
"D0, P[2], (!!seq)::[3]\n",
"D0, P[0], (!!int)::2\n",
"D0, P[0], (!!seq)::[3]\n",
},
},
{

View File

@ -45,13 +45,12 @@ func keysOperator(_ *dataTreeNavigator, context Context, _ *ExpressionNode) (Con
candidate := el.Value.(*CandidateNode)
var targetNode *CandidateNode
switch candidate.Kind {
case MappingNode:
if candidate.Kind == MappingNode {
targetNode = getMapKeys(candidate)
case SequenceNode:
} else if candidate.Kind == SequenceNode {
targetNode = getIndices(candidate)
default:
return Context{}, fmt.Errorf("cannot get keys of %v, keys only works for maps and arrays", candidate.Tag)
} else {
return Context{}, fmt.Errorf("Cannot get keys of %v, keys only works for maps and arrays", candidate.Tag)
}
results.PushBack(targetNode)
@ -65,20 +64,19 @@ func getMapKeys(node *CandidateNode) *CandidateNode {
for index := 0; index < len(node.Content); index = index + 2 {
contents = append(contents, node.Content[index])
}
seq := &CandidateNode{Kind: SequenceNode, Tag: "!!seq"}
seq.AddChildren(contents)
return seq
return &CandidateNode{Kind: SequenceNode, Tag: "!!seq", Content: contents}
}
func getIndices(node *CandidateNode) *CandidateNode {
var contents = make([]*CandidateNode, len(node.Content))
for index := range node.Content {
contents[index] = createScalarNode(index, fmt.Sprintf("%v", index))
contents[index] = &CandidateNode{
Kind: ScalarNode,
Tag: "!!int",
Value: fmt.Sprintf("%v", index),
}
}
seq := &CandidateNode{Kind: SequenceNode, Tag: "!!seq"}
seq.AddChildren(contents)
return seq
return &CandidateNode{Kind: SequenceNode, Tag: "!!seq", Content: contents}
}

View File

@ -45,8 +45,8 @@ var keysOperatorScenarios = []expressionScenario{
document: `{dog: woof, cat: meow}`,
expression: `keys[]`,
expected: []string{
"D0, P[0], (!!str)::dog\n",
"D0, P[1], (!!str)::cat\n",
"D0, P[dog], (!!str)::dog\n",
"D0, P[cat], (!!str)::cat\n",
},
},
{

View File

@ -82,7 +82,7 @@ func loadStringOperator(d *dataTreeNavigator, context Context, expressionNode *E
contentsCandidate, err := loadString(filename)
if err != nil {
return Context{}, fmt.Errorf("failed to load %v: %w", filename, err)
return Context{}, fmt.Errorf("Failed to load %v: %w", filename, err)
}
results.PushBack(contentsCandidate)
@ -118,7 +118,7 @@ func loadOperator(d *dataTreeNavigator, context Context, expressionNode *Express
contentsCandidate, err := loadWithDecoder(filename, loadPrefs.decoder)
if err != nil {
return Context{}, fmt.Errorf("failed to load %v: %w", filename, err)
return Context{}, fmt.Errorf("Failed to load %v: %w", filename, err)
}
results.PushBack(contentsCandidate)

View File

@ -41,10 +41,6 @@ func mapOperator(d *dataTreeNavigator, context Context, expressionNode *Expressi
if err != nil {
return Context{}, err
}
if splatted.MatchingNodes.Len() == 0 {
results.PushBack(candidate.Copy())
continue
}
result, err := d.GetMatchingNodes(splatted, expressionNode.RHS)
log.Debug("expressionNode.Rhs %v", expressionNode.RHS.Operation.OperationType)

View File

@ -15,53 +15,6 @@ var mapOperatorScenarios = []expressionScenario{
"D0, P[], (!!seq)::[6, 7, 8]\n",
},
},
{
description: "mapping against an empty array should do nothing",
skipDoc: true,
document: `[]`,
document2: `["cat"]`,
expression: `map(3)`,
expected: []string{
"D0, P[], (!!seq)::[]\n",
"D0, P[], (!!seq)::[3]\n",
},
},
{
description: "mapping against an empty array should do nothing",
skipDoc: true,
document: `[[], [5]]`,
expression: `.[] |= map(3)`,
expected: []string{
"D0, P[], (!!seq)::[[], [3]]\n",
},
},
{
description: "mapping against an empty array should do nothing #2",
skipDoc: true,
document: `[]`,
document2: `[5]`,
expression: `map(3 + .)`,
expected: []string{
"D0, P[], (!!seq)::[]\n",
"D0, P[], (!!seq)::[8]\n",
},
},
{
description: "mapping against an empty array should do nothing",
skipDoc: true,
document: `[[], [5]]`,
expression: `.[] |= map(3 + .)`,
expected: []string{
"D0, P[], (!!seq)::[[], [8]]\n",
},
},
{
skipDoc: true,
expression: `[] | map(. + 42)`,
expected: []string{
"D0, P[], (!!seq)::[]\n",
},
},
{
skipDoc: true,
document: `[1,2]`,
@ -79,26 +32,6 @@ var mapOperatorScenarios = []expressionScenario{
"D0, P[], (!!seq)::[2, 3, 4]\n",
},
},
{
skipDoc: true,
document: `{}`,
document2: `{b: 12}`,
expression: `map_values(3)`,
expected: []string{
"D0, P[], (!!map)::{}\n",
"D0, P[], (!!map)::{b: 3}\n",
},
},
{
skipDoc: true,
document: `{}`,
document2: `{b: 12}`,
expression: `map_values(3 + .)`,
expected: []string{
"D0, P[], (!!map)::{}\n",
"D0, P[], (!!map)::{b: 15}\n",
},
},
{
skipDoc: true,
document: `{a: 1, b: 2, c: 3}`,

View File

@ -154,9 +154,9 @@ func repeatString(lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error
if err != nil {
return nil, err
} else if count < 0 {
return nil, fmt.Errorf("cannot repeat string by a negative number (%v)", count)
return nil, fmt.Errorf("Cannot repeat string by a negative number (%v)", count)
} else if count > 10000000 {
return nil, fmt.Errorf("cannot repeat string by more than 100 million (%v)", count)
return nil, fmt.Errorf("Cannot repeat string by more than 100 million (%v)", count)
}
target.Value = strings.Repeat(stringNode.Value, count)

View File

@ -206,7 +206,7 @@ var multiplyOperatorScenarios = []expressionScenario{
skipDoc: true,
document: `n: -4`,
expression: `"banana" * .n`,
expectedError: "cannot repeat string by a negative number (-4)",
expectedError: "Cannot repeat string by a negative number (-4)",
},
{
description: "Multiply string X by more than 100 million",
@ -214,7 +214,7 @@ var multiplyOperatorScenarios = []expressionScenario{
skipDoc: true,
document: `n: 100000001`,
expression: `"banana" * .n`,
expectedError: "cannot repeat string by more than 100 million (100000001)",
expectedError: "Cannot repeat string by more than 100 million (100000001)",
},
{
description: "Multiply int node X string",

View File

@ -58,12 +58,11 @@ func omitOperator(d *dataTreeNavigator, context Context, expressionNode *Express
var replacement *CandidateNode
switch node.Kind {
case MappingNode:
if node.Kind == MappingNode {
replacement = omitMap(node, indicesToOmit)
case SequenceNode:
} else if node.Kind == SequenceNode {
replacement = omitSequence(node, indicesToOmit)
default:
} else {
log.Debugf("Omit from type %v (%v) is noop", node.Tag, node.GetNicePath())
return context, nil
}

View File

@ -22,16 +22,15 @@ func getPathArrayFromNode(funcName string, node *CandidateNode) ([]interface{},
path := make([]interface{}, len(node.Content))
for i, childNode := range node.Content {
switch childNode.Tag {
case "!!str":
if childNode.Tag == "!!str" {
path[i] = childNode.Value
case "!!int":
} else if childNode.Tag == "!!int" {
number, err := parseInt(childNode.Value)
if err != nil {
return nil, fmt.Errorf("%v: could not parse %v as an int: %w", funcName, childNode.Value, err)
}
path[i] = number
default:
} else {
return nil, fmt.Errorf("%v: expected either a !!str or !!int in the path, found %v instead", funcName, childNode.Tag)
}

View File

@ -64,16 +64,15 @@ func pickOperator(d *dataTreeNavigator, context Context, expressionNode *Express
node := el.Value.(*CandidateNode)
var replacement *CandidateNode
switch node.Kind {
case MappingNode:
if node.Kind == MappingNode {
replacement = pickMap(node, indicesToPick)
case SequenceNode:
} else if node.Kind == SequenceNode {
replacement, err = pickSequence(node, indicesToPick)
if err != nil {
return Context{}, err
}
default:
} else {
return Context{}, fmt.Errorf("cannot pick indices from type %v (%v)", node.Tag, node.GetNicePath())
}

View File

@ -17,8 +17,8 @@ var reverseOperatorScenarios = []expressionScenario{
document: "[1, 2]",
expression: `reverse[]`,
expected: []string{
"D0, P[0], (!!int)::2\n",
"D0, P[1], (!!int)::1\n",
"D0, P[1], (!!int)::2\n",
"D0, P[0], (!!int)::1\n",
},
},
{

View File

@ -5,12 +5,6 @@ import (
)
var selectOperatorScenarios = []expressionScenario{
{
skipDoc: true,
document: `cat: pants`,
expression: `select(.nope) | key + " why though?"`,
expected: []string{},
},
{
skipDoc: true,
document: `cat`,

View File

@ -26,12 +26,7 @@ func shuffleOperator(_ *dataTreeNavigator, context Context, _ *ExpressionNode) (
a := result.Content
myRand.Shuffle(len(a), func(i, j int) {
a[i], a[j] = a[j], a[i]
oldIndex := a[i].Key.Value
a[i].Key.Value = a[j].Key.Value
a[j].Key.Value = oldIndex
})
myRand.Shuffle(len(a), func(i, j int) { a[i], a[j] = a[j], a[i] })
results.PushBack(result)
}

View File

@ -17,9 +17,9 @@ var shuffleOperatorScenarios = []expressionScenario{
document: "[1, 2, 3]",
expression: `shuffle[]`,
expected: []string{
"D0, P[0], (!!int)::3\n",
"D0, P[1], (!!int)::1\n",
"D0, P[2], (!!int)::2\n",
"D0, P[2], (!!int)::3\n",
"D0, P[0], (!!int)::1\n",
"D0, P[1], (!!int)::2\n",
},
},

View File

@ -47,12 +47,11 @@ func sortByOperator(d *dataTreeNavigator, context Context, expressionNode *Expre
sort.Stable(sortableArray)
sortedList := candidate.CopyWithoutContent()
switch candidate.Kind {
case MappingNode:
if candidate.Kind == MappingNode {
for _, sortedNode := range sortableArray {
sortedList.AddKeyValueChild(sortedNode.Node.Key, sortedNode.Node)
}
case SequenceNode:
} else if candidate.Kind == SequenceNode {
for _, sortedNode := range sortableArray {
sortedList.AddChild(sortedNode.Node)
}

View File

@ -17,8 +17,8 @@ var sortByOperatorScenarios = []expressionScenario{
document: "[{a: banana},{a: apple}]",
expression: `sort_by(.a)[]`,
expected: []string{
"D0, P[0], (!!map)::{a: apple}\n",
"D0, P[1], (!!map)::{a: banana}\n",
"D0, P[1], (!!map)::{a: apple}\n",
"D0, P[0], (!!map)::{a: banana}\n",
},
},
{
@ -27,9 +27,9 @@ var sortByOperatorScenarios = []expressionScenario{
document: "[{a: banana},null,{a: apple}]",
expression: `sort_by(.a)[]`,
expected: []string{
"D0, P[0], (!!null)::null\n",
"D0, P[1], (!!map)::{a: apple}\n",
"D0, P[2], (!!map)::{a: banana}\n",
"D0, P[1], (!!null)::null\n",
"D0, P[2], (!!map)::{a: apple}\n",
"D0, P[0], (!!map)::{a: banana}\n",
},
},
{
@ -149,8 +149,8 @@ var sortByOperatorScenarios = []expressionScenario{
document: "[8,null]",
expression: `sort[]`,
expected: []string{
"D0, P[0], (!!null)::null\n",
"D0, P[1], (!!int)::8\n",
"D0, P[1], (!!null)::null\n",
"D0, P[0], (!!int)::8\n",
},
},
{

View File

@ -410,7 +410,7 @@ func extractMatchArguments(d *dataTreeNavigator, context Context, expressionNode
return nil, matchPrefs, fmt.Errorf(`'i' is not a valid option for match. To ignore case, use an expression like match("(?i)cat")`)
}
if len(paramText) > 0 {
return nil, matchPrefs, fmt.Errorf(`unrecognised match params '%v', please see docs at https://mikefarah.gitbook.io/yq/operators/string-operators`, paramText)
return nil, matchPrefs, fmt.Errorf(`Unrecognised match params '%v', please see docs at https://mikefarah.gitbook.io/yq/operators/string-operators`, paramText)
}
}

View File

@ -19,7 +19,7 @@ func parseStyle(customStyle string) (Style, error) {
} else if customStyle == "flow" {
return FlowStyle, nil
} else if customStyle != "" {
return 0, fmt.Errorf("unknown style %v", customStyle)
return 0, fmt.Errorf("Unknown style %v", customStyle)
}
return 0, nil
}

Some files were not shown because too many files have changed in this diff Show More