Skip to content

Commit 914ec74

Browse files
authored
feat: Go implementation for manifestYamlDoc and escapeStringJson (#2)
* Builtins for escapeStringJson and manifestYamlDoc * Benchmark and tests
1 parent fed90cd commit 914ec74

10 files changed

+453
-9
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
std.escapeStringJson("Lorem ipsum dolor sit amet, consectetur \"adipiscing\" elit. Nullam \\nec sagittis \\u0065lit, sed do \\teiusmod tempor incididunt ut labore et dolore magna aliqua.\nUt enim ad minim veniam, quis nostrud exercitation ullamco \flaboris nisi ut aliquip ex ea commodo consequat.\rDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\n\tExcepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit \\anim id est laborum.\nCurabitur pretium tincidunt lacus. Nulla gravida orci a odio. \\Nullam varius, turpis et commodo pharetra, est eros bibendum elit, nec luctus magna felis sollicitudin mauris.\nInteger in mauris eu nibh euismod gravida. Duis ac tellus et risus vulputate vehicula. Donec lobortis risus a elit. Etiam tempor. Ut ullamcorper, ligula eu tempor congue, eros est euismod turpis, id tincidunt sapien risus a quam. Maecenas fermentum consequat mi. Donec fermentum. Pellentesque malesuada nulla a mi. Duis sapien sem, aliquet nec, commodo eget, consequat quis, neque. Aliquam faucibus, elit ut dictum aliquet, felis nisl adipiscing sapien, sed malesuada diam lacus eget erat. Cras mollis scelerisque nunc. Nullam arcu. Aliquam consequat. Curabitur augue lorem, dapibus quis, laoreet et, pretium ac, nisi. Aenean magna nisl, mollis quis, molestie eu, feugiat in, orci. In hac habitasse platea dictumst.")
+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{
2+
bar: {
3+
prometheusOperator+: {
4+
service+: {
5+
spec+: {
6+
ports: [
7+
{
8+
name: 'https',
9+
port: 8443,
10+
targetPort: 'https',
11+
},
12+
],
13+
},
14+
},
15+
serviceMonitor+: {
16+
spec+: {
17+
endpoints: [
18+
{
19+
port: 'https',
20+
scheme: 'https',
21+
honorLabels: true,
22+
bearerTokenFile: '/var/run/secrets/kubernetes.io/serviceaccount/token',
23+
tlsConfig: {
24+
insecureSkipVerify: true,
25+
},
26+
},
27+
],
28+
},
29+
},
30+
clusterRole+: {
31+
rules+: [
32+
{
33+
apiGroups: ['authentication.k8s.io'],
34+
resources: ['tokenreviews'],
35+
verbs: ['create'],
36+
},
37+
{
38+
apiGroups: ['authorization.k8s.io'],
39+
resources: ['subjectaccessreviews'],
40+
verbs: ['create'],
41+
},
42+
],
43+
},
44+
},
45+
additional+: {
46+
'$schema': "http://json-schema.org/draft-07/schema#",
47+
'09': ['no', 'yes'],
48+
},
49+
},
50+
nothing: std.manifestYamlDoc(self.bar, indent_array_in_object=true, quote_keys=true),
51+
}

builtins.go

+240-9
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"io"
3030
"math"
3131
"reflect"
32+
"regexp"
3233
"sort"
3334
"strconv"
3435
"strings"
@@ -233,17 +234,25 @@ func builtinLength(i *interpreter, x value) (value, error) {
233234
return makeValueNumber(float64(num)), nil
234235
}
235236

236-
func builtinToString(i *interpreter, x value) (value, error) {
237+
func valueToString(i *interpreter, x value) (string, error) {
237238
switch x := x.(type) {
238239
case valueString:
239-
return x, nil
240+
return x.getGoString(), nil
240241
}
242+
241243
var buf bytes.Buffer
242-
err := i.manifestAndSerializeJSON(&buf, x, false, "")
244+
if err := i.manifestAndSerializeJSON(&buf, x, false, ""); err != nil {
245+
return "", err
246+
}
247+
return buf.String(), nil
248+
}
249+
250+
func builtinToString(i *interpreter, x value) (value, error) {
251+
s, err := valueToString(i, x)
243252
if err != nil {
244253
return nil, err
245254
}
246-
return makeValueString(buf.String()), nil
255+
return makeValueString(s), nil
247256
}
248257

249258
func builtinTrace(i *interpreter, x value, y value) (value, error) {
@@ -1556,8 +1565,16 @@ func tomlIsSection(i *interpreter, val value) (bool, error) {
15561565
}
15571566
}
15581567

1559-
// tomlEncodeString encodes a string as quoted TOML string
1560-
func tomlEncodeString(s string) string {
1568+
func builtinEscapeStringJson(i *interpreter, v value) (value, error) {
1569+
s, err := valueToString(i, v)
1570+
if err != nil {
1571+
return nil, err
1572+
}
1573+
1574+
return makeValueString(unparseString(s)), nil
1575+
}
1576+
1577+
func escapeStringJson(s string) string {
15611578
res := "\""
15621579

15631580
for _, c := range s {
@@ -1589,6 +1606,11 @@ func tomlEncodeString(s string) string {
15891606
return res
15901607
}
15911608

1609+
// tomlEncodeString encodes a string as quoted TOML string
1610+
func tomlEncodeString(s string) string {
1611+
return unparseString(s)
1612+
}
1613+
15921614
// tomlEncodeKey encodes a key - returning same string if it does not need quoting,
15931615
// otherwise return it quoted; returns empty key as ”
15941616
func tomlEncodeKey(s string) string {
@@ -1980,6 +2002,209 @@ func builtinManifestJSONEx(i *interpreter, arguments []value) (value, error) {
19802002
return makeValueString(finalString), nil
19812003
}
19822004

2005+
const (
2006+
yamlIndent = " "
2007+
)
2008+
2009+
var (
2010+
yamlReserved = []string{
2011+
// Boolean types taken from https://yaml.org/type/bool.html
2012+
"true", "false", "yes", "no", "on", "off", "y", "n",
2013+
// Numerical words taken from https://yaml.org/type/float.html
2014+
".nan", "-.inf", "+.inf", ".inf", "null",
2015+
// Invalid keys that contain no invalid characters
2016+
"-", "---", "''",
2017+
}
2018+
yamlTimestampPattern = regexp.MustCompile(`^(?:[0-9]*-){2}[0-9]*$`)
2019+
yamlBinaryPattern = regexp.MustCompile(`^[-+]?0b[0-1_]+$`)
2020+
yamlHexPattern = regexp.MustCompile(`[-+]?0x[0-9a-fA-F_]+`)
2021+
)
2022+
2023+
func yamlReservedString(s string) bool {
2024+
for _, r := range yamlReserved {
2025+
if strings.EqualFold(s, r) {
2026+
return true
2027+
}
2028+
}
2029+
return false
2030+
}
2031+
2032+
func yamlBareSafe(s string) bool {
2033+
if len(s) == 0 {
2034+
return false
2035+
}
2036+
2037+
// String contains unsafe char
2038+
for _, c := range s {
2039+
isAlpha := (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')
2040+
isDigit := c >= '0' && c <= '9'
2041+
2042+
if !isAlpha && !isDigit && c != '_' && c != '-' && c != '/' && c != '.' {
2043+
return false
2044+
}
2045+
}
2046+
2047+
if yamlReservedString(s) {
2048+
return false
2049+
}
2050+
2051+
if yamlTimestampPattern.MatchString(s) {
2052+
return false
2053+
}
2054+
2055+
// Check binary /
2056+
if yamlBinaryPattern.MatchString(s) || yamlHexPattern.MatchString(s) {
2057+
return false
2058+
}
2059+
2060+
// Is integer
2061+
if _, err := strconv.Atoi(s); err == nil {
2062+
return false
2063+
}
2064+
// Is float
2065+
if _, err := strconv.ParseFloat(s, 64); err == nil {
2066+
return false
2067+
}
2068+
2069+
return true
2070+
}
2071+
2072+
func builtinManifestYamlDoc(i *interpreter, arguments []value) (value, error) {
2073+
val := arguments[0]
2074+
vindentArrInObj, err := i.getBoolean(arguments[1])
2075+
if err != nil {
2076+
return nil, err
2077+
}
2078+
vQuoteKeys, err := i.getBoolean(arguments[2])
2079+
if err != nil {
2080+
return nil, err
2081+
}
2082+
2083+
var buf bytes.Buffer
2084+
2085+
var aux func(ov value, buf *bytes.Buffer, cindent string) error
2086+
aux = func(ov value, buf *bytes.Buffer, cindent string) error {
2087+
switch v := ov.(type) {
2088+
case *valueNull:
2089+
buf.WriteString("null")
2090+
case *valueBoolean:
2091+
if v.value {
2092+
buf.WriteString("true")
2093+
} else {
2094+
buf.WriteString("false")
2095+
}
2096+
case valueString:
2097+
s := v.getGoString()
2098+
if s == "" {
2099+
buf.WriteString(`""`)
2100+
} else if strings.HasSuffix(s, "\n") {
2101+
s := strings.TrimSuffix(s, "\n")
2102+
buf.WriteString("|")
2103+
for _, line := range strings.Split(s, "\n") {
2104+
buf.WriteByte('\n')
2105+
buf.WriteString(cindent)
2106+
buf.WriteString(yamlIndent)
2107+
buf.WriteString(line)
2108+
}
2109+
} else {
2110+
buf.WriteString(unparseString(s))
2111+
}
2112+
case *valueNumber:
2113+
buf.WriteString(strconv.FormatFloat(v.value, 'f', -1, 64))
2114+
case *valueArray:
2115+
if v.length() == 0 {
2116+
buf.WriteString("[]")
2117+
return nil
2118+
}
2119+
for ix, elem := range v.elements {
2120+
if ix != 0 {
2121+
buf.WriteByte('\n')
2122+
buf.WriteString(cindent)
2123+
}
2124+
thunkValue, err := elem.getValue(i)
2125+
if err != nil {
2126+
return err
2127+
}
2128+
buf.WriteByte('-')
2129+
2130+
if v, isArr := thunkValue.(*valueArray); isArr && v.length() > 0 {
2131+
buf.WriteByte('\n')
2132+
buf.WriteString(cindent)
2133+
buf.WriteString(yamlIndent)
2134+
} else {
2135+
buf.WriteByte(' ')
2136+
}
2137+
2138+
prevIndent := cindent
2139+
switch thunkValue.(type) {
2140+
case *valueArray, *valueObject:
2141+
cindent = cindent + yamlIndent
2142+
}
2143+
2144+
if err := aux(thunkValue, buf, cindent); err != nil {
2145+
return err
2146+
}
2147+
cindent = prevIndent
2148+
}
2149+
case *valueObject:
2150+
fields := objectFields(v, withoutHidden)
2151+
if len(fields) == 0 {
2152+
buf.WriteString("{}")
2153+
return nil
2154+
}
2155+
sort.Strings(fields)
2156+
for ix, fieldName := range fields {
2157+
fieldValue, err := v.index(i, fieldName)
2158+
if err != nil {
2159+
return err
2160+
}
2161+
2162+
if ix != 0 {
2163+
buf.WriteByte('\n')
2164+
buf.WriteString(cindent)
2165+
}
2166+
2167+
keyStr := fieldName
2168+
if vQuoteKeys.value || !yamlBareSafe(fieldName) {
2169+
keyStr = escapeStringJson(fieldName)
2170+
}
2171+
buf.WriteString(keyStr)
2172+
buf.WriteByte(':')
2173+
2174+
prevIndent := cindent
2175+
if v, isArr := fieldValue.(*valueArray); isArr && v.length() > 0 {
2176+
buf.WriteByte('\n')
2177+
buf.WriteString(cindent)
2178+
if vindentArrInObj.value {
2179+
buf.WriteString(yamlIndent)
2180+
cindent = cindent + yamlIndent
2181+
}
2182+
} else if v, isObj := fieldValue.(*valueObject); isObj {
2183+
if len(objectFields(v, withoutHidden)) > 0 {
2184+
buf.WriteByte('\n')
2185+
buf.WriteString(cindent)
2186+
buf.WriteString(yamlIndent)
2187+
cindent = cindent + yamlIndent
2188+
} else {
2189+
buf.WriteByte(' ')
2190+
}
2191+
} else {
2192+
buf.WriteByte(' ')
2193+
}
2194+
aux(fieldValue, buf, cindent)
2195+
cindent = prevIndent
2196+
}
2197+
}
2198+
return nil
2199+
}
2200+
2201+
if err := aux(val, &buf, ""); err != nil {
2202+
return nil, err
2203+
}
2204+
2205+
return makeValueString(buf.String()), nil
2206+
}
2207+
19832208
func builtinExtVar(i *interpreter, name value) (value, error) {
19842209
str, err := i.getString(name)
19852210
if err != nil {
@@ -2097,12 +2322,12 @@ func builtinAvg(i *interpreter, arrv value) (value, error) {
20972322
if err != nil {
20982323
return nil, err
20992324
}
2100-
2325+
21012326
len := float64(arr.length())
21022327
if len == 0 {
21032328
return nil, i.Error("Cannot calculate average of an empty array.")
21042329
}
2105-
2330+
21062331
sumValue, err := builtinSum(i, arrv)
21072332
if err != nil {
21082333
return nil, err
@@ -2112,7 +2337,7 @@ func builtinAvg(i *interpreter, arrv value) (value, error) {
21122337
return nil, err
21132338
}
21142339

2115-
avg := sum.value/len
2340+
avg := sum.value / len
21162341
return makeValueNumber(avg), nil
21172342
}
21182343

@@ -2459,6 +2684,7 @@ var funcBuiltins = buildBuiltinMap([]builtin{
24592684
&unaryBuiltin{name: "extVar", function: builtinExtVar, params: ast.Identifiers{"x"}},
24602685
&unaryBuiltin{name: "length", function: builtinLength, params: ast.Identifiers{"x"}},
24612686
&unaryBuiltin{name: "toString", function: builtinToString, params: ast.Identifiers{"a"}},
2687+
&unaryBuiltin{name: "escapeStringJson", function: builtinEscapeStringJson, params: ast.Identifiers{"str_"}},
24622688
&binaryBuiltin{name: "trace", function: builtinTrace, params: ast.Identifiers{"str", "rest"}},
24632689
&binaryBuiltin{name: "makeArray", function: builtinMakeArray, params: ast.Identifiers{"sz", "func"}},
24642690
&binaryBuiltin{name: "flatMap", function: builtinFlatMap, params: ast.Identifiers{"func", "arr"}},
@@ -2524,6 +2750,11 @@ var funcBuiltins = buildBuiltinMap([]builtin{
25242750
{name: "newline", defaultValue: &valueFlatString{value: []rune("\n")}},
25252751
{name: "key_val_sep", defaultValue: &valueFlatString{value: []rune(": ")}}}},
25262752
&generalBuiltin{name: "manifestTomlEx", function: builtinManifestTomlEx, params: []generalBuiltinParameter{{name: "value"}, {name: "indent"}}},
2753+
&generalBuiltin{name: "manifestYamlDoc", function: builtinManifestYamlDoc, params: []generalBuiltinParameter{
2754+
{name: "value"},
2755+
{name: "indent_array_in_object", defaultValue: &valueBoolean{value: false}},
2756+
{name: "quote_keys", defaultValue: &valueBoolean{value: true}},
2757+
}},
25272758
&unaryBuiltin{name: "base64", function: builtinBase64, params: ast.Identifiers{"input"}},
25282759
&unaryBuiltin{name: "encodeUTF8", function: builtinEncodeUTF8, params: ast.Identifiers{"str"}},
25292760
&unaryBuiltin{name: "decodeUTF8", function: builtinDecodeUTF8, params: ast.Identifiers{"arr"}},

builtins_benchmark_test.go

+8
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ func Benchmark_Builtin_base64_byte_array(b *testing.B) {
5656
RunBenchmark(b, "base64_byte_array")
5757
}
5858

59+
func Benchmark_Builtin_escapeStringJson(b *testing.B) {
60+
RunBenchmark(b, "escapeStringJson")
61+
}
62+
5963
func Benchmark_Builtin_manifestJsonEx(b *testing.B) {
6064
RunBenchmark(b, "manifestJsonEx")
6165
}
@@ -64,6 +68,10 @@ func Benchmark_Builtin_manifestTomlEx(b *testing.B) {
6468
RunBenchmark(b, "manifestTomlEx")
6569
}
6670

71+
func Benchmark_Builtin_manifestYamlDoc(b *testing.B) {
72+
RunBenchmark(b, "manifestYamlDoc")
73+
}
74+
6775
func Benchmark_Builtin_comparison(b *testing.B) {
6876
RunBenchmark(b, "comparison")
6977
}

0 commit comments

Comments
 (0)