Skip to content

Instantly share code, notes, and snippets.

@RohitRox
Last active January 18, 2019 07:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save RohitRox/977f6f66e96e7b20ac81a0a2ade92654 to your computer and use it in GitHub Desktop.
Save RohitRox/977f6f66e96e7b20ac81a0a2ade92654 to your computer and use it in GitHub Desktop.
Golang Templating
package main
import (
"encoding/json"
"fmt"
"regexp"
"strconv"
)
func main() {
template := `{
"ApplicationLogger": {
"GlobalLogLevel": ${GlobalLogLevel},
},
"InstrumentationNewRelic": {
"Enabled": ${NewRelicEnabled},
"License": ${NewRelicKey}
},
"IntegerAttr": ${IntegerAttr},
"MissingAdditionalProp": ${MissingAdditionalProp}
}`
exp := `\${([a-zA-Z0-9_]+)}`
re := regexp.MustCompile(exp)
configJSON := `{
"GlobalLogLevel" : "INFO",
"NewRelicEnabled" : true,
"NewRelicKey" : "xxx-xxx-xxx",
"IntegerAttr" : 50,
"AdditionalAttr" : "whatever",
"AnArray": ["val1", "val2"]
}`
configs := make(map[string]interface{})
err := json.Unmarshal([]byte(configJSON), &configs)
if err != nil {
fmt.Printf("JSON Parse Error: %s\n", err)
return
}
fv := func(s string) string {
subMat := re.FindStringSubmatch(s)
token := subMat[1]
configProp := configs[token]
switch configProp.(type) {
case string:
return strconv.Quote(configProp.(string))
case bool:
return fmt.Sprintf("%v", configProp)
case int, int8, int16, int32, int64, float32, float64:
return fmt.Sprintf("%v", configProp)
// TODO
// case []interface{}:
default:
return strconv.Quote("")
}
}
template = re.ReplaceAllStringFunc(template, fv)
fmt.Println(template)
// output
// {
// "ApplicationLogger": {
// "GlobalLogLevel": "INFO",
// },
// "InstrumentationNewRelic": {
// "Enabled": true,
// "License": "xxx-xxx-xxx"
// },
// "IntegerAttr": 50,
// "MissingAdditionalProp": ""
// }
}
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"text/template"
)
func main() {
configJSON := `{
"GlobalLogLevel" : "INFO",
"NewRelicEnabled" : true,
"NewRelicKey" : "xxx-xxx-xxx",
"IntegerAttr" : 50,
"AdditionalAttr" : "whatever",
"AnArray": ["val1", "val2"]
}`
configs := make(map[string]interface{})
err := json.Unmarshal([]byte(configJSON), &configs)
if err != nil {
fmt.Printf("JSON Parse Error: %s\n", err)
return
}
configTemplate := `{
"ApplicationLogger": {
"GlobalLogLevel": "{{ .GlobalLogLevel }}",
},
"InstrumentationNewRelic": {
"Enabled": {{ .NewRelicEnabled }},
"License": "{{ .NewRelicKey }}"
},
"IntegerAttr": {{ .IntegerAttr }}
}`
tmpl, err := template.New("ConfigTemplate").Option("missingkey=error").Parse(configTemplate)
if err != nil {
log.Fatal(err)
}
err = tmpl.Execute(os.Stdout, configs)
if err != nil {
log.Fatal(err)
}
}
@RohitRox
Copy link
Author

RohitRox commented Jan 17, 2019

With Regex:

  • we can specify our own cursor (used ${AttrName} in the example)
  • we can handle the data type and do mappings in our own way
  • we can handle missing values in our own way
  • increases developer code complexity with template parsing and generation
  • yet to see how to handle complext data types like array

With Text/Template:

  • Go's core library, well tested and optimized
  • Need to follow specified cursor, a period '.' and curlys, {{ .AttrName }}
  • The lib allows programmatic template access and programs inside the template if needed (we may not be using any of that to keep templates simple)
  • Handles data types fairly well, arrays are still tricky and have not tested
  • minimal developer code complexity with templating algorithm
  • Missing keys can be <no value> or can be errored out. An arbirtary default value cannot be set.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment