From 3c7645cc853247bdcee2984c500f6695b543856e Mon Sep 17 00:00:00 2001 From: psemeniuk Date: Thu, 22 Jun 2017 20:59:45 +0200 Subject: [PATCH 1/3] Add "required" function support in templates; add "defaultEmpty" and "requiredEmpty" functions that treat empty string same as nil --- template.go | 99 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 71 insertions(+), 28 deletions(-) diff --git a/template.go b/template.go index a42d15c..14d9b43 100644 --- a/template.go +++ b/template.go @@ -33,30 +33,70 @@ func contains(item map[string]string, key string) bool { return false } +func _checkDefaultValueArgs(args interface{}) error { + vargs, _ := args.([]interface{}) + + if len(vargs) == 0 { + return fmt.Errorf("default called with no values!") + } + + if len(vargs) < 2 { + return fmt.Errorf("default called with no default value") + } + + if vargs[1] == nil { + return fmt.Errorf("default called with nil default value!") + } + + if _, ok := vargs[1].(string); !ok { + return fmt.Errorf("default is not a string value. hint: surround it w/ double quotes.") + } + + return nil +} + func defaultValue(args ...interface{}) (string, error) { - if len(args) == 0 { - return "", fmt.Errorf("default called with no values!") + if err := _checkDefaultValueArgs(args); err != nil { + return "", err } - if len(args) > 0 { - if args[0] != nil { - return args[0].(string), nil - } + if args[0] != nil { + return args[0].(string), nil } - if len(args) > 1 { - if args[1] == nil { - return "", fmt.Errorf("default called with nil default value!") - } + return args[1].(string), nil +} - if _, ok := args[1].(string); !ok { - return "", fmt.Errorf("default is not a string value. hint: surround it w/ double quotes.") - } +func defaultValueEmpty(args ...interface{}) (string, error) { + if err := _checkDefaultValueArgs(args); err != nil { + return "", err + } + if args[0] == nil || len(args[0].(string)) == 0 { return args[1].(string), nil } - return "", fmt.Errorf("default called with no default value") + return args[0].(string), nil +} + +func requiredValue(val interface{}) (interface{}, error) { + if val != nil { + return val, nil + } + + return nil, fmt.Errorf("Required value is nil") +} + +func requiredValueEmpty(val interface{}) (interface{}, error) { + if _, err := requiredValue(val); err != nil { + return nil, err + } + + if val, ok := val.(string); ok && len(val) == 0 { + return nil, fmt.Errorf("Required value is empty") + } + + return val, nil } func parseUrl(rawurl string) *url.URL { @@ -101,7 +141,7 @@ func loop(args ...int) (<-chan int, error) { case 3: start, stop, step = args[0], args[1], args[2] default: - return nil, fmt.Errorf("wrong number of arguments, expected 1-3"+ + return nil, fmt.Errorf("wrong number of arguments, expected 1-3" + ", but got %d", len(args)) } @@ -117,19 +157,22 @@ func loop(args ...int) (<-chan int, error) { func generateFile(templatePath, destPath string) bool { tmpl := template.New(filepath.Base(templatePath)).Funcs(template.FuncMap{ - "contains": contains, - "exists": exists, - "split": strings.Split, - "replace": strings.Replace, - "default": defaultValue, - "parseUrl": parseUrl, - "atoi": strconv.Atoi, - "add": add, - "isTrue": isTrue, - "lower": strings.ToLower, - "upper": strings.ToUpper, - "jsonQuery": jsonQuery, - "loop": loop, + "contains": contains, + "exists": exists, + "split": strings.Split, + "replace": strings.Replace, + "default": defaultValue, + "defaultEmpty": defaultValueEmpty, + "required": requiredValue, + "requiredEmpty": requiredValueEmpty, + "parseUrl": parseUrl, + "atoi": strconv.Atoi, + "add": add, + "isTrue": isTrue, + "lower": strings.ToLower, + "upper": strings.ToUpper, + "jsonQuery": jsonQuery, + "loop": loop, }) if len(delims) > 0 { From ceea95fb2b227dfb52082dbe5f1ecd37fdd0183c Mon Sep 17 00:00:00 2001 From: psemeniuk Date: Thu, 22 Jun 2017 21:02:58 +0200 Subject: [PATCH 2/3] Add "envSlice" function that can aggregate array passed by enviroment variables --- template.go | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/template.go b/template.go index 14d9b43..d088104 100644 --- a/template.go +++ b/template.go @@ -155,6 +155,51 @@ func loop(args ...int) (<-chan int, error) { return c, nil } +func envSlice(envParamPrefix string) ([]map[string]interface{}, error) { + variables := make(map[string]interface{}) + + biggestIndex := 0 + + for _, i := range os.Environ() { + sep := strings.Index(i, "=") + key := i[0:sep] + val := i[sep + 1:] + + if strings.HasPrefix(key, envParamPrefix) { + suffix := key[strings.LastIndex(key, "_") + 1:] + if num, ok := strconv.Atoi(suffix); ok == nil { + if biggestIndex < num { + biggestIndex = num + } + variables[key] = val + } + } + } + + var result []map[string]interface{} + if biggestIndex > 0 { + result = make([]map[string]interface{}, biggestIndex) + + for i := 0; i < biggestIndex; i++ { + result[i] = make(map[string]interface{}) + } + + for key, value := range variables { + indexStr := key[strings.LastIndex(key, "_") + 1:] + resultIndex, _ := strconv.Atoi(indexStr) + resultIndex -= 1 + resultKey := key[0:strings.LastIndex(key, "_")] + + result[resultIndex][resultKey] = value + } + + } else { + result = make([]map[string]interface{}, 0) + } + + return result, nil +} + func generateFile(templatePath, destPath string) bool { tmpl := template.New(filepath.Base(templatePath)).Funcs(template.FuncMap{ "contains": contains, @@ -173,6 +218,7 @@ func generateFile(templatePath, destPath string) bool { "upper": strings.ToUpper, "jsonQuery": jsonQuery, "loop": loop, + "envSlice": envSlice, }) if len(delims) > 0 { From 91a33bb285e63364ddb5fdc79a88e0b448d90ef1 Mon Sep 17 00:00:00 2001 From: psemeniuk Date: Fri, 23 Jun 2017 19:05:14 +0200 Subject: [PATCH 3/3] Description of new functions --- README.md | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/README.md b/README.md index 6af4985..e94eb07 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,9 @@ variables within a template with `.Env`. There are a few built in functions as well: * `default $var $default` - Returns a default value for one that does not exist. `{{ default .Env.VERSION "0.1.2" }}` + * `defaultEmpty $var $default` - Returns a default value for one that does not exist or is empty in case of string. `{{ default .Env.VERSION "0.1.2" }}` + * `required $val` Returns a value passed as argument or error if value does not exist. `{{ required .Env.VERSION }}` + * `requiredEmpty $val` Returns a value passed as argument or error if value does not exist or is empty in case of string. `{{ requiredEmpty .Env.VERSION }}` * `contains $map $key` - Returns true if a string is within another string * `exists $path` - Determines if a file path exists or not. `{{ exists "/etc/default/myapp" }}` * `split $string $sep` - Splits a string into an array using a separator string. Alias for [`strings.Split`][go.string.Split]. `{{ split .Env.PATH ":" }}` @@ -178,6 +181,7 @@ There are a few built in functions as well: * `upper $value` - Uppercase a string. * `jsonQuery $json $query` - Returns the result of a selection query against a json document. * `loop` - Create for loops. + * `envSlice` - Returns the array filled with maps of environment variables passed as collection (variables that contains numeric suffix) and matched to $prefix `{{ envSlice "VARIABLE_NAME" }}` ### jsonQuery @@ -224,6 +228,34 @@ i = {{ $i }} {{ end }} ``` +### envSlice + +`envSlice` can be useful if we want to pass multiple values of the same property by environment variables. It can be also used to pass different values with same index + +For variables: +``` +EXAMPLEVAR_SUBVAR_1 +EXAMPLEVAR_SUBVAR_2 +EXAMPLEVAR_ANOTHERSUBVAR_1 +EXAMPLEVAR_ANOTHERSUBVAR_2 +``` +Let's assume that we call envSlice with $prefix = "EXAMPLEVAR". Function return an array. Each entry will have a map of variables that starts with `EXAMPLEVAR` and having same index +Returned collection can be used in loop. For example: + +``` +{{ range $i := envSlice ("EXAMPLEVAR") }} + +subvar value: {{ $i.EXAMPLEVAR_SUBVAR }} +anotherSubvar value: {{ $i.EXAMPLEVAR_ANOTHERSUBVAR }} + +--- + +``` + +##### Limitations +Note that the last index of array will be highest index found in variables matched by prefix. So if we define EXAMPLEVAR_1 and EXAMPLEVAR_100 the loop will be iterate 100 times with filled values only in first and last index + + ## License MIT