Skip to content
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

feat: add regex.replace(s, p, v) to builtin #5179

Merged
merged 2 commits into from
Sep 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 14 additions & 0 deletions ast/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ var DefaultBuiltins = [...]*Builtin{
RegexTemplateMatch,
RegexFind,
RegexFindAllStringSubmatch,
RegexReplace,

// Sets
SetDiff,
Expand Down Expand Up @@ -1185,6 +1186,19 @@ The old string comparisons are done in argument order.`,
),
}

var RegexReplace = &Builtin{
Name: "regex.replace",
Description: `Find and replaces the text using the regular expression pattern.`,
Decl: types.NewFunction(
types.Args(
types.Named("s", types.S).Description("string being processed"),
types.Named("pattern", types.S).Description("regex pattern to be applied"),
types.Named("value", types.S).Description("regex value"),
),
types.Named("output", types.S),
),
}

var Trim = &Builtin{
Name: "trim",
Description: "Returns `value` with all leading or trailing instances of the `cutset` characters removed.",
Expand Down
30 changes: 30 additions & 0 deletions builtin_metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@
"regex.globs_match",
"regex.is_valid",
"regex.match",
"regex.replace",
"regex.split",
"regex.template_match"
],
Expand Down Expand Up @@ -10095,6 +10096,35 @@
},
"wasm": true
},
"regex.replace": {
"args": [
{
"description": "string being processed",
"name": "s",
"type": "string"
},
{
"description": "regex pattern to be applied",
"name": "pattern",
"type": "string"
},
{
"description": "regex value",
"name": "value",
"type": "string"
}
],
"available": [
"edge"
],
"description": "Find and replaces the text using the regular expression pattern.",
"introduced": "edge",
"result": {
"name": "output",
"type": "string"
},
"wasm": false
},
"regex.split": {
"args": [
{
Expand Down
20 changes: 20 additions & 0 deletions capabilities.json
Original file line number Diff line number Diff line change
Expand Up @@ -3137,6 +3137,26 @@
"type": "function"
}
},
{
"name": "regex.replace",
"decl": {
"args": [
{
"type": "string"
},
{
"type": "string"
},
{
"type": "string"
}
],
"result": {
"type": "string"
},
"type": "function"
}
},
{
"name": "regex.split",
"decl": {
Expand Down
39 changes: 39 additions & 0 deletions test/cases/testdata/regexreplace/test-regexreplace-0001.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
cases:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don’t think you’ll need to be that comprehensive here, but a single test for an entire built-in function seems a bit sparse. How does this work with eg capturing group substitutions? How are errors dealt with?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea! I've covered applying the pattern to subgroups and the error case. Currently, it returns the err as similar to what happens in other regex functions. please let me know if those are enough or if we need more cases to be written

- data: {}
modules:
- |
package test

p = x {
s := "-wy-wxxy-"
x := regex.replace(s, "w(x*)y", "0")
}
note: 'regex.replace: test pattern match and replace'
query: data.test.p = x
want_result:
- x: -0-0-
- data: {}
modules:
- |
package test

p = x {
s := "foo"
x := regex.replace(s, "(foo)", "$1$1")
}
note: 'regex.replace: work with groups'
query: data.test.p = x
want_result:
- x: foofoo
- data: {}
modules:
- |
package test

p = x {
s := "foo"
x := regex.replace(s, "[", "$1")
}
note: 'regex.replace: bad regex pattern: Syntax error'
query: data.test.p = x
want_result: []
27 changes: 27 additions & 0 deletions topdown/regex.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,32 @@ func builtinRegexFindAllStringSubmatch(a, b, c ast.Value) (ast.Value, error) {
return ast.NewArray(outer...), nil
}

func builtinRegexReplace(_ BuiltinContext, operands []*ast.Term, iter func(*ast.Term) error) error {
base, err := builtins.StringOperand(operands[0].Value, 1)
if err != nil {
return err
}

pattern, err := builtins.StringOperand(operands[1].Value, 2)
if err != nil {
return err
}

value, err := builtins.StringOperand(operands[2].Value, 3)
if err != nil {
return err
}

re, err := getRegexp(string(pattern))
if err != nil {
return err
}

res := re.ReplaceAllString(string(base), string(value))

return iter(ast.StringTerm(res))
}

func init() {
regexpCache = map[string]*regexp.Regexp{}
RegisterBuiltinFunc(ast.RegexIsValid.Name, builtinRegexIsValid)
Expand All @@ -215,4 +241,5 @@ func init() {
RegisterFunctionalBuiltin4(ast.RegexTemplateMatch.Name, builtinRegexMatchTemplate)
RegisterFunctionalBuiltin3(ast.RegexFind.Name, builtinRegexFind)
RegisterFunctionalBuiltin3(ast.RegexFindAllStringSubmatch.Name, builtinRegexFindAllStringSubmatch)
RegisterBuiltinFunc(ast.RegexReplace.Name, builtinRegexReplace)
}