mirror of
https://github.com/mikefarah/yq.git
synced 2026-06-27 07:27:49 +00:00
* Initial plan * Add string slicing support to yq Agent-Logs-Url: https://github.com/mikefarah/yq/sessions/a8525fbb-77a7-4bb0-a3a7-b24f99ae8710 Co-authored-by: mikefarah <1151925+mikefarah@users.noreply.github.com> * Fix sliceStringNode signature and fix test descriptions/expressions Agent-Logs-Url: https://github.com/mikefarah/yq/sessions/58726b13-68ae-4f93-971f-eb70459edcf4 Co-authored-by: mikefarah <1151925+mikefarah@users.noreply.github.com> * Update pkg/yqlib/operator_slice.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Fix array slice out-of-bounds panic with very negative indices Agent-Logs-Url: https://github.com/mikefarah/yq/sessions/7c146762-d251-45fd-8555-2488f59fc57b Co-authored-by: mikefarah <1151925+mikefarah@users.noreply.github.com> * S2-S4: tighten lexer condition, fix doc header, add Unicode example Agent-Logs-Url: https://github.com/mikefarah/yq/sessions/ec06083e-e20a-45d2-bf7e-4e1fa7be1073 Co-authored-by: mikefarah <1151925+mikefarah@users.noreply.github.com> * Fix spelling: multibyte -> multi-byte in Unicode test subdescription Agent-Logs-Url: https://github.com/mikefarah/yq/sessions/6e7b304b-5b52-4e89-8bad-ba22813305c7 Co-authored-by: mikefarah <1151925+mikefarah@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: mikefarah <1151925+mikefarah@users.noreply.github.com> Co-authored-by: Mike Farah <mikefarah@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
98 lines
3.1 KiB
Go
98 lines
3.1 KiB
Go
package yqlib
|
|
|
|
import (
|
|
"container/list"
|
|
"fmt"
|
|
)
|
|
|
|
func getSliceNumber(d *dataTreeNavigator, context Context, node *CandidateNode, expressionNode *ExpressionNode) (int, error) {
|
|
result, err := d.GetMatchingNodes(context.SingleChildContext(node), expressionNode)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if result.MatchingNodes.Len() != 1 {
|
|
return 0, fmt.Errorf("expected to find 1 number, got %v instead", result.MatchingNodes.Len())
|
|
}
|
|
return parseInt(result.MatchingNodes.Front().Value.(*CandidateNode).Value)
|
|
}
|
|
|
|
// clampSliceIndex resolves a possibly-negative slice index against
|
|
// length and clamps the result to [0, length].
|
|
func clampSliceIndex(index, length int) int {
|
|
if index < 0 {
|
|
index += length
|
|
}
|
|
if index < 0 {
|
|
return 0
|
|
}
|
|
if index > length {
|
|
return length
|
|
}
|
|
return index
|
|
}
|
|
|
|
func sliceStringNode(lhsNode *CandidateNode, firstNumber int, secondNumber int) *CandidateNode {
|
|
runes := []rune(lhsNode.Value)
|
|
length := len(runes)
|
|
|
|
relativeFirstNumber := clampSliceIndex(firstNumber, length)
|
|
relativeSecondNumber := clampSliceIndex(secondNumber, length)
|
|
if relativeSecondNumber < relativeFirstNumber {
|
|
relativeSecondNumber = relativeFirstNumber
|
|
}
|
|
|
|
log.Debugf("sliceStringNode: slice from %v to %v", relativeFirstNumber, relativeSecondNumber)
|
|
|
|
slicedString := string(runes[relativeFirstNumber:relativeSecondNumber])
|
|
replacement := lhsNode.CreateReplacement(ScalarNode, lhsNode.Tag, slicedString)
|
|
replacement.Style = lhsNode.Style
|
|
return replacement
|
|
}
|
|
|
|
func sliceArrayOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
|
|
|
log.Debug("slice array operator!")
|
|
log.Debugf("lhs: %v", expressionNode.LHS.Operation.toString())
|
|
log.Debugf("rhs: %v", expressionNode.RHS.Operation.toString())
|
|
|
|
results := list.New()
|
|
|
|
for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
|
|
lhsNode := el.Value.(*CandidateNode)
|
|
|
|
firstNumber, err := getSliceNumber(d, context, lhsNode, expressionNode.LHS)
|
|
if err != nil {
|
|
return Context{}, err
|
|
}
|
|
|
|
secondNumber, err := getSliceNumber(d, context, lhsNode, expressionNode.RHS)
|
|
if err != nil {
|
|
return Context{}, err
|
|
}
|
|
|
|
if lhsNode.Kind == ScalarNode && lhsNode.guessTagFromCustomType() == "!!str" {
|
|
results.PushBack(sliceStringNode(lhsNode, firstNumber, secondNumber))
|
|
continue
|
|
}
|
|
|
|
relativeFirstNumber := clampSliceIndex(firstNumber, len(lhsNode.Content))
|
|
relativeSecondNumber := clampSliceIndex(secondNumber, len(lhsNode.Content))
|
|
|
|
log.Debugf("calculateIndicesToTraverse: slice from %v to %v", relativeFirstNumber, relativeSecondNumber)
|
|
|
|
var newResults []*CandidateNode
|
|
for i := relativeFirstNumber; i < relativeSecondNumber; i++ {
|
|
newResults = append(newResults, lhsNode.Content[i])
|
|
}
|
|
|
|
sliceArrayNode := lhsNode.CreateReplacement(SequenceNode, lhsNode.Tag, "")
|
|
sliceArrayNode.AddChildren(newResults)
|
|
results.PushBack(sliceArrayNode)
|
|
|
|
}
|
|
|
|
// result is now the context that has the nodes we need to put back into a sequence.
|
|
//what about multiple arrays in the context? I think we need to create an array for each one
|
|
return context.ChildContext(results), nil
|
|
}
|