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>
This commit is contained in:
parent
0b0fc106dc
commit
ced66f22d6
@ -7,6 +7,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/cli/internal/lazyregexp"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -14,11 +16,21 @@ const (
|
|||||||
subst = "[_a-z][_a-z0-9]*(?::?[-?][^}]*)?"
|
subst = "[_a-z][_a-z0-9]*(?::?[-?][^}]*)?"
|
||||||
)
|
)
|
||||||
|
|
||||||
var defaultPattern = regexp.MustCompile(fmt.Sprintf(
|
var defaultPattern = lazyregexp.New(fmt.Sprintf(
|
||||||
"%s(?i:(?P<escaped>%s)|(?P<named>%s)|{(?P<braced>%s)}|(?P<invalid>))",
|
"%s(?i:(?P<escaped>%s)|(?P<named>%s)|{(?P<braced>%s)}|(?P<invalid>))",
|
||||||
delimiter, delimiter, subst, subst,
|
delimiter, delimiter, subst, subst,
|
||||||
))
|
))
|
||||||
|
|
||||||
|
// regexper is an internal interface to allow passing a [lazyregexp.Regexp]
|
||||||
|
// in places where a custom ("regular") [regexp.Regexp] is accepted. It defines
|
||||||
|
// only the methods we currently use.
|
||||||
|
type regexper interface {
|
||||||
|
FindAllStringSubmatch(s string, n int) [][]string
|
||||||
|
FindStringSubmatch(s string) []string
|
||||||
|
ReplaceAllStringFunc(src string, repl func(string) string) string
|
||||||
|
SubexpNames() []string
|
||||||
|
}
|
||||||
|
|
||||||
// DefaultSubstituteFuncs contains the default SubstituteFunc used by the docker cli
|
// DefaultSubstituteFuncs contains the default SubstituteFunc used by the docker cli
|
||||||
var DefaultSubstituteFuncs = []SubstituteFunc{
|
var DefaultSubstituteFuncs = []SubstituteFunc{
|
||||||
softDefault,
|
softDefault,
|
||||||
@ -51,10 +63,16 @@ type SubstituteFunc func(string, Mapping) (string, bool, error)
|
|||||||
// SubstituteWith substitutes variables in the string with their values.
|
// SubstituteWith substitutes variables in the string with their values.
|
||||||
// It accepts additional substitute function.
|
// It accepts additional substitute function.
|
||||||
func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, subsFuncs ...SubstituteFunc) (string, error) {
|
func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, subsFuncs ...SubstituteFunc) (string, error) {
|
||||||
|
return substituteWith(template, mapping, pattern, subsFuncs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubstituteWith substitutes variables in the string with their values.
|
||||||
|
// It accepts additional substitute function.
|
||||||
|
func substituteWith(template string, mapping Mapping, pattern regexper, subsFuncs ...SubstituteFunc) (string, error) {
|
||||||
var err error
|
var err error
|
||||||
result := pattern.ReplaceAllStringFunc(template, func(substring string) string {
|
result := pattern.ReplaceAllStringFunc(template, func(substring string) string {
|
||||||
matches := pattern.FindStringSubmatch(substring)
|
matches := pattern.FindStringSubmatch(substring)
|
||||||
groups := matchGroups(matches, pattern)
|
groups := matchGroups(matches, defaultPattern)
|
||||||
if escaped := groups["escaped"]; escaped != "" {
|
if escaped := groups["escaped"]; escaped != "" {
|
||||||
return escaped
|
return escaped
|
||||||
}
|
}
|
||||||
@ -93,19 +111,23 @@ func SubstituteWith(template string, mapping Mapping, pattern *regexp.Regexp, su
|
|||||||
|
|
||||||
// Substitute variables in the string with their values
|
// Substitute variables in the string with their values
|
||||||
func Substitute(template string, mapping Mapping) (string, error) {
|
func Substitute(template string, mapping Mapping) (string, error) {
|
||||||
return SubstituteWith(template, mapping, defaultPattern, DefaultSubstituteFuncs...)
|
return substituteWith(template, mapping, defaultPattern, DefaultSubstituteFuncs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtractVariables returns a map of all the variables defined in the specified
|
// ExtractVariables returns a map of all the variables defined in the specified
|
||||||
// composefile (dict representation) and their default value if any.
|
// composefile (dict representation) and their default value if any.
|
||||||
func ExtractVariables(configDict map[string]any, pattern *regexp.Regexp) map[string]string {
|
func ExtractVariables(configDict map[string]any, pattern *regexp.Regexp) map[string]string {
|
||||||
|
return extractVariables(configDict, pattern)
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractVariables(configDict map[string]any, pattern regexper) map[string]string {
|
||||||
if pattern == nil {
|
if pattern == nil {
|
||||||
pattern = defaultPattern
|
pattern = defaultPattern
|
||||||
}
|
}
|
||||||
return recurseExtract(configDict, pattern)
|
return recurseExtract(configDict, pattern)
|
||||||
}
|
}
|
||||||
|
|
||||||
func recurseExtract(value any, pattern *regexp.Regexp) map[string]string {
|
func recurseExtract(value any, pattern regexper) map[string]string {
|
||||||
m := map[string]string{}
|
m := map[string]string{}
|
||||||
|
|
||||||
switch val := value.(type) {
|
switch val := value.(type) {
|
||||||
@ -141,7 +163,7 @@ type extractedValue struct {
|
|||||||
value string
|
value string
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractVariable(value any, pattern *regexp.Regexp) ([]extractedValue, bool) {
|
func extractVariable(value any, pattern regexper) ([]extractedValue, bool) {
|
||||||
sValue, ok := value.(string)
|
sValue, ok := value.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return []extractedValue{}, false
|
return []extractedValue{}, false
|
||||||
@ -227,7 +249,7 @@ func withRequired(substitution string, mapping Mapping, sep string, valid func(s
|
|||||||
return value, true, nil
|
return value, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchGroups(matches []string, pattern *regexp.Regexp) map[string]string {
|
func matchGroups(matches []string, pattern regexper) map[string]string {
|
||||||
groups := make(map[string]string)
|
groups := make(map[string]string)
|
||||||
for i, name := range pattern.SubexpNames()[1:] {
|
for i, name := range pattern.SubexpNames()[1:] {
|
||||||
groups[name] = matches[i+1]
|
groups[name] = matches[i+1]
|
||||||
|
@ -169,15 +169,15 @@ func TestSubstituteWithCustomFunc(t *testing.T) {
|
|||||||
return value, true, nil
|
return value, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := SubstituteWith("ok ${FOO}", defaultMapping, defaultPattern, errIsMissing)
|
result, err := substituteWith("ok ${FOO}", defaultMapping, defaultPattern, errIsMissing)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Check(t, is.Equal("ok first", result))
|
assert.Check(t, is.Equal("ok first", result))
|
||||||
|
|
||||||
result, err = SubstituteWith("ok ${BAR}", defaultMapping, defaultPattern, errIsMissing)
|
result, err = substituteWith("ok ${BAR}", defaultMapping, defaultPattern, errIsMissing)
|
||||||
assert.NilError(t, err)
|
assert.NilError(t, err)
|
||||||
assert.Check(t, is.Equal("ok ", result))
|
assert.Check(t, is.Equal("ok ", result))
|
||||||
|
|
||||||
_, err = SubstituteWith("ok ${NOTHERE}", defaultMapping, defaultPattern, errIsMissing)
|
_, err = substituteWith("ok ${NOTHERE}", defaultMapping, defaultPattern, errIsMissing)
|
||||||
assert.Check(t, is.ErrorContains(err, "required variable"))
|
assert.Check(t, is.ErrorContains(err, "required variable"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -278,7 +278,7 @@ func TestExtractVariables(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
actual := ExtractVariables(tc.dict, defaultPattern)
|
actual := extractVariables(tc.dict, defaultPattern)
|
||||||
assert.Check(t, is.DeepEqual(actual, tc.expected))
|
assert.Check(t, is.DeepEqual(actual, tc.expected))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user