1// Package dynreplacer provides a simple template engine for handling dynamic2// content replacement. It is similar to strings.Replacer but with lazy3// execution of replacements, making it more optimization-friendly in several4// cases. While strings.Replacer requires all replacement values to be computed5// upfront, dynreplacer only executes the callback functions for placeholders6// that actually exist in the template, avoiding unnecessary computations.7//8// The package ensures efficient, non-recursive replacement of placeholders in a9// single pass. This lazy evaluation approach is particularly beneficial when:10// - Some replacement values are expensive to compute11// - Not all placeholders are guaranteed to be present in the template12// - Templates are reused with different content13//14// Example usage:15//16// r := dynreplacer.New(17// dynreplacer.Pair{":name:", func() string { return "World" }},18// dynreplacer.Pair{":greeting:", func() string { return "Hello" }},19// )20// result := r.Replace("Hello :name:!") // Returns "Hello World!"21//22// The replacer caches computed values, so subsequent calls with the same23// placeholder will reuse the cached value instead of executing the callback24// again:25//26// r := dynreplacer.New()27// r.RegisterCallback(":expensive:", func() string { return "computed" })28// r.Replace("Value1: :expensive:") // Computes the value29// r.Replace("Value2: :expensive:") // Uses cached value30// r.ClearCache() // Force re-computation on next use31package dynreplacer3233import (34 "strings"35)3637// Replacer manages dynamic placeholders, their associated functions, and cached38// values.39type Replacer struct {40 callbacks map[string]func() string41 cachedValues map[string]string42}4344// Pair represents a placeholder and its callback function45type Pair struct {46 Placeholder string47 Callback func() string48}4950// New creates a new Replacer instance with optional initial replacements.51// It accepts pairs where each pair consists of a placeholder string and52// its corresponding callback function.53//54// Example:55//56// New(57// Pair{":name:", func() string { return "World" }},58// Pair{":greeting:", func() string { return "Hello" }},59// )60func New(pairs ...Pair) *Replacer {61 r := &Replacer{62 callbacks: make(map[string]func() string),63 cachedValues: make(map[string]string),64 }6566 for _, pair := range pairs {67 r.RegisterCallback(pair.Placeholder, pair.Callback)68 }6970 return r71}7273// RegisterCallback associates a placeholder with a function to generate its74// content.75func (r *Replacer) RegisterCallback(placeholder string, callback func() string) {76 r.callbacks[placeholder] = callback77}7879// Replace processes the given layout, replacing placeholders with cached or80// newly computed values.81func (r *Replacer) Replace(layout string) string {82 replacements := []string{}8384 // Check for placeholders and compute/retrieve values85 hasReplacements := false86 for placeholder, callback := range r.callbacks {87 if strings.Contains(layout, placeholder) {88 value, exists := r.cachedValues[placeholder]89 if !exists {90 value = callback()91 r.cachedValues[placeholder] = value92 }93 replacements = append(replacements, placeholder, value)94 hasReplacements = true95 }96 }9798 // If no replacements were found, return the original layout99 if !hasReplacements {100 return layout101 }102103 // Create a strings.Replacer with all computed replacements104 replacer := strings.NewReplacer(replacements...)105 return replacer.Replace(layout)106}107108// ClearCache clears all cached values, forcing re-computation on next Replace.109func (r *Replacer) ClearCache() {110 r.cachedValues = make(map[string]string)111}112Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.