package yqlib import ( "container/list" "fmt" "strings" "github.com/jinzhu/copier" yaml "gopkg.in/yaml.v3" ) type CandidateNode struct { Node *yaml.Node // the actual node Parent *CandidateNode // parent node Key *yaml.Node // node key, if this is a value from a map (or index in an array) LeadingContent string TrailingContent string Path []interface{} /// the path we took to get to this node Document uint // the document index of this node Filename string FileIndex int // when performing op against all nodes given, this will treat all the nodes as one // (e.g. top level cross document merge). This property does not propagate to child nodes. EvaluateTogether bool IsMapKey bool } func (n *CandidateNode) GetKey() string { keyPrefix := "" if n.IsMapKey { keyPrefix = "key-" } return fmt.Sprintf("%v%v - %v", keyPrefix, n.Document, n.Path) } func (n *CandidateNode) GetNiceTag() string { return unwrapDoc(n.Node).Tag } func (n *CandidateNode) GetNicePath() string { if n.Path != nil && len(n.Path) >= 0 { pathStr := make([]string, len(n.Path)) for i, v := range n.Path { pathStr[i] = fmt.Sprintf("%v", v) } return strings.Join(pathStr, ".") } return "" } func (n *CandidateNode) AsList() *list.List { elMap := list.New() elMap.PushBack(n) return elMap } func (n *CandidateNode) CreateChildInMap(key *yaml.Node, node *yaml.Node) *CandidateNode { var value interface{} if key != nil { value = key.Value } return &CandidateNode{ Node: node, Path: n.createChildPath(value), Parent: n, Key: key, Document: n.Document, Filename: n.Filename, FileIndex: n.FileIndex, } } func (n *CandidateNode) CreateChildInArray(index int, node *yaml.Node) *CandidateNode { return &CandidateNode{ Node: node, Path: n.createChildPath(index), Parent: n, Key: &yaml.Node{Kind: yaml.ScalarNode, Value: fmt.Sprintf("%v", index), Tag: "!!int"}, Document: n.Document, Filename: n.Filename, FileIndex: n.FileIndex, } } func (n *CandidateNode) CreateReplacement(node *yaml.Node) *CandidateNode { return &CandidateNode{ Node: node, Path: n.createChildPath(nil), Parent: n.Parent, Key: n.Key, IsMapKey: n.IsMapKey, Document: n.Document, Filename: n.Filename, FileIndex: n.FileIndex, } } func (n *CandidateNode) CreateReplacementWithDocWrappers(node *yaml.Node) *CandidateNode { replacement := n.CreateReplacement(node) replacement.LeadingContent = n.LeadingContent replacement.TrailingContent = n.TrailingContent return replacement } func (n *CandidateNode) createChildPath(path interface{}) []interface{} { if path == nil { newPath := make([]interface{}, len(n.Path)) copy(newPath, n.Path) return newPath } //don't use append as they may actually modify the path of the original node! newPath := make([]interface{}, len(n.Path)+1) copy(newPath, n.Path) newPath[len(n.Path)] = path return newPath } func (n *CandidateNode) Copy() (*CandidateNode, error) { clone := &CandidateNode{} err := copier.Copy(clone, n) if err != nil { return nil, err } clone.Node = deepClone(n.Node) return clone, nil } // updates this candidate from the given candidate node func (n *CandidateNode) UpdateFrom(other *CandidateNode, prefs assignPreferences) { // if this is an empty map or empty array, use the style of other node. if (n.Node.Kind != yaml.ScalarNode && len(n.Node.Content) == 0) || // if the tag has changed (e.g. from str to bool) (guessTagFromCustomType(n.Node) != guessTagFromCustomType(other.Node)) { n.Node.Style = other.Node.Style } n.Node.Content = deepCloneContent(other.Node.Content) n.Node.Kind = other.Node.Kind n.Node.Value = other.Node.Value n.UpdateAttributesFrom(other, prefs) } func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode, prefs assignPreferences) { log.Debug("UpdateAttributesFrom: n: %v other: %v", n.GetKey(), other.GetKey()) if n.Node.Kind != other.Node.Kind { // clear out the contents when switching to a different type // e.g. map to array n.Node.Content = make([]*yaml.Node, 0) n.Node.Value = "" } n.Node.Kind = other.Node.Kind // don't clobber custom tags... if prefs.ClobberCustomTags || strings.HasPrefix(n.Node.Tag, "!!") || n.Node.Tag == "" { n.Node.Tag = other.Node.Tag } n.Node.Alias = other.Node.Alias if !prefs.DontOverWriteAnchor { n.Node.Anchor = other.Node.Anchor } // merge will pickup the style of the new thing // when autocreating nodes if n.Node.Style == 0 { n.Node.Style = other.Node.Style } if other.Node.FootComment != "" { n.Node.FootComment = other.Node.FootComment } if other.TrailingContent != "" { n.TrailingContent = other.TrailingContent } if other.Node.HeadComment != "" { n.Node.HeadComment = other.Node.HeadComment } if other.Node.LineComment != "" { n.Node.LineComment = other.Node.LineComment } }