@@ -29,6 +29,7 @@ import (
29
29
"io"
30
30
"math"
31
31
"reflect"
32
+ "regexp"
32
33
"sort"
33
34
"strconv"
34
35
"strings"
@@ -233,17 +234,25 @@ func builtinLength(i *interpreter, x value) (value, error) {
233
234
return makeValueNumber (float64 (num )), nil
234
235
}
235
236
236
- func builtinToString (i * interpreter , x value ) (value , error ) {
237
+ func valueToString (i * interpreter , x value ) (string , error ) {
237
238
switch x := x .(type ) {
238
239
case valueString :
239
- return x , nil
240
+ return x . getGoString () , nil
240
241
}
242
+
241
243
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 )
243
252
if err != nil {
244
253
return nil , err
245
254
}
246
- return makeValueString (buf . String () ), nil
255
+ return makeValueString (s ), nil
247
256
}
248
257
249
258
func builtinTrace (i * interpreter , x value , y value ) (value , error ) {
@@ -1597,8 +1606,16 @@ func tomlIsSection(i *interpreter, val value) (bool, error) {
1597
1606
}
1598
1607
}
1599
1608
1600
- // tomlEncodeString encodes a string as quoted TOML string
1601
- func tomlEncodeString (s string ) string {
1609
+ func builtinEscapeStringJson (i * interpreter , v value ) (value , error ) {
1610
+ s , err := valueToString (i , v )
1611
+ if err != nil {
1612
+ return nil , err
1613
+ }
1614
+
1615
+ return makeValueString (unparseString (s )), nil
1616
+ }
1617
+
1618
+ func escapeStringJson (s string ) string {
1602
1619
res := "\" "
1603
1620
1604
1621
for _ , c := range s {
@@ -1630,6 +1647,11 @@ func tomlEncodeString(s string) string {
1630
1647
return res
1631
1648
}
1632
1649
1650
+ // tomlEncodeString encodes a string as quoted TOML string
1651
+ func tomlEncodeString (s string ) string {
1652
+ return unparseString (s )
1653
+ }
1654
+
1633
1655
// tomlEncodeKey encodes a key - returning same string if it does not need quoting,
1634
1656
// otherwise return it quoted; returns empty key as ”
1635
1657
func tomlEncodeKey (s string ) string {
@@ -2021,6 +2043,209 @@ func builtinManifestJSONEx(i *interpreter, arguments []value) (value, error) {
2021
2043
return makeValueString (finalString ), nil
2022
2044
}
2023
2045
2046
+ const (
2047
+ yamlIndent = " "
2048
+ )
2049
+
2050
+ var (
2051
+ yamlReserved = []string {
2052
+ // Boolean types taken from https://yaml.org/type/bool.html
2053
+ "true" , "false" , "yes" , "no" , "on" , "off" , "y" , "n" ,
2054
+ // Numerical words taken from https://yaml.org/type/float.html
2055
+ ".nan" , "-.inf" , "+.inf" , ".inf" , "null" ,
2056
+ // Invalid keys that contain no invalid characters
2057
+ "-" , "---" , "''" ,
2058
+ }
2059
+ yamlTimestampPattern = regexp .MustCompile (`^(?:[0-9]*-){2}[0-9]*$` )
2060
+ yamlBinaryPattern = regexp .MustCompile (`^[-+]?0b[0-1_]+$` )
2061
+ yamlHexPattern = regexp .MustCompile (`[-+]?0x[0-9a-fA-F_]+` )
2062
+ )
2063
+
2064
+ func yamlReservedString (s string ) bool {
2065
+ for _ , r := range yamlReserved {
2066
+ if strings .EqualFold (s , r ) {
2067
+ return true
2068
+ }
2069
+ }
2070
+ return false
2071
+ }
2072
+
2073
+ func yamlBareSafe (s string ) bool {
2074
+ if len (s ) == 0 {
2075
+ return false
2076
+ }
2077
+
2078
+ // String contains unsafe char
2079
+ for _ , c := range s {
2080
+ isAlpha := (c >= 'A' && c <= 'Z' ) || (c >= 'a' && c <= 'z' )
2081
+ isDigit := c >= '0' && c <= '9'
2082
+
2083
+ if ! isAlpha && ! isDigit && c != '_' && c != '-' && c != '/' && c != '.' {
2084
+ return false
2085
+ }
2086
+ }
2087
+
2088
+ if yamlReservedString (s ) {
2089
+ return false
2090
+ }
2091
+
2092
+ if yamlTimestampPattern .MatchString (s ) {
2093
+ return false
2094
+ }
2095
+
2096
+ // Check binary /
2097
+ if yamlBinaryPattern .MatchString (s ) || yamlHexPattern .MatchString (s ) {
2098
+ return false
2099
+ }
2100
+
2101
+ // Is integer
2102
+ if _ , err := strconv .Atoi (s ); err == nil {
2103
+ return false
2104
+ }
2105
+ // Is float
2106
+ if _ , err := strconv .ParseFloat (s , 64 ); err == nil {
2107
+ return false
2108
+ }
2109
+
2110
+ return true
2111
+ }
2112
+
2113
+ func builtinManifestYamlDoc (i * interpreter , arguments []value ) (value , error ) {
2114
+ val := arguments [0 ]
2115
+ vindentArrInObj , err := i .getBoolean (arguments [1 ])
2116
+ if err != nil {
2117
+ return nil , err
2118
+ }
2119
+ vQuoteKeys , err := i .getBoolean (arguments [2 ])
2120
+ if err != nil {
2121
+ return nil , err
2122
+ }
2123
+
2124
+ var buf bytes.Buffer
2125
+
2126
+ var aux func (ov value , buf * bytes.Buffer , cindent string ) error
2127
+ aux = func (ov value , buf * bytes.Buffer , cindent string ) error {
2128
+ switch v := ov .(type ) {
2129
+ case * valueNull :
2130
+ buf .WriteString ("null" )
2131
+ case * valueBoolean :
2132
+ if v .value {
2133
+ buf .WriteString ("true" )
2134
+ } else {
2135
+ buf .WriteString ("false" )
2136
+ }
2137
+ case valueString :
2138
+ s := v .getGoString ()
2139
+ if s == "" {
2140
+ buf .WriteString (`""` )
2141
+ } else if strings .HasSuffix (s , "\n " ) {
2142
+ s := strings .TrimSuffix (s , "\n " )
2143
+ buf .WriteString ("|" )
2144
+ for _ , line := range strings .Split (s , "\n " ) {
2145
+ buf .WriteByte ('\n' )
2146
+ buf .WriteString (cindent )
2147
+ buf .WriteString (yamlIndent )
2148
+ buf .WriteString (line )
2149
+ }
2150
+ } else {
2151
+ buf .WriteString (unparseString (s ))
2152
+ }
2153
+ case * valueNumber :
2154
+ buf .WriteString (strconv .FormatFloat (v .value , 'f' , - 1 , 64 ))
2155
+ case * valueArray :
2156
+ if v .length () == 0 {
2157
+ buf .WriteString ("[]" )
2158
+ return nil
2159
+ }
2160
+ for ix , elem := range v .elements {
2161
+ if ix != 0 {
2162
+ buf .WriteByte ('\n' )
2163
+ buf .WriteString (cindent )
2164
+ }
2165
+ thunkValue , err := elem .getValue (i )
2166
+ if err != nil {
2167
+ return err
2168
+ }
2169
+ buf .WriteByte ('-' )
2170
+
2171
+ if v , isArr := thunkValue .(* valueArray ); isArr && v .length () > 0 {
2172
+ buf .WriteByte ('\n' )
2173
+ buf .WriteString (cindent )
2174
+ buf .WriteString (yamlIndent )
2175
+ } else {
2176
+ buf .WriteByte (' ' )
2177
+ }
2178
+
2179
+ prevIndent := cindent
2180
+ switch thunkValue .(type ) {
2181
+ case * valueArray , * valueObject :
2182
+ cindent = cindent + yamlIndent
2183
+ }
2184
+
2185
+ if err := aux (thunkValue , buf , cindent ); err != nil {
2186
+ return err
2187
+ }
2188
+ cindent = prevIndent
2189
+ }
2190
+ case * valueObject :
2191
+ fields := objectFields (v , withoutHidden )
2192
+ if len (fields ) == 0 {
2193
+ buf .WriteString ("{}" )
2194
+ return nil
2195
+ }
2196
+ sort .Strings (fields )
2197
+ for ix , fieldName := range fields {
2198
+ fieldValue , err := v .index (i , fieldName )
2199
+ if err != nil {
2200
+ return err
2201
+ }
2202
+
2203
+ if ix != 0 {
2204
+ buf .WriteByte ('\n' )
2205
+ buf .WriteString (cindent )
2206
+ }
2207
+
2208
+ keyStr := fieldName
2209
+ if vQuoteKeys .value || ! yamlBareSafe (fieldName ) {
2210
+ keyStr = escapeStringJson (fieldName )
2211
+ }
2212
+ buf .WriteString (keyStr )
2213
+ buf .WriteByte (':' )
2214
+
2215
+ prevIndent := cindent
2216
+ if v , isArr := fieldValue .(* valueArray ); isArr && v .length () > 0 {
2217
+ buf .WriteByte ('\n' )
2218
+ buf .WriteString (cindent )
2219
+ if vindentArrInObj .value {
2220
+ buf .WriteString (yamlIndent )
2221
+ cindent = cindent + yamlIndent
2222
+ }
2223
+ } else if v , isObj := fieldValue .(* valueObject ); isObj {
2224
+ if len (objectFields (v , withoutHidden )) > 0 {
2225
+ buf .WriteByte ('\n' )
2226
+ buf .WriteString (cindent )
2227
+ buf .WriteString (yamlIndent )
2228
+ cindent = cindent + yamlIndent
2229
+ } else {
2230
+ buf .WriteByte (' ' )
2231
+ }
2232
+ } else {
2233
+ buf .WriteByte (' ' )
2234
+ }
2235
+ aux (fieldValue , buf , cindent )
2236
+ cindent = prevIndent
2237
+ }
2238
+ }
2239
+ return nil
2240
+ }
2241
+
2242
+ if err := aux (val , & buf , "" ); err != nil {
2243
+ return nil , err
2244
+ }
2245
+
2246
+ return makeValueString (buf .String ()), nil
2247
+ }
2248
+
2024
2249
func builtinExtVar (i * interpreter , name value ) (value , error ) {
2025
2250
str , err := i .getString (name )
2026
2251
if err != nil {
@@ -2138,12 +2363,12 @@ func builtinAvg(i *interpreter, arrv value) (value, error) {
2138
2363
if err != nil {
2139
2364
return nil , err
2140
2365
}
2141
-
2366
+
2142
2367
len := float64 (arr .length ())
2143
2368
if len == 0 {
2144
2369
return nil , i .Error ("Cannot calculate average of an empty array." )
2145
2370
}
2146
-
2371
+
2147
2372
sumValue , err := builtinSum (i , arrv )
2148
2373
if err != nil {
2149
2374
return nil , err
@@ -2153,7 +2378,7 @@ func builtinAvg(i *interpreter, arrv value) (value, error) {
2153
2378
return nil , err
2154
2379
}
2155
2380
2156
- avg := sum .value / len
2381
+ avg := sum .value / len
2157
2382
return makeValueNumber (avg ), nil
2158
2383
}
2159
2384
@@ -2500,6 +2725,7 @@ var funcBuiltins = buildBuiltinMap([]builtin{
2500
2725
& unaryBuiltin {name : "extVar" , function : builtinExtVar , params : ast.Identifiers {"x" }},
2501
2726
& unaryBuiltin {name : "length" , function : builtinLength , params : ast.Identifiers {"x" }},
2502
2727
& unaryBuiltin {name : "toString" , function : builtinToString , params : ast.Identifiers {"a" }},
2728
+ & unaryBuiltin {name : "escapeStringJson" , function : builtinEscapeStringJson , params : ast.Identifiers {"str_" }},
2503
2729
& binaryBuiltin {name : "trace" , function : builtinTrace , params : ast.Identifiers {"str" , "rest" }},
2504
2730
& binaryBuiltin {name : "makeArray" , function : builtinMakeArray , params : ast.Identifiers {"sz" , "func" }},
2505
2731
& binaryBuiltin {name : "flatMap" , function : builtinFlatMap , params : ast.Identifiers {"func" , "arr" }},
@@ -2566,6 +2792,11 @@ var funcBuiltins = buildBuiltinMap([]builtin{
2566
2792
{name : "newline" , defaultValue : & valueFlatString {value : []rune ("\n " )}},
2567
2793
{name : "key_val_sep" , defaultValue : & valueFlatString {value : []rune (": " )}}}},
2568
2794
& generalBuiltin {name : "manifestTomlEx" , function : builtinManifestTomlEx , params : []generalBuiltinParameter {{name : "value" }, {name : "indent" }}},
2795
+ & generalBuiltin {name : "manifestYamlDoc" , function : builtinManifestYamlDoc , params : []generalBuiltinParameter {
2796
+ {name : "value" },
2797
+ {name : "indent_array_in_object" , defaultValue : & valueBoolean {value : false }},
2798
+ {name : "quote_keys" , defaultValue : & valueBoolean {value : true }},
2799
+ }},
2569
2800
& unaryBuiltin {name : "base64" , function : builtinBase64 , params : ast.Identifiers {"input" }},
2570
2801
& unaryBuiltin {name : "encodeUTF8" , function : builtinEncodeUTF8 , params : ast.Identifiers {"str" }},
2571
2802
& unaryBuiltin {name : "decodeUTF8" , function : builtinDecodeUTF8 , params : ast.Identifiers {"arr" }},
0 commit comments