1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
|
package dencoding
import (
"github.com/tomwright/dasel/v2/util"
"gopkg.in/yaml.v3"
"io"
"strconv"
)
// YAMLEncoder wraps a standard yaml encoder to implement custom ordering logic.
type YAMLEncoder struct {
encoder *yaml.Encoder
}
// NewYAMLEncoder returns a new dencoding YAMLEncoder.
func NewYAMLEncoder(w io.Writer, options ...YAMLEncoderOption) *YAMLEncoder {
yamlEncoder := yaml.NewEncoder(w)
encoder := &YAMLEncoder{
encoder: yamlEncoder,
}
for _, o := range options {
o.ApplyEncoder(encoder)
}
return encoder
}
// Encode encodes the given value and writes the encodes bytes to the stream.
func (encoder *YAMLEncoder) Encode(v any) error {
// We rely on Map.MarshalYAML to ensure ordering.
return encoder.encoder.Encode(v)
}
// Close cleans up the encoder.
func (encoder *YAMLEncoder) Close() error {
return encoder.encoder.Close()
}
// MarshalYAML YAML encodes the map and returns the bytes.
// This maintains ordering.
func (m *Map) MarshalYAML() (any, error) {
return yamlOrderedMapToNode(m)
}
// YAMLEncodeIndent sets the indentation when encoding YAML.
func YAMLEncodeIndent(spaces int) YAMLEncoderOption {
return yamlEncodeIndent{spaces: spaces}
}
type yamlEncodeIndent struct {
spaces int
}
func (option yamlEncodeIndent) ApplyEncoder(encoder *YAMLEncoder) {
encoder.encoder.SetIndent(option.spaces)
}
func yamlValueToNode(value any) (*yaml.Node, error) {
switch v := value.(type) {
case *Map:
return yamlOrderedMapToNode(v)
case []any:
return yamlSliceToNode(v)
default:
return yamlScalarToNode(v)
}
}
func yamlOrderedMapToNode(value *Map) (*yaml.Node, error) {
mapNode := &yaml.Node{
Kind: yaml.MappingNode,
Style: yaml.TaggedStyle & yaml.DoubleQuotedStyle & yaml.SingleQuotedStyle & yaml.LiteralStyle & yaml.FoldedStyle & yaml.FlowStyle,
Content: make([]*yaml.Node, 0),
}
for _, key := range value.keys {
keyNode, err := yamlValueToNode(key)
if err != nil {
return nil, err
}
valueNode, err := yamlValueToNode(value.data[key])
if err != nil {
return nil, err
}
mapNode.Content = append(mapNode.Content, keyNode, valueNode)
}
return mapNode, nil
}
func yamlSliceToNode(value []any) (*yaml.Node, error) {
node := &yaml.Node{
Kind: yaml.SequenceNode,
Content: make([]*yaml.Node, len(value)),
}
for i, v := range value {
indexNode, err := yamlValueToNode(v)
if err != nil {
return nil, err
}
node.Content[i] = indexNode
}
return node, nil
}
func yamlScalarToNode(value any) (*yaml.Node, error) {
res := &yaml.Node{
Kind: yaml.ScalarNode,
Value: util.ToString(value),
}
switch v := value.(type) {
case string:
if v == "true" || v == "false" {
// If the string can be evaluated as a bool, quote it.
res.Style = yaml.DoubleQuotedStyle
} else if _, err := strconv.ParseInt(v, 0, 64); err == nil {
// If the string can be evaluated as a number, quote it.
res.Style = yaml.DoubleQuotedStyle
}
}
return res, nil
}
|