mirror of
https://github.com/mikefarah/yq.git
synced 2024-12-19 20:19:04 +00:00
7103b78d38
* toml wip * wip * Fixed auto parsing toml * Added build flag not to include toml * Parse toml docs and tests * minor updates
370 lines
9.2 KiB
Go
370 lines
9.2 KiB
Go
//go:build !yq_notoml
|
|
|
|
package yqlib
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strconv"
|
|
"time"
|
|
|
|
toml "github.com/pelletier/go-toml/v2/unstable"
|
|
yaml "gopkg.in/yaml.v3"
|
|
)
|
|
|
|
type tomlDecoder struct {
|
|
parser toml.Parser
|
|
finished bool
|
|
d DataTreeNavigator
|
|
rootMap *CandidateNode
|
|
}
|
|
|
|
func NewTomlDecoder() Decoder {
|
|
return &tomlDecoder{
|
|
finished: false,
|
|
d: NewDataTreeNavigator(),
|
|
}
|
|
}
|
|
|
|
func (dec *tomlDecoder) Init(reader io.Reader) error {
|
|
dec.parser = toml.Parser{}
|
|
buf := new(bytes.Buffer)
|
|
_, err := buf.ReadFrom(reader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
dec.parser.Reset(buf.Bytes())
|
|
dec.rootMap = &CandidateNode{
|
|
Node: &yaml.Node{
|
|
Kind: yaml.MappingNode,
|
|
Tag: "!!map",
|
|
}}
|
|
return nil
|
|
}
|
|
|
|
func (dec *tomlDecoder) getFullPath(tomlNode *toml.Node) []interface{} {
|
|
path := make([]interface{}, 0)
|
|
for {
|
|
path = append(path, string(tomlNode.Data))
|
|
tomlNode = tomlNode.Next()
|
|
if tomlNode == nil {
|
|
return path
|
|
}
|
|
}
|
|
}
|
|
|
|
func (dec *tomlDecoder) processKeyValueIntoMap(rootMap *CandidateNode, tomlNode *toml.Node) error {
|
|
value := tomlNode.Value()
|
|
path := dec.getFullPath(value.Next())
|
|
log.Debug("!!!processKeyValueIntoMap: %v", path)
|
|
|
|
valueNode, err := dec.decodeNode(value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
context := Context{}
|
|
context = context.SingleChildContext(rootMap)
|
|
|
|
return dec.d.DeeplyAssign(context, path, valueNode)
|
|
}
|
|
|
|
func (dec *tomlDecoder) decodeKeyValuesIntoMap(rootMap *CandidateNode, tomlNode *toml.Node) (bool, error) {
|
|
log.Debug("!! DECODE_KV_INTO_MAP -- processing first (current) entry")
|
|
if err := dec.processKeyValueIntoMap(rootMap, tomlNode); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
for dec.parser.NextExpression() {
|
|
nextItem := dec.parser.Expression()
|
|
log.Debug("!! DECODE_KV_INTO_MAP -- next exp, its a %v", nextItem.Kind)
|
|
|
|
if nextItem.Kind == toml.KeyValue {
|
|
if err := dec.processKeyValueIntoMap(rootMap, nextItem); err != nil {
|
|
return false, err
|
|
}
|
|
} else {
|
|
// run out of key values
|
|
log.Debug("! DECODE_KV_INTO_MAP - ok we are done in decodeKeyValuesIntoMap, gota a %v", nextItem.Kind)
|
|
log.Debug("! DECODE_KV_INTO_MAP - processAgainstCurrentExp = true!")
|
|
return true, nil
|
|
}
|
|
}
|
|
log.Debug("! DECODE_KV_INTO_MAP - no more things to read in")
|
|
return false, nil
|
|
}
|
|
|
|
func (dec *tomlDecoder) createInlineTableMap(tomlNode *toml.Node) (*yaml.Node, error) {
|
|
content := make([]*yaml.Node, 0)
|
|
log.Debug("!! createInlineTableMap")
|
|
|
|
iterator := tomlNode.Children()
|
|
for iterator.Next() {
|
|
child := iterator.Node()
|
|
if child.Kind != toml.KeyValue {
|
|
return nil, fmt.Errorf("only keyvalue pairs are supported in inlinetables, got %v instead", child.Kind)
|
|
}
|
|
|
|
keyValues := &CandidateNode{
|
|
Node: &yaml.Node{
|
|
Kind: yaml.MappingNode,
|
|
Tag: "!!map",
|
|
},
|
|
}
|
|
|
|
if err := dec.processKeyValueIntoMap(keyValues, child); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
content = append(content, keyValues.Node.Content...)
|
|
}
|
|
|
|
return &yaml.Node{
|
|
Kind: yaml.MappingNode,
|
|
Tag: "!!map",
|
|
Content: content,
|
|
}, nil
|
|
}
|
|
|
|
func (dec *tomlDecoder) createArray(tomlNode *toml.Node) (*yaml.Node, error) {
|
|
content := make([]*yaml.Node, 0)
|
|
iterator := tomlNode.Children()
|
|
for iterator.Next() {
|
|
child := iterator.Node()
|
|
yamlNode, err := dec.decodeNode(child)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
content = append(content, yamlNode)
|
|
}
|
|
|
|
return &yaml.Node{
|
|
Kind: yaml.SequenceNode,
|
|
Tag: "!!seq",
|
|
Content: content,
|
|
}, nil
|
|
|
|
}
|
|
|
|
func (dec *tomlDecoder) createStringScalar(tomlNode *toml.Node) (*yaml.Node, error) {
|
|
content := string(tomlNode.Data)
|
|
return createScalarNode(content, content), nil
|
|
}
|
|
|
|
func (dec *tomlDecoder) createBoolScalar(tomlNode *toml.Node) (*yaml.Node, error) {
|
|
content := string(tomlNode.Data)
|
|
return createScalarNode(content == "true", content), nil
|
|
}
|
|
|
|
func (dec *tomlDecoder) createIntegerScalar(tomlNode *toml.Node) (*yaml.Node, error) {
|
|
content := string(tomlNode.Data)
|
|
_, num, err := parseInt64(content)
|
|
return createScalarNode(num, content), err
|
|
}
|
|
|
|
func (dec *tomlDecoder) createDateTimeScalar(tomlNode *toml.Node) (*yaml.Node, error) {
|
|
content := string(tomlNode.Data)
|
|
val, err := parseDateTime(time.RFC3339, content)
|
|
return createScalarNode(val, content), err
|
|
}
|
|
|
|
func (dec *tomlDecoder) createFloatScalar(tomlNode *toml.Node) (*yaml.Node, error) {
|
|
content := string(tomlNode.Data)
|
|
num, err := strconv.ParseFloat(content, 64)
|
|
return createScalarNode(num, content), err
|
|
}
|
|
|
|
func (dec *tomlDecoder) decodeNode(tomlNode *toml.Node) (*yaml.Node, error) {
|
|
switch tomlNode.Kind {
|
|
case toml.Key, toml.String:
|
|
return dec.createStringScalar(tomlNode)
|
|
case toml.Bool:
|
|
return dec.createBoolScalar(tomlNode)
|
|
case toml.Integer:
|
|
return dec.createIntegerScalar(tomlNode)
|
|
case toml.DateTime:
|
|
return dec.createDateTimeScalar(tomlNode)
|
|
case toml.Float:
|
|
return dec.createFloatScalar(tomlNode)
|
|
case toml.Array:
|
|
return dec.createArray(tomlNode)
|
|
case toml.InlineTable:
|
|
return dec.createInlineTableMap(tomlNode)
|
|
default:
|
|
return nil, fmt.Errorf("unsupported type %v", tomlNode.Kind)
|
|
}
|
|
|
|
}
|
|
|
|
func (dec *tomlDecoder) Decode() (*CandidateNode, error) {
|
|
if dec.finished {
|
|
return nil, io.EOF
|
|
}
|
|
//
|
|
// toml library likes to panic
|
|
var deferredError error
|
|
defer func() { //catch or finally
|
|
if r := recover(); r != nil {
|
|
var ok bool
|
|
deferredError, ok = r.(error)
|
|
if !ok {
|
|
deferredError = fmt.Errorf("pkg: %v", r)
|
|
}
|
|
}
|
|
}()
|
|
|
|
log.Debug("ok here we go")
|
|
var runAgainstCurrentExp = false
|
|
var err error
|
|
for runAgainstCurrentExp || dec.parser.NextExpression() {
|
|
|
|
if runAgainstCurrentExp {
|
|
log.Debug("running against current exp")
|
|
}
|
|
|
|
currentNode := dec.parser.Expression()
|
|
|
|
log.Debug("currentNode: %v ", currentNode.Kind)
|
|
runAgainstCurrentExp, err = dec.processTopLevelNode(currentNode)
|
|
if err != nil {
|
|
return dec.rootMap, err
|
|
}
|
|
|
|
}
|
|
|
|
err = dec.parser.Error()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// must have finished
|
|
dec.finished = true
|
|
|
|
if len(dec.rootMap.Node.Content) == 0 {
|
|
return nil, io.EOF
|
|
}
|
|
|
|
return &CandidateNode{
|
|
Node: &yaml.Node{
|
|
Kind: yaml.DocumentNode,
|
|
Content: []*yaml.Node{dec.rootMap.Node},
|
|
},
|
|
}, deferredError
|
|
|
|
}
|
|
|
|
func (dec *tomlDecoder) processTopLevelNode(currentNode *toml.Node) (bool, error) {
|
|
var runAgainstCurrentExp bool
|
|
var err error
|
|
log.Debug("!!!!!!!!!!!!Going to process %v state is current %v", currentNode.Kind, NodeToString(dec.rootMap))
|
|
if currentNode.Kind == toml.Table {
|
|
runAgainstCurrentExp, err = dec.processTable((currentNode))
|
|
} else if currentNode.Kind == toml.ArrayTable {
|
|
runAgainstCurrentExp, err = dec.processArrayTable((currentNode))
|
|
} else {
|
|
runAgainstCurrentExp, err = dec.decodeKeyValuesIntoMap(dec.rootMap, currentNode)
|
|
}
|
|
|
|
log.Debug("!!!!!!!!!!!!DONE Processing state is now %v", NodeToString(dec.rootMap))
|
|
return runAgainstCurrentExp, err
|
|
}
|
|
|
|
func (dec *tomlDecoder) processTable(currentNode *toml.Node) (bool, error) {
|
|
log.Debug("!!! processing table")
|
|
fullPath := dec.getFullPath(currentNode.Child())
|
|
log.Debug("!!!fullpath: %v", fullPath)
|
|
|
|
hasValue := dec.parser.NextExpression()
|
|
if !hasValue {
|
|
return false, fmt.Errorf("error retrieving table %v value: %w", fullPath, dec.parser.Error())
|
|
}
|
|
|
|
tableNodeValue := &CandidateNode{
|
|
Node: &yaml.Node{
|
|
Kind: yaml.MappingNode,
|
|
Tag: "!!map",
|
|
},
|
|
}
|
|
|
|
tableValue := dec.parser.Expression()
|
|
if tableValue.Kind != toml.KeyValue {
|
|
log.Debug("got an empty table, returning")
|
|
return true, nil
|
|
}
|
|
|
|
runAgainstCurrentExp, err := dec.decodeKeyValuesIntoMap(tableNodeValue, tableValue)
|
|
log.Debugf("table node err: %w", err)
|
|
if err != nil && !errors.Is(io.EOF, err) {
|
|
return false, err
|
|
}
|
|
c := Context{}
|
|
|
|
c = c.SingleChildContext(dec.rootMap)
|
|
err = dec.d.DeeplyAssign(c, fullPath, tableNodeValue.Node)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return runAgainstCurrentExp, nil
|
|
}
|
|
|
|
func (dec *tomlDecoder) arrayAppend(context Context, path []interface{}, rhsNode *yaml.Node) error {
|
|
rhsCandidateNode := &CandidateNode{
|
|
Path: path,
|
|
Node: &yaml.Node{
|
|
Kind: yaml.SequenceNode,
|
|
Tag: "!!seq",
|
|
Content: []*yaml.Node{rhsNode},
|
|
},
|
|
}
|
|
|
|
assignmentOp := &Operation{OperationType: addAssignOpType}
|
|
|
|
rhsOp := &Operation{OperationType: valueOpType, CandidateNode: rhsCandidateNode}
|
|
|
|
assignmentOpNode := &ExpressionNode{
|
|
Operation: assignmentOp,
|
|
LHS: createTraversalTree(path, traversePreferences{}, false),
|
|
RHS: &ExpressionNode{Operation: rhsOp},
|
|
}
|
|
|
|
_, err := dec.d.GetMatchingNodes(context, assignmentOpNode)
|
|
return err
|
|
}
|
|
|
|
func (dec *tomlDecoder) processArrayTable(currentNode *toml.Node) (bool, error) {
|
|
log.Debug("!!! processing table")
|
|
fullPath := dec.getFullPath(currentNode.Child())
|
|
log.Debug("!!!fullpath: %v", fullPath)
|
|
|
|
// need to use the array append exp to add another entry to
|
|
// this array: fullpath += [ thing ]
|
|
|
|
hasValue := dec.parser.NextExpression()
|
|
if !hasValue {
|
|
return false, fmt.Errorf("error retrieving table %v value: %w", fullPath, dec.parser.Error())
|
|
}
|
|
|
|
tableNodeValue := &CandidateNode{
|
|
Node: &yaml.Node{
|
|
Kind: yaml.MappingNode,
|
|
Tag: "!!map",
|
|
},
|
|
}
|
|
|
|
tableValue := dec.parser.Expression()
|
|
runAgainstCurrentExp, err := dec.decodeKeyValuesIntoMap(tableNodeValue, tableValue)
|
|
log.Debugf("table node err: %w", err)
|
|
if err != nil && !errors.Is(io.EOF, err) {
|
|
return false, err
|
|
}
|
|
c := Context{}
|
|
|
|
c = c.SingleChildContext(dec.rootMap)
|
|
|
|
// += function
|
|
err = dec.arrayAppend(c, fullPath, tableNodeValue.Node)
|
|
|
|
return runAgainstCurrentExp, err
|
|
}
|