github.com/coveo/gotemplate@v2.7.7+incompatible/template/extra_data.go (about) 1 package template 2 3 import ( 4 "fmt" 5 "reflect" 6 "strconv" 7 "strings" 8 "unicode/utf8" 9 10 "github.com/coveo/gotemplate/collections" 11 "github.com/coveo/gotemplate/hcl" 12 "github.com/coveo/gotemplate/json" 13 "github.com/coveo/gotemplate/utils" 14 "github.com/coveo/gotemplate/xml" 15 "github.com/coveo/gotemplate/yaml" 16 ) 17 18 const ( 19 dataBase = "Data Manipulation" 20 dataConversion = "Data Conversion" 21 ) 22 23 var dataFuncsBase = dictionary{ 24 "String": toStringClass, 25 "append": addElements, 26 "array": array, 27 "bool": strconv.ParseBool, 28 "char": toChar, 29 "contains": contains, 30 "content": content, 31 "dict": createDict, 32 "extract": extract, 33 "get": get, 34 "hasKey": hasKey, 35 "initial": initial, 36 "intersect": intersect, 37 "isNil": func(value interface{}) bool { return value == nil }, 38 "isSet": func(value interface{}) bool { return value != nil }, 39 "isZero": isZero, 40 "key": key, 41 "keys": keys, 42 "lenc": utf8.RuneCountInString, 43 "list": collections.NewList, 44 "merge": merge, 45 "omit": omit, 46 "pick": pick, 47 "pickv": pickv, 48 "pluck": pluck, 49 "prepend": prepend, 50 "rest": rest, 51 "reverse": reverse, 52 "safeIndex": safeIndex, 53 "set": set, 54 "slice": slice, 55 "string": toString, 56 "undef": collections.IfUndef, 57 "unique": unique, 58 "union": union, 59 "unset": unset, 60 "values": values, 61 "without": without, 62 } 63 64 var dataFuncsConversion = dictionary{ 65 "toBash": collections.ToBash, 66 "toHcl": toHCL, 67 "toInternalHcl": toInternalHCL, 68 "toJson": toJSON, 69 "toPrettyHcl": toPrettyHCL, 70 "toPrettyJson": toPrettyJSON, 71 "toPrettyTFVars": toPrettyTFVars, 72 "toQuotedHcl": toQuotedHCL, 73 "toQuotedJson": toQuotedJSON, 74 "toQuotedTFVars": toQuotedTFVars, 75 "toTFVars": toTFVars, 76 "toYaml": toYAML, 77 //"toXml": toXML, 78 } 79 80 var dataFuncsArgs = arguments{ 81 "append": {"list", "elements"}, 82 "array": {"value"}, 83 "bool": {"str"}, 84 "char": {"value"}, 85 "contains": {"list", "elements"}, 86 "content": {"keymap"}, 87 "data": {"data", "context"}, 88 "extract": {"source", "indexes"}, 89 "get": {"map", "key", "default"}, 90 "hasKey": {"dictionary", "key"}, 91 "hcl": {"hcl", "context"}, 92 "initial": {"list"}, 93 "intersect": {"list", "elements"}, 94 "json": {"json", "context"}, 95 "key": {"value"}, 96 "keys": {"dictionary"}, 97 "lenc": {"str"}, 98 "merge": {"destination", "sources"}, 99 "omit": {"dict", "keys"}, 100 "pick": {"dict", "keys"}, 101 "pickv": {"dict", "message", "keys"}, 102 "pluck": {"key", "dictionaries"}, 103 "prepend": {"list", "elements"}, 104 "rest": {"list"}, 105 "reverse": {"list"}, 106 "safeIndex": {"value", "index", "default"}, 107 "set": {"dict", "key", "value"}, 108 "slice": {"value", "args"}, 109 "string": {"value"}, 110 "String": {"value"}, 111 "toBash": {"value"}, 112 "toHcl": {"value"}, 113 "toInternalHcl": {"value"}, 114 "toJson": {"value"}, 115 "toPrettyHcl": {"value"}, 116 "toPrettyJson": {"value"}, 117 "toPrettyTFVars": {"value"}, 118 "toQuotedHcl": {"value"}, 119 "toQuotedJson": {"value"}, 120 "toQuotedTFVars": {"value"}, 121 "toTFVars": {"value"}, 122 "toYaml": {"value"}, 123 "undef": {"default", "values"}, 124 "unique": {"list"}, 125 "union": {"list", "elements"}, 126 "unset": {"dictionary", "key"}, 127 "without": {"list", "elements"}, 128 "xml": {"yaml", "context"}, 129 "yaml": {"yaml", "context"}, 130 } 131 132 var dataFuncsAliases = aliases{ 133 "append": {"push"}, 134 "contains": {"has"}, 135 "data": {"DATA", "fromData", "fromDATA"}, 136 "dict": {"dictionary"}, 137 "hcl": {"HCL", "fromHcl", "fromHCL", "tfvars", "fromTFVars", "TFVARS", "fromTFVARS"}, 138 "isNil": {"isNull"}, 139 "isZero": {"isEmpty"}, 140 "json": {"JSON", "fromJson", "fromJSON"}, 141 "lenc": {"nbChars"}, 142 "list": {"tuple"}, 143 "toHcl": {"toHCL"}, 144 "toInternalHcl": {"toInternalHCL", "toIHCL", "toIHcl"}, 145 "toJson": {"toJSON"}, 146 "toPrettyHcl": {"toPrettyHCL"}, 147 "toPrettyJson": {"toPrettyJSON"}, 148 "toPrettyXml": {"toPrettyXML"}, 149 "toQuotedHcl": {"toQuotedHCL"}, 150 "toQuotedJson": {"toQuotedJSON"}, 151 "toXml": {"toXML"}, 152 "toYaml": {"toYAML"}, 153 "undef": {"ifUndef"}, 154 "unique": {"uniq"}, 155 "unset": {"delete", "remove"}, 156 "xml": {"XML", "fromXml", "fromXML"}, 157 "yaml": {"YAML", "fromYaml", "fromYAML"}, 158 } 159 160 var dataFuncsHelp = descriptions{ 161 "String": "Returns a String class object that allows invoking standard string operations as method.", 162 "append": "Append new items to an existing list, creating a new list.", 163 "array": "Ensures that the supplied argument is an array (if it is already an array/slice, there is no change, if not, the argument is replaced by []interface{} with a single value).", 164 "bool": "Converts the `string` into boolean value (`string` must be `True`, `true`, `TRUE`, `1` or `False`, `false`, `FALSE`, `0`)", 165 "char": "Returns the character corresponging to the supplied integer value", 166 "contains": "Test to see if a list has a particular elements.", 167 "content": "Returns the content of a single element map (used to retrieve content in a declaration like `value \"name\" { a = 1 b = 3}`)", 168 "data": "Tries to convert the supplied data string into data structure (Go spec). It will try to convert HCL, YAML and JSON format. If context is omitted, default context is used.", 169 "dict": "Returns a new dictionary from a list of pairs (key, value).", 170 "extract": "Extracts values from a slice or a map, indexes could be either integers for slice or strings for maps", 171 "get": "Returns the value associated with the supplied map, key and map could be inverted for convenience (i.e. when using piping mode)", 172 "hasKey": "Returns true if the dictionary contains the specified key.", 173 "hcl": "Converts the supplied hcl string into data structure (Go spec). If context is omitted, default context is used.", 174 "initial": "Returns but the last element. ", 175 "intersect": "Returns a list that is the intersection of the list and all arguments (removing duplicates).", 176 "isNil": "Returns true if the supplied value is nil.", 177 "isSet": "Returns true if the supplied value is not nil.", 178 "isZero": "Returns true if the supplied value is false, 0, nil or empty.", 179 "json": "Converts the supplied json string into data structure (Go spec). If context is omitted, default context is used.", 180 "key": "Returns the key name of a single element map (used to retrieve name in a declaration like `value \"name\" { a = 1 b = 3}`)", 181 "keys": "Returns a list of all of the keys in a dict (in alphabetical order).", 182 "lenc": "Returns the number of actual character in a string.", 183 "list": "Returns a generic list from the supplied arguments.", 184 "merge": "Merges two or more dictionaries into one, giving precedence to the dest dictionary.", 185 "omit": "Returns a new dict with all the keys that do not match the given keys.", 186 "pick": "Selects just the given keys out of a dictionary, creating a new dict.", 187 "pickv": "Same as pick, but returns an error message if there are intruders in supplied dictionary.", 188 "pluck": "Extracts a list of values matching the supplied key from a list of dictionary.", 189 "prepend": "Push elements onto the front of a list, creating a new list.", 190 "rest": "Gets the tail of the list (everything but the first item)", 191 "reverse": "Produces a new list with the reversed elements of the given list.", 192 "safeIndex": "Returns the element at index position or default if index is outside bounds.", 193 "set": "Adds the value to the supplied map using key as identifier.", 194 "slice": "Returns a slice of the supplied object (equivalent to object[from:to]).", 195 "string": "Converts the supplied value into its string representation.", 196 "toBash": "Converts the supplied value to bash compatible representation.", 197 "toHcl": "Converts the supplied value to compact HCL representation.", 198 "toInternalHcl": "Converts the supplied value to compact HCL representation used inside outer HCL definition.", 199 "toJson": "Converts the supplied value to compact JSON representation.", 200 "toPrettyHcl": "Converts the supplied value to pretty HCL representation.", 201 "toPrettyJson": "Converts the supplied value to pretty JSON representation.", 202 "toPrettyTFVars": "Converts the supplied value to pretty HCL representation (without multiple map declarations).", 203 "toPrettyXml": "Converts the supplied value to pretty XML representation.", 204 "toQuotedHcl": "Converts the supplied value to compact quoted HCL representation.", 205 "toQuotedJson": "Converts the supplied value to compact quoted JSON representation.", 206 "toQuotedTFVars": "Converts the supplied value to compact HCL representation (without multiple map declarations).", 207 "toTFVars": "Converts the supplied value to compact HCL representation (without multiple map declarations).", 208 "toXml": "Converts the supplied value to XML representation.", 209 "toYaml": "Converts the supplied value to YAML representation.", 210 "undef": "Returns the default value if value is not set, alias `undef` (differs from Sprig `default` function as empty value such as 0, false, \"\" are not considered as unset).", 211 "union": "Returns a list that is the union of the list and all arguments (removing duplicates).", 212 "unique": "Generates a list with all of the duplicates removed.", 213 "unset": "Removes an element from a dictionary.", 214 "without": "Filters items out of a list.", 215 "xml": "Converts the supplied xml string into data structure (Go spec). If context is omitted, default context is used.", 216 "yaml": "Converts the supplied yaml string into data structure (Go spec). If context is omitted, default context is used.", 217 } 218 219 func (t *Template) addDataFuncs() { 220 options := FuncOptions{ 221 FuncHelp: dataFuncsHelp, 222 FuncArgs: dataFuncsArgs, 223 FuncAliases: dataFuncsAliases, 224 } 225 t.AddFunctions(dataFuncsBase, dataBase, options) 226 t.AddFunctions(dataFuncsConversion, dataConversion, options) 227 t.AddFunctions(dictionary{ 228 "data": t.dataConverter, 229 "hcl": t.hclConverter, 230 "json": t.jsonConverter, 231 //"xml": t.xmlConverter, 232 "yaml": t.yamlConverter, 233 }, dataConversion, options) 234 t.optionsEnabled[Data] = true 235 } 236 237 func toChar(value interface{}) (r interface{}, err error) { 238 defer func() { err = trapError(err, recover()) }() 239 return process(value, func(a interface{}) interface{} { 240 return string(toInt(a)) 241 }) 242 } 243 244 func toString(s interface{}) string { return fmt.Sprint(s) } 245 func toStringClass(s interface{}) utils.String { return utils.String(toString(s)) } 246 247 func toHCL(v interface{}) (string, error) { 248 output, err := hcl.Marshal(v) 249 return string(output), err 250 } 251 252 func toInternalHCL(v interface{}) (string, error) { 253 output, err := hcl.MarshalInternal(v) 254 return string(output), err 255 } 256 257 func toPrettyHCL(v interface{}) (string, error) { 258 output, err := hcl.MarshalIndent(v, "", " ") 259 return string(output), err 260 } 261 262 func toQuotedHCL(v interface{}) (string, error) { 263 output, err := hcl.Marshal(v) 264 result := fmt.Sprintf("%q", output) 265 return result[1 : len(result)-1], err 266 } 267 268 func toTFVars(v interface{}) (string, error) { 269 output, err := hcl.MarshalTFVars(v) 270 return string(output), err 271 } 272 273 func toPrettyTFVars(v interface{}) (string, error) { 274 output, err := hcl.MarshalTFVarsIndent(v, "", " ") 275 return string(output), err 276 } 277 278 func toQuotedTFVars(v interface{}) (string, error) { 279 output, err := hcl.MarshalTFVars(v) 280 result := fmt.Sprintf("%q", output) 281 return result[1 : len(result)-1], err 282 } 283 284 func toXML(v interface{}) (string, error) { 285 output, err := xml.Marshal(v) 286 return string(output), err 287 } 288 289 func toYAML(v interface{}) (string, error) { 290 output, err := yaml.Marshal(v) 291 return string(output), err 292 } 293 294 func toJSON(v interface{}) (string, error) { 295 output, err := json.Marshal(v) 296 return string(output), err 297 } 298 299 func toPrettyJSON(v interface{}) (string, error) { 300 output, err := json.MarshalIndent(v, "", " ") 301 return string(output), err 302 } 303 304 func toQuotedJSON(v interface{}) (string, error) { 305 output, err := json.Marshal(v) 306 result := fmt.Sprintf("%q", output) 307 return result[1 : len(result)-1], err 308 } 309 310 func array(value interface{}) interface{} { 311 if value == nil { 312 return value 313 } 314 switch reflect.TypeOf(value).Kind() { 315 case reflect.Slice, reflect.Array: 316 return value 317 default: 318 return []interface{}{value} 319 } 320 } 321 322 func get(arg1, arg2 interface{}, defValue ...interface{}) (interface{}, error) { 323 // In pipe execution, the map is often the last parameter, but we also support to 324 // put the map as the first parameter. 325 var result interface{} 326 if dict, err := collections.TryAsDictionary(arg1); err == nil { 327 result = dict.Get(arg2) 328 } else if dict, err = collections.TryAsDictionary(arg2); err == nil { 329 result = dict.Get(arg1) 330 } else { 331 return nil, fmt.Errorf("Must supply dictionary object") 332 } 333 if result == nil { 334 switch len(defValue) { 335 case 0: 336 break 337 case 1: 338 result = defValue[0] 339 default: 340 result = defValue 341 } 342 } 343 return result, nil 344 } 345 346 func hasKey(arg1, arg2 interface{}) (interface{}, error) { 347 // In pipe execution, the map is often the last parameter, but we also support to 348 // put the map as the first parameter. 349 if dict, err := collections.TryAsDictionary(arg1); err == nil { 350 return dict.Has(arg2), nil 351 } else if dict, err = collections.TryAsDictionary(arg2); err == nil { 352 return dict.Has(arg1), nil 353 } else { 354 return nil, fmt.Errorf("Must supply dictionary object") 355 } 356 } 357 358 func set(arg1, arg2, arg3 interface{}) (string, error) { 359 // In pipe execution, the map is often the last parameter, but we also support to 360 // put the map as the first parameter. 361 if dict, err := collections.TryAsDictionary(arg1); err == nil { 362 dict.Set(arg2, arg3) 363 } else if dict, err = collections.TryAsDictionary(arg3); err == nil { 364 dict.Set(arg1, arg2) 365 } else { 366 return "", fmt.Errorf("Must supply dictionary object") 367 } 368 return "", nil 369 } 370 371 func unset(arg1, arg2 interface{}) (string, error) { 372 // In pipe execution, the map is often the last parameter, but we also support to 373 // put the map as the first parameter. 374 if dict, err := collections.TryAsDictionary(arg1); err == nil { 375 dict.Delete(arg2) 376 } else if dict, err = collections.TryAsDictionary(arg2); err == nil { 377 dict.Delete(arg1) 378 } else { 379 return "", fmt.Errorf("Must supply dictionary object") 380 } 381 return "", nil 382 } 383 384 func merge(target iDictionary, dict iDictionary, otherDicts ...iDictionary) iDictionary { 385 return target.Merge(dict, otherDicts...) 386 } 387 388 func key(v interface{}) (interface{}, error) { 389 key, _, err := getSingleMapElement(v) 390 return key, err 391 } 392 393 func content(v interface{}) (interface{}, error) { 394 _, value, err := getSingleMapElement(v) 395 return value, err 396 } 397 398 type marshaler func(interface{}) ([]byte, error) 399 type unMarshaler func([]byte, interface{}) error 400 401 // Internal function used to actually convert the supplied string and apply a conversion function over it to get a go map 402 func (t Template) converter(from unMarshaler, content string, sourceWithError bool, context ...interface{}) (result interface{}, err error) { 403 if err = from([]byte(content), &result); err != nil && sourceWithError { 404 source := "\n" 405 for i, line := range collections.SplitLines(content) { 406 source += fmt.Sprintf("%4d %s\n", i+1, line) 407 } 408 err = fmt.Errorf("%s\n%v", source, err) 409 } 410 return 411 } 412 413 // Apply a converter to the result of the template execution of the supplied string 414 func (t Template) templateConverter(to marshaler, from unMarshaler, source interface{}, context ...interface{}) (result interface{}, err error) { 415 if source == nil { 416 return nil, nil 417 } 418 if reflect.TypeOf(source).Kind() != reflect.String { 419 if source, err = to(source); err != nil { 420 return 421 } 422 source = string(source.([]byte)) 423 } 424 425 var content string 426 if content, _, err = t.runTemplate(fmt.Sprint(source), context...); err == nil { 427 result, err = t.converter(from, content, true, context...) 428 } 429 return 430 } 431 432 func (t Template) xmlConverter(source interface{}, context ...interface{}) (interface{}, error) { 433 return t.templateConverter(xml.Marshal, xml.Unmarshal, source, context...) 434 } 435 436 func (t Template) yamlConverter(source interface{}, context ...interface{}) (interface{}, error) { 437 return t.templateConverter(yaml.Marshal, yaml.Unmarshal, source, context...) 438 } 439 440 func (t Template) jsonConverter(source interface{}, context ...interface{}) (interface{}, error) { 441 return t.templateConverter(json.Marshal, json.Unmarshal, source, context...) 442 } 443 444 func (t Template) hclConverter(source interface{}, context ...interface{}) (result interface{}, err error) { 445 return t.templateConverter(hcl.Marshal, hcl.Unmarshal, source, context...) 446 } 447 448 func (t Template) dataConverter(source interface{}, context ...interface{}) (result interface{}, err error) { 449 return t.templateConverter( 450 func(in interface{}) ([]byte, error) { return []byte(fmt.Sprint(in)), nil }, 451 func(bs []byte, out interface{}) error { return collections.ConvertData(string(bs), out) }, 452 source, context...) 453 } 454 455 func pick(dict iDictionary, keys ...interface{}) iDictionary { 456 return dict.Clone(keys...) 457 } 458 459 func omit(dict iDictionary, key interface{}, otherKeys ...interface{}) iDictionary { 460 return dict.Omit(key, otherKeys...) 461 } 462 463 func pickv(dict iDictionary, message string, key interface{}, otherKeys ...interface{}) (interface{}, error) { 464 o := dict.Omit(key, otherKeys...) 465 466 if o.Len() > 0 { 467 over := strings.Join(toStrings(o.GetKeys()), ", ") 468 if strings.Contains(message, "%v") { 469 message = fmt.Sprintf(message, over) 470 } else { 471 message = iif(message == "", "Unwanted values", message).(string) 472 message = fmt.Sprintf("%s %s", message, over) 473 } 474 return nil, fmt.Errorf(message) 475 } 476 return pick(dict, append(otherKeys, key)), nil 477 } 478 479 func keys(dict iDictionary) iList { return dict.GetKeys() } 480 func values(dict iDictionary) iList { return dict.GetValues() } 481 482 func createDict(v ...interface{}) (iDictionary, error) { 483 if len(v)%2 != 0 { 484 return nil, fmt.Errorf("Must supply even number of arguments (keypair)") 485 } 486 487 result := collections.CreateDictionary(len(v) / 2) 488 for i := 0; i < len(v); i += 2 { 489 result.Set(v[i], v[i+1]) 490 } 491 return result, nil 492 } 493 494 func pluck(key interface{}, dicts ...iDictionary) iList { 495 result := collections.CreateList(0, len(dicts)) 496 for i := range dicts { 497 if dicts[i].Has(key) { 498 result.Append(dicts[i].Get(key)) 499 } 500 } 501 return result 502 } 503 504 func rest(list interface{}) (interface{}, error) { return slice(list, 1, -1) } 505 func initial(list interface{}) (interface{}, error) { return slice(list, 0, -2) } 506 507 func addElements(list interface{}, elements ...interface{}) (r iList, err error) { 508 defer func() { err = trapError(err, recover()) }() 509 return collections.AsList(list).Append(elements...), nil 510 } 511 512 func prepend(list interface{}, elements ...interface{}) (r iList, err error) { 513 defer func() { err = trapError(err, recover()) }() 514 return collections.AsList(list).Prepend(elements...), nil 515 } 516 517 func reverse(list interface{}) (r iList, err error) { 518 defer func() { err = trapError(err, recover()) }() 519 return collections.AsList(list).Reverse(), nil 520 } 521 522 func unique(list interface{}) (r iList, err error) { 523 defer func() { err = trapError(err, recover()) }() 524 return collections.AsList(list).Unique(), nil 525 } 526 527 func contains(list interface{}, elements ...interface{}) (r bool, err error) { 528 // Then, the list argument must be a real list of elements 529 defer func() { err = trapError(err, recover()) }() 530 if _, err := collections.TryAsList(list); err != nil && len(elements) == 1 { 531 if _, err2 := collections.TryAsList(elements[0]); err2 != nil { 532 str, subStr := elements[0], list 533 if s, isString := str.(collections.String); isString { 534 // Check if the str argument is of type String 535 str = string(s) 536 } 537 538 if s, isString := str.(string); isString { 539 // Check if the list argument is of type string 540 return strings.Contains(s, fmt.Sprint(subStr)), nil 541 } 542 return false, err 543 } 544 // Sprig has bad documentation and inverse the arguments, so we try to support both modes. 545 list, elements = elements[0], []interface{}{list} 546 } 547 return collections.AsList(list).Contains(elements...), nil 548 } 549 550 func intersect(list interface{}, elements ...interface{}) (r iList, err error) { 551 defer func() { err = trapError(err, recover()) }() 552 return collections.AsList(list).Intersect(elements...), nil 553 } 554 555 func union(list interface{}, elements ...interface{}) (r iList, err error) { 556 defer func() { err = trapError(err, recover()) }() 557 return collections.AsList(list).Union(elements...), nil 558 } 559 560 func without(list interface{}, elements ...interface{}) (r iList, err error) { 561 defer func() { err = trapError(err, recover()) }() 562 return collections.AsList(list).Without(elements...), nil 563 } 564 565 func isZero(value interface{}) bool { 566 return sprigDef(0, value) == 0 567 }