Skip to content

Commit 0f4a387

Browse files
authored
Merge pull request #286 from compose-spec/refactor-interpolation-symbol-choice
Refactor interpolation symbols choice algorithm.
2 parents 7be7b11 + 2ea3f17 commit 0f4a387

File tree

2 files changed

+88
-43
lines changed

2 files changed

+88
-43
lines changed

template/template.go

Lines changed: 56 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package template
1919
import (
2020
"fmt"
2121
"regexp"
22+
"sort"
2223
"strings"
2324

2425
"github.com/sirupsen/logrus"
@@ -60,10 +61,12 @@ type SubstituteFunc func(string, Mapping) (string, bool, error)
6061
// SubstituteWith substitute variables in the string with their values.
6162
// It accepts additional substitute function.
6263
func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, subsFuncs ...SubstituteFunc) (string, error) {
64+
var err error
65+
6366
if len(subsFuncs) == 0 {
64-
subsFuncs = getDefaultSortedSubstitutionFunctions(template)
67+
_, subsFunc := getSubstitutionFunctionForTemplate(template)
68+
subsFuncs = []SubstituteFunc{subsFunc}
6569
}
66-
var err error
6770
result := pattern.ReplaceAllStringFunc(template, func(substring string) string {
6871
closingBraceIndex := getFirstBraceClosingIndex(substring)
6972
rest := ""
@@ -121,23 +124,31 @@ func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, su
121124
return result, err
122125
}
123126

124-
func getDefaultSortedSubstitutionFunctions(template string, fns ...SubstituteFunc) []SubstituteFunc {
125-
hyphenIndex := strings.IndexByte(template, '-')
126-
questionIndex := strings.IndexByte(template, '?')
127-
if hyphenIndex < 0 || hyphenIndex > questionIndex {
128-
return []SubstituteFunc{
129-
requiredNonEmpty,
130-
required,
131-
softDefault,
132-
hardDefault,
133-
}
134-
}
135-
return []SubstituteFunc{
136-
softDefault,
137-
hardDefault,
138-
requiredNonEmpty,
139-
required,
127+
func getSubstitutionFunctionForTemplate(template string) (string, SubstituteFunc) {
128+
interpolationMapping := []struct {
129+
string
130+
SubstituteFunc
131+
}{
132+
{":?", requiredErrorWhenEmptyOrUnset},
133+
{"?", requiredErrorWhenUnset},
134+
{":-", defaultWhenEmptyOrUnset},
135+
{"-", defaultWhenUnset},
136+
{":+", defaultWhenNotEmpty},
137+
{"+", defaultWhenSet},
140138
}
139+
sort.Slice(interpolationMapping, func(i, j int) bool {
140+
idxI := strings.Index(template, interpolationMapping[i].string)
141+
idxJ := strings.Index(template, interpolationMapping[j].string)
142+
if idxI < 0 {
143+
return false
144+
}
145+
if idxJ < 0 {
146+
return true
147+
}
148+
return idxI < idxJ
149+
})
150+
151+
return interpolationMapping[0].string, interpolationMapping[0].SubstituteFunc
141152
}
142153

143154
func getFirstBraceClosingIndex(s string) int {
@@ -252,26 +263,36 @@ func extractVariable(value interface{}, pattern *regexp.Regexp) ([]Variable, boo
252263
}
253264

254265
// Soft default (fall back if unset or empty)
255-
func softDefault(substitution string, mapping Mapping) (string, bool, error) {
256-
sep := ":-"
257-
if !strings.Contains(substitution, sep) {
258-
return "", false, nil
259-
}
260-
name, defaultValue := partition(substitution, sep)
261-
defaultValue, err := Substitute(defaultValue, mapping)
262-
if err != nil {
263-
return "", false, err
264-
}
265-
value, ok := mapping(name)
266-
if !ok || value == "" {
267-
return defaultValue, true, nil
268-
}
269-
return value, true, nil
266+
func defaultWhenEmptyOrUnset(substitution string, mapping Mapping) (string, bool, error) {
267+
return withDefaultWhenAbsence(substitution, mapping, true)
270268
}
271269

272270
// Hard default (fall back if-and-only-if empty)
273-
func hardDefault(substitution string, mapping Mapping) (string, bool, error) {
271+
func defaultWhenUnset(substitution string, mapping Mapping) (string, bool, error) {
272+
return withDefaultWhenAbsence(substitution, mapping, false)
273+
}
274+
275+
func defaultWhenNotEmpty(substitution string, mapping Mapping) (string, bool, error) {
276+
return "", false, nil // TODO Implement ":+"
277+
}
278+
279+
func defaultWhenSet(substitution string, mapping Mapping) (string, bool, error) {
280+
return "", false, nil // TODO Implement "+"
281+
}
282+
283+
func requiredErrorWhenEmptyOrUnset(substitution string, mapping Mapping) (string, bool, error) {
284+
return withRequired(substitution, mapping, ":?", func(v string) bool { return v != "" })
285+
}
286+
287+
func requiredErrorWhenUnset(substitution string, mapping Mapping) (string, bool, error) {
288+
return withRequired(substitution, mapping, "?", func(_ string) bool { return true })
289+
}
290+
291+
func withDefaultWhenAbsence(substitution string, mapping Mapping, emptyOrUnset bool) (string, bool, error) {
274292
sep := "-"
293+
if emptyOrUnset {
294+
sep = ":-"
295+
}
275296
if !strings.Contains(substitution, sep) {
276297
return "", false, nil
277298
}
@@ -281,20 +302,12 @@ func hardDefault(substitution string, mapping Mapping) (string, bool, error) {
281302
return "", false, err
282303
}
283304
value, ok := mapping(name)
284-
if !ok {
305+
if !ok || (emptyOrUnset && value == "") {
285306
return defaultValue, true, nil
286307
}
287308
return value, true, nil
288309
}
289310

290-
func requiredNonEmpty(substitution string, mapping Mapping) (string, bool, error) {
291-
return withRequired(substitution, mapping, ":?", func(v string) bool { return v != "" })
292-
}
293-
294-
func required(substitution string, mapping Mapping) (string, bool, error) {
295-
return withRequired(substitution, mapping, "?", func(_ string) bool { return true })
296-
}
297-
298311
func withRequired(substitution string, mapping Mapping, sep string, valid func(string) bool) (string, bool, error) {
299312
if !strings.Contains(substitution, sep) {
300313
return "", false, nil

template/template_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,3 +407,35 @@ func TestExtractVariables(t *testing.T) {
407407
})
408408
}
409409
}
410+
411+
func TestSubstitutionFunctionChoice(t *testing.T) {
412+
testcases := []struct {
413+
name string
414+
input string
415+
symbol string
416+
}{
417+
{"Error when EMPTY or UNSET", "VARNAME:?val?ue", ":?"},
418+
{"Error when UNSET 1", "VARNAME?val:?ue", "?"},
419+
{"Error when UNSET 2", "VARNAME?va-lu+e:?e", "?"},
420+
{"Error when UNSET 3", "VARNAME?va+lu-e:?e", "?"},
421+
422+
{"Default when EMPTY or UNSET", "VARNAME:-value", ":-"},
423+
{"Default when UNSET 1", "VARNAME-va:-lu:?e", "-"},
424+
{"Default when UNSET 2", "VARNAME-va+lu?e", "-"},
425+
{"Default when UNSET 3", "VARNAME-va?lu+e", "-"},
426+
427+
{"Default when NOT EMPTY", "VARNAME:+va:?lu:-e", ":+"},
428+
{"Default when SET 1", "VARNAME+va:+lue", "+"},
429+
{"Default when SET 2", "VARNAME+va?lu-e", "+"},
430+
{"Default when SET 3", "VARNAME+va-lu?e", "+"},
431+
}
432+
433+
for _, tc := range testcases {
434+
t.Run(tc.name, func(t *testing.T) {
435+
symbol, _ := getSubstitutionFunctionForTemplate(tc.input)
436+
assert.Equal(t, symbol, tc.symbol,
437+
fmt.Sprintf("Wrong on output for: %s got symbol -> %#v", tc.input, symbol),
438+
)
439+
})
440+
}
441+
}

0 commit comments

Comments
 (0)