Bump github.com/gookit/config/v2 from 2.2.3 to 2.2.4

Bumps [github.com/gookit/config/v2](https://github.com/gookit/config) from 2.2.3 to 2.2.4.
- [Release notes](https://github.com/gookit/config/releases)
- [Commits](https://github.com/gookit/config/compare/v2.2.3...v2.2.4)

---
updated-dependencies:
- dependency-name: github.com/gookit/config/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
This commit is contained in:
dependabot[bot]
2023-11-03 10:26:28 +01:00
committed by Ralf Haferkamp
parent 30784affc4
commit 0df009eae0
141 changed files with 7314 additions and 4044 deletions
+277
View File
@@ -0,0 +1,277 @@
package textutil
import (
"fmt"
"io"
"regexp"
"strings"
"text/template"
"github.com/gookit/goutil/arrutil"
"github.com/gookit/goutil/basefn"
"github.com/gookit/goutil/fsutil"
"github.com/gookit/goutil/reflects"
"github.com/gookit/goutil/structs"
"github.com/gookit/goutil/strutil"
)
// LTemplateOptFn lite template option func
type LTemplateOptFn func(opt *LiteTemplateOpt)
// LiteTemplateOpt template options for LiteTemplate
type LiteTemplateOpt struct {
// func name alias map. eg: {"up_first": "upFirst"}
nameMp structs.Aliases
Funcs template.FuncMap
Left, Right string
ParseDef bool
ParseEnv bool
}
// SetVarFmt custom sets the variable format in template
func (o *LiteTemplateOpt) SetVarFmt(varFmt string) {
o.Left, o.Right = strutil.TrimCut(varFmt, ",")
}
// LiteTemplate implement a simple text template engine.
//
// - support parse template vars
// - support access multi-level map field. eg: {{ user.name }}
// - support parse default value
// - support parse env vars
// - support custom pipeline func handle. eg: {{ name | upper }} {{ name | def:guest }}
//
// NOTE: not support control flow, eg: if/else/for/with
type LiteTemplate struct {
LiteTemplateOpt
vr VarReplacer
// template func map. refer the text/template
//
// Func allow return 1 or 2 values, if return 2 values, the second value is error.
fxs map[string]*reflects.FuncX
}
// NewLiteTemplate instance
func NewLiteTemplate(opFns ...LTemplateOptFn) *LiteTemplate {
st := &LiteTemplate{
fxs: make(map[string]*reflects.FuncX),
// with default options
LiteTemplateOpt: LiteTemplateOpt{
Left: "{{",
Right: "}}",
ParseDef: true,
ParseEnv: true,
},
}
st.vr.RenderFn = st.renderVars
for _, fn := range opFns {
fn(&st.LiteTemplateOpt)
}
st.Init()
return st
}
// Init LiteTemplate
func (t *LiteTemplate) Init() {
if t.vr.init {
return
}
// init var replacer
t.vr.init = true
t.initReplacer(&t.vr)
// add built-in funcs
t.AddFuncs(builtInFuncs)
t.nameMp.AddAliasMap(map[string]string{
"up_first": "upFirst",
"lc_first": "lcFirst",
"def": "default",
})
// add custom funcs
if len(t.Funcs) > 0 {
t.AddFuncs(t.Funcs)
}
}
func (t *LiteTemplate) initReplacer(vr *VarReplacer) {
vr.flatSubs = true
vr.parseDef = t.ParseDef
vr.parseEnv = t.ParseEnv
vr.Left, vr.Right = t.Left, t.Right
basefn.PanicIf(vr.Right == "", "var format right chars is required")
vr.lLen, vr.rLen = len(vr.Left), len(vr.Right)
rightLast := string(vr.Right[vr.rLen-1]) // 排除匹配,防止匹配到类似 "{} adb ddf {var}"
// eg: \{(?s:([^\}]+?))\}
// (?s:...) - 让 "." 匹配换行
// (?s:(.+?)) - 第二个 "?" 非贪婪匹配
pattern := regexp.QuoteMeta(vr.Left) + `(?s:([^` + regexp.QuoteMeta(rightLast) + `]+?))` + regexp.QuoteMeta(vr.Right)
vr.varReg = regexp.MustCompile(pattern)
}
// AddFuncs add custom template functions
func (t *LiteTemplate) AddFuncs(fns map[string]any) {
for name, fn := range fns {
t.fxs[name] = reflects.NewFunc(fn)
}
}
// RenderString render template string with vars
func (t *LiteTemplate) RenderString(s string, vars map[string]any) string {
return t.vr.Replace(s, vars)
}
// RenderFile render template file with vars
func (t *LiteTemplate) RenderFile(filePath string, vars map[string]any) (string, error) {
// read file contents
s, err := fsutil.ReadStringOrErr(filePath)
if err != nil {
return "", err
}
return t.vr.Replace(s, vars), nil
}
// RenderWrite render template string with vars, and write result to writer
func (t *LiteTemplate) RenderWrite(wr io.Writer, s string, vars map[string]any) error {
s = t.vr.Replace(s, vars)
_, err := io.WriteString(wr, s)
return err
}
func (t *LiteTemplate) renderVars(s string, varMap map[string]string) string {
return t.vr.varReg.ReplaceAllStringFunc(s, func(sub string) string {
// var name or pipe expression.
name := strings.TrimSpace(sub[t.vr.lLen : len(sub)-t.vr.rLen])
name = strings.TrimLeft(name, "$.")
var defVal string
var pipes []string
if strings.ContainsRune(name, '|') {
pipes = strutil.Split(name, "|")
// compatible default value. eg: {{ name | inhere }}
if len(pipes) == 2 && !strings.ContainsRune(pipes[1], ':') && !t.isFunc(pipes[1]) {
name, defVal = pipes[0], pipes[1]
pipes = nil // clear pipes
} else { // collect pipe functions
name, pipes = pipes[0], pipes[1:]
}
}
if val, ok := varMap[name]; ok {
if len(pipes) > 0 {
var err error
val, err = t.applyPipes(val, pipes)
if err != nil {
return fmt.Sprintf("Render var %q error: %v", name, err)
}
}
return val
}
// var not found
if len(defVal) > 0 {
return defVal
}
if t.vr.NotFound != nil {
if val, ok := t.vr.NotFound(name); ok {
return val
}
}
// check is default func. eg: {{ name | def:guest }}
if len(pipes) == 1 && strings.ContainsRune(pipes[0], ':') {
fName, argVal := strutil.TrimCut(pipes[0], ":")
if t.isDefaultFunc(fName) {
return argVal
}
}
t.vr.missVars = append(t.vr.missVars, name)
return sub
})
}
func (t *LiteTemplate) applyPipes(val any, pipes []string) (string, error) {
var err error
// pipe expr: "trim|upper|substr:1,2"
// =>
// pipes: ["trim", "upper", "substr:1,2"]
for _, name := range pipes {
args := []any{val}
// has custom args. eg: "substr:1,2"
if strings.ContainsRune(name, ':') {
var argStr string
name, argStr = strutil.TrimCut(name, ":")
if otherArgs := parseArgStr(argStr); len(otherArgs) > 0 {
args = append(args, otherArgs...)
}
}
name = t.nameMp.ResolveAlias(name)
// call pipe func
if fx, ok := t.fxs[name]; ok {
val, err = fx.Call2(args...)
if err != nil {
return "", err
}
} else {
return "", fmt.Errorf("template func %q not found", name)
}
}
return strutil.ToString(val)
}
func (t *LiteTemplate) isFunc(name string) bool {
_, ok := t.fxs[name]
if !ok {
// check name alias
return t.nameMp.HasAlias(name)
}
return ok
}
func (t *LiteTemplate) isDefaultFunc(name string) bool {
return name == "default" || name == "def"
}
var stdTpl = NewLiteTemplate()
// RenderFile render template file with vars
func RenderFile(filePath string, vars map[string]any) (string, error) {
return stdTpl.RenderFile(filePath, vars)
}
// RenderString render str template string or file.
func RenderString(input string, data map[string]any) string {
return stdTpl.RenderString(input, data)
}
// RenderWrite render template string with vars, and write result to writer
func RenderWrite(wr io.Writer, s string, vars map[string]any) error {
return stdTpl.RenderWrite(wr, s, vars)
}
func parseArgStr(argStr string) (ss []any) {
if argStr == "" { // no arg
return
}
if len(argStr) == 1 { // one char
return []any{argStr}
}
return arrutil.StringsToAnys(strutil.Split(argStr, ","))
}
+90
View File
@@ -0,0 +1,90 @@
package textutil
import (
"bytes"
"io"
"strings"
"text/template"
"github.com/gookit/goutil"
"github.com/gookit/goutil/basefn"
"github.com/gookit/goutil/fsutil"
"github.com/gookit/goutil/strutil"
)
var builtInFuncs = template.FuncMap{
// don't escape content
"raw": func(s string) string {
return s
},
"trim": strings.TrimSpace,
// lower first char
"lcFirst": strutil.LowerFirst,
// upper first char
"upFirst": strutil.UpperFirst,
// upper case
"upper": strings.ToUpper,
// lower case
"lower": strings.ToLower,
// cut sub-string
"substr": strutil.Substr,
// default value
"default": func(v, defVal any) string {
if goutil.IsEmpty(v) {
return strutil.SafeString(defVal)
}
return strutil.SafeString(v)
},
// join strings
"join": func(ss []string, sep string) string {
return strings.Join(ss, sep)
},
}
// TextRenderOpt render text template options
type TextRenderOpt struct {
// Output use custom output writer
Output io.Writer
// Funcs add custom template functions
Funcs template.FuncMap
}
// RenderOptFn render option func
type RenderOptFn func(opt *TextRenderOpt)
// NewRenderOpt create a new render options
func NewRenderOpt(optFns []RenderOptFn) *TextRenderOpt {
opt := &TextRenderOpt{}
for _, fn := range optFns {
fn(opt)
}
return opt
}
// RenderGoTpl render input text or template file.
func RenderGoTpl(input string, data any, optFns ...RenderOptFn) string {
opt := NewRenderOpt(optFns)
t := template.New("text-renderer")
t.Funcs(builtInFuncs)
if len(opt.Funcs) > 0 {
t.Funcs(opt.Funcs)
}
if !strings.Contains(input, "{{") && fsutil.IsFile(input) {
template.Must(t.ParseFiles(input))
} else {
template.Must(t.Parse(input))
}
// use custom output writer
if opt.Output != nil {
basefn.MustOK(t.Execute(opt.Output, data))
return "" // return empty string
}
// use buffer receive rendered content
buf := new(bytes.Buffer)
basefn.MustOK(t.Execute(buf, data))
return buf.String()
}
+35 -18
View File
@@ -6,7 +6,7 @@ import (
"strings"
"github.com/gookit/goutil/arrutil"
"github.com/gookit/goutil/internal/comfunc"
"github.com/gookit/goutil/internal/varexpr"
"github.com/gookit/goutil/maputil"
"github.com/gookit/goutil/strutil"
)
@@ -33,11 +33,17 @@ type VarReplacer struct {
keepMissVars bool
// missing vars list
missVars []string
// NotFound handler
// NotFound hook func. on var-name not found
NotFound FallbackFn
// RenderFn custom render func
RenderFn func(s string, vs map[string]string) string
}
// NewVarReplacer instance
// NewVarReplacer instance.
//
// Usage:
//
// rpl := NewVarReplacer("{{,}}")
func NewVarReplacer(format string, opFns ...func(vp *VarReplacer)) *VarReplacer {
vp := &VarReplacer{flatSubs: true}
for _, fn := range opFns {
@@ -46,7 +52,11 @@ func NewVarReplacer(format string, opFns ...func(vp *VarReplacer)) *VarReplacer
return vp.WithFormat(format)
}
// NewFullReplacer instance
// NewFullReplacer instance. will enable parse env and parse default.
//
// Usage:
//
// rpl := NewFullReplacer("{{,}}")
func NewFullReplacer(format string) *VarReplacer {
return NewVarReplacer(format, func(vp *VarReplacer) {
vp.WithParseEnv().WithParseDefault().KeepMissingVars()
@@ -90,8 +100,8 @@ func (r *VarReplacer) WithFormat(format string) *VarReplacer {
return r
}
// Init var matcher
func (r *VarReplacer) Init() *VarReplacer {
// Init var replacer
func (r *VarReplacer) Init() {
if !r.init {
r.lLen, r.rLen = len(r.Left), len(r.Right)
if r.Right != "" {
@@ -100,9 +110,9 @@ func (r *VarReplacer) Init() *VarReplacer {
// no right tag. eg: $name, $user.age
r.varReg = regexp.MustCompile(regexp.QuoteMeta(r.Left) + `(\w[\w-]*(?:\.[\w-]+)*)`)
}
}
return r
r.init = true
}
}
// ParseVars the text contents and collect vars
@@ -114,13 +124,13 @@ func (r *VarReplacer) ParseVars(s string) []string {
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 {
return r.Render(s, tplVars)
}
// Render any-map vars in the text contents
func (r *VarReplacer) Render(s string, tplVars map[string]any) string {
if !strings.Contains(s, r.Left) {
return s
}
@@ -128,14 +138,15 @@ func (r *VarReplacer) Replace(s string, tplVars map[string]any) string {
return s
}
var varMap map[string]string
r.Init()
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)
varMap[path] = varexpr.SafeParse(val.String())
} else {
varMap[path] = val.String()
}
@@ -147,7 +158,7 @@ func (r *VarReplacer) Replace(s string, tplVars map[string]any) string {
varMap = maputil.ToStringMap(tplVars)
}
return r.Init().doReplace(s, varMap)
return r.doReplace(s, varMap)
}
// ReplaceSMap string-map vars in the text contents
@@ -163,11 +174,12 @@ func (r *VarReplacer) RenderSimple(s string, varMap map[string]string) string {
if r.parseEnv {
for name, val := range varMap {
varMap[name] = comfunc.ParseEnvVar(val, nil)
varMap[name] = varexpr.SafeParse(val)
}
}
return r.Init().doReplace(s, varMap)
r.Init()
return r.doReplace(s, varMap)
}
// MissVars list
@@ -186,6 +198,11 @@ func (r *VarReplacer) doReplace(s string, varMap map[string]string) string {
r.missVars = make([]string, 0) // clear on each replace
}
// use custom render func
if r.RenderFn != nil {
return r.RenderFn(s, varMap)
}
return r.varReg.ReplaceAllStringFunc(s, func(sub string) string {
name := strings.TrimSpace(sub[r.lLen : len(sub)-r.rLen])