diff --git a/pkg/configutils/variable.go b/pkg/configutils/variable.go index 22e4622..448f6a6 100644 --- a/pkg/configutils/variable.go +++ b/pkg/configutils/variable.go @@ -1,21 +1,30 @@ package configutils import ( + "crypto/sha1" + "crypto/sha256" + "encoding/base64" + "fmt" + stringutil "github.com/iwind/TeaGo/utils/string" + "net/url" "regexp" "strings" "sync" ) // VariableHolder 变量信息存储类型 -type VariableHolder string -type VariableHolders = []interface{} +type VariableHolder struct { + Param string + Modifiers []string +} +type VariableHolders = []any -var variableMapping = map[string][]interface{}{} // source => [holder1, ...] -var variableLocker = sync.RWMutex{} -var regexpNamedVariable = regexp.MustCompile(`\${[@\w.-]+}`) +var variableMapping = map[string][]any{} // source => [holder1, ...] +var variableLocker = &sync.RWMutex{} +var regexpNamedVariable = regexp.MustCompile(`\${[@\w.|-]+}`) var stringBuilderPool = sync.Pool{ - New: func() interface{} { + New: func() any { return &strings.Builder{} }, } @@ -46,7 +55,11 @@ func ParseVariables(source string, replacer func(varName string) (value string)) var h = holders[0] holder, ok := h.(VariableHolder) if ok { - return replacer(string(holder)) + var value = replacer(holder.Param) + if holder.Modifiers != nil { + value = doStringModifiers(value, holder.Modifiers) + } + return replacer(value) } return source } @@ -58,7 +71,11 @@ func ParseVariables(source string, replacer func(varName string) (value string)) for _, h := range holders { holder, ok := h.(VariableHolder) if ok { - builder.WriteString(replacer(string(holder))) + var value = replacer(holder.Param) + if holder.Modifiers != nil { + value = doStringModifiers(value, holder.Modifiers) + } + builder.WriteString(value) } else { builder.Write(h.([]byte)) } @@ -86,11 +103,15 @@ func ParseVariablesFromHolders(holders VariableHolders, replacer func(varName st } // replace - result := strings.Builder{} + var result = strings.Builder{} for _, h := range holders { holder, ok := h.(VariableHolder) if ok { - result.WriteString(replacer(string(holder))) + var value = replacer(holder.Param) + if holder.Modifiers != nil { + value = doStringModifiers(value, holder.Modifiers) + } + result.WriteString(value) } else { result.Write(h.([]byte)) } @@ -100,12 +121,24 @@ func ParseVariablesFromHolders(holders VariableHolders, replacer func(varName st // ParseHolders 分析占位 func ParseHolders(source string) (holders VariableHolders) { - indexes := regexpNamedVariable.FindAllStringIndex(source, -1) - before := 0 + var indexes = regexpNamedVariable.FindAllStringIndex(source, -1) + var before = 0 for _, loc := range indexes { holders = append(holders, []byte(source[before:loc[0]])) - holder := source[loc[0]+2 : loc[1]-1] - holders = append(holders, VariableHolder(holder)) + var holder = source[loc[0]+2 : loc[1]-1] + + if strings.Contains(holder, "|") { + var holderPieces = strings.Split(holder, "|") + holders = append(holders, VariableHolder{ + Param: holderPieces[0], + Modifiers: holderPieces[1:], + }) + } else { + holders = append(holders, VariableHolder{ + Param: holder, + Modifiers: nil, + }) + } before = loc[1] } if before < len(source) { @@ -121,3 +154,36 @@ func HasVariables(source string) bool { } return regexpNamedVariable.MatchString(source) } + +// 执行变量后的修饰符 +func doStringModifiers(value string, modifiers []string) string { + for _, modifier := range modifiers { + switch modifier { + case "urlEncode": + value = url.QueryEscape(value) + case "urlDecode": + value2, err := url.QueryUnescape(value) + if err == nil { + value = value2 + } + case "base64Encode": + value = base64.StdEncoding.EncodeToString([]byte(value)) + case "base64Decode": + value2, err := base64.StdEncoding.DecodeString(value) + if err == nil { + value = string(value2) + } + case "md5": + value = stringutil.Md5(value) + case "sha1": + value = fmt.Sprintf("%x", sha1.Sum([]byte(value))) + case "sha256": + value = fmt.Sprintf("%x", sha256.Sum256([]byte(value))) + case "toLowerCase": + value = strings.ToLower(value) + case "toUpperCase": + value = strings.ToUpper(value) + } + } + return value +} diff --git a/pkg/configutils/variable_test.go b/pkg/configutils/variable_test.go index 03932f1..1af4ebb 100644 --- a/pkg/configutils/variable_test.go +++ b/pkg/configutils/variable_test.go @@ -1,6 +1,8 @@ -package configutils +package configutils_test import ( + "github.com/TeaOSLab/EdgeCommon/pkg/configutils" + "github.com/iwind/TeaGo/assert" "github.com/iwind/TeaGo/types" "runtime" "strconv" @@ -8,48 +10,128 @@ import ( ) func TestParseVariables(t *testing.T) { + var a = assert.NewAssertion(t) + { - v := ParseVariables("hello, ${name}, world", func(s string) string { + var v = configutils.ParseVariables("hello, ${name}, world", func(s string) string { return "Lu" }) t.Log(v) + a.IsTrue(v == "hello, Lu, world") } { - v := ParseVariables("hello, world", func(s string) string { + var v = configutils.ParseVariables("hello, world", func(s string) string { return "Lu" }) t.Log(v) + a.IsTrue(v == "hello, world") } { - v := ParseVariables("${name}", func(s string) string { + var v = configutils.ParseVariables("${name}", func(s string) string { return "Lu" }) t.Log(v) + a.IsTrue(v == "Lu") } } func TestParseNoVariables(t *testing.T) { for i := 0; i < 2; i++ { - v := ParseVariables("hello, world", func(s string) string { + var v = configutils.ParseVariables("hello, world", func(s string) string { return "Lu" }) t.Log(v) } } +func TestParseVariables_Modifier(t *testing.T) { + t.Log(configutils.ParseVariables("${url|urlEncode}", func(varName string) (value string) { + switch varName { + case "url": + return "/hello/world?a=1" + } + return "${" + varName + "}" + })) + t.Log(configutils.ParseVariables("${url|urlDecode}", func(varName string) (value string) { + switch varName { + case "url": + return "%2Fhello%2Fworld%3Fa%3D1" + } + return "${" + varName + "}" + })) + t.Log(configutils.ParseVariables("${url|urlDecode|urlEncode}", func(varName string) (value string) { + switch varName { + case "url": + return "%2Fhello%2Fworld%3Fa%3D1" + } + return "${" + varName + "}" + })) + t.Log(configutils.ParseVariables("${var|base64Encode}", func(varName string) (value string) { + switch varName { + case "var": + return "123456" + } + return "${" + varName + "}" + })) + t.Log(configutils.ParseVariables("${var|base64Encode|base64Decode}", func(varName string) (value string) { + switch varName { + case "var": + return "123456" + } + return "${" + varName + "}" + })) + t.Log(configutils.ParseVariables("${var|md5}", func(varName string) (value string) { + switch varName { + case "var": + return "123456" + } + return "${" + varName + "}" + })) + t.Log(configutils.ParseVariables("${var|sha1}", func(varName string) (value string) { + switch varName { + case "var": + return "123456" + } + return "${" + varName + "}" + })) + t.Log(configutils.ParseVariables("${var|sha256}", func(varName string) (value string) { + switch varName { + case "var": + return "123456" + } + return "${" + varName + "}" + })) + t.Log(configutils.ParseVariables("${var|toLowerCase}", func(varName string) (value string) { + switch varName { + case "var": + return "ABC" + } + return "${" + varName + "}" + })) + t.Log(configutils.ParseVariables("${var|toUpperCase}", func(varName string) (value string) { + switch varName { + case "var": + return "abc" + } + return "${" + varName + "}" + })) +} + func TestParseHolders(t *testing.T) { - var holders = ParseHolders("hello, ${name}, world") + var holders = configutils.ParseHolders("hello, ${name|urlencode}, world") + t.Log("===holders begin===") for _, h := range holders { t.Log(types.String(h)) } + t.Log("===holders end===") - t.Log("parse result:", ParseVariablesFromHolders(holders, func(s string) string { + t.Log("parse result:", configutils.ParseVariablesFromHolders(holders, func(s string) string { return "[" + s + "]" })) } func BenchmarkParseVariables(b *testing.B) { - _ = ParseVariables("hello, ${name}, ${age}, ${gender}, ${home}, world", func(s string) string { + _ = configutils.ParseVariables("hello, ${name}, ${age}, ${gender}, ${home}, world", func(s string) string { return "Lu" }) @@ -57,7 +139,7 @@ func BenchmarkParseVariables(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { - _ = ParseVariables("hello, ${name}, ${age}, ${gender}, ${home}, world", func(s string) string { + _ = configutils.ParseVariables("hello, ${name}, ${age}, ${gender}, ${home}, world", func(s string) string { return "Lu" }) } @@ -65,10 +147,10 @@ func BenchmarkParseVariables(b *testing.B) { } func BenchmarkParseVariablesFromHolders(b *testing.B) { - var holders = ParseHolders("hello, ${name}, ${age}, ${gender}, ${home}, world") + var holders = configutils.ParseHolders("hello, ${name}, ${age}, ${gender}, ${home}, world") for i := 0; i < b.N; i++ { - _ = ParseVariablesFromHolders(holders, func(s string) string { + _ = configutils.ParseVariablesFromHolders(holders, func(s string) string { return "Lu" }) } @@ -76,7 +158,7 @@ func BenchmarkParseVariablesFromHolders(b *testing.B) { func BenchmarkParseVariablesUnique(b *testing.B) { for i := 0; i < b.N; i++ { - _ = ParseVariables("hello, ${name} "+strconv.Itoa(i), func(s string) string { + _ = configutils.ParseVariables("hello, ${name} "+strconv.Itoa(i%100_000), func(s string) string { return "Lu" }) } @@ -86,7 +168,7 @@ func BenchmarkParseVariablesUnique_Single(b *testing.B) { runtime.GOMAXPROCS(1) for i := 0; i < b.N; i++ { - _ = ParseVariables("${name}", func(s string) string { + _ = configutils.ParseVariables("${name}", func(s string) string { return "Lu" }) } @@ -94,7 +176,7 @@ func BenchmarkParseVariablesUnique_Single(b *testing.B) { func BenchmarkParseNoVariables(b *testing.B) { for i := 0; i < b.N; i++ { - _ = ParseVariables("hello, world", func(s string) string { + _ = configutils.ParseVariables("hello, world", func(s string) string { return "Lu" }) } @@ -102,7 +184,7 @@ func BenchmarkParseNoVariables(b *testing.B) { func BenchmarkParseEmpty(b *testing.B) { for i := 0; i < b.N; i++ { - _ = ParseVariables("", func(s string) string { + _ = configutils.ParseVariables("", func(s string) string { return "Lu" }) }