diff --git a/CHANGELOG.md b/CHANGELOG.md index fc8a3e0f..2898d227 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fixed a bug that could cause a panic. - `type()` now returns `null` instead of `unknown` for null values. +- Added YAML support for merge tag/aliases. Thanks to [pmeier](https://github.com/pmeier). [Issue 285](https://github.com/TomWright/dasel/issues/285). ## [v2.7.0] - 2024-03-14 diff --git a/dencoding/yaml.go b/dencoding/yaml.go index 7a62e512..cb0d46dc 100644 --- a/dencoding/yaml.go +++ b/dencoding/yaml.go @@ -10,6 +10,7 @@ const ( yamlTagInt = "!!int" yamlTagFloat = "!!float" yamlTagTimestamp = "!!timestamp" + yamlTagMerge = "!!merge" ) // YAMLEncoderOption is identifies an option that can be applied to a YAML encoder. diff --git a/dencoding/yaml_decoder.go b/dencoding/yaml_decoder.go index 48e04c36..d868cf3c 100644 --- a/dencoding/yaml_decoder.go +++ b/dencoding/yaml_decoder.go @@ -76,10 +76,18 @@ func (decoder *YAMLDecoder) getMappingNodeValue(node *yaml.Node) (any, error) { keyNode := node.Content[i] valueNode := node.Content[i+1] - keyValue, err := decoder.getNodeValue(keyNode) - if err != nil { - return nil, err + var keyValue any + if keyNode.ShortTag() == yamlTagMerge { + keyValue = valueNode.Value + valueNode = valueNode.Alias + } else { + var err error + keyValue, err = decoder.getNodeValue(keyNode) + if err != nil { + return nil, err + } } + value, err := decoder.getNodeValue(valueNode) if err != nil { return nil, err diff --git a/dencoding/yaml_decoder_test.go b/dencoding/yaml_decoder_test.go index 3d4f29ff..34679bb0 100644 --- a/dencoding/yaml_decoder_test.go +++ b/dencoding/yaml_decoder_test.go @@ -2,10 +2,11 @@ package dencoding_test import ( "bytes" - "github.com/tomwright/dasel/v2/dencoding" "io" "reflect" "testing" + + "github.com/tomwright/dasel/v2/dencoding" ) func TestYAMLDecoder_Decode(t *testing.T) { @@ -96,4 +97,55 @@ key2: value6 } }) + t.Run("YamlAliases", func(t *testing.T) { + b := []byte(`foo: &foo + bar: 1 + baz: "baz" +spam: + ham: "eggs" + <<: *foo +`) + + dec := dencoding.NewYAMLDecoder(bytes.NewReader(b)) + + got := make([]any, 0) + for { + var v any + if err := dec.Decode(&v); err != nil { + if err == io.EOF { + break + } + t.Errorf("unexpected error: %v", err) + return + } + got = append(got, v) + } + + fooMap := dencoding.NewMap(). + Set("bar", int64(1)). + Set("baz", "baz") + spamMap := dencoding.NewMap(). + Set("ham", "eggs"). + Set("foo", fooMap) + + exp := dencoding.NewMap(). + Set("foo", fooMap). + Set("spam", spamMap) + + if len(got) != 1 { + t.Errorf("expected result len of %d, got %d", 1, len(got)) + return + } + + gotMap, ok := got[0].(*dencoding.Map) + if !ok { + t.Errorf("expected result to be of type %T, got %T", exp, got[0]) + return + } + + if !reflect.DeepEqual(exp, gotMap) { + t.Errorf("expected %v, got %v", exp, gotMap) + } + }) + } diff --git a/internal/command/select_test.go b/internal/command/select_test.go index e7bfbc52..31a39550 100644 --- a/internal/command/select_test.go +++ b/internal/command/select_test.go @@ -252,6 +252,28 @@ octal: 8`)), nil, )) + t.Run("Issue285 - YAML alias on read", runTest( + []string{"-r", "yaml", "-w", "yaml"}, + []byte(`foo: &foo + bar: 1 + baz: baz +spam: + ham: eggs + <<: *foo +`), + []byte(`foo: + bar: 1 + baz: baz +spam: + ham: eggs + foo: + bar: 1 + baz: baz +`), + nil, + nil, + )) + t.Run("OrDefaultString", runTest( []string{"-r", "json", "all().orDefault(locale,string(nope))"}, []byte(`{ @@ -449,7 +471,7 @@ d.e.f`)), nil, nil, )) - + t.Run("Issue346", func(t *testing.T) { t.Run("Select null or default string", runTest( []string{"-r", "json", "orDefault(foo,string(nope))"},