goccy yaml decoder supports anchor/aliases

This commit is contained in:
Mike Farah 2025-05-03 20:01:16 +10:00
parent c59fa8de59
commit f89605e3c0
5 changed files with 270 additions and 161 deletions

4
go.mod
View File

@ -8,7 +8,7 @@ require (
github.com/elliotchance/orderedmap v1.8.0
github.com/fatih/color v1.18.0
github.com/goccy/go-json v0.10.5
github.com/goccy/go-yaml v1.13.3
github.com/goccy/go-yaml v1.17.1
github.com/jinzhu/copier v0.4.0
github.com/magiconair/properties v1.8.10
github.com/pelletier/go-toml/v2 v2.2.3
@ -24,7 +24,7 @@ require (
require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
golang.org/x/sys v0.32.0 // indirect
)

24
go.sum
View File

@ -16,33 +16,20 @@ github.com/elliotchance/orderedmap v1.8.0 h1:TrOREecvh3JbS+NCgwposXG5ZTFHtEsQiCG
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/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.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/goccy/go-yaml v1.17.1 h1:LI34wktB2xEE3ONG/2Ar54+/HJVBriAGJ55PHls4YuY=
github.com/goccy/go-yaml v1.17.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
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/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.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
github.com/magiconair/properties v1.8.10/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-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
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.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
@ -62,11 +49,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
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=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY=
golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E=
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.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=

View File

@ -9,14 +9,14 @@ import (
goccyToken "github.com/goccy/go-yaml/token"
)
func (o *CandidateNode) goccyDecodeIntoChild(childNode ast.Node, cm yaml.CommentMap) (*CandidateNode, error) {
func (o *CandidateNode) goccyDecodeIntoChild(childNode ast.Node, cm yaml.CommentMap, anchorMap map[string]*CandidateNode) (*CandidateNode, error) {
newChild := o.CreateChild()
err := newChild.UnmarshalGoccyYAML(childNode, cm)
err := newChild.UnmarshalGoccyYAML(childNode, cm, anchorMap)
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())
@ -62,6 +62,7 @@ func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap) er
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
@ -92,7 +93,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
@ -106,7 +107,7 @@ func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap) er
o.Style = FlowStyle
}
for _, mappingValueNode := range mappingNode.Values {
err := o.goccyProcessMappingValueNode(mappingValueNode, cm)
err := o.goccyProcessMappingValueNode(mappingValueNode, cm, anchorMap)
if err != nil {
return ast.ErrInvalidAnchorName
}
@ -120,7 +121,7 @@ func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap) er
o.Kind = MappingNode
o.Tag = "!!map"
mappingValueNode := node.(*ast.MappingValueNode)
err := o.goccyProcessMappingValueNode(mappingValueNode, cm)
err := o.goccyProcessMappingValueNode(mappingValueNode, cm, anchorMap)
if err != nil {
return ast.ErrInvalidAnchorName
}
@ -141,7 +142,7 @@ func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap) er
keyNode.Kind = ScalarNode
keyNode.Value = fmt.Sprintf("%v", i)
valueNode, err := o.goccyDecodeIntoChild(astSeq[i], cm)
valueNode, err := o.goccyDecodeIntoChild(astSeq[i], cm, anchorMap)
if err != nil {
return err
}
@ -149,6 +150,22 @@ func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap) er
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]
default:
log.Debugf("UnmarshalYAML - node idea of the type!!")
@ -157,16 +174,16 @@ func (o *CandidateNode) UnmarshalGoccyYAML(node ast.Node, cm yaml.CommentMap) er
return nil
}
func (o *CandidateNode) goccyProcessMappingValueNode(mappingEntry *ast.MappingValueNode, cm yaml.CommentMap) error {
func (o *CandidateNode) goccyProcessMappingValueNode(mappingEntry *ast.MappingValueNode, cm yaml.CommentMap, anchorMap map[string]*CandidateNode) error {
log.Debug("UnmarshalYAML MAP KEY entry %v", mappingEntry.Key)
keyNode, err := o.goccyDecodeIntoChild(mappingEntry.Key, cm)
keyNode, err := o.goccyDecodeIntoChild(mappingEntry.Key, cm, anchorMap)
if err != nil {
return err
}
keyNode.IsMapKey = true
log.Debug("UnmarshalYAML MAP VALUE entry %v", mappingEntry.Value)
valueNode, err := o.goccyDecodeIntoChild(mappingEntry.Value, cm)
valueNode, err := o.goccyDecodeIntoChild(mappingEntry.Value, cm, anchorMap)
if err != nil {
return err
}

View File

@ -16,6 +16,8 @@ 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 {
@ -25,6 +27,7 @@ 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
}
@ -38,7 +41,7 @@ func (dec *goccyYamlDecoder) Decode() (*CandidateNode, error) {
}
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
}

View File

@ -7,132 +7,138 @@ import (
)
var goccyYamlFormatScenarios = []formatScenario{
{
description: "basic - 3",
skipDoc: true,
input: "3",
expected: "3\n",
},
{
description: "basic - 3.1",
skipDoc: true,
input: "3.1",
expected: "3.1\n",
},
{
description: "basic - mike",
skipDoc: true,
input: "mike: 3",
expected: "mike: 3\n",
},
{
description: "basic - map multiple entries",
skipDoc: true,
input: "mike: 3\nfred: 12\n",
expected: "mike: 3\nfred: 12\n",
},
{
description: "basic - 3.1",
skipDoc: true,
input: "{\n mike: 3\n}",
expected: "{mike: 3}\n",
},
{
description: "basic - tag with number",
skipDoc: true,
input: "mike: !!cat 3",
expected: "mike: !!cat 3\n",
},
{
description: "basic - array of numbers",
skipDoc: true,
input: "- 3",
expected: "- 3\n",
},
{
description: "basic - single line array",
skipDoc: true,
input: "[3]",
expected: "[3]\n",
},
{
description: "basic - plain string",
skipDoc: true,
input: `a: meow`,
expected: "a: meow\n",
},
{
description: "basic - double quoted string",
skipDoc: true,
input: `a: "meow"`,
expected: "a: \"meow\"\n",
},
{
description: "basic - single quoted string",
skipDoc: true,
input: `a: 'meow'`,
expected: "a: 'meow'\n",
},
{
description: "basic - string block",
skipDoc: true,
input: "a: |\n meow\n",
expected: "a: |\n meow\n",
},
{
description: "basic - long string",
skipDoc: true,
input: "a: the cute cat wrote a long sentence that wasn't wrapped at all.\n",
expected: "a: the cute cat wrote a long sentence that wasn't wrapped at all.\n",
},
{
description: "basic - string block",
skipDoc: true,
input: "a: |-\n meow\n",
expected: "a: |-\n meow\n",
},
{
description: "basic - line comment",
skipDoc: true,
input: "a: meow # line comment\n",
expected: "a: meow # line comment\n",
},
{
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,
input: "a: meow\n# foot comment\n",
expected: "a: meow\n# foot comment\n",
},
{
description: "basic - foot comment",
skipDoc: true,
input: "a: meow\nb: woof\n# foot comment\n",
expected: "a: meow\nb: woof\n# foot comment\n",
},
{
description: "basic - boolean",
skipDoc: true,
input: "true\n",
expected: "true\n",
},
{
description: "basic - null",
skipDoc: true,
input: "a: null\n",
expected: "a: null\n",
},
{
description: "basic - ~",
skipDoc: true,
input: "a: ~\n",
expected: "a: ~\n",
},
// {
// description: "basic - 3",
// skipDoc: true,
// input: "3",
// expected: "3\n",
// },
// {
// description: "basic - 3.1",
// skipDoc: true,
// input: "3.1",
// expected: "3.1\n",
// },
// {
// description: "basic - mike",
// skipDoc: true,
// input: "mike: 3",
// expected: "mike: 3\n",
// },
// {
// description: "basic - map multiple entries",
// skipDoc: true,
// input: "mike: 3\nfred: 12\n",
// expected: "mike: 3\nfred: 12\n",
// },
// {
// description: "basic - 3.1",
// skipDoc: true,
// input: "{\n mike: 3\n}",
// expected: "{mike: 3}\n",
// },
// {
// description: "basic - tag with number",
// skipDoc: true,
// input: "mike: !!cat 3",
// expected: "mike: !!cat 3\n",
// },
// {
// description: "basic - array of numbers",
// skipDoc: true,
// input: "- 3",
// expected: "- 3\n",
// },
// {
// description: "basic - single line array",
// skipDoc: true,
// input: "[3]",
// expected: "[3]\n",
// },
// {
// description: "basic - plain string",
// skipDoc: true,
// input: `a: meow`,
// expected: "a: meow\n",
// },
// {
// description: "basic - double quoted string",
// skipDoc: true,
// input: `a: "meow"`,
// expected: "a: \"meow\"\n",
// },
// {
// description: "basic - single quoted string",
// skipDoc: true,
// input: `a: 'meow'`,
// expected: "a: 'meow'\n",
// },
// {
// description: "basic - string block",
// skipDoc: true,
// input: "a: |\n meow\n",
// expected: "a: |\n meow\n",
// },
// {
// description: "basic - long string",
// skipDoc: true,
// input: "a: the cute cat wrote a long sentence that wasn't wrapped at all.\n",
// expected: "a: the cute cat wrote a long sentence that wasn't wrapped at all.\n",
// },
// {
// description: "basic - string block",
// skipDoc: true,
// input: "a: |-\n meow\n",
// expected: "a: |-\n meow\n",
// },
// {
// description: "basic - line comment",
// skipDoc: true,
// 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 - foot comment",
// skipDoc: true,
// input: "a: meow\n# foot comment\n",
// expected: "a: meow\n# foot comment\n",
// },
// {
// description: "basic - foot comment",
// skipDoc: true,
// input: "a: meow\nb: woof\n# foot comment\n",
// expected: "a: meow\nb: woof\n# foot comment\n",
// },
// {
// description: "basic - boolean",
// skipDoc: true,
// input: "true\n",
// expected: "true\n",
// },
// {
// description: "basic - null",
// skipDoc: true,
// input: "a: null\n",
// expected: "a: null\n",
// },
// {
// description: "basic - ~",
// skipDoc: true,
// input: "a: ~\n",
// expected: "a: ~\n",
// },
// {
// description: "basic - ~",
// skipDoc: true,
@ -148,8 +154,107 @@ var goccyYamlFormatScenarios = []formatScenario{
// {
// skipDoc: true,
// description: "trailing comment",
// input: "test:\n# this comment will be removed",
// expected: "test:\n# this comment will be removed",
// input: "test: null\n# this comment will be removed",
// expected: "test: null\n# this comment will be removed\n",
// },
// {
// description: "doc separator",
// skipDoc: true,
// input: "# hi\n---\na: cat\n---",
// expected: "---\na: cat\n",
// },
// {
// description: "scalar with doc separator",
// skipDoc: true,
// input: "--- cat",
// expected: "---\ncat\n",
// },
// {
// 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: 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: "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",
// },
}