github.com/swaros/contxt/module/taskrun@v0.0.0-20240305083542-3dbd4436ac40/varimport.go (about) 1 // Copyright (c) 2020 Thomas Ziegler <thomas.zglr@googlemail.com>. All rights reserved. 2 // 3 // # Licensed under the MIT License 4 // 5 // Permission is hereby granted, free of charge, to any person obtaining a copy 6 // of this software and associated documentation files (the "Software"), to deal 7 // in the Software without restriction, including without limitation the rights 8 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 // copies of the Software, and to permit persons to whom the Software is 10 // furnished to do so, subject to the following conditions: 11 // 12 // The above copyright notice and this permission notice shall be included in all 13 // copies or substantial portions of the Software. 14 // 15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 // SOFTWARE. 22 package taskrun 23 24 import ( 25 "bytes" 26 "encoding/json" 27 "errors" 28 "fmt" 29 30 "os" 31 "path/filepath" 32 "reflect" 33 "regexp" 34 "strconv" 35 "strings" 36 "text/template" 37 38 "github.com/imdario/mergo" 39 "github.com/tidwall/gjson" 40 41 "github.com/swaros/contxt/module/configure" 42 "github.com/swaros/contxt/module/systools" 43 "github.com/swaros/manout" 44 45 "github.com/sirupsen/logrus" 46 47 "github.com/Masterminds/sprig/v3" 48 "github.com/ghodss/yaml" 49 ) 50 51 const ( 52 inlineCmdSep = " " 53 startMark = "#@" 54 inlineMark = "#@-" 55 iterateMark = "#@foreach" 56 endMark = "#@end" 57 fromJSONMark = "#@import-json" 58 fromJSONCmdMark = "#@import-json-exec" 59 parseVarsMark = "#@var" 60 setvarMark = "#@set" 61 setvarInMap = "#@set-in-map" 62 exportToYaml = "#@export-to-yaml" 63 exportToJson = "#@export-to-json" 64 addvarMark = "#@add" 65 equalsMark = "#@if-equals" 66 notEqualsMark = "#@if-not-equals" 67 osCheck = "#@if-os" 68 codeLinePH = "__LINE__" 69 codeKeyPH = "__KEY__" 70 writeVarToFile = "#@var-to-file" 71 ) 72 73 // TryParse to parse a line and set a value depending on the line command 74 func TryParse(script []string, regularScript func(string) (bool, int)) (bool, int, []string) { 75 inIteration := false 76 inIfState := false 77 ifState := true 78 var iterationLines []string 79 var parsedScript []string 80 var iterationCollect gjson.Result 81 for _, line := range script { 82 line = HandlePlaceHolder(line) 83 if len(line) > len(startMark) && line[0:len(startMark)] == startMark { 84 //parts := strings.Split(line, inlineCmdSep) 85 parts := SplitQuoted(line, inlineCmdSep) 86 87 GetLogger().WithField("keywords", parts).Debug("try to parse parts") 88 if len(parts) < 1 { 89 continue 90 } 91 switch parts[0] { 92 93 case osCheck: 94 if !inIfState { 95 if len(parts) == 2 { 96 leftEq := parts[1] 97 rightEq := configure.GetOs() 98 inIfState = true 99 ifState = leftEq == rightEq 100 } else { 101 manout.Error("invalid usage ", equalsMark, " need: str1 str2 ") 102 } 103 } else { 104 manout.Error("invalid usage ", equalsMark, " can not be used in another if") 105 } 106 107 case equalsMark: 108 if !inIfState { 109 if len(parts) == 3 { 110 leftEq := parts[1] 111 rightEq := parts[2] 112 inIfState = true 113 ifState = leftEq == rightEq 114 GetLogger().WithFields(logrus.Fields{"condition": ifState, "left": leftEq, "right": rightEq}).Debug(equalsMark) 115 } else { 116 manout.Error("invalid usage ", equalsMark, " need: str1 str2 (got:", len(parts), ")") 117 } 118 } else { 119 manout.Error("invalid usage ", equalsMark, " can not be used in another if") 120 } 121 122 case notEqualsMark: 123 if !inIfState { 124 if len(parts) == 3 { 125 leftEq := parts[1] 126 rightEq := parts[2] 127 inIfState = true 128 ifState = leftEq != rightEq 129 GetLogger().WithFields(logrus.Fields{"condition": ifState, "left": leftEq, "right": rightEq}).Debug(notEqualsMark) 130 } else { 131 manout.Error("invalid usage ", notEqualsMark, " need: str1 str2 (got:", len(parts), ")") 132 } 133 } else { 134 manout.Error("invalid usage ", equalsMark, " can not be used in another if") 135 } 136 137 case inlineMark: 138 if inIteration { 139 iterationLines = append(iterationLines, strings.Replace(line, inlineMark+" ", "", 4)) 140 GetLogger().WithField("code", iterationLines).Debug("append to subscript") 141 } else { 142 manout.Error("invalid usage", inlineMark, " only valid while in iteration") 143 } 144 145 case fromJSONMark: 146 if len(parts) == 3 { 147 err := AddJSON(parts[1], parts[2]) 148 if err != nil { 149 manout.Error("import from json string failed", parts[2], err) 150 } 151 } else { 152 manout.Error("invalid usage", fromJSONMark, " needs 2 arguments. <keyname> <json-source-string>") 153 } 154 155 case fromJSONCmdMark: 156 if len(parts) >= 3 { 157 returnValue := "" 158 restSlice := parts[2:] 159 keyname := parts[1] 160 cmd := strings.Join(restSlice, " ") 161 GetLogger().WithFields(logrus.Fields{"key": keyname, "cmd": restSlice}).Info("execute for import-json-exec") 162 //GetLogger().WithField("slice", restSlice).Info("execute for import-json-exec") 163 exec, args := GetExecDefaults() 164 execCode, realExitCode, execErr := ExecuteScriptLine(exec, args, cmd, func(output string, e error) bool { 165 returnValue = returnValue + output 166 GetLogger().WithField("cmd-output", output).Info("result of command") 167 return true 168 }, func(proc *os.Process) { 169 GetLogger().WithField("import-json-proc", proc).Trace("import-json-process") 170 }) 171 172 if execErr != nil { 173 GetLogger().WithFields(logrus.Fields{ 174 "intern": execCode, 175 "process-exit": realExitCode, 176 "key": keyname, 177 "cmd": restSlice}).Error("execute for import-json-exec failed") 178 } else { 179 180 err := AddJSON(keyname, returnValue) 181 if err != nil { 182 GetLogger().WithField("error-on-parsing-string", returnValue).Debug("result of command") 183 manout.Error("import from json string failed", err, ' ', systools.StringSubLeft(returnValue, 75)) 184 } 185 } 186 } else { 187 manout.Error("invalid usage", fromJSONCmdMark, " needs 2 arguments at least. <keyname> <bash-command>") 188 } 189 case addvarMark: 190 if len(parts) >= 2 { 191 setKeyname := parts[1] 192 setValue := strings.Join(parts[2:], " ") 193 if ok := AppendToPH(setKeyname, HandlePlaceHolder(setValue)); !ok { 194 manout.Error("variable must exists for add ", addvarMark, " ", setKeyname) 195 } 196 } else { 197 manout.Error("invalid usage", setvarMark, " needs 2 arguments at least. <keyname> <value>") 198 } 199 case setvarMark: 200 if len(parts) >= 2 { 201 setKeyname := parts[1] 202 setValue := strings.Join(parts[2:], " ") 203 SetPH(setKeyname, HandlePlaceHolder(setValue)) 204 } else { 205 manout.Error("invalid usage", setvarMark, " needs 2 arguments at least. <keyname> <value>") 206 } 207 case setvarInMap: 208 if len(parts) >= 3 { 209 mapName := parts[1] 210 path := parts[2] 211 setValue := strings.Join(parts[3:], " ") 212 if err := SetJSONValueByPath(mapName, path, setValue); err != nil { 213 manout.Error(err.Error()) 214 } 215 } else { 216 manout.Error("invalid usage", setvarInMap, " needs 3 arguments at least. <mapName> <json.path> <value>") 217 } 218 case writeVarToFile: 219 if len(parts) == 3 { 220 varName := parts[1] 221 fileName := parts[2] 222 ExportVarToFile(varName, fileName) 223 } else { 224 manout.Error("invalid usage", writeVarToFile, " needs 2 arguments at least. <variable> <filename>") 225 } 226 case exportToJson: 227 if len(parts) == 3 { 228 mapKey := parts[1] 229 varName := parts[2] 230 if exists, newStr := GetDataAsJson(mapKey); exists { 231 SetPH(varName, HandlePlaceHolder(newStr)) 232 } else { 233 manout.Error("map with key ", mapKey, " not exists") 234 } 235 } else { 236 manout.Error("invalid usage", exportToJson, " needs 2 arguments at least. <map-key> <variable>") 237 } 238 case exportToYaml: 239 if len(parts) == 3 { 240 mapKey := parts[1] 241 varName := parts[2] 242 if exists, newStr := GetDataAsYaml(mapKey); exists { 243 SetPH(varName, HandlePlaceHolder(newStr)) 244 } else { 245 manout.Error("map with key ", mapKey, " not exists") 246 } 247 } else { 248 manout.Error("invalid usage", exportToYaml, " needs 2 arguments at least. <map-key> <variable>") 249 } 250 case parseVarsMark: 251 if len(parts) >= 2 { 252 var returnValues []string 253 restSlice := parts[2:] 254 cmd := strings.Join(restSlice, " ") 255 exec, args := GetExecDefaults() 256 internalCode, cmdCode, errorFromCm := ExecuteScriptLine(exec, args, cmd, func(output string, e error) bool { 257 if e == nil { 258 returnValues = append(returnValues, output) 259 } 260 return true 261 262 }, func(proc *os.Process) { 263 GetLogger().WithField(parseVarsMark, proc).Trace("sub process") 264 }) 265 266 if internalCode == systools.ExitOk && errorFromCm == nil && cmdCode == 0 { 267 GetLogger().WithField("values", returnValues).Trace("got values") 268 SetPH(parts[1], HandlePlaceHolder(strings.Join(returnValues, "\n"))) 269 } else { 270 GetLogger().WithFields(logrus.Fields{ 271 "returnCode": cmdCode, 272 "error": errorFromCm.Error, 273 }).Error("subcommand failed.") 274 manout.Error("Subcommand failed", cmd, " ... was used to get json context. ", errorFromCm.Error()) 275 manout.Error("cmd:", exec, " ", args, " ", cmd) 276 } 277 278 } else { 279 manout.Error("invalid usage", parseVarsMark, " needs 2 arguments at least. <varibale-name> <bash-command>") 280 } 281 282 case endMark: 283 284 if inIfState { 285 GetLogger().Debug("IF: DONE") 286 inIfState = false 287 ifState = true 288 } 289 if inIteration { 290 GetLogger().Debug("ITERATION: DONE") 291 inIteration = false 292 abortFound := false 293 returnCode := systools.ExitOk 294 295 iterationCollect.ForEach(func(key gjson.Result, value gjson.Result) bool { 296 var parsedExecLines []string 297 for _, iLine := range iterationLines { 298 iLine = strings.Replace(iLine, codeLinePH, value.String(), 1) 299 iLine = strings.Replace(iLine, codeKeyPH, key.String(), 1) 300 parsedExecLines = append(parsedExecLines, iLine) 301 } 302 GetLogger().WithFields(logrus.Fields{ 303 "key": key, 304 "value": value, 305 "subscript": parsedExecLines, 306 }).Debug("... delegate script") 307 abort, rCode, subs := TryParse(parsedExecLines, regularScript) 308 returnCode = rCode 309 parsedScript = append(parsedScript, subs...) 310 311 if abort { 312 abortFound = true 313 return false 314 } 315 return true 316 }) 317 318 if abortFound { 319 return true, returnCode, parsedScript 320 } 321 } 322 323 case iterateMark: 324 if len(parts) == 3 { 325 impMap, found := GetJSONPathResult(parts[1], parts[2]) 326 if !found { 327 manout.Error("undefined data from path", parts[1], parts[2]) 328 } else { 329 inIteration = true 330 iterationCollect = impMap 331 GetLogger().WithField("data", impMap).Debug("ITERATION: START") 332 } 333 } else { 334 manout.Error("invalid arguments", "#@iterate needs <name-of-import> <path-to-data>") 335 } 336 default: 337 GetLogger().WithField("unknown", parts[0]).Error("there is no command exists") 338 manout.Error("ERROR depending inline macros annotated with "+startMark, " there is no macro defined named ", parts[0]) 339 } 340 } else { 341 parsedScript = append(parsedScript, line) 342 // execute the *real* script lines 343 if ifState { 344 abort, returnCode := regularScript(line) 345 if abort { 346 return true, returnCode, parsedScript 347 } 348 } else { 349 GetLogger().WithField("code", line).Debug("ignored because of if state") 350 } 351 } 352 } 353 GetLogger().WithFields(logrus.Fields{ 354 "parsed": parsedScript, 355 }).Debug("... parsed result") 356 return false, systools.ExitOk, parsedScript 357 } 358 359 func GetArgQuotedEntries(oristr string) ([]string, bool) { 360 var result []string 361 found := false 362 re := regexp.MustCompile(`'[^']+'`) 363 newStrs := re.FindAllString(oristr, -1) 364 for _, s := range newStrs { 365 found = true 366 result = append(result, s) 367 368 } 369 return result, found 370 } 371 372 func SplitQuoted(oristr string, sep string) []string { 373 var result []string 374 var placeHolder map[string]string = make(map[string]string) 375 376 found := false 377 re := regexp.MustCompile(`'[^']+'`) 378 newStrs := re.FindAllString(oristr, -1) 379 i := 0 380 for _, s := range newStrs { 381 pl := "[$" + strconv.Itoa(i) + "]" 382 placeHolder[pl] = strings.ReplaceAll(s, `'`, "") 383 oristr = strings.Replace(oristr, s, pl, 1) 384 found = true 385 i++ 386 } 387 result = strings.Split(oristr, sep) 388 if !found { 389 return result 390 } 391 392 for index, val := range result { 393 if orgStr, fnd := placeHolder[val]; fnd { 394 result[index] = orgStr 395 } 396 } 397 398 return result 399 } 400 401 // YAMLToMap Convert yaml source string into map 402 func YAMLToMap(source string) (map[string]interface{}, error) { 403 jsond, jerr := yaml.YAMLToJSON([]byte(source)) 404 if jerr != nil { 405 return nil, jerr 406 } 407 m := make(map[string]interface{}) 408 if err := json.Unmarshal([]byte(jsond), &m); err != nil { 409 return nil, err 410 } 411 return m, nil 412 } 413 414 func ExportVarToFile(variable string, filename string) error { 415 strData := GetPH(variable) 416 if strData == "" { 417 return errors.New("variable " + variable + " can not be used for export to file. not exists or empty") 418 } 419 f, err := os.Create(filename) 420 if err != nil { 421 return err 422 } 423 defer f.Close() 424 var scopeVars map[string]string // empty but required 425 if _, err2 := f.WriteString(handlePlaceHolder(strData, scopeVars)); err != nil { 426 return err2 427 } 428 429 return nil 430 } 431 432 // ImportYAMLFile imports a yaml file as used for json map 433 func ImportYAMLFile(filename string) (map[string]interface{}, error) { 434 if data, err := parseFileAsTemplateToByte(filename); err == nil { 435 jsond, jerr := yaml.YAMLToJSON(data) 436 if jerr != nil { 437 return nil, jerr 438 } 439 m := make(map[string]interface{}) 440 if err := json.Unmarshal([]byte(jsond), &m); err != nil { 441 return nil, err 442 } 443 if GetLogger().IsLevelEnabled(logrus.TraceLevel) { 444 traceMap(m, filename) 445 } 446 return m, nil 447 } else { 448 return nil, err 449 } 450 } 451 452 // ImportJSONFile imports a json file for reading 453 func ImportJSONFile(fileName string) (map[string]interface{}, error) { 454 455 if data, err := parseFileAsTemplateToByte(fileName); err == nil { 456 m := make(map[string]interface{}) 457 err = json.Unmarshal([]byte(data), &m) 458 if err != nil { 459 return testAndConvertJsonType(data) 460 } 461 if GetLogger().IsLevelEnabled(logrus.TraceLevel) { 462 traceMap(m, fileName) 463 } 464 return m, nil 465 } else { 466 return nil, err 467 } 468 469 } 470 471 // testAndConvertJsonType try to read a json string that might be an []interface{} 472 // if this succeeds then we convert it to an map[string]interface{} 473 // or return the UNmarschal error if this is failing too 474 func testAndConvertJsonType(data []byte) (map[string]interface{}, error) { 475 var m []interface{} 476 convert := make(map[string]interface{}) 477 if err := json.Unmarshal([]byte(data), &m); err == nil { 478 for key, val := range m { 479 keyStr := fmt.Sprintf("%d", key) 480 switch val.(type) { 481 case string, interface{}: 482 convert[keyStr] = val 483 default: 484 return nil, errors.New("unsupported json structure") 485 486 } 487 488 } 489 return convert, err 490 } else { 491 return convert, err 492 } 493 } 494 495 func traceMap(mapShow map[string]interface{}, add string) { 496 for k, v := range mapShow { 497 //mapShow[k] = v 498 //GetLogger().WithField("VAR", v).Trace("imported placeholder from " + add + " " + k) 499 GetLogger().WithFields(logrus.Fields{ 500 "source": add, 501 "key": k, 502 "content": v, 503 }).Trace("imported content") 504 } 505 } 506 507 // MergeVariableMap merges two maps 508 func MergeVariableMap(mapin map[string]interface{}, maporigin map[string]interface{}) map[string]interface{} { 509 if err := mergo.Merge(&maporigin, mapin, mergo.WithOverride); err != nil { 510 manout.Error("FATAL", "error while trying merge map") 511 systools.Exit(systools.ErrorTemplate) 512 } 513 return maporigin 514 } 515 516 // ImportFolders import a list of folders recusiv 517 func ImportFolders(templatePath string, paths ...string) (string, error) { 518 mapOrigin := GetOriginMap() 519 520 template, terr := ImportFileContent(templatePath) 521 if terr != nil { 522 return "", terr 523 } 524 525 for _, path := range paths { 526 path = HandlePlaceHolder(path) 527 GetLogger().WithField("folder", path).Debug("process path") 528 pathMap, parseErr := ImportFolder(path, templatePath) 529 if parseErr != nil { 530 return "", parseErr 531 } 532 mapOrigin = MergeVariableMap(pathMap, mapOrigin) 533 UpdateOriginMap(mapOrigin) 534 } 535 result, herr := HandleJSONMap(template, mapOrigin) 536 if herr != nil { 537 return "", herr 538 } 539 template = result 540 541 return template, nil 542 } 543 544 func parseFileAsTemplateToByte(path string) ([]byte, error) { 545 var data []byte 546 if parsedCnt, err := ParseFileAsTemplate(path); err != nil { 547 return nil, err 548 } else { 549 data = []byte(parsedCnt) 550 return data, nil 551 } 552 553 } 554 555 func ParseFileAsTemplate(path string) (string, error) { 556 path = HandlePlaceHolder(path) // take care about placeholders 557 mapOrigin := GetOriginMap() // load the current maps 558 fileContent, terr := ImportFileContent(path) // load file content as string 559 if terr != nil { 560 return "", terr 561 } 562 563 // parsing as template 564 if result, herr := HandleJSONMap(fileContent, mapOrigin); herr != nil { 565 return "", herr 566 } else { 567 return result, nil 568 } 569 } 570 571 func GetOriginMap() map[string]interface{} { 572 exists, storedData := GetData("CTX_VAR_MAP") 573 if exists { 574 GetLogger().WithField("DATA", storedData).Trace("returning existing Variables map") 575 return storedData 576 } 577 mapOrigin := make(map[string]interface{}) 578 GetLogger().Trace("returning NEW Variables map") 579 return mapOrigin 580 } 581 582 // UpdateOriginMap updates the main templating map with an new one 583 func UpdateOriginMap(mapData map[string]interface{}) { 584 GetLogger().WithField("DATA", mapData).Trace("update variables map") 585 AddData("CTX_VAR_MAP", mapData) 586 } 587 588 // copies the placeholder to the origin map 589 // so there can be used in templates to 590 // this should be done after initilize the application. 591 // but not while runtime 592 func CopyPlaceHolder2Origin() { 593 origin := GetOriginMap() 594 GetPlaceHoldersFnc(func(phKey, phValue string) { 595 origin[phKey] = phValue 596 }) 597 UpdateOriginMap(origin) 598 } 599 600 // ImportFolder reads folder recursiv and reads all .json, .yml and .yaml files 601 func ImportFolder(path string, _ string) (map[string]interface{}, error) { 602 603 //var mapOrigin map[string]interface{} 604 //mapOrigin = make(map[string]interface{}) 605 mapOrigin := GetOriginMap() 606 607 err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error { 608 if err != nil { 609 return err 610 } 611 var jsonMap map[string]interface{} 612 var loaderr error 613 hit := false 614 if !info.IsDir() { 615 var extension = filepath.Ext(path) 616 var basename = filepath.Base(path) 617 if basename == ".contxt.yml" { 618 return nil 619 } 620 switch extension { 621 case ".json": 622 GetLogger().WithField("file", path).Debug("parsing included file (JSON)") 623 jsonMap, loaderr = ImportJSONFile(path) 624 hit = true 625 case ".yaml", ".yml": 626 GetLogger().WithField("file", path).Debug("parsing included file (YAML)") 627 jsonMap, loaderr = ImportYAMLFile(path) 628 hit = true 629 } 630 if loaderr != nil { 631 return loaderr 632 } 633 if hit { 634 GetLogger().WithFields(logrus.Fields{ 635 "origin": mapOrigin, 636 "imported": jsonMap, 637 }).Trace("merged Variable map") 638 mapOrigin = MergeVariableMap(jsonMap, mapOrigin) 639 GetLogger().WithField("result", mapOrigin).Trace("result of merge") 640 } 641 } 642 643 return nil 644 }) 645 return mapOrigin, err 646 } 647 648 // ImportFileContent imports a file and returns content as string 649 func ImportFileContent(filename string) (string, error) { 650 GetLogger().WithField("file", filename).Debug("import file content") 651 data, err := os.ReadFile(filename) 652 if err != nil { 653 fmt.Println("File reading error", err) 654 return "", err 655 } 656 return string(data), nil 657 } 658 659 // HandleJSONMap parsing json content for text/template 660 func HandleJSONMap(tmpl string, m map[string]interface{}) (string, error) { 661 tf := template.FuncMap{ 662 "isInt": func(i interface{}) bool { 663 v := reflect.ValueOf(i) 664 switch v.Kind() { 665 case reflect.Int, reflect.Int8, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64: 666 return true 667 default: 668 return false 669 } 670 }, 671 "isString": func(i interface{}) bool { 672 v := reflect.ValueOf(i) 673 switch v.Kind() { 674 case reflect.String: 675 return true 676 default: 677 return false 678 } 679 }, 680 "isSlice": func(i interface{}) bool { 681 v := reflect.ValueOf(i) 682 switch v.Kind() { 683 case reflect.Slice: 684 return true 685 default: 686 return false 687 } 688 }, 689 "isArray": func(i interface{}) bool { 690 v := reflect.ValueOf(i) 691 switch v.Kind() { 692 case reflect.Array: 693 return true 694 default: 695 return false 696 } 697 }, 698 "isMap": func(i interface{}) bool { 699 v := reflect.ValueOf(i) 700 switch v.Kind() { 701 case reflect.Map: 702 return true 703 default: 704 return false 705 } 706 }, 707 } 708 funcMap := MergeVariableMap(tf, sprig.FuncMap()) 709 tpl := template.New("contxt-map-string-func").Funcs(funcMap) 710 tt, err := tpl.Parse(tmpl) 711 if err != nil { 712 return "", err 713 } 714 out := new(bytes.Buffer) 715 tt.Execute(out, &m) 716 if err != nil { 717 return "", err 718 } 719 return out.String(), nil 720 721 } 722 723 /* 724 func IsList(i interface{}) bool { 725 v := reflect.ValueOf(i).Kind() 726 return v == reflect.Array || v == reflect.Slice 727 } 728 729 func IsNumber(i interface{}) bool { 730 v := reflect.ValueOf(i).Kind() 731 switch v { 732 case reflect.Int, reflect.Int8, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64: 733 return true 734 default: 735 return false 736 } 737 } 738 739 func IsInt(i interface{}) bool { 740 v := reflect.ValueOf(i).Kind() 741 switch v { 742 case reflect.Int, reflect.Int8, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint32, reflect.Uint64: 743 return true 744 default: 745 return false 746 } 747 } 748 749 func IsFloat(i interface{}) bool { 750 v := reflect.ValueOf(i).Kind() 751 return v == reflect.Float32 || v == reflect.Float64 752 } 753 */