Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add 'default' value in case of presence of a variable #291

Merged
merged 1 commit into from Jul 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
45 changes: 36 additions & 9 deletions template/template.go
Expand Up @@ -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<escaped>%s)|(?P<named>%s)|{(?P<braced>%s)}|(?P<invalid>))",
Expand Down Expand Up @@ -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) {
Expand All @@ -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, ":?"):
Expand All @@ -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
Expand All @@ -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) {
Expand All @@ -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 {
Expand Down
62 changes: 62 additions & 0 deletions template/template_test.go
Expand Up @@ -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)
Expand Down Expand Up @@ -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",
Comment on lines +192 to +193
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Omg 😵

(nice test case, env var substitution just makes my head hurt 😂)

},
}

for _, tc := range testCases {
Expand Down Expand Up @@ -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
Expand Down