@@ -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 ) {
@@ -1556,8 +1565,16 @@ func tomlIsSection(i *interpreter, val value) (bool, error) {
1556
1565
}
1557
1566
}
1558
1567
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 {
1561
1578
res := "\" "
1562
1579
1563
1580
for _ , c := range s {
@@ -1589,6 +1606,11 @@ func tomlEncodeString(s string) string {
1589
1606
return res
1590
1607
}
1591
1608
1609
+ // tomlEncodeString encodes a string as quoted TOML string
1610
+ func tomlEncodeString (s string ) string {
1611
+ return unparseString (s )
1612
+ }
1613
+
1592
1614
// tomlEncodeKey encodes a key - returning same string if it does not need quoting,
1593
1615
// otherwise return it quoted; returns empty key as ”
1594
1616
func tomlEncodeKey (s string ) string {
@@ -1980,6 +2002,209 @@ func builtinManifestJSONEx(i *interpreter, arguments []value) (value, error) {
1980
2002
return makeValueString (finalString ), nil
1981
2003
}
1982
2004
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
+
1983
2208
func builtinExtVar (i * interpreter , name value ) (value , error ) {
1984
2209
str , err := i .getString (name )
1985
2210
if err != nil {
@@ -2097,12 +2322,12 @@ func builtinAvg(i *interpreter, arrv value) (value, error) {
2097
2322
if err != nil {
2098
2323
return nil , err
2099
2324
}
2100
-
2325
+
2101
2326
len := float64 (arr .length ())
2102
2327
if len == 0 {
2103
2328
return nil , i .Error ("Cannot calculate average of an empty array." )
2104
2329
}
2105
-
2330
+
2106
2331
sumValue , err := builtinSum (i , arrv )
2107
2332
if err != nil {
2108
2333
return nil , err
@@ -2112,7 +2337,7 @@ func builtinAvg(i *interpreter, arrv value) (value, error) {
2112
2337
return nil , err
2113
2338
}
2114
2339
2115
- avg := sum .value / len
2340
+ avg := sum .value / len
2116
2341
return makeValueNumber (avg ), nil
2117
2342
}
2118
2343
@@ -2459,6 +2684,7 @@ var funcBuiltins = buildBuiltinMap([]builtin{
2459
2684
& unaryBuiltin {name : "extVar" , function : builtinExtVar , params : ast.Identifiers {"x" }},
2460
2685
& unaryBuiltin {name : "length" , function : builtinLength , params : ast.Identifiers {"x" }},
2461
2686
& unaryBuiltin {name : "toString" , function : builtinToString , params : ast.Identifiers {"a" }},
2687
+ & unaryBuiltin {name : "escapeStringJson" , function : builtinEscapeStringJson , params : ast.Identifiers {"str_" }},
2462
2688
& binaryBuiltin {name : "trace" , function : builtinTrace , params : ast.Identifiers {"str" , "rest" }},
2463
2689
& binaryBuiltin {name : "makeArray" , function : builtinMakeArray , params : ast.Identifiers {"sz" , "func" }},
2464
2690
& binaryBuiltin {name : "flatMap" , function : builtinFlatMap , params : ast.Identifiers {"func" , "arr" }},
@@ -2524,6 +2750,11 @@ var funcBuiltins = buildBuiltinMap([]builtin{
2524
2750
{name : "newline" , defaultValue : & valueFlatString {value : []rune ("\n " )}},
2525
2751
{name : "key_val_sep" , defaultValue : & valueFlatString {value : []rune (": " )}}}},
2526
2752
& 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
+ }},
2527
2758
& unaryBuiltin {name : "base64" , function : builtinBase64 , params : ast.Identifiers {"input" }},
2528
2759
& unaryBuiltin {name : "encodeUTF8" , function : builtinEncodeUTF8 , params : ast.Identifiers {"str" }},
2529
2760
& unaryBuiltin {name : "decodeUTF8" , function : builtinDecodeUTF8 , params : ast.Identifiers {"arr" }},
0 commit comments