Skip to content

Commit

Permalink
Merge pull request #76 from MarilynFranklin/support-nested-variable-e…
Browse files Browse the repository at this point in the history
…xpansion

Support nested variable expansion
  • Loading branch information
ulyssessouza authored Nov 12, 2021
2 parents e9fed32 + 8007e3c commit 8e87de4
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 10 deletions.
32 changes: 22 additions & 10 deletions template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,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 All @@ -35,14 +35,6 @@ var patternString = fmt.Sprintf(

var defaultPattern = regexp.MustCompile(patternString)

// DefaultSubstituteFuncs contains the default SubstituteFunc used by the docker cli
var DefaultSubstituteFuncs = []SubstituteFunc{
softDefault,
hardDefault,
requiredNonEmpty,
required,
}

// InvalidTemplateError is returned when a variable template is not in a valid
// format
type InvalidTemplateError struct {
Expand All @@ -67,6 +59,14 @@ type SubstituteFunc func(string, Mapping) (string, bool, error)
// SubstituteWith substitute variables in the string with their values.
// It accepts additional substitute function.
func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, subsFuncs ...SubstituteFunc) (string, error) {
if len(subsFuncs) == 0 {
subsFuncs = []SubstituteFunc{
softDefault,
hardDefault,
requiredNonEmpty,
required,
}
}
var err error
result := pattern.ReplaceAllStringFunc(template, func(substring string) string {
matches := pattern.FindStringSubmatch(substring)
Expand Down Expand Up @@ -116,7 +116,7 @@ func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, su

// Substitute variables in the string with their values
func Substitute(template string, mapping Mapping) (string, error) {
return SubstituteWith(template, mapping, defaultPattern, DefaultSubstituteFuncs...)
return SubstituteWith(template, mapping, defaultPattern)
}

// ExtractVariables returns a map of all the variables defined in the specified
Expand Down Expand Up @@ -215,6 +215,10 @@ func softDefault(substitution string, mapping Mapping) (string, bool, error) {
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 || value == "" {
return defaultValue, true, nil
Expand All @@ -229,6 +233,10 @@ func hardDefault(substitution string, mapping Mapping) (string, bool, error) {
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 {
return defaultValue, true, nil
Expand All @@ -249,6 +257,10 @@ func withRequired(substitution string, mapping Mapping, sep string, valid func(s
return "", false, nil
}
name, errorMessage := partition(substitution, sep)
errorMessage, err := Substitute(errorMessage, mapping)
if err != nil {
return "", false, err
}
value, ok := mapping(name)
if !ok || !valid(value) {
return "", true, &InvalidTemplateError{
Expand Down
60 changes: 60 additions & 0 deletions template/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,44 @@ func TestNonAlphanumericDefault(t *testing.T) {
assert.Check(t, is.Equal("ok /non:-alphanumeric", result))
}

func TestDefaultsWithNestedExpansion(t *testing.T) {
testCases := []struct {
template string
expected string
}{
{
template: "ok ${UNSET_VAR-$FOO}",
expected: "ok first",
},
{
template: "ok ${UNSET_VAR-${FOO}}",
expected: "ok first",
},
{
template: "ok ${UNSET_VAR-${FOO} ${FOO}}",
expected: "ok first first",
},
{
template: "ok ${BAR:-$FOO}",
expected: "ok first",
},
{
template: "ok ${BAR:-${FOO}}",
expected: "ok first",
},
{
template: "ok ${BAR:-${FOO} ${FOO}}",
expected: "ok first first",
},
}

for _, tc := range testCases {
result, err := Substitute(tc.template, defaultMapping)
assert.NilError(t, err)
assert.Check(t, is.Equal(tc.expected, result))
}
}

func TestMandatoryVariableErrors(t *testing.T) {
testCases := []struct {
template string
Expand Down Expand Up @@ -153,6 +191,28 @@ func TestMandatoryVariableErrors(t *testing.T) {
}
}

func TestMandatoryVariableErrorsWithNestedExpansion(t *testing.T) {
testCases := []struct {
template string
expectedError string
}{
{
template: "not ok ${UNSET_VAR:?Mandatory Variable ${FOO}}",
expectedError: "required variable UNSET_VAR is missing a value: Mandatory Variable first",
},
{
template: "not ok ${UNSET_VAR?Mandatory Variable ${FOO}}",
expectedError: "required variable UNSET_VAR is missing a value: Mandatory Variable first",
},
}

for _, tc := range testCases {
_, err := Substitute(tc.template, defaultMapping)
assert.ErrorContains(t, err, tc.expectedError)
assert.ErrorType(t, err, reflect.TypeOf(&InvalidTemplateError{}))
}
}

func TestDefaultsForMandatoryVariables(t *testing.T) {
testCases := []struct {
template string
Expand Down

0 comments on commit 8e87de4

Please sign in to comment.