package yqlib import ( "bytes" "fmt" "io" "strconv" "strings" "github.com/magiconair/properties" "gopkg.in/yaml.v3" ) type propertiesDecoder struct { reader io.Reader finished bool d DataTreeNavigator } func NewPropertiesDecoder() Decoder { return &propertiesDecoder{d: NewDataTreeNavigator(), finished: false} } func (dec *propertiesDecoder) Init(reader io.Reader) error { dec.reader = reader dec.finished = false return nil } func parsePropKey(key string) []interface{} { pathStrArray := strings.Split(key, ".") path := make([]interface{}, len(pathStrArray)) for i, pathStr := range pathStrArray { num, err := strconv.ParseInt(pathStr, 10, 32) if err == nil { path[i] = num } else { path[i] = pathStr } } return path } func (dec *propertiesDecoder) processComment(c string) string { if c == "" { return "" } return "# " + c } func (dec *propertiesDecoder) applyPropertyComments(context Context, path []interface{}, comments []string) error { assignmentOp := &Operation{OperationType: assignOpType, Preferences: assignPreferences{}} rhsCandidateNode := &CandidateNode{ Path: path, Node: &yaml.Node{ Tag: "!!str", Value: fmt.Sprintf("%v", path[len(path)-1]), HeadComment: dec.processComment(strings.Join(comments, "\n")), Kind: yaml.ScalarNode, }, } rhsCandidateNode.Node.Tag = guessTagFromCustomType(rhsCandidateNode.Node) rhsOp := &Operation{OperationType: valueOpType, CandidateNode: rhsCandidateNode} assignmentOpNode := &ExpressionNode{ Operation: assignmentOp, LHS: createTraversalTree(path, traversePreferences{}, true), RHS: &ExpressionNode{Operation: rhsOp}, } _, err := dec.d.GetMatchingNodes(context, assignmentOpNode) return err } func (dec *propertiesDecoder) applyProperty(context Context, properties *properties.Properties, key string) error { value, _ := properties.Get(key) path := parsePropKey(key) propertyComments := properties.GetComments(key) if len(propertyComments) > 0 { err := dec.applyPropertyComments(context, path, propertyComments) if err != nil { return nil } } rhsNode := &yaml.Node{ Value: value, Tag: "!!str", Kind: yaml.ScalarNode, } rhsNode.Tag = guessTagFromCustomType(rhsNode) rhsCandidateNode := &CandidateNode{ Path: path, Node: rhsNode, } assignmentOp := &Operation{OperationType: assignOpType, Preferences: assignPreferences{}} 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 *propertiesDecoder) Decode() (*CandidateNode, error) { if dec.finished { return nil, io.EOF } buf := new(bytes.Buffer) if _, err := buf.ReadFrom(dec.reader); err != nil { return nil, err } if buf.Len() == 0 { dec.finished = true return nil, io.EOF } properties, err := properties.LoadString(buf.String()) if err != nil { return nil, err } properties.DisableExpansion = true rootMap := &CandidateNode{ Node: &yaml.Node{ Kind: yaml.MappingNode, Tag: "!!map", }, } context := Context{} context = context.SingleChildContext(rootMap) for _, key := range properties.Keys() { if err := dec.applyProperty(context, properties, key); err != nil { return nil, err } } dec.finished = true return &CandidateNode{ Node: &yaml.Node{ Kind: yaml.DocumentNode, Content: []*yaml.Node{rootMap.Node}, }, }, nil }