mirror of
https://github.com/datarhei/core.git
synced 2025-09-26 20:11:29 +08:00
173 lines
5.2 KiB
Go
173 lines
5.2 KiB
Go
package replace
|
|
|
|
import (
|
|
"net/url"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"github.com/datarhei/core/v16/glob"
|
|
"github.com/datarhei/core/v16/restream/app"
|
|
)
|
|
|
|
type TemplateFn func(config *app.Config, section string) string
|
|
|
|
type Replacer interface {
|
|
// RegisterTemplate registers a template for a specific placeholder. Template
|
|
// may contain placeholders as well of the form {name}. They will be replaced
|
|
// by the parameters of the placeholder (see Replace). If a parameter is not of
|
|
// a template is not present, default values can be provided.
|
|
RegisterTemplate(placeholder, template string, defaults map[string]string)
|
|
|
|
// RegisterTemplateFunc does the same as RegisterTemplate, but the template
|
|
// is returned by the template function.
|
|
RegisterTemplateFunc(placeholder string, template TemplateFn, defaults map[string]string)
|
|
|
|
// Replace replaces all occurences of placeholder in str with value. The placeholder is of the
|
|
// form {placeholder}. It is possible to escape a characters in value with \\ by appending a ^
|
|
// and the character to escape to the placeholder name, e.g. {placeholder^:} to escape ":".
|
|
// A placeholder may also have parameters of the form {placeholder,key1=value1,key2=value2}.
|
|
// If the value has placeholders itself (see RegisterTemplate), they will be replaced by
|
|
// the value of the corresponding key in the parameters.
|
|
// If the value is an empty string, the registered templates will be searched for that
|
|
// placeholder. If no template is found, the placeholder will be replaced by the empty string.
|
|
// A placeholder name may consist on of the letters a-z and ':'. The placeholder may contain
|
|
// a glob pattern to find the appropriate template.
|
|
Replace(str, placeholder, value string, vars map[string]string, config *app.Config, section string) string
|
|
}
|
|
|
|
type template struct {
|
|
fn TemplateFn
|
|
defaults map[string]string
|
|
}
|
|
|
|
type replacer struct {
|
|
templates map[string]template
|
|
|
|
re *regexp.Regexp
|
|
templateRe *regexp.Regexp
|
|
}
|
|
|
|
// New returns a Replacer
|
|
func New() Replacer {
|
|
r := &replacer{
|
|
templates: make(map[string]template),
|
|
re: regexp.MustCompile(`{([a-z]+(?::[0-9A-Za-z]+)?)(?:\^(.))?(?:,(.*?))?}`),
|
|
templateRe: regexp.MustCompile(`{([a-z:]+)}`),
|
|
}
|
|
|
|
return r
|
|
}
|
|
|
|
func (r *replacer) RegisterTemplate(placeholder, tmpl string, defaults map[string]string) {
|
|
r.RegisterTemplateFunc(placeholder, func(*app.Config, string) string { return tmpl }, defaults)
|
|
}
|
|
|
|
func (r *replacer) RegisterTemplateFunc(placeholder string, templateFn TemplateFn, defaults map[string]string) {
|
|
r.templates[placeholder] = template{
|
|
fn: templateFn,
|
|
defaults: defaults,
|
|
}
|
|
}
|
|
|
|
func (r *replacer) Replace(str, placeholder, value string, vars map[string]string, config *app.Config, section string) string {
|
|
str = r.re.ReplaceAllStringFunc(str, func(match string) string {
|
|
matches := r.re.FindStringSubmatch(match)
|
|
|
|
if ok, _ := glob.Match(placeholder, matches[1], ':'); !ok {
|
|
return match
|
|
}
|
|
|
|
placeholder := matches[1]
|
|
|
|
// We need a copy from the value
|
|
v := value
|
|
var tmpl template = template{
|
|
fn: func(*app.Config, string) string { return v },
|
|
}
|
|
|
|
// Check for a registered template
|
|
if len(v) == 0 {
|
|
t, ok := r.templates[placeholder]
|
|
if ok {
|
|
tmpl = t
|
|
}
|
|
}
|
|
|
|
v = tmpl.fn(config, section)
|
|
v = r.compileTemplate(v, matches[3], vars, tmpl.defaults)
|
|
|
|
if len(matches[2]) != 0 {
|
|
// If there's a character to escape, we also have to escape the
|
|
// escape character, but only if it is different from the character
|
|
// to escape.
|
|
if matches[2] != "\\" {
|
|
v = strings.ReplaceAll(v, "\\", "\\\\\\")
|
|
}
|
|
v = strings.ReplaceAll(v, matches[2], "\\\\"+matches[2])
|
|
}
|
|
|
|
return strings.Replace(match, match, v, 1)
|
|
})
|
|
|
|
return str
|
|
}
|
|
|
|
// compileTemplate fills in the placeholder in the template with the values from the params
|
|
// string. The placeholders in the template are delimited by {} and their name may only
|
|
// contain the letters a-z. The params string is a comma-separated string of key=value pairs.
|
|
// Example: the template is "Hello {who}!", the params string is "who=World". The key is the
|
|
// placeholder name and will be replaced with the value. The resulting string is "Hello World!".
|
|
// If a placeholder name is not present in the params string, it will not be replaced. The key
|
|
// and values can be escaped as in net/url.QueryEscape.
|
|
func (r *replacer) compileTemplate(str, params string, vars map[string]string, defaults map[string]string) string {
|
|
if len(params) == 0 && len(defaults) == 0 {
|
|
return str
|
|
}
|
|
|
|
p := make(map[string]string)
|
|
|
|
// Copy the defaults
|
|
for key, value := range defaults {
|
|
p[key] = value
|
|
}
|
|
|
|
// taken from net/url.ParseQuery
|
|
for params != "" {
|
|
var key string
|
|
key, params, _ = strings.Cut(params, ",")
|
|
if key == "" {
|
|
continue
|
|
}
|
|
|
|
key, value, _ := strings.Cut(key, "=")
|
|
key, err := url.QueryUnescape(key)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
value, err = url.QueryUnescape(value)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
for name, v := range vars {
|
|
value = strings.ReplaceAll(value, "$"+name, v)
|
|
}
|
|
|
|
p[key] = value
|
|
}
|
|
|
|
str = r.templateRe.ReplaceAllStringFunc(str, func(match string) string {
|
|
matches := r.templateRe.FindStringSubmatch(match)
|
|
|
|
value, ok := p[matches[1]]
|
|
if !ok {
|
|
return match
|
|
}
|
|
|
|
return strings.Replace(match, matches[0], value, 1)
|
|
})
|
|
|
|
return str
|
|
}
|