From 9374e1987c63e16ea1247bc75e517c5fc9a168c5 Mon Sep 17 00:00:00 2001 From: Inhere Date: Thu, 2 Mar 2023 19:52:40 +0800 Subject: [PATCH] :sparkles: feat(str/textutil): text.VarReplacer support string-map as replace context - support replace var only with left mark - update and enhance replace logic --- strutil/convert.go | 2 ++ strutil/strutil.go | 4 ++-- strutil/textutil/textutil.go | 7 ++++++ strutil/textutil/textutil_test.go | 40 +++++++++++++++++++++++++++++-- strutil/textutil/var_replacer.go | 25 +++++++++++++++---- 5 files changed, 70 insertions(+), 8 deletions(-) diff --git a/strutil/convert.go b/strutil/convert.go index 7eb7e1fd5..9d98f459e 100644 --- a/strutil/convert.go +++ b/strutil/convert.go @@ -41,6 +41,8 @@ var ( func Quote(s string) string { return strconv.Quote(s) } // Unquote remove start and end quotes by single-quote or double-quote +// +// tip: strconv.Unquote cannot unquote single-quote func Unquote(s string) string { ln := len(s) if ln < 2 { diff --git a/strutil/strutil.go b/strutil/strutil.go index a3125fd3a..1e964fda7 100644 --- a/strutil/strutil.go +++ b/strutil/strutil.go @@ -19,11 +19,11 @@ func OrCond(cond bool, s1, s2 string) string { } // OrElse return s OR nv(new-value) on s is empty -func OrElse(s, newVal string) string { +func OrElse(s, orVal string) string { if s != "" { return s } - return newVal + return orVal } // OrHandle return fn(s) on s is not empty. diff --git a/strutil/textutil/textutil.go b/strutil/textutil/textutil.go index 284f43aff..3ccd9db27 100644 --- a/strutil/textutil/textutil.go +++ b/strutil/textutil/textutil.go @@ -17,6 +17,13 @@ func ReplaceVars(text string, vars map[string]any, format string) string { return NewVarReplacer(format).Replace(text, vars) } +// RenderSMap by regex replace given tpl vars. +// +// If format is empty, will use {const defaultVarFormat} +func RenderSMap(text string, vars map[string]string, format string) string { + return NewVarReplacer(format).RenderSimple(text, vars) +} + // IsMatchAll keywords in the give text string. // // TIP: can use ^ for exclude match. diff --git a/strutil/textutil/textutil_test.go b/strutil/textutil/textutil_test.go index 2f9a728b3..28dc3f9b1 100644 --- a/strutil/textutil/textutil_test.go +++ b/strutil/textutil/textutil_test.go @@ -9,7 +9,6 @@ import ( ) func TestReplaceVars(t *testing.T) { - format := "" tplVars := map[string]any{ "name": "inhere", "key_01": "inhere", @@ -33,7 +32,7 @@ func TestReplaceVars(t *testing.T) { for i, tt := range tests { t.Run(strutil.JoinAny(" ", "case", i), func(t *testing.T) { - if got := textutil.ReplaceVars(tt.tplText, tplVars, format); got != tt.want { + if got := textutil.ReplaceVars(tt.tplText, tplVars, ""); got != tt.want { t.Errorf("ReplaceVars() = %v, want = %v", got, tt.want) } }) @@ -41,9 +40,46 @@ func TestReplaceVars(t *testing.T) { // custom format assert.Equal(t, "hi inhere", textutil.ReplaceVars("hi {$name}", tplVars, "{$,}")) + assert.Equal(t, "hi inhere age is 230", textutil.ReplaceVars("hi $name age is $info.age", tplVars, "$,")) assert.Equal(t, "hi {$name}", textutil.ReplaceVars("hi {$name}", nil, "{$,}")) } +func TestRenderSMap(t *testing.T) { + tplVars := map[string]string{ + "name": "inhere", + "age": "234", + "key_01": "inhere", + "key-02": "inhere", + } + + tests := []struct { + tplText string + want string + }{ + {"hi inhere", "hi inhere"}, + {"hi {{name}}", "hi inhere"}, + {"hi {{ name}}", "hi inhere"}, + {"hi {{name }}", "hi inhere"}, + {"hi {{ name }}", "hi inhere"}, + {"hi {{ key_01 }}", "hi inhere"}, + {"hi {{ key-02 }}", "hi inhere"}, + } + + for i, tt := range tests { + t.Run(strutil.JoinAny(" ", "case", i), func(t *testing.T) { + if got := textutil.RenderSMap(tt.tplText, tplVars, ""); got != tt.want { + t.Errorf("RenderSMap() = %v, want = %v", got, tt.want) + } + }) + } + + // custom format + assert.Equal(t, "hi inhere", textutil.RenderSMap("hi {$name}", tplVars, "{$,}")) + assert.Equal(t, "hi inhere age is 234", textutil.RenderSMap("hi $name age is $age", tplVars, "$,")) + assert.Equal(t, "hi inhere age is 234.", textutil.RenderSMap("hi $name age is $age.", tplVars, "$,")) + assert.Equal(t, "hi {$name}", textutil.RenderSMap("hi {$name}", nil, "{$,}")) +} + 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 80982db49..0446d867f 100644 --- a/strutil/textutil/var_replacer.go +++ b/strutil/textutil/var_replacer.go @@ -35,25 +35,42 @@ func (r *VarReplacer) WithFormat(format string) *VarReplacer { func (r *VarReplacer) Init() *VarReplacer { if !r.init { r.lLen, r.rLen = len(r.Left), len(r.Right) - r.varReg = regexp.MustCompile(regexp.QuoteMeta(r.Left) + `([\w\s.-]+)` + regexp.QuoteMeta(r.Right)) + if r.Right != "" { + r.varReg = regexp.MustCompile(regexp.QuoteMeta(r.Left) + `([\w\s.-]+)` + regexp.QuoteMeta(r.Right)) + } else { + // no right tag. eg: $name, $user.age + // r.varReg = regexp.MustCompile(regexp.QuoteMeta(r.Left) + `([\w.-]+)`) + r.varReg = regexp.MustCompile(regexp.QuoteMeta(r.Left) + `(\w[\w-]*(?:\.[\w-]+)*)`) + } } return r } -// Replace vars in the text contents +// 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 } - r.Init() - varMap := make(map[string]string, len(tplVars)*2) maputil.FlatWithFunc(tplVars, func(path string, val reflect.Value) { varMap[path] = strutil.QuietString(val.Interface()) }) + return r.Init().doReplace(s, varMap) +} + +// RenderSimple string-map vars in the text contents +func (r *VarReplacer) RenderSimple(s string, varMap map[string]string) string { + if len(varMap) == 0 || !strings.Contains(s, r.Left) { + return s + } + return r.Init().doReplace(s, varMap) +} + +// Replace string-map vars in the text contents +func (r *VarReplacer) doReplace(s string, varMap map[string]string) string { 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 {