diff --git a/strutil/check.go b/strutil/check.go index d23473d02..1cb661ff8 100644 --- a/strutil/check.go +++ b/strutil/check.go @@ -113,6 +113,16 @@ func HasSuffix(s string, suffix string) bool { return strings.HasSuffix(s, suffi // IsEndOf alias of the strings.HasSuffix func IsEndOf(s, suffix string) bool { return strings.HasSuffix(s, suffix) } +// HasOneSuffix the string end withs one of the subs +func HasOneSuffix(s string, suffixes []string) bool { + for _, suffix := range suffixes { + if strings.HasSuffix(s, suffix) { + return true + } + } + return false +} + // IsValidUtf8 valid utf8 string check func IsValidUtf8(s string) bool { return utf8.ValidString(s) } diff --git a/strutil/textutil/textutil_test.go b/strutil/textutil/textutil_test.go index 6db53a5c2..8c112db2a 100644 --- a/strutil/textutil/textutil_test.go +++ b/strutil/textutil/textutil_test.go @@ -80,6 +80,25 @@ func TestRenderSMap(t *testing.T) { assert.Equal(t, "hi {$name}", textutil.RenderSMap("hi {$name}", nil, "{$,}")) } +func TestVarReplacer_ParseVars(t *testing.T) { + vp := textutil.NewVarReplacer("") + str := "hi {{ name }}, age {{age}}, age {{age }}" + ss := vp.ParseVars(str) + + assert.NotEmpty(t, ss) + assert.Len(t, ss, 2) + assert.Contains(t, ss, "name") + assert.Contains(t, ss, "age") + + tplVars := map[string]any{ + "name": "inhere", + "age": 234, + } + assert.Equal(t, "hi inhere, age 234, age 234", vp.Render(str, tplVars)) + vp.DisableFlatten() + assert.Equal(t, "hi inhere, age 234, age 234", vp.Render(str, tplVars)) +} + func TestIsMatchAll(t *testing.T) { str := "hi inhere, age is 120" assert.True(t, textutil.IsMatchAll(str, []string{"hi", "inhere"})) diff --git a/strutil/textutil/var_replacer.go b/strutil/textutil/var_replacer.go index 3c5c30a48..60e94a284 100644 --- a/strutil/textutil/var_replacer.go +++ b/strutil/textutil/var_replacer.go @@ -5,6 +5,7 @@ import ( "regexp" "strings" + "github.com/gookit/goutil/arrutil" "github.com/gookit/goutil/internal/comfunc" "github.com/gookit/goutil/maputil" "github.com/gookit/goutil/strutil" @@ -14,20 +15,30 @@ const defaultVarFormat = "{{,}}" // VarReplacer struct type VarReplacer struct { - init bool + init bool + Left, Right string lLen, rLen int + varReg *regexp.Regexp + // flatten sub map in vars + flatSubs bool parseEnv bool - varReg *regexp.Regexp + missVars []string } // NewVarReplacer instance func NewVarReplacer(format string) *VarReplacer { - return (&VarReplacer{}).WithFormat(format) + return (&VarReplacer{flatSubs: true}).WithFormat(format) } -// WithParseEnv custom var template +// DisableFlatten on the input vars map +func (r *VarReplacer) DisableFlatten() *VarReplacer { + r.flatSubs = false + return r +} + +// WithParseEnv on the input vars value func (r *VarReplacer) WithParseEnv() *VarReplacer { r.parseEnv = true return r @@ -56,24 +67,44 @@ func (r *VarReplacer) Init() *VarReplacer { return r } +// ParseVars the text contents and collect vars +func (r *VarReplacer) ParseVars(s string) []string { + ss := arrutil.StringsMap(r.varReg.FindAllString(s, -1), func(val string) string { + return strings.TrimSpace(val[r.lLen : len(val)-r.rLen]) + }) + + return arrutil.Unique(ss) +} + +// Render any-map vars in the text contents +func (r *VarReplacer) Render(s string, tplVars map[string]any) string { + return r.Replace(s, tplVars) +} + // Replace any-map vars in the text contents func (r *VarReplacer) Replace(s string, tplVars map[string]any) string { if len(tplVars) == 0 || !strings.Contains(s, r.Left) { return s } - varMap := make(map[string]string, len(tplVars)*2) - maputil.FlatWithFunc(tplVars, func(path string, val reflect.Value) { - if val.Kind() == reflect.String { - if r.parseEnv { - varMap[path] = comfunc.ParseEnvVar(val.String(), nil) + var varMap map[string]string + + if r.flatSubs { + varMap = make(map[string]string, len(tplVars)*2) + maputil.FlatWithFunc(tplVars, func(path string, val reflect.Value) { + if val.Kind() == reflect.String { + if r.parseEnv { + varMap[path] = comfunc.ParseEnvVar(val.String(), nil) + } else { + varMap[path] = val.String() + } } else { - varMap[path] = val.String() + varMap[path] = strutil.QuietString(val.Interface()) } - } else { - varMap[path] = strutil.QuietString(val.Interface()) - } - }) + }) + } else { + varMap = maputil.ToStringMap(tplVars) + } return r.Init().doReplace(s, varMap) } @@ -98,13 +129,22 @@ func (r *VarReplacer) RenderSimple(s string, varMap map[string]string) string { return r.Init().doReplace(s, varMap) } +// MissVars list +func (r *VarReplacer) MissVars() []string { + return r.missVars +} + // Replace string-map vars in the text contents func (r *VarReplacer) doReplace(s string, varMap map[string]string) string { + r.missVars = make([]string, 0) // clear each replace + return r.varReg.ReplaceAllStringFunc(s, func(sub string) string { varName := strings.TrimSpace(sub[r.lLen : len(sub)-r.rLen]) if val, ok := varMap[varName]; ok { return val } + + r.missVars = append(r.missVars, varName) return sub }) }