yq/pkg/yqlib/candidate_node.go

441 lines
9.1 KiB
Go
Raw Normal View History

2020-11-03 23:48:43 +00:00
package yqlib
2020-10-13 01:51:37 +00:00
import (
"container/list"
2020-10-13 01:51:37 +00:00
"fmt"
2023-04-12 09:04:02 +00:00
"strconv"
2021-12-04 23:53:37 +00:00
"strings"
2023-04-03 06:16:07 +00:00
)
type Kind uint32
const (
2023-06-05 22:27:59 +00:00
SequenceNode Kind = 1 << iota
2023-04-03 06:16:07 +00:00
MappingNode
ScalarNode
AliasNode
)
type Style uint32
2020-10-13 01:51:37 +00:00
2023-04-03 06:16:07 +00:00
const (
TaggedStyle Style = 1 << iota
DoubleQuotedStyle
SingleQuotedStyle
LiteralStyle
FoldedStyle
FlowStyle
2020-10-13 01:51:37 +00:00
)
2023-04-03 07:27:41 +00:00
func createStringScalarNode(stringValue string) *CandidateNode {
var node = &CandidateNode{Kind: ScalarNode}
node.Value = stringValue
node.Tag = "!!str"
return node
}
2023-04-03 06:16:07 +00:00
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
}
2020-10-13 01:51:37 +00:00
type CandidateNode struct {
2023-04-03 06:16:07 +00:00
Kind Kind
Style Style
Tag string
Value string
Anchor string
Alias *CandidateNode
Content []*CandidateNode
HeadComment string
LineComment string
FootComment string
2021-11-12 04:02:28 +00:00
Parent *CandidateNode // parent node
2023-04-03 06:16:07 +00:00
Key *CandidateNode // node key, if this is a value from a map (or index in an array)
2021-11-12 04:02:28 +00:00
LeadingContent string
TrailingContent string
2021-11-12 04:02:28 +00:00
document uint // the document index of this node
filename string
2023-04-03 06:16:07 +00:00
Line int
Column int
fileIndex int
2021-01-17 23:15:31 +00:00
// when performing op against all nodes given, this will treat all the nodes as one
2022-05-24 08:18:27 +00:00
// (e.g. top level cross document merge). This property does not propagate to child nodes.
2021-01-17 23:15:31 +00:00
EvaluateTogether bool
2021-02-08 02:58:46 +00:00
IsMapKey bool
2020-10-13 01:51:37 +00:00
}
2023-04-11 05:33:32 +00:00
func (n *CandidateNode) CreateChild() *CandidateNode {
return &CandidateNode{
Parent: n,
2023-04-11 05:33:32 +00:00
}
}
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
}
2023-04-11 02:39:22 +00:00
func (n *CandidateNode) GetKey() string {
keyPrefix := ""
if n.IsMapKey {
2023-05-30 01:58:41 +00:00
keyPrefix = fmt.Sprintf("key-%v-", n.Value)
2023-04-11 02:39:22 +00:00
}
key := ""
if n.Key != nil {
key = n.Key.Value
}
return fmt.Sprintf("%v%v - %v", keyPrefix, n.GetDocument(), key)
2023-04-11 02:39:22 +00:00
}
2020-10-13 01:51:37 +00:00
2023-04-03 06:16:07 +00:00
func (n *CandidateNode) unwrapDocument() *CandidateNode {
2023-06-05 22:27:59 +00:00
// if n.Kind == DocumentNode {
// return n.Content[0]
// }
2023-04-03 06:16:07 +00:00
return n
}
2021-12-04 23:53:37 +00:00
func (n *CandidateNode) GetNiceTag() string {
2023-04-03 06:16:07 +00:00
return n.unwrapDocument().Tag
2021-12-04 23:53:37 +00:00
}
2023-04-10 04:13:28 +00:00
func (n *CandidateNode) getParsedKey() interface{} {
2023-04-17 06:20:00 +00:00
if n.IsMapKey {
return n.Value
}
2023-04-10 04:13:28 +00:00
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{} {
2023-04-11 05:33:32 +00:00
key := n.getParsedKey()
2023-04-17 07:23:48 +00:00
if n.Parent != nil && key != nil {
return append(n.Parent.GetPath(), key)
}
2023-04-11 05:33:32 +00:00
if key != nil {
return []interface{}{key}
}
return make([]interface{}, 0)
2023-04-10 04:13:28 +00:00
}
2021-12-04 23:53:37 +00:00
func (n *CandidateNode) GetNicePath() string {
2023-04-10 04:13:28 +00:00
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)
}
2021-12-04 23:53:37 +00:00
}
}
2023-04-10 04:13:28 +00:00
return sb.String()
2021-12-04 23:53:37 +00:00
}
func (n *CandidateNode) AsList() *list.List {
elMap := list.New()
elMap.PushBack(n)
return elMap
}
2023-05-09 03:51:21 +00:00
func (n *CandidateNode) SetParent(parent *CandidateNode) {
n.Parent = parent
}
2023-05-29 22:46:51 +00:00
func (n *CandidateNode) AddKeyValueChild(rawKey *CandidateNode, rawValue *CandidateNode) (*CandidateNode, *CandidateNode) {
2023-04-17 07:23:48 +00:00
key := rawKey.unwrapDocument().Copy()
2023-05-05 04:13:18 +00:00
key.SetParent(n)
2023-04-17 07:23:48 +00:00
key.IsMapKey = true
value := rawValue.unwrapDocument().Copy()
2023-05-05 04:13:18 +00:00
value.SetParent(n)
2023-04-17 07:23:48 +00:00
value.Key = key
n.Content = append(n.Content, key, value)
2023-05-29 22:46:51 +00:00
return key, value
2023-04-17 06:20:00 +00:00
}
2023-05-09 03:51:21 +00:00
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)
2023-05-05 04:13:18 +00:00
}
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]
2023-05-09 03:51:21 +00:00
n.AddKeyValueChild(key, value)
2023-05-05 04:13:18 +00:00
}
} else {
for _, rawChild := range children {
2023-05-09 03:51:21 +00:00
n.AddChild(rawChild)
2023-05-05 04:13:18 +00:00
}
}
}
2023-04-12 09:04:02 +00:00
func (n *CandidateNode) GetValueRep() (interface{}, error) {
2023-04-13 04:34:34 +00:00
log.Debugf("GetValueRep for %v value: %v", n.GetNicePath(), n.Value)
2023-04-12 09:04:02 +00:00
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":
2023-05-09 03:51:21 +00:00
return isTruthyNode(n), nil
2023-04-13 04:44:39 +00:00
case "!!null":
return nil, nil
2023-04-12 09:04:02 +00:00
}
return n.Value, nil
}
2023-04-03 06:16:07 +00:00
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
}
2023-04-03 07:27:41 +00:00
guessedTag := dataBucket.unwrapDocument().Tag
2023-04-03 06:16:07 +00:00
log.Info("im guessing the tag %v is a %v", n.Tag, guessedTag)
return guessedTag
}
2023-04-08 11:27:47 +00:00
func (n *CandidateNode) CreateReplacement(kind Kind, tag string, value string) *CandidateNode {
2023-04-11 02:39:22 +00:00
node := &CandidateNode{
Kind: kind,
Tag: tag,
Value: value,
}
2023-04-11 05:33:32 +00:00
return n.CopyAsReplacement(node)
2023-04-11 02:39:22 +00:00
}
func (n *CandidateNode) CopyAsReplacement(replacement *CandidateNode) *CandidateNode {
2023-04-11 05:33:32 +00:00
newCopy := replacement.Copy()
newCopy.Parent = n.Parent
2023-04-17 07:23:48 +00:00
2023-05-09 03:51:21 +00:00
if n.IsMapKey {
newCopy.Key = n
} else {
newCopy.Key = n.Key
}
2023-04-17 07:23:48 +00:00
2023-04-11 05:33:32 +00:00
return newCopy
}
2023-04-09 01:14:51 +00:00
func (n *CandidateNode) CreateReplacementWithDocWrappers(kind Kind, tag string, style Style) *CandidateNode {
replacement := n.CreateReplacement(kind, tag, "")
2023-04-08 11:27:47 +00:00
replacement.LeadingContent = n.LeadingContent
replacement.TrailingContent = n.TrailingContent
2023-04-09 01:14:51 +00:00
replacement.Style = style
2023-04-08 11:27:47 +00:00
return replacement
}
2023-04-03 06:16:07 +00:00
func (n *CandidateNode) Copy() *CandidateNode {
2023-04-08 10:17:13 +00:00
return n.doCopy(true)
}
func (n *CandidateNode) CopyWithoutContent() *CandidateNode {
return n.doCopy(false)
}
func (n *CandidateNode) doCopy(cloneContent bool) *CandidateNode {
var content []*CandidateNode
2023-04-11 05:33:32 +00:00
var copyKey *CandidateNode
if n.Key != nil {
copyKey = n.Key.Copy()
}
2023-04-17 07:23:48 +00:00
clone := &CandidateNode{
2023-04-03 06:16:07 +00:00
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,
2023-04-08 10:17:13 +00:00
Content: content,
2023-04-03 06:16:07 +00:00
HeadComment: n.HeadComment,
LineComment: n.LineComment,
FootComment: n.FootComment,
Parent: n.Parent,
2023-04-11 05:33:32 +00:00
Key: copyKey,
2023-04-03 06:16:07 +00:00
LeadingContent: n.LeadingContent,
TrailingContent: n.TrailingContent,
document: n.document,
filename: n.filename,
fileIndex: n.fileIndex,
2023-04-03 06:16:07 +00:00
Line: n.Line,
Column: n.Column,
EvaluateTogether: n.EvaluateTogether,
IsMapKey: n.IsMapKey,
2020-11-13 03:07:11 +00:00
}
2023-04-17 07:23:48 +00:00
2023-05-05 04:13:18 +00:00
if cloneContent {
clone.AddChildren(n.Content)
2023-04-17 07:23:48 +00:00
}
return clone
2020-10-21 02:54:51 +00:00
}
2020-10-16 01:29:26 +00:00
// updates this candidate from the given candidate node
func (n *CandidateNode) UpdateFrom(other *CandidateNode, prefs assignPreferences) {
2021-01-01 23:27:32 +00:00
2022-02-20 03:29:52 +00:00
// if this is an empty map or empty array, use the style of other node.
2023-04-03 06:16:07 +00:00
if (n.Kind != ScalarNode && len(n.Content) == 0) ||
2022-02-24 22:14:41 +00:00
// if the tag has changed (e.g. from str to bool)
2023-04-03 06:16:07 +00:00
(n.guessTagFromCustomType() != other.guessTagFromCustomType()) {
n.Style = other.Style
2022-02-20 03:29:52 +00:00
}
2023-05-05 04:13:18 +00:00
n.Content = make([]*CandidateNode, 0)
2023-05-30 05:05:28 +00:00
n.Kind = other.Kind
2023-05-05 04:13:18 +00:00
n.AddChildren(other.Content)
2023-04-03 06:16:07 +00:00
n.Value = other.Value
2022-02-20 03:29:52 +00:00
n.UpdateAttributesFrom(other, prefs)
2020-10-19 05:14:29 +00:00
}
func (n *CandidateNode) UpdateAttributesFrom(other *CandidateNode, prefs assignPreferences) {
2023-04-13 05:40:41 +00:00
log.Debug("UpdateAttributesFrom: n: %v other: %v", NodeToString(n), NodeToString(other))
2023-04-03 06:16:07 +00:00
if n.Kind != other.Kind {
2020-10-19 05:36:46 +00:00
// clear out the contents when switching to a different type
// e.g. map to array
2023-04-03 06:16:07 +00:00
n.Content = make([]*CandidateNode, 0)
n.Value = ""
2020-10-19 05:36:46 +00:00
}
2023-04-03 06:16:07 +00:00
n.Kind = other.Kind
2022-01-22 02:47:22 +00:00
// don't clobber custom tags...
2023-04-03 06:16:07 +00:00
if prefs.ClobberCustomTags || strings.HasPrefix(n.Tag, "!!") || n.Tag == "" {
n.Tag = other.Tag
2022-01-22 02:47:22 +00:00
}
2023-04-03 06:16:07 +00:00
n.Alias = other.Alias
if !prefs.DontOverWriteAnchor {
2023-04-03 06:16:07 +00:00
n.Anchor = other.Anchor
}
2020-10-28 00:34:01 +00:00
// merge will pickup the style of the new thing
// when autocreating nodes
2023-04-03 06:16:07 +00:00
if n.Style == 0 {
n.Style = other.Style
2020-10-28 00:34:01 +00:00
}
2021-03-19 01:54:03 +00:00
2023-04-03 06:16:07 +00:00
if other.FootComment != "" {
n.FootComment = other.FootComment
2021-03-19 01:54:03 +00:00
}
if other.TrailingContent != "" {
n.TrailingContent = other.TrailingContent
}
2023-04-03 06:16:07 +00:00
if other.HeadComment != "" {
n.HeadComment = other.HeadComment
2021-03-19 01:54:03 +00:00
}
2023-04-03 06:16:07 +00:00
if other.LineComment != "" {
n.LineComment = other.LineComment
2021-03-19 01:54:03 +00:00
}
2020-10-16 01:29:26 +00:00
}