-
-
Notifications
You must be signed in to change notification settings - Fork 756
Add string slicing support #2639
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
2692987
6d345ac
9a9399a
778088d
341e252
1afa3c7
adb150d
7bc2d0c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| # Slice/Splice Array | ||
| # Slice/Splice Array or String | ||
|
|
||
| The slice array operator takes an array as input and returns a subarray. Like the `jq` equivalent, `.[10:15]` will return an array of length 5, starting from index 10 inclusive, up to index 15 exclusive. Negative numbers count backwards from the end of the array. | ||
| The slice operator works on both arrays and strings. Like the `jq` equivalent, `.[10:15]` will return a subarray (or substring) of length 5, starting from index 10 inclusive, up to index 15 exclusive. Negative numbers count backwards from the end of the array or string. | ||
|
|
||
| You may leave out the first or second number, which will refer to the start or end of the array respectively. | ||
| You may leave out the first or second number, which will refer to the start or end of the array or string respectively. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,6 +16,38 @@ | |
| return parseInt(result.MatchingNodes.Front().Value.(*CandidateNode).Value) | ||
| } | ||
|
|
||
| func sliceStringNode(lhsNode *CandidateNode, firstNumber int, secondNumber int) (*CandidateNode, error) { | ||
|
Check failure on line 19 in pkg/yqlib/operator_slice.go
|
||
| runes := []rune(lhsNode.Value) | ||
| length := len(runes) | ||
|
|
||
| relativeFirstNumber := firstNumber | ||
| if relativeFirstNumber < 0 { | ||
| relativeFirstNumber = length + firstNumber | ||
| } | ||
| if relativeFirstNumber < 0 { | ||
| relativeFirstNumber = 0 | ||
| } | ||
|
|
||
| relativeSecondNumber := secondNumber | ||
| if relativeSecondNumber < 0 { | ||
| relativeSecondNumber = length + secondNumber | ||
| } else if relativeSecondNumber > length { | ||
| relativeSecondNumber = length | ||
| } | ||
|
|
||
| log.Debugf("sliceStringNode: slice from %v to %v", relativeFirstNumber, relativeSecondNumber) | ||
|
|
||
| if relativeFirstNumber > length { | ||
| relativeFirstNumber = length | ||
| } | ||
| if relativeSecondNumber < relativeFirstNumber { | ||
| relativeSecondNumber = relativeFirstNumber | ||
| } | ||
|
|
||
| slicedString := string(runes[relativeFirstNumber:relativeSecondNumber]) | ||
| return lhsNode.CreateReplacement(ScalarNode, "!!str", slicedString), nil | ||
| } | ||
|
|
||
| func sliceArrayOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { | ||
|
|
||
| log.Debug("slice array operator!") | ||
|
|
@@ -28,20 +60,29 @@ | |
| lhsNode := el.Value.(*CandidateNode) | ||
|
|
||
| firstNumber, err := getSliceNumber(d, context, lhsNode, expressionNode.LHS) | ||
|
|
||
| if err != nil { | ||
| return Context{}, err | ||
| } | ||
| relativeFirstNumber := firstNumber | ||
| if relativeFirstNumber < 0 { | ||
| relativeFirstNumber = len(lhsNode.Content) + firstNumber | ||
| } | ||
|
|
||
| secondNumber, err := getSliceNumber(d, context, lhsNode, expressionNode.RHS) | ||
| if err != nil { | ||
| return Context{}, err | ||
| } | ||
|
|
||
| if lhsNode.Kind == ScalarNode && lhsNode.guessTagFromCustomType() == "!!str" { | ||
| slicedNode, err := sliceStringNode(lhsNode, firstNumber, secondNumber) | ||
| if err != nil { | ||
| return Context{}, err | ||
| } | ||
| results.PushBack(slicedNode) | ||
| continue | ||
| } | ||
|
|
||
| relativeFirstNumber := firstNumber | ||
| if relativeFirstNumber < 0 { | ||
| relativeFirstNumber = len(lhsNode.Content) + firstNumber | ||
| } | ||
|
||
|
|
||
| relativeSecondNumber := secondNumber | ||
| if relativeSecondNumber < 0 { | ||
| relativeSecondNumber = len(lhsNode.Content) + secondNumber | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -98,6 +98,84 @@ var sliceArrayScenarios = []expressionScenario{ | |
| "D0, P[], (!!seq)::- cat1\n", | ||
| }, | ||
| }, | ||
| { | ||
| description: "Slicing strings", | ||
| document: `country: Australia`, | ||
| expression: `.country[4:]`, | ||
| expected: []string{ | ||
| "D0, P[country], (!!str)::ralia\n", | ||
| }, | ||
| }, | ||
| { | ||
| description: "Slicing strings - without the second number", | ||
| subdescription: "Finishes at the end of the string", | ||
| document: `country: Australia`, | ||
| expression: `.country[0:5]`, | ||
|
||
| expected: []string{ | ||
| "D0, P[country], (!!str)::Austr\n", | ||
| }, | ||
| }, | ||
| { | ||
| description: "Slicing strings - without the first number", | ||
| subdescription: "Starts from the start of the string", | ||
| document: `country: Australia`, | ||
| expression: `.country[:5]`, | ||
| expected: []string{ | ||
| "D0, P[country], (!!str)::Austr\n", | ||
| }, | ||
| }, | ||
| { | ||
| description: "Slicing strings - use negative numbers to count backwards from the end", | ||
| subdescription: "Negative indices count from the end of the string", | ||
| document: `country: Australia`, | ||
| expression: `.country[-5:]`, | ||
| expected: []string{ | ||
| "D0, P[country], (!!str)::ralia\n", | ||
| }, | ||
| }, | ||
| { | ||
| skipDoc: true, | ||
| document: `country: Australia`, | ||
| expression: `.country[1:-1]`, | ||
| expected: []string{ | ||
| "D0, P[country], (!!str)::ustrali\n", | ||
| }, | ||
| }, | ||
| { | ||
| skipDoc: true, | ||
| document: `country: Australia`, | ||
| expression: `.country[:]`, | ||
| expected: []string{ | ||
| "D0, P[country], (!!str)::Australia\n", | ||
| }, | ||
| }, | ||
| { | ||
| skipDoc: true, | ||
| description: "second index beyond string length clamps", | ||
| document: `country: Australia`, | ||
| expression: `.country[:100]`, | ||
| expected: []string{ | ||
| "D0, P[country], (!!str)::Australia\n", | ||
| }, | ||
| }, | ||
| { | ||
| skipDoc: true, | ||
| description: "first index beyond string length returns empty string", | ||
| document: `country: Australia`, | ||
| expression: `.country[100:]`, | ||
| expected: []string{ | ||
| "D0, P[country], (!!str)::\n", | ||
| }, | ||
| }, | ||
| { | ||
| skipDoc: true, | ||
| description: "Unicode string slicing", | ||
| document: `greeting: héllo`, | ||
| expression: `.greeting[1:3]`, | ||
| expected: []string{ | ||
| "D0, P[greeting], (!!str)::él\n", | ||
| }, | ||
| }, | ||
| } | ||
|
|
||
| func TestSliceOperatorScenarios(t *testing.T) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -298,4 +298,8 @@ subsubarray | |
| Ffile | ||
| Fquery | ||
| coverpkg | ||
| gsub | ||
| gsub | ||
| ralia | ||
| Austr | ||
| ustrali | ||
| héllo | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test says without the second number, but both numbers are present