Skip to content

Commit

Permalink
Reimplement yaml parser
Browse files Browse the repository at this point in the history
  • Loading branch information
TomWright committed Oct 27, 2024
1 parent ba0aa78 commit 3ac9c8b
Show file tree
Hide file tree
Showing 3 changed files with 427 additions and 10 deletions.
9 changes: 7 additions & 2 deletions model/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ func NewValue(v any) *Value {
return val
case reflect.Value:
return &Value{
Value: val,
Value: val,
Metadata: make(map[string]any),
}
case nil:
return NewNullValue()
Expand All @@ -57,13 +58,17 @@ func NewValue(v any) *Value {
res.Elem().Set(reflect.ValueOf(v))
}
return &Value{
Value: res,
Value: res,
Metadata: make(map[string]any),
}
}
}

// Interface returns the value as an interface.
func (v *Value) Interface() any {
if v.IsNull() {
return nil
}
return v.Value.Interface()
}

Expand Down
242 changes: 234 additions & 8 deletions parsing/yaml/yaml.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package yaml

import (
"bytes"
"fmt"
"io"
"strconv"

"github.com/tomwright/dasel/v3/model"
"github.com/tomwright/dasel/v3/parsing"
Expand Down Expand Up @@ -32,21 +35,244 @@ type yamlReader struct{}
// Read reads a value from a byte slice.
func (j *yamlReader) Read(data []byte) (*model.Value, error) {
d := yaml.NewDecoder(bytes.NewReader(data))
var unmarshalled any
if err := d.Decode(&unmarshalled); err != nil {
return nil, err
res := make([]*yamlValue, 0)
for {
unmarshalled := &yamlValue{}
if err := d.Decode(&unmarshalled); err != nil {
if err == io.EOF {
break
}
return nil, err
}
res = append(res, unmarshalled)
}

switch len(res) {
case 0:
return model.NewNullValue(), nil
case 1:
return res[0].value, nil
default:
slice := model.NewSliceValue()
slice.MarkAsBranch()
for _, v := range res {
if err := slice.Append(v.value); err != nil {
return nil, err
}
}
return slice, nil
}
return model.NewValue(&unmarshalled), nil
}

type yamlWriter struct{}

// Write writes a value to a byte slice.
func (j *yamlWriter) Write(value *model.Value) ([]byte, error) {
buf := new(bytes.Buffer)
e := yaml.NewEncoder(buf)
if err := e.Encode(value.Interface()); err != nil {
if value.IsBranch() {
res := make([]byte, 0)
sliceLen, err := value.SliceLen()
if err != nil {
return nil, err
}
if err := value.RangeSlice(func(i int, val *model.Value) error {
yv := &yamlValue{value: val}
marshalled, err := yaml.Marshal(yv)
if err != nil {
return err
}
res = append(res, marshalled...)
if i < sliceLen-1 {
res = append(res, []byte("---\n")...)
}
return nil
}); err != nil {
return nil, err
}
return res, nil
}

yv := &yamlValue{value: value}
res, err := yv.ToNode()
if err != nil {
return nil, err
}
return yaml.Marshal(res)
}

type yamlValue struct {
node *yaml.Node
value *model.Value
}

func (yv *yamlValue) UnmarshalYAML(value *yaml.Node) error {
yv.node = value
switch value.Kind {
case yaml.ScalarNode:
switch value.Tag {
case "!!bool":
yv.value = model.NewBoolValue(value.Value == "true")
case "!!int":
i, err := strconv.Atoi(value.Value)
if err != nil {
return err
}
yv.value = model.NewIntValue(int64(i))
case "!!float":
f, err := strconv.ParseFloat(value.Value, 64)
if err != nil {
return err
}
yv.value = model.NewFloatValue(f)
default:
yv.value = model.NewStringValue(value.Value)
}
case yaml.DocumentNode:
yv.value = model.NewNullValue()
case yaml.SequenceNode:
res := model.NewSliceValue()
for _, item := range value.Content {
newItem := &yamlValue{}
if err := newItem.UnmarshalYAML(item); err != nil {
return err
}
if err := res.Append(newItem.value); err != nil {
return err
}
}
yv.value = res
case yaml.MappingNode:
res := model.NewMapValue()
for i := 0; i < len(value.Content); i += 2 {
key := value.Content[i]
val := value.Content[i+1]

newKey := &yamlValue{}
if err := newKey.UnmarshalYAML(key); err != nil {
return err
}

newVal := &yamlValue{}
if err := newVal.UnmarshalYAML(val); err != nil {
return err
}

keyStr, err := newKey.value.StringValue()
if err != nil {
return fmt.Errorf("keys are expected to be strings: %w", err)
}

if err := res.SetMapKey(keyStr, newVal.value); err != nil {
return err
}
}
yv.value = res
case yaml.AliasNode:
newVal := &yamlValue{}
if err := newVal.UnmarshalYAML(value.Alias); err != nil {
return err
}
yv.value = newVal.value
yv.value.Metadata["yaml-alias"] = value.Value
}
return nil
}

func (yv *yamlValue) ToNode() (*yaml.Node, error) {
res := &yaml.Node{}

yamlAlias, ok := yv.value.Metadata["yaml-alias"].(string)
if ok {
//res.Kind = yaml.ScalarNode
res.Kind = yaml.AliasNode
res.Value = yamlAlias
//res.Alias = &yaml.Node{
// Kind: yaml.ScalarNode,
// Value: yamlAlias,
//}
return res, nil
}

switch yv.value.Type() {
case model.TypeString:
v, err := yv.value.StringValue()
if err != nil {
return nil, err
}
res.Kind = yaml.ScalarNode
res.Value = v
res.Tag = "!!str"
case model.TypeBool:
v, err := yv.value.BoolValue()
if err != nil {
return nil, err
}
res.Kind = yaml.ScalarNode
res.Value = fmt.Sprintf("%t", v)
res.Tag = "!!bool"
case model.TypeInt:
v, err := yv.value.IntValue()
if err != nil {
return nil, err
}
res.Kind = yaml.ScalarNode
res.Value = fmt.Sprintf("%d", v)
res.Tag = "!!int"
case model.TypeFloat:
v, err := yv.value.FloatValue()
if err != nil {
return nil, err
}
res.Kind = yaml.ScalarNode
res.Value = fmt.Sprintf("%g", v)
res.Tag = "!!float"
case model.TypeMap:
res.Kind = yaml.MappingNode
if err := yv.value.RangeMap(func(key string, val *model.Value) error {
keyNode := &yamlValue{value: model.NewStringValue(key)}
valNode := &yamlValue{value: val}

marshalledKey, err := keyNode.ToNode()
if err != nil {
return err
}
marshalledVal, err := valNode.ToNode()
if err != nil {
return err
}

res.Content = append(res.Content, marshalledKey)
res.Content = append(res.Content, marshalledVal)

return nil
}); err != nil {
return nil, err
}
case model.TypeSlice:
res.Kind = yaml.SequenceNode
if err := yv.value.RangeSlice(func(i int, val *model.Value) error {
valNode := &yamlValue{value: val}
marshalledVal, err := valNode.ToNode()
if err != nil {
return err
}
res.Content = append(res.Content, marshalledVal)
return nil
}); err != nil {
return nil, err
}
case model.TypeNull:
res.Kind = yaml.DocumentNode
case model.TypeUnknown:
return nil, fmt.Errorf("unknown type: %s", yv.value.Type())
}

return res, nil
}

func (yv *yamlValue) MarshalYAML() (any, error) {
res, err := yv.ToNode()
if err != nil {
return nil, err
}
return buf.Bytes(), nil
return res, nil
}
Loading

0 comments on commit 3ac9c8b

Please sign in to comment.