package yqlib import ( "container/list" "fmt" "strconv" "strings" ) type Kind uint32 const ( DocumentNode Kind = 1 << iota SequenceNode MappingNode ScalarNode AliasNode ) type Style uint32 const ( TaggedStyle Style = 1 << iota DoubleQuotedStyle SingleQuotedStyle LiteralStyle FoldedStyle FlowStyle ) func createStringScalarNode(stringValue string) *CandidateNode { var node = &CandidateNode{Kind: ScalarNode} node.Value = stringValue node.Tag = "!!str" return node } func createScalarNode(value interface{}, stringValue string) *CandidateNode { var node = &CandidateNode{Kind: ScalarNode} node.Value = stringValue switch value.(type) { case float32, float64: node.Tag = "!!float" case int, int64, int32: node.Tag = "!!int" case bool: node.Tag = "!!bool" case string: node.Tag = "!!str" case nil: node.Tag = "!!null" } return node } type CandidateNode struct { Kind Kind Style Style Tag string Value string Anchor string Alias *CandidateNode Content []*CandidateNode HeadComment string LineComment string FootComment string Parent *CandidateNode // parent node Key *CandidateNode // node key, if this is a value from a map (or index in an array) LeadingContent string TrailingContent string document uint // the document index of this node filename string Line int Column int 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) CreateChild() *CandidateNode { return &CandidateNode{ Parent: n, } } func (n *CandidateNode) SetDocument(idx uint) { n.document = idx } func (n *CandidateNode) GetDocument() uint { // defer to parent if n.Parent != nil { return n.Parent.GetDocument() } return n.document } func (n *CandidateNode) SetFilename(name string) { n.filename = name } func (n *CandidateNode) GetFilename() string { if n.Parent != nil { return n.Parent.GetFilename() } return n.filename } func (n *CandidateNode) SetFileIndex(idx int) { n.fileIndex = idx } func (n *CandidateNode) GetFileIndex() int { if n.Parent != nil { return n.Parent.GetFileIndex() } return n.fileIndex } func (n *CandidateNode) GetKey() string { keyPrefix := "" if n.IsMapKey { keyPrefix = "key-" } key := "" if n.Key != nil { key = n.Key.Value } return fmt.Sprintf("%v%v - %v", keyPrefix, n.GetDocument(), key) } func (n *CandidateNode) unwrapDocument() *CandidateNode { if n.Kind == DocumentNode { return n.Content[0] } return n } func (n *CandidateNode) GetNiceTag() string { return n.unwrapDocument().Tag } func (n *CandidateNode) getParsedKey() interface{} { if n.IsMapKey { return n.Value } if n.Key == nil { return nil } if n.Key.Tag == "!!str" { return n.Key.Value } index, err := parseInt(n.Key.Value) if err != nil { return n.Key.Value } return index } func (n *CandidateNode) GetPath() []interface{} { key := n.getParsedKey() if n.Parent != nil && key != nil { return append(n.Parent.GetPath(), key) } if key != nil { return []interface{}{key} } return make([]interface{}, 0) } func (n *CandidateNode) GetNicePath() string { var sb strings.Builder path := n.GetPath() for i, element := range path { elementStr := fmt.Sprintf("%v", element) switch element.(type) { case int: sb.WriteString("[" + elementStr + "]") default: if i == 0 { sb.WriteString(elementStr) } else if strings.ContainsRune(elementStr, '.') { sb.WriteString("[" + elementStr + "]") } else { sb.WriteString("." + elementStr) } } } return sb.String() } func (n *CandidateNode) AsList() *list.List { elMap := list.New() elMap.PushBack(n) return elMap } func (n *CandidateNode) SetParent(parent *CandidateNode) { n.Parent = parent } func (n *CandidateNode) AddKeyValueChild(rawKey *CandidateNode, rawValue *CandidateNode) { key := rawKey.unwrapDocument().Copy() key.SetParent(n) key.IsMapKey = true value := rawValue.unwrapDocument().Copy() value.SetParent(n) value.Key = key n.Content = append(n.Content, key, value) } func (n *CandidateNode) AddChild(rawChild *CandidateNode) { value := rawChild.unwrapDocument().Copy() value.SetParent(n) if value.Key != nil { value.Key.SetParent(n) } else { index := len(n.Content) keyNode := createScalarNode(index, fmt.Sprintf("%v", index)) keyNode.SetParent(n) value.Key = keyNode } n.Content = append(n.Content, value) } func (n *CandidateNode) AddChildren(children []*CandidateNode) { if n.Kind == MappingNode { for i := 0; i < len(children); i += 2 { key := children[i] value := children[i+1] n.AddKeyValueChild(key, value) } } else { for _, rawChild := range children { n.AddChild(rawChild) } } } func (n *CandidateNode) GetValueRep() (interface{}, error) { log.Debugf("GetValueRep for %v value: %v", n.GetNicePath(), n.Value) realTag := n.guessTagFromCustomType() switch realTag { case "!!int": _, val, err := parseInt64(n.Value) return val, err case "!!float": // need to test this return strconv.ParseFloat(n.Value, 64) case "!!bool": return isTruthyNode(n), nil case "!!null": return nil, nil } return n.Value, nil } func (n *CandidateNode) guessTagFromCustomType() string { if strings.HasPrefix(n.Tag, "!!") { return n.Tag } else if n.Value == "" { log.Debug("guessTagFromCustomType: node has no value to guess the type with") return n.Tag } dataBucket, errorReading := parseSnippet(n.Value) if errorReading != nil { log.Debug("guessTagFromCustomType: could not guess underlying tag type %v", errorReading) return n.Tag } guessedTag := dataBucket.unwrapDocument().Tag log.Info("im guessing the tag %v is a %v", n.Tag, guessedTag) return guessedTag } func (n *CandidateNode) CreateReplacement(kind Kind, tag string, value string) *CandidateNode { node := &CandidateNode{ Kind: kind, Tag: tag, Value: value, } return n.CopyAsReplacement(node) } func (n *CandidateNode) CopyAsReplacement(replacement *CandidateNode) *CandidateNode { newCopy := replacement.Copy() newCopy.Parent = n.Parent if n.IsMapKey { newCopy.Key = n } else { newCopy.Key = n.Key } return newCopy } func (n *CandidateNode) CreateReplacementWithDocWrappers(kind Kind, tag string, style Style) *CandidateNode { replacement := n.CreateReplacement(kind, tag, "") replacement.LeadingContent = n.LeadingContent replacement.TrailingContent = n.TrailingContent replacement.Style = style return replacement } func (n *CandidateNode) Copy() *CandidateNode { return n.doCopy(true) } func (n *CandidateNode) CopyWithoutContent() *CandidateNode { return n.doCopy(false) } func (n *CandidateNode) doCopy(cloneContent bool) *CandidateNode { var content []*CandidateNode var copyKey *CandidateNode if n.Key != nil { copyKey = n.Key.Copy() } clone := &CandidateNode{ Kind: n.Kind, Style: n.Style, Tag: n.Tag, Value: n.Value, Anchor: n.Anchor, // ok not to clone this, // as its a reference to somewhere else. Alias: n.Alias, Content: content, HeadComment: n.HeadComment, LineComment: n.LineComment, FootComment: n.FootComment, Parent: n.Parent, Key: copyKey, LeadingContent: n.LeadingContent, TrailingContent: n.TrailingContent, document: n.document, filename: n.filename, fileIndex: n.fileIndex, Line: n.Line, Column: n.Column, EvaluateTogether: n.EvaluateTogether, IsMapKey: n.IsMapKey, } if cloneContent { clone.AddChildren(n.Content) } return clone } // 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.Kind != ScalarNode && len(n.Content) == 0) || // if the tag has changed (e.g. from str to bool) (n.guessTagFromCustomType() != other.guessTagFromCustomType()) { n.Style = other.Style } n.Content = make([]*CandidateNode, 0) n.AddChildren(other.Content) n.Kind = other.Kind n.Value = other.Value n.UpdateAttributesFrom(other, prefs) } func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode, prefs assignPreferences) { log.Debug("UpdateAttributesFrom: n: %v other: %v", NodeToString(n), NodeToString(other)) if n.Kind != other.Kind { // clear out the contents when switching to a different type // e.g. map to array n.Content = make([]*CandidateNode, 0) n.Value = "" } n.Kind = other.Kind // don't clobber custom tags... if prefs.ClobberCustomTags || strings.HasPrefix(n.Tag, "!!") || n.Tag == "" { n.Tag = other.Tag } n.Alias = other.Alias if !prefs.DontOverWriteAnchor { n.Anchor = other.Anchor } // merge will pickup the style of the new thing // when autocreating nodes if n.Style == 0 { n.Style = other.Style } if other.FootComment != "" { n.FootComment = other.FootComment } if other.TrailingContent != "" { n.TrailingContent = other.TrailingContent } if other.HeadComment != "" { n.HeadComment = other.HeadComment } if other.LineComment != "" { n.LineComment = other.LineComment } }