Skip to content

Commit a317b17

Browse files
authored
Merge pull request #291 from compose-spec/add-presence-var-default
Add 'default' value in case of presence of a variable
2 parents 0f4a387 + 202f25a commit a317b17

File tree

2 files changed

+98
-9
lines changed

2 files changed

+98
-9
lines changed

template/template.go

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import (
2828
var delimiter = "\\$"
2929
var substitutionNamed = "[_a-z][_a-z0-9]*"
3030

31-
var substitutionBraced = "[_a-z][_a-z0-9]*(?::?[-?](.*}|[^}]*))?"
31+
var substitutionBraced = "[_a-z][_a-z0-9]*(?::?[-+?](.*}|[^}]*))?"
3232

3333
var patternString = fmt.Sprintf(
3434
"%s(?i:(?P<escaped>%s)|(?P<named>%s)|{(?P<braced>%s)}|(?P<invalid>))",
@@ -214,9 +214,10 @@ func recurseExtract(value interface{}, pattern *regexp.Regexp) map[string]Variab
214214
}
215215

216216
type Variable struct {
217-
Name string
218-
DefaultValue string
219-
Required bool
217+
Name string
218+
DefaultValue string
219+
PresenceValue string
220+
Required bool
220221
}
221222

222223
func extractVariable(value interface{}, pattern *regexp.Regexp) ([]Variable, bool) {
@@ -240,6 +241,7 @@ func extractVariable(value interface{}, pattern *regexp.Regexp) ([]Variable, boo
240241
}
241242
name := val
242243
var defaultValue string
244+
var presenceValue string
243245
var required bool
244246
switch {
245247
case strings.Contains(val, ":?"):
@@ -252,11 +254,16 @@ func extractVariable(value interface{}, pattern *regexp.Regexp) ([]Variable, boo
252254
name, defaultValue = partition(val, ":-")
253255
case strings.Contains(val, "-"):
254256
name, defaultValue = partition(val, "-")
257+
case strings.Contains(val, ":+"):
258+
name, presenceValue = partition(val, ":+")
259+
case strings.Contains(val, "+"):
260+
name, presenceValue = partition(val, "+")
255261
}
256262
values = append(values, Variable{
257-
Name: name,
258-
DefaultValue: defaultValue,
259-
Required: required,
263+
Name: name,
264+
DefaultValue: defaultValue,
265+
PresenceValue: presenceValue,
266+
Required: required,
260267
})
261268
}
262269
return values, len(values) > 0
@@ -273,11 +280,11 @@ func defaultWhenUnset(substitution string, mapping Mapping) (string, bool, error
273280
}
274281

275282
func defaultWhenNotEmpty(substitution string, mapping Mapping) (string, bool, error) {
276-
return "", false, nil // TODO Implement ":+"
283+
return withDefaultWhenPresence(substitution, mapping, true)
277284
}
278285

279286
func defaultWhenSet(substitution string, mapping Mapping) (string, bool, error) {
280-
return "", false, nil // TODO Implement "+"
287+
return withDefaultWhenPresence(substitution, mapping, false)
281288
}
282289

283290
func requiredErrorWhenEmptyOrUnset(substitution string, mapping Mapping) (string, bool, error) {
@@ -288,6 +295,26 @@ func requiredErrorWhenUnset(substitution string, mapping Mapping) (string, bool,
288295
return withRequired(substitution, mapping, "?", func(_ string) bool { return true })
289296
}
290297

298+
func withDefaultWhenPresence(substitution string, mapping Mapping, notEmpty bool) (string, bool, error) {
299+
sep := "+"
300+
if notEmpty {
301+
sep = ":+"
302+
}
303+
if !strings.Contains(substitution, sep) {
304+
return "", false, nil
305+
}
306+
name, defaultValue := partition(substitution, sep)
307+
defaultValue, err := Substitute(defaultValue, mapping)
308+
if err != nil {
309+
return "", false, err
310+
}
311+
value, ok := mapping(name)
312+
if ok && (!notEmpty || (notEmpty && value != "")) {
313+
return defaultValue, true, nil
314+
}
315+
return value, true, nil
316+
}
317+
291318
func withDefaultWhenAbsence(substitution string, mapping Mapping, emptyOrUnset bool) (string, bool, error) {
292319
sep := "-"
293320
if emptyOrUnset {

template/template_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,42 @@ func TestEmptyValueWithHardDefault(t *testing.T) {
113113
assert.Check(t, is.Equal("ok ", result))
114114
}
115115

116+
func TestPresentValueWithUnset(t *testing.T) {
117+
result, err := Substitute("ok ${UNSET_VAR:+presence_value}", defaultMapping)
118+
assert.NilError(t, err)
119+
assert.Check(t, is.Equal("ok ", result))
120+
}
121+
122+
func TestPresentValueWithUnset2(t *testing.T) {
123+
result, err := Substitute("ok ${UNSET_VAR+presence_value}", defaultMapping)
124+
assert.NilError(t, err)
125+
assert.Check(t, is.Equal("ok ", result))
126+
}
127+
128+
func TestPresentValueWithNonEmpty(t *testing.T) {
129+
result, err := Substitute("ok ${FOO:+presence_value}", defaultMapping)
130+
assert.NilError(t, err)
131+
assert.Check(t, is.Equal("ok presence_value", result))
132+
}
133+
134+
func TestPresentValueAndNonEmptyWithNonEmpty(t *testing.T) {
135+
result, err := Substitute("ok ${FOO+presence_value}", defaultMapping)
136+
assert.NilError(t, err)
137+
assert.Check(t, is.Equal("ok presence_value", result))
138+
}
139+
140+
func TestPresentValueWithSet(t *testing.T) {
141+
result, err := Substitute("ok ${BAR+presence_value}", defaultMapping)
142+
assert.NilError(t, err)
143+
assert.Check(t, is.Equal("ok presence_value", result))
144+
}
145+
146+
func TestPresentValueAndNotEmptyWithSet(t *testing.T) {
147+
result, err := Substitute("ok ${BAR:+presence_value}", defaultMapping)
148+
assert.NilError(t, err)
149+
assert.Check(t, is.Equal("ok ", result))
150+
}
151+
116152
func TestNonAlphanumericDefault(t *testing.T) {
117153
result, err := Substitute("ok ${BAR:-/non:-alphanumeric}", defaultMapping)
118154
assert.NilError(t, err)
@@ -148,6 +184,14 @@ func TestDefaultsWithNestedExpansion(t *testing.T) {
148184
template: "ok ${BAR:-${FOO} ${FOO}}",
149185
expected: "ok first first",
150186
},
187+
{
188+
template: "ok ${BAR+$FOO}",
189+
expected: "ok first",
190+
},
191+
{
192+
template: "ok ${BAR+$FOO ${FOO:+second}}",
193+
expected: "ok first second",
194+
},
151195
}
152196

153197
for _, tc := range testCases {
@@ -398,6 +442,24 @@ func TestExtractVariables(t *testing.T) {
398442
"project": {Name: "project", DefaultValue: "cli"},
399443
},
400444
},
445+
{
446+
name: "presence-value-nonEmpty",
447+
dict: map[string]interface{}{
448+
"foo": "${bar:+foo}",
449+
},
450+
expected: map[string]Variable{
451+
"bar": {Name: "bar", PresenceValue: "foo"},
452+
},
453+
},
454+
{
455+
name: "presence-value",
456+
dict: map[string]interface{}{
457+
"foo": "${bar+foo}",
458+
},
459+
expected: map[string]Variable{
460+
"bar": {Name: "bar", PresenceValue: "foo"},
461+
},
462+
},
401463
}
402464
for _, tc := range testCases {
403465
tc := tc

0 commit comments

Comments
 (0)