1. Allow variables in placeholders for parameter values, e.g.
{rtmp,name=$processid}. The variable starts with a $ letter.
The recognized variables are provided with the Replace func.
2. The template func recieves the process config and the name of
the section where this placeholder is located, i.e. "global",
"input", or "output".
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:]+)(?:\^(.))?(?:,(.*?))?}`),
|
|
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, kind 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, kind)
|
|
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
|
|
}
|