diff --git a/pkg/yqlib/doc/String Operators.md b/pkg/yqlib/doc/String Operators.md index a3e73fb2..d18e323a 100644 --- a/pkg/yqlib/doc/String Operators.md +++ b/pkg/yqlib/doc/String Operators.md @@ -1,3 +1,4 @@ +# String Operators ## Join strings Given a sample.yml file of: @@ -17,3 +18,35 @@ will output cat; meow; 1; ; true ``` +## Split strings +Given a sample.yml file of: +```yaml +cat; meow; 1; ; true +``` +then +```bash +yq eval 'split("; ")' sample.yml +``` +will output +```yaml +- cat +- meow +- "1" +- "" +- "true" +``` + +## Split strings one match +Given a sample.yml file of: +```yaml +word +``` +then +```bash +yq eval 'split("; ")' sample.yml +``` +will output +```yaml +- word +``` + diff --git a/pkg/yqlib/doc/headers/String Operators.md b/pkg/yqlib/doc/headers/String Operators.md new file mode 100644 index 00000000..54c3cb1c --- /dev/null +++ b/pkg/yqlib/doc/headers/String Operators.md @@ -0,0 +1 @@ +# String Operators diff --git a/pkg/yqlib/expression_tokeniser.go b/pkg/yqlib/expression_tokeniser.go index 4b6e58eb..934cd03e 100644 --- a/pkg/yqlib/expression_tokeniser.go +++ b/pkg/yqlib/expression_tokeniser.go @@ -244,6 +244,7 @@ func initLexer() (*lex.Lexer, error) { lexer.Add([]byte(`splitDoc`), opToken(splitDocumentOpType)) lexer.Add([]byte(`join`), opToken(joinStringOpType)) + lexer.Add([]byte(`split`), opToken(splitStringOpType)) lexer.Add([]byte(`style`), opAssignableToken(getStyleOpType, assignStyleOpType)) diff --git a/pkg/yqlib/lib.go b/pkg/yqlib/lib.go index abcdeecf..0798d05a 100644 --- a/pkg/yqlib/lib.go +++ b/pkg/yqlib/lib.go @@ -65,6 +65,7 @@ var getPathOpType = &operationType{Type: "GET_PATH", NumArgs: 0, Precedence: 50, var explodeOpType = &operationType{Type: "EXPLODE", NumArgs: 1, Precedence: 50, Handler: explodeOperator} var sortKeysOpType = &operationType{Type: "SORT_KEYS", NumArgs: 1, Precedence: 50, Handler: sortKeysOperator} var joinStringOpType = &operationType{Type: "JOIN", NumArgs: 1, Precedence: 50, Handler: joinStringOperator} +var splitStringOpType = &operationType{Type: "SPLIT", NumArgs: 1, Precedence: 50, Handler: splitStringOperator} var collectObjectOpType = &operationType{Type: "COLLECT_OBJECT", NumArgs: 0, Precedence: 50, Handler: collectObjectOperator} var traversePathOpType = &operationType{Type: "TRAVERSE_PATH", NumArgs: 0, Precedence: 50, Handler: traversePathOperator} diff --git a/pkg/yqlib/operator_strings.go b/pkg/yqlib/operator_strings.go index c5bf278d..479e3395 100644 --- a/pkg/yqlib/operator_strings.go +++ b/pkg/yqlib/operator_strings.go @@ -48,3 +48,49 @@ func join(content []*yaml.Node, joinStr string) *yaml.Node { return &yaml.Node{Kind: yaml.ScalarNode, Value: strings.Join(stringsToJoin, joinStr), Tag: "!!str"} } + +func splitStringOperator(d *dataTreeNavigator, matchMap *list.List, expressionNode *ExpressionNode) (*list.List, error) { + log.Debugf("-- splitStringOperator") + splitStr := "" + + rhs, err := d.GetMatchingNodes(matchMap, expressionNode.Rhs) + if err != nil { + return nil, err + } + if rhs.Front() != nil { + splitStr = rhs.Front().Value.(*CandidateNode).Node.Value + } + + var results = list.New() + + for el := matchMap.Front(); el != nil; el = el.Next() { + candidate := el.Value.(*CandidateNode) + node := unwrapDoc(candidate.Node) + if node.Tag == "!!null" { + continue + } + if node.Tag != "!!str" { + return nil, fmt.Errorf("Cannot split %v, can only split strings", node.Tag) + } + targetNode := split(node.Value, splitStr) + result := candidate.CreateChild(nil, targetNode) + results.PushBack(result) + } + + return results, nil +} + +func split(value string, spltStr string) *yaml.Node { + var contents []*yaml.Node + + if value != "" { + var newStrings = strings.Split(value, spltStr) + contents = make([]*yaml.Node, len(newStrings)) + + for index, str := range newStrings { + contents[index] = &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: str} + } + } + + return &yaml.Node{Kind: yaml.SequenceNode, Tag: "!!seq", Content: contents} +} diff --git a/pkg/yqlib/operator_strings_test.go b/pkg/yqlib/operator_strings_test.go index c33cb5cd..53208c1c 100644 --- a/pkg/yqlib/operator_strings_test.go +++ b/pkg/yqlib/operator_strings_test.go @@ -13,6 +13,35 @@ var stringsOperatorScenarios = []expressionScenario{ "D0, P[], (!!str)::cat; meow; 1; ; true\n", }, }, + { + description: "Split strings", + document: `"cat; meow; 1; ; true"`, + expression: `split("; ")`, + expected: []string{ + "D0, P[], (!!seq)::- cat\n- meow\n- \"1\"\n- \"\"\n- \"true\"\n", + }, + }, + { + description: "Split strings one match", + document: `"word"`, + expression: `split("; ")`, + expected: []string{ + "D0, P[], (!!seq)::- word\n", + }, + }, + { + skipDoc: true, + document: `""`, + expression: `split("; ")`, + expected: []string{ + "D0, P[], (!!seq)::[]\n", // dont actually want this, just not to error + }, + }, + { + skipDoc: true, + expression: `split("; ")`, + expected: []string{}, + }, } func TestStringsOperatorScenarios(t *testing.T) {