From 202f25a17c1d1521df2fefaa23661333f8c1bb46 Mon Sep 17 00:00:00 2001 From: Ulysses Souza Date: Wed, 27 Jul 2022 12:26:16 +0200 Subject: [PATCH] Add 'default' value in case of presence of a variable Signed-off-by: Ulysses Souza --- template/template.go | 45 ++++++++++++++++++++++------ template/template_test.go | 62 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 9 deletions(-) diff --git a/template/template.go b/template/template.go index e9fd112b..daed7aa0 100644 --- a/template/template.go +++ b/template/template.go @@ -28,7 +28,7 @@ import ( var delimiter = "\\$" var substitutionNamed = "[_a-z][_a-z0-9]*" -var substitutionBraced = "[_a-z][_a-z0-9]*(?::?[-?](.*}|[^}]*))?" +var substitutionBraced = "[_a-z][_a-z0-9]*(?::?[-+?](.*}|[^}]*))?" var patternString = fmt.Sprintf( "%s(?i:(?P%s)|(?P%s)|{(?P%s)}|(?P))", @@ -214,9 +214,10 @@ func recurseExtract(value interface{}, pattern *regexp.Regexp) map[string]Variab } type Variable struct { - Name string - DefaultValue string - Required bool + Name string + DefaultValue string + PresenceValue string + Required bool } func extractVariable(value interface{}, pattern *regexp.Regexp) ([]Variable, bool) { @@ -240,6 +241,7 @@ func extractVariable(value interface{}, pattern *regexp.Regexp) ([]Variable, boo } name := val var defaultValue string + var presenceValue string var required bool switch { case strings.Contains(val, ":?"): @@ -252,11 +254,16 @@ func extractVariable(value interface{}, pattern *regexp.Regexp) ([]Variable, boo name, defaultValue = partition(val, ":-") case strings.Contains(val, "-"): name, defaultValue = partition(val, "-") + case strings.Contains(val, ":+"): + name, presenceValue = partition(val, ":+") + case strings.Contains(val, "+"): + name, presenceValue = partition(val, "+") } values = append(values, Variable{ - Name: name, - DefaultValue: defaultValue, - Required: required, + Name: name, + DefaultValue: defaultValue, + PresenceValue: presenceValue, + Required: required, }) } return values, len(values) > 0 @@ -273,11 +280,11 @@ func defaultWhenUnset(substitution string, mapping Mapping) (string, bool, error } func defaultWhenNotEmpty(substitution string, mapping Mapping) (string, bool, error) { - return "", false, nil // TODO Implement ":+" + return withDefaultWhenPresence(substitution, mapping, true) } func defaultWhenSet(substitution string, mapping Mapping) (string, bool, error) { - return "", false, nil // TODO Implement "+" + return withDefaultWhenPresence(substitution, mapping, false) } func requiredErrorWhenEmptyOrUnset(substitution string, mapping Mapping) (string, bool, error) { @@ -288,6 +295,26 @@ func requiredErrorWhenUnset(substitution string, mapping Mapping) (string, bool, return withRequired(substitution, mapping, "?", func(_ string) bool { return true }) } +func withDefaultWhenPresence(substitution string, mapping Mapping, notEmpty bool) (string, bool, error) { + sep := "+" + if notEmpty { + sep = ":+" + } + if !strings.Contains(substitution, sep) { + return "", false, nil + } + name, defaultValue := partition(substitution, sep) + defaultValue, err := Substitute(defaultValue, mapping) + if err != nil { + return "", false, err + } + value, ok := mapping(name) + if ok && (!notEmpty || (notEmpty && value != "")) { + return defaultValue, true, nil + } + return value, true, nil +} + func withDefaultWhenAbsence(substitution string, mapping Mapping, emptyOrUnset bool) (string, bool, error) { sep := "-" if emptyOrUnset { diff --git a/template/template_test.go b/template/template_test.go index a38b4195..83ebe806 100644 --- a/template/template_test.go +++ b/template/template_test.go @@ -113,6 +113,42 @@ func TestEmptyValueWithHardDefault(t *testing.T) { assert.Check(t, is.Equal("ok ", result)) } +func TestPresentValueWithUnset(t *testing.T) { + result, err := Substitute("ok ${UNSET_VAR:+presence_value}", defaultMapping) + assert.NilError(t, err) + assert.Check(t, is.Equal("ok ", result)) +} + +func TestPresentValueWithUnset2(t *testing.T) { + result, err := Substitute("ok ${UNSET_VAR+presence_value}", defaultMapping) + assert.NilError(t, err) + assert.Check(t, is.Equal("ok ", result)) +} + +func TestPresentValueWithNonEmpty(t *testing.T) { + result, err := Substitute("ok ${FOO:+presence_value}", defaultMapping) + assert.NilError(t, err) + assert.Check(t, is.Equal("ok presence_value", result)) +} + +func TestPresentValueAndNonEmptyWithNonEmpty(t *testing.T) { + result, err := Substitute("ok ${FOO+presence_value}", defaultMapping) + assert.NilError(t, err) + assert.Check(t, is.Equal("ok presence_value", result)) +} + +func TestPresentValueWithSet(t *testing.T) { + result, err := Substitute("ok ${BAR+presence_value}", defaultMapping) + assert.NilError(t, err) + assert.Check(t, is.Equal("ok presence_value", result)) +} + +func TestPresentValueAndNotEmptyWithSet(t *testing.T) { + result, err := Substitute("ok ${BAR:+presence_value}", defaultMapping) + assert.NilError(t, err) + assert.Check(t, is.Equal("ok ", result)) +} + func TestNonAlphanumericDefault(t *testing.T) { result, err := Substitute("ok ${BAR:-/non:-alphanumeric}", defaultMapping) assert.NilError(t, err) @@ -148,6 +184,14 @@ func TestDefaultsWithNestedExpansion(t *testing.T) { template: "ok ${BAR:-${FOO} ${FOO}}", expected: "ok first first", }, + { + template: "ok ${BAR+$FOO}", + expected: "ok first", + }, + { + template: "ok ${BAR+$FOO ${FOO:+second}}", + expected: "ok first second", + }, } for _, tc := range testCases { @@ -398,6 +442,24 @@ func TestExtractVariables(t *testing.T) { "project": {Name: "project", DefaultValue: "cli"}, }, }, + { + name: "presence-value-nonEmpty", + dict: map[string]interface{}{ + "foo": "${bar:+foo}", + }, + expected: map[string]Variable{ + "bar": {Name: "bar", PresenceValue: "foo"}, + }, + }, + { + name: "presence-value", + dict: map[string]interface{}{ + "foo": "${bar+foo}", + }, + expected: map[string]Variable{ + "bar": {Name: "bar", PresenceValue: "foo"}, + }, + }, } for _, tc := range testCases { tc := tc