From 8f119f1b8162374fd2e694a380ba6f5f606721e5 Mon Sep 17 00:00:00 2001
From: Angus Lees <gus@inodes.org>
Date: Wed, 28 Jun 2017 16:57:50 +1000
Subject: [PATCH] Add several regex native functions

- `escapeStringRegex`
- `regexMatch`
- `regexSubst`

They do what you expect, as implemented by the golang `regexp` package.
---
 lib/kubecfg.libsonnet     | 18 +++++++++++++++++-
 lib/kubecfg_test.jsonnet  |  8 ++++++++
 utils/nativefuncs.go      | 15 +++++++++++++++
 utils/nativefuncs_test.go | 34 ++++++++++++++++++++++++++++++++++
 4 files changed, 74 insertions(+), 1 deletion(-)

diff --git a/lib/kubecfg.libsonnet b/lib/kubecfg.libsonnet
index 90111d1e..4744f53b 100644
--- a/lib/kubecfg.libsonnet
+++ b/lib/kubecfg.libsonnet
@@ -9,8 +9,24 @@
   // element.
   parseYaml:: std.native("parseYaml"),
 
+  // escapeStringRegex(s): Quote the regex metacharacters found in s.
+  // The result is a regex that will match the original literal
+  // characters.
+  escapeStringRegex:: std.native("escapeStringRegex"),
+
   // resolveImage(image): convert the docker image string from
   // image:tag into a more specific image@digest, depending on kubecfg
   // command line flags.
-  resolveImage:: std.native("resolveImage")
+  resolveImage:: std.native("resolveImage"),
+
+  // regexMatch(regex, string): Returns true if regex is found in
+  // string. Regex is as implemented in golang regexp package
+  // (python-ish).
+  regexMatch:: std.native("regexMatch"),
+
+  // regexSubst(regex, src, repl): Return the result of replacing
+  // regex in src with repl.  Replacement string may include $1, etc
+  // to refer to submatches.  Regex is as implemented in golang regexp
+  // package (python-ish).
+  regexSubst:: std.native("regexSubst"),
 }
diff --git a/lib/kubecfg_test.jsonnet b/lib/kubecfg_test.jsonnet
index e6aa3f2c..27e66178 100644
--- a/lib/kubecfg_test.jsonnet
+++ b/lib/kubecfg_test.jsonnet
@@ -15,6 +15,14 @@ assert x == [[3, 4], {foo: "bar", baz: "xyzzy"}] : "got " + x;
 local i = kubecfg.resolveImage("busybox");
 assert i == "busybox:latest" : "got " + i;
 
+assert kubecfg.regexMatch("o$", "foo");
+
+local r1 = kubecfg.escapeStringRegex("f[o");
+assert r1 == "f\\[o" : "got " + r1;
+
+local r2 = kubecfg.regexSubst("e", "tree", "oll");
+assert r2 == "trolloll" : "got " + r2;
+
 // Kubecfg wants to see something that looks like a k8s object
 {
   apiVersion: "test",
diff --git a/utils/nativefuncs.go b/utils/nativefuncs.go
index 749eb12a..3a1129d2 100644
--- a/utils/nativefuncs.go
+++ b/utils/nativefuncs.go
@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"encoding/json"
 	"io"
+	"regexp"
 
 	jsonnet "github.com/strickyak/jsonnet_cgo"
 	"k8s.io/client-go/pkg/util/yaml"
@@ -48,4 +49,18 @@ func RegisterNativeFuncs(vm *jsonnet.VM, resolver Resolver) {
 	vm.NativeCallback("resolveImage", []string{"image"}, func(image string) (string, error) {
 		return resolveImage(resolver, image)
 	})
+
+	vm.NativeCallback("escapeStringRegex", []string{"str"}, func(s string) (string, error) {
+		return regexp.QuoteMeta(s), nil
+	})
+
+	vm.NativeCallback("regexMatch", []string{"regex", "string"}, regexp.MatchString)
+
+	vm.NativeCallback("regexSubst", []string{"regex", "src", "repl"}, func(regex, src, repl string) (string, error) {
+		r, err := regexp.Compile(regex)
+		if err != nil {
+			return "", err
+		}
+		return r.ReplaceAllString(src, repl), nil
+	})
 }
diff --git a/utils/nativefuncs_test.go b/utils/nativefuncs_test.go
index 891e656b..c807b4d0 100644
--- a/utils/nativefuncs_test.go
+++ b/utils/nativefuncs_test.go
@@ -57,3 +57,37 @@ func TestParseYaml(t *testing.T) {
     a[0] + a[1]`)
 	check(t, err, x, "\"helloworld\"\n")
 }
+
+func TestRegexMatch(t *testing.T) {
+	vm := jsonnet.Make()
+	defer vm.Destroy()
+	RegisterNativeFuncs(vm, NewIdentityResolver())
+
+	_, err := vm.EvaluateSnippet("failtest", `std.native("regexMatch")("[f", "foo")`)
+	if err == nil {
+		t.Errorf("regexMatch succeeded with invalid regex")
+	}
+
+	x, err := vm.EvaluateSnippet("test", `std.native("regexMatch")("foo.*", "seafood")`)
+	check(t, err, x, "true\n")
+
+	x, err = vm.EvaluateSnippet("test", `std.native("regexMatch")("bar.*", "seafood")`)
+	check(t, err, x, "false\n")
+}
+
+func TestRegexSubst(t *testing.T) {
+	vm := jsonnet.Make()
+	defer vm.Destroy()
+	RegisterNativeFuncs(vm, NewIdentityResolver())
+
+	_, err := vm.EvaluateSnippet("failtest", `std.native("regexSubst")("[f",s "foo", "bar")`)
+	if err == nil {
+		t.Errorf("regexSubst succeeded with invalid regex")
+	}
+
+	x, err := vm.EvaluateSnippet("test", `std.native("regexSubst")("a(x*)b", "-ab-axxb-", "T")`)
+	check(t, err, x, "\"-T-T-\"\n")
+
+	x, err = vm.EvaluateSnippet("test", `std.native("regexSubst")("a(x*)b", "-ab-axxb-", "${1}W")`)
+	check(t, err, x, "\"-W-xxW-\"\n")
+}
-- 
GitLab