Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit ced66f2

Browse files
committedApr 10, 2025
cli/compose/template: use lazyregexp to compile regexes on first use
This package needed an (internal) interface to abstract the lazy-regexp. For this, I split the implementation from the exported implementation; this also revealed that some functions are not used (at least not in our code base), and we could consider deprecating these. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
1 parent 0b0fc10 commit ced66f2

File tree

2 files changed

+32
-10
lines changed

2 files changed

+32
-10
lines changed
 

‎cli/compose/template/template.go

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,30 @@ import (
77
"fmt"
88
"regexp"
99
"strings"
10+
11+
"github.com/docker/cli/internal/lazyregexp"
1012
)
1113

1214
const (
1315
delimiter = "\\$"
1416
subst = "[_a-z][_a-z0-9]*(?::?[-?][^}]*)?"
1517
)
1618

17-
var defaultPattern = regexp.MustCompile(fmt.Sprintf(
19+
var defaultPattern = lazyregexp.New(fmt.Sprintf(
1820
"%s(?i:(?P<escaped>%s)|(?P<named>%s)|{(?P<braced>%s)}|(?P<invalid>))",
1921
delimiter, delimiter, subst, subst,
2022
))
2123

24+
// regexper is an internal interface to allow passing a [lazyregexp.Regexp]
25+
// in places where a custom ("regular") [regexp.Regexp] is accepted. It defines
26+
// only the methods we currently use.
27+
type regexper interface {
28+
FindAllStringSubmatch(s string, n int) [][]string
29+
FindStringSubmatch(s string) []string
30+
ReplaceAllStringFunc(src string, repl func(string) string) string
31+
SubexpNames() []string
32+
}
33+
2234
// DefaultSubstituteFuncs contains the default SubstituteFunc used by the docker cli
2335
var DefaultSubstituteFuncs = []SubstituteFunc{
2436
softDefault,
@@ -51,10 +63,16 @@ type SubstituteFunc func(string, Mapping) (string, bool, error)
5163
// SubstituteWith substitutes variables in the string with their values.
5264
// It accepts additional substitute function.
5365
func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, subsFuncs ...SubstituteFunc) (string, error) {
66+
return substituteWith(template, mapping, pattern, subsFuncs...)
67+
}
68+
69+
// SubstituteWith substitutes variables in the string with their values.
70+
// It accepts additional substitute function.
71+
func substituteWith(template string, mapping Mapping, pattern regexper, subsFuncs ...SubstituteFunc) (string, error) {
5472
var err error
5573
result := pattern.ReplaceAllStringFunc(template, func(substring string) string {
5674
matches := pattern.FindStringSubmatch(substring)
57-
groups := matchGroups(matches, pattern)
75+
groups := matchGroups(matches, defaultPattern)
5876
if escaped := groups["escaped"]; escaped != "" {
5977
return escaped
6078
}
@@ -93,19 +111,23 @@ func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, su
93111

94112
// Substitute variables in the string with their values
95113
func Substitute(template string, mapping Mapping) (string, error) {
96-
return SubstituteWith(template, mapping, defaultPattern, DefaultSubstituteFuncs...)
114+
return substituteWith(template, mapping, defaultPattern, DefaultSubstituteFuncs...)
97115
}
98116

99117
// ExtractVariables returns a map of all the variables defined in the specified
100118
// composefile (dict representation) and their default value if any.
101119
func ExtractVariables(configDict map[string]any, pattern *regexp.Regexp) map[string]string {
120+
return extractVariables(configDict, pattern)
121+
}
122+
123+
func extractVariables(configDict map[string]any, pattern regexper) map[string]string {
102124
if pattern == nil {
103125
pattern = defaultPattern
104126
}
105127
return recurseExtract(configDict, pattern)
106128
}
107129

108-
func recurseExtract(value any, pattern *regexp.Regexp) map[string]string {
130+
func recurseExtract(value any, pattern regexper) map[string]string {
109131
m := map[string]string{}
110132

111133
switch val := value.(type) {
@@ -141,7 +163,7 @@ type extractedValue struct {
141163
value string
142164
}
143165

144-
func extractVariable(value any, pattern *regexp.Regexp) ([]extractedValue, bool) {
166+
func extractVariable(value any, pattern regexper) ([]extractedValue, bool) {
145167
sValue, ok := value.(string)
146168
if !ok {
147169
return []extractedValue{}, false
@@ -227,7 +249,7 @@ func withRequired(substitution string, mapping Mapping, sep string, valid func(s
227249
return value, true, nil
228250
}
229251

230-
func matchGroups(matches []string, pattern *regexp.Regexp) map[string]string {
252+
func matchGroups(matches []string, pattern regexper) map[string]string {
231253
groups := make(map[string]string)
232254
for i, name := range pattern.SubexpNames()[1:] {
233255
groups[name] = matches[i+1]

‎cli/compose/template/template_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -169,15 +169,15 @@ func TestSubstituteWithCustomFunc(t *testing.T) {
169169
return value, true, nil
170170
}
171171

172-
result, err := SubstituteWith("ok ${FOO}", defaultMapping, defaultPattern, errIsMissing)
172+
result, err := substituteWith("ok ${FOO}", defaultMapping, defaultPattern, errIsMissing)
173173
assert.NilError(t, err)
174174
assert.Check(t, is.Equal("ok first", result))
175175

176-
result, err = SubstituteWith("ok ${BAR}", defaultMapping, defaultPattern, errIsMissing)
176+
result, err = substituteWith("ok ${BAR}", defaultMapping, defaultPattern, errIsMissing)
177177
assert.NilError(t, err)
178178
assert.Check(t, is.Equal("ok ", result))
179179

180-
_, err = SubstituteWith("ok ${NOTHERE}", defaultMapping, defaultPattern, errIsMissing)
180+
_, err = substituteWith("ok ${NOTHERE}", defaultMapping, defaultPattern, errIsMissing)
181181
assert.Check(t, is.ErrorContains(err, "required variable"))
182182
}
183183

@@ -278,7 +278,7 @@ func TestExtractVariables(t *testing.T) {
278278
}
279279
for _, tc := range testCases {
280280
t.Run(tc.name, func(t *testing.T) {
281-
actual := ExtractVariables(tc.dict, defaultPattern)
281+
actual := extractVariables(tc.dict, defaultPattern)
282282
assert.Check(t, is.DeepEqual(actual, tc.expected))
283283
})
284284
}

0 commit comments

Comments
 (0)
Please sign in to comment.