Skip to content

Marshal goccy #2373

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
255 changes: 255 additions & 0 deletions pkg/yqlib/candidate_node_goccy_yaml.go
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ package yqlib

import (
"fmt"
"strconv"
"strings"

yaml "github.com/goccy/go-yaml"
@@ -205,3 +206,257 @@ func (o *CandidateNode) goccyProcessMappingValueNode(mappingEntry *ast.MappingVa

return nil
}

func (o *CandidateNode) MarshalGoccyYAML() (ast.Node, error) {
marshaller := &goccyMarshaller{}
return marshaller.Marshal("$", o)
}

type goccyMarshaller struct {
currentToken *goccyToken.Token
}

func (m *goccyMarshaller) PushToken(t *goccyToken.Token) *goccyToken.Token {
if m.currentToken == nil {
m.currentToken = t
return m.currentToken
}

m.currentToken.Next = t
t.Prev = m.currentToken
m.currentToken = t

return m.currentToken
}

func (m *goccyMarshaller) Marshal(path string, o *CandidateNode) (ast.Node, error) {
var node ast.Node
var err error
switch o.Kind {
case AliasNode:
node, err = m.marshalAlias(path, o)
if err != nil {
return nil, err
}
case ScalarNode:
node, err = m.marshalScalar(path, o)
if err != nil {
return nil, err
}
case SequenceNode:
node, err = m.marshalSequence(path, o)
if err != nil {
return nil, err
}
case MappingNode:
node, err = m.marshalMapping(path, o)
}

// TODO: Fix Tag and Anchor tokens
if o.Tag != "" && !strings.HasPrefix(o.Tag, "!!") {
tag := ast.Tag(goccyToken.Tag(o.Tag, o.Tag, &goccyToken.Position{}))
tag.Value = node
node = tag
}

if o.Anchor != "" {
anc := ast.Anchor(goccyToken.Anchor(o.Anchor, &goccyToken.Position{}))
anc.Value = node
node = anc
}

node.SetPath(path)

return node, nil
}

func (_ *goccyMarshaller) getPosition(o *CandidateNode) *goccyToken.Position {
return &goccyToken.Position{
Line: o.Line,
Column: o.Column,
}
}

func (m *goccyMarshaller) marshalAlias(path string, o *CandidateNode) (ast.Node, error) {
t := m.PushToken(goccyToken.Alias(o.Value, m.getPosition(o)))
var value ast.Node
var err error
value, err = m.Marshal(path, o.Content[0])
if err != nil {
return nil, err
}

alias := ast.Alias(t)
alias.Value = value
return alias, nil
}

func (m *goccyMarshaller) marshalSequence(path string, o *CandidateNode) (ast.Node, error) {
isFlowStyle := o.Style == FlowStyle
seq := ast.Sequence(
nil,
isFlowStyle,
)

if isFlowStyle {
seq.Start = m.PushToken(goccyToken.SequenceStart("[", &goccyToken.Position{}))
}

var err error
values := make([]ast.Node, len(o.Content))
for i, content := range o.Content {
if !isFlowStyle {
entryToken := goccyToken.SequenceEntry("-", &goccyToken.Position{Line: content.Line, Column: o.Column})
m.PushToken(entryToken)
if seq.Start == nil {
seq.Start = entryToken
}
}

values[i], err = m.Marshal(path+"["+strconv.Itoa(i)+"]", content)
if err != nil {
return nil, err
}

if isFlowStyle && i < len(o.Content)-1 {
m.PushToken(goccyToken.CollectEntry(",", &goccyToken.Position{}))
}
}

if isFlowStyle {
seq.End = m.PushToken(goccyToken.SequenceEnd("]", &goccyToken.Position{}))
}

seq.Values = values
seq.ValueHeadComments = []*ast.CommentGroupNode{ast.CommentGroup([]*goccyToken.Token{goccyToken.Comment(o.HeadComment, o.HeadComment, &goccyToken.Position{})})}
if o.FootComment != "" {
seq.FootComment = goccyMarshalFootComment(o.FootComment)
}

return seq, nil
}

func (m *goccyMarshaller) marshalMapping(path string, o *CandidateNode) (ast.Node, error) {
isFlowStyle := o.Style == FlowStyle
mapping := ast.Mapping(nil, isFlowStyle)

if isFlowStyle {
mapping.Start = m.PushToken(goccyToken.MappingStart("{", m.getPosition(o)))
}

values := make([]*ast.MappingValueNode, len(o.Content)/2)
var err error
for i := 0; i < len(o.Content); i += 2 {
var childNode *ast.MappingValueNode
childNode, err = m.marshalMappingValueNode(path, o, o.Content[i], o.Content[i+1])
if err != nil {
return nil, err
}

if o.Content[i].FootComment != "" {
childNode.FootComment = goccyMarshalFootComment(o.Content[i].FootComment)
}

values[i/2] = childNode
if isFlowStyle && i < len(o.Content)/2-1 {
m.PushToken(goccyToken.CollectEntry(",", &goccyToken.Position{}))
}
}

mapping.Values = values

if !isFlowStyle && len(values) > 0 {
mapping.Start = values[0].Start
}

if isFlowStyle {
mapping.End = m.PushToken(goccyToken.MappingEnd("}", &goccyToken.Position{}))
}

if o.FootComment != "" {
mapping.FootComment = goccyMarshalFootComment(o.FootComment)
}

return mapping, nil
}

func (m *goccyMarshaller) marshalMappingValueNode(path string, entryNode, keyNode, valueNode *CandidateNode) (*ast.MappingValueNode, error) {
var key ast.MapKeyNode
var err error
key, err = m.marshalMapKeyNode(path, keyNode)
if err != nil {
return nil, err
}

separatorToken := m.PushToken(goccyToken.MappingValue(&goccyToken.Position{
Line: key.GetToken().Position.Line,
Column: key.GetToken().Position.Column + len(key.String()),
}))

value, err := m.Marshal(key.GetPath(), valueNode)
if err != nil {
return nil, err
}

childNode := ast.MappingValue(
separatorToken,
key,
value,
)
childNode.SetPath(key.GetPath())

return childNode, nil
}

func goccyMarshalFootComment(comment string) *ast.CommentGroupNode {
// TODO: Fix Tokens
return ast.CommentGroup([]*goccyToken.Token{goccyToken.Comment(comment, comment, &goccyToken.Position{})})
}

func (m *goccyMarshaller) marshalMapKeyNode(path string, o *CandidateNode) (ast.MapKeyNode, error) {
if o.Kind != ScalarNode {
return nil, fmt.Errorf("cannot unmarshal non-scalar node to MapKeyNode")
}

key, err := m.marshalScalar(path, o)
if err != nil {
return nil, err
}
key.SetPath(path + "." + key.String())

return key, nil
}

func (m *goccyMarshaller) marshalScalar(path string, o *CandidateNode) (ast.ScalarNode, error) {
v, err := o.GetValueRep()
if err != nil {
return nil, err
}

scalarToken := m.PushToken(goccyToken.Literal(o.Value, o.Value, m.getPosition(o)))

if v == nil {
n := ast.Null(scalarToken)
n.SetPath(path)
return n, nil
}

var n ast.ScalarNode
switch v.(type) {
case float64:
n = ast.Float(scalarToken)
case int64:
n = ast.Integer(scalarToken)
case bool:
n = ast.Bool(scalarToken)
case string:
n = ast.String(scalarToken)
}

if n != nil {
n.SetPath(path)
return n, nil
}

return nil, fmt.Errorf("unknown scalar value type")
}
56 changes: 56 additions & 0 deletions pkg/yqlib/candidate_node_goccy_yaml_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package yqlib

import (
"fmt"
"testing"

"github.com/goccy/go-yaml/parser"
"gopkg.in/yaml.v3"
)

func TestMarshalGoccyYAML(t *testing.T) {
input := `
a:
b: 2
c: &anc
d: !mytag ef
e: 3.0
s:
- 1
- 2
t: [5, five]
f: [6, {y: true}]
`

goccyAst, err := parser.ParseBytes([]byte(input), parser.ParseComments)
fmt.Println(goccyAst)

var yamlNode yaml.Node
err = yaml.Unmarshal([]byte(input), &yamlNode)
if err != nil {
t.Error(err)
return
}

candidate := &CandidateNode{}
err = candidate.UnmarshalYAML(yamlNode.Content[0], make(map[string]*CandidateNode))
if err != nil {
t.Error(err)
return
}

goccyNode, err := candidate.MarshalGoccyYAML()
if err != nil {
t.Error(err)
return
}

parsed, err := parser.ParseBytes([]byte(input), parser.ParseComments)
if err != nil {
t.Error(err)
return
}

fmt.Println(parsed)
fmt.Println(goccyNode)
}