mirror of
https://github.com/mikefarah/yq.git
synced 2024-11-14 23:28:06 +00:00
122 lines
2.8 KiB
Go
122 lines
2.8 KiB
Go
|
package yqlib
|
||
|
|
||
|
import (
|
||
|
"container/list"
|
||
|
"fmt"
|
||
|
)
|
||
|
|
||
|
func getUniqueElementTag(seq *CandidateNode) (string, error) {
|
||
|
switch l := len(seq.Content); l {
|
||
|
case 0:
|
||
|
return "", nil
|
||
|
default:
|
||
|
result := seq.Content[0].Tag
|
||
|
for i := 1; i < l; i++ {
|
||
|
t := seq.Content[i].Tag
|
||
|
if t != result {
|
||
|
return "", fmt.Errorf("sequence contains elements of %v and %v types", result, t)
|
||
|
}
|
||
|
}
|
||
|
return result, nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var nullNodeFactory = func() *CandidateNode { return createScalarNode(nil, "") }
|
||
|
|
||
|
func pad[E any](array []E, length int, factory func() E) []E {
|
||
|
sz := len(array)
|
||
|
if sz >= length {
|
||
|
return array
|
||
|
}
|
||
|
pad := make([]E, length-sz)
|
||
|
for i := 0; i < len(pad); i++ {
|
||
|
pad[i] = factory()
|
||
|
}
|
||
|
return append(array, pad...)
|
||
|
}
|
||
|
|
||
|
func pivotSequences(seq *CandidateNode) *CandidateNode {
|
||
|
sz := len(seq.Content)
|
||
|
if sz == 0 {
|
||
|
return seq
|
||
|
}
|
||
|
m := make(map[int][]*CandidateNode)
|
||
|
|
||
|
for i := 0; i < sz; i++ {
|
||
|
row := seq.Content[i]
|
||
|
for j := 0; j < len(row.Content); j++ {
|
||
|
e := m[j]
|
||
|
if e == nil {
|
||
|
e = make([]*CandidateNode, 0, sz)
|
||
|
}
|
||
|
m[j] = append(pad(e, i, nullNodeFactory), row.Content[j])
|
||
|
}
|
||
|
}
|
||
|
result := CandidateNode{Kind: SequenceNode}
|
||
|
|
||
|
for i := 0; i < len(m); i++ {
|
||
|
e := CandidateNode{Kind: SequenceNode}
|
||
|
e.AddChildren(pad(m[i], sz, nullNodeFactory))
|
||
|
result.AddChild(&e)
|
||
|
}
|
||
|
return &result
|
||
|
}
|
||
|
|
||
|
func pivotMaps(seq *CandidateNode) *CandidateNode {
|
||
|
sz := len(seq.Content)
|
||
|
if sz == 0 {
|
||
|
return &CandidateNode{Kind: MappingNode}
|
||
|
}
|
||
|
m := make(map[string][]*CandidateNode)
|
||
|
keys := make([]string, 0)
|
||
|
|
||
|
for i := 0; i < sz; i++ {
|
||
|
row := seq.Content[i]
|
||
|
for j := 0; j < len(row.Content); j += 2 {
|
||
|
k := row.Content[j].Value
|
||
|
v := row.Content[j+1]
|
||
|
e := m[k]
|
||
|
if e == nil {
|
||
|
keys = append(keys, k)
|
||
|
e = make([]*CandidateNode, 0, sz)
|
||
|
}
|
||
|
m[k] = append(pad(e, i, nullNodeFactory), v)
|
||
|
}
|
||
|
}
|
||
|
result := CandidateNode{Kind: MappingNode}
|
||
|
for _, k := range keys {
|
||
|
pivotRow := CandidateNode{Kind: SequenceNode}
|
||
|
pivotRow.AddChildren(
|
||
|
pad(m[k], sz, nullNodeFactory))
|
||
|
result.AddKeyValueChild(createScalarNode(k, k), &pivotRow)
|
||
|
}
|
||
|
return &result
|
||
|
}
|
||
|
|
||
|
func pivotOperator(_ *dataTreeNavigator, context Context, _ *ExpressionNode) (Context, error) {
|
||
|
log.Debug("Pivot")
|
||
|
results := list.New()
|
||
|
|
||
|
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
||
|
candidate := el.Value.(*CandidateNode)
|
||
|
if candidate.Tag != "!!seq" {
|
||
|
return Context{}, fmt.Errorf("cannot pivot node of type %v", candidate.Tag)
|
||
|
}
|
||
|
tag, err := getUniqueElementTag(candidate)
|
||
|
if err != nil {
|
||
|
return Context{}, err
|
||
|
}
|
||
|
var pivot *CandidateNode
|
||
|
switch tag {
|
||
|
case "!!seq":
|
||
|
pivot = pivotSequences(candidate)
|
||
|
case "!!map":
|
||
|
pivot = pivotMaps(candidate)
|
||
|
default:
|
||
|
return Context{}, fmt.Errorf("can only pivot elements of !!seq or !!map types, received %v", tag)
|
||
|
}
|
||
|
results.PushBack(pivot)
|
||
|
}
|
||
|
return context.ChildContext(results), nil
|
||
|
}
|