diff --git a/pkg/yqlib/candidate_node_goccy_yaml.go b/pkg/yqlib/candidate_node_goccy_yaml.go index 7974c7231f..99855dfcc2 100644 --- a/pkg/yqlib/candidate_node_goccy_yaml.go +++ b/pkg/yqlib/candidate_node_goccy_yaml.go @@ -16,7 +16,7 @@ func (o *CandidateNode) goccyDecodeIntoChild(childNode ast.Node, cm yaml.Comment return newChild, err } -func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap) error { +func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap, anchorMap map[string]*CandidateNode) error { log.Debugf("UnmarshalYAML %v", node) log.Debugf("UnmarshalYAML %v", node.Type().String()) log.Debugf("UnmarshalYAML Node Value: %v", node.String()) @@ -49,6 +49,13 @@ func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap) er } } + o.Anchor = node. + + if o.Anchor != "" { + anchorMap[o.Anchor] = o + } + + if o.Ali o.Value = node.String() switch node.Type() { @@ -92,7 +99,7 @@ func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap) er // to solve the multiline > problem o.Value = astLiteral.Value.Value case ast.TagType: - if err := o.UnmarshalGoccyYAML(node.(*ast.TagNode).Value, cm); err != nil { + if err := o.UnmarshalGoccyYAML(node.(*ast.TagNode).Value, cm, anchorMap); err != nil { return err } o.Tag = node.(*ast.TagNode).Start.Value diff --git a/pkg/yqlib/decoder_goccy_yaml.go b/pkg/yqlib/decoder_goccy_yaml.go index 7494d7cddf..2499127bad 100644 --- a/pkg/yqlib/decoder_goccy_yaml.go +++ b/pkg/yqlib/decoder_goccy_yaml.go @@ -3,15 +3,21 @@ package yqlib import ( + "bytes" + "errors" "io" + "regexp" yaml "github.com/goccy/go-yaml" "github.com/goccy/go-yaml/ast" ) type goccyYamlDecoder struct { - decoder yaml.Decoder - cm yaml.CommentMap + decoder yaml.Decoder + cm yaml.CommentMap + bufferRead bytes.Buffer + readAnything bool + anchorMap map[string]*CandidateNode } func NewGoccyYAMLDecoder() Decoder { @@ -20,21 +26,40 @@ 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.readAnything = false + dec.anchorMap = make(map[string]*CandidateNode) + readerToUse := io.TeeReader(reader, &dec.bufferRead) + dec.decoder = *yaml.NewDecoder(readerToUse, yaml.CommentToMap(dec.cm), yaml.UseOrderedMap()) return nil } func (dec *goccyYamlDecoder) Decode() (*CandidateNode, error) { + var commentLineRegEx = regexp.MustCompile(`^\s*#`) + var ast ast.Node err := dec.decoder.Decode(&ast) - if err != nil { + if errors.Is(err, io.EOF) && !dec.readAnything { + + content := dec.bufferRead.String() + // only null fix + if content == "null" || content == "~" { + dec.readAnything = true + return createScalarNode(nil, content), nil + } else if commentLineRegEx.MatchString(content) { + dec.readAnything = true + node := createScalarNode(nil, "") + node.LeadingContent = content + return node, nil + } + return nil, err + } else if err != nil { return nil, err } candidateNode := &CandidateNode{} - if err := candidateNode.UnmarshalGoccyYAML(ast, dec.cm); err != nil { + if err := candidateNode.UnmarshalGoccyYAML(ast, dec.cm, dec.anchorMap); err != nil { return nil, err } diff --git a/pkg/yqlib/yaml_test.go b/pkg/yqlib/yaml_test.go index 6306dc77ee..641ce73225 100644 --- a/pkg/yqlib/yaml_test.go +++ b/pkg/yqlib/yaml_test.go @@ -19,6 +19,12 @@ var yamlFormatScenarios = []formatScenario{ input: "~", expected: "~\n", }, + { + description: "comment", + skipDoc: true, + input: "# cat", + expected: "# cat\n", + }, { description: "octal", skipDoc: true, @@ -32,6 +38,13 @@ var yamlFormatScenarios = []formatScenario{ input: "[null]", expected: "[null]\n", }, + { + description: "simple 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, @@ -100,7 +113,7 @@ var yamlParseScenarios = []expressionScenario{ } func testYamlScenario(t *testing.T, s formatScenario) { - test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewYamlDecoder(NewDefaultYamlPreferences()), NewYamlEncoder(2, false, ConfiguredYamlPreferences)), s.description) + test.AssertResultWithContext(t, s.expected, mustProcessFormatScenario(s, NewGoccyYAMLDecoder(), NewYamlEncoder(2, false, ConfiguredYamlPreferences)), s.description) } func TestYamlParseScenarios(t *testing.T) {