dynreplacer.gno

3.58 Kb ยท 111 lines
  1// Package dynreplacer provides a simple template engine for handling dynamic
  2// content replacement. It is similar to strings.Replacer but with lazy
  3// execution of replacements, making it more optimization-friendly in several
  4// cases. While strings.Replacer requires all replacement values to be computed
  5// upfront, dynreplacer only executes the callback functions for placeholders
  6// that actually exist in the template, avoiding unnecessary computations.
  7//
  8// The package ensures efficient, non-recursive replacement of placeholders in a
  9// single pass. This lazy evaluation approach is particularly beneficial when:
 10// - Some replacement values are expensive to compute
 11// - Not all placeholders are guaranteed to be present in the template
 12// - Templates are reused with different content
 13//
 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 same
 23// placeholder will reuse the cached value instead of executing the callback
 24// again:
 25//
 26//	r := dynreplacer.New()
 27//	r.RegisterCallback(":expensive:", func() string { return "computed" })
 28//	r.Replace("Value1: :expensive:") // Computes the value
 29//	r.Replace("Value2: :expensive:") // Uses cached value
 30//	r.ClearCache()                   // Force re-computation on next use
 31package dynreplacer
 32
 33import (
 34	"strings"
 35)
 36
 37// Replacer manages dynamic placeholders, their associated functions, and cached
 38// values.
 39type Replacer struct {
 40	callbacks    map[string]func() string
 41	cachedValues map[string]string
 42}
 43
 44// Pair represents a placeholder and its callback function
 45type Pair struct {
 46	Placeholder string
 47	Callback    func() string
 48}
 49
 50// New creates a new Replacer instance with optional initial replacements.
 51// It accepts pairs where each pair consists of a placeholder string and
 52// 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	}
 65
 66	for _, pair := range pairs {
 67		r.RegisterCallback(pair.Placeholder, pair.Callback)
 68	}
 69
 70	return r
 71}
 72
 73// RegisterCallback associates a placeholder with a function to generate its
 74// content.
 75func (r *Replacer) RegisterCallback(placeholder string, callback func() string) {
 76	r.callbacks[placeholder] = callback
 77}
 78
 79// Replace processes the given layout, replacing placeholders with cached or
 80// newly computed values.
 81func (r *Replacer) Replace(layout string) string {
 82	replacements := []string{}
 83
 84	// Check for placeholders and compute/retrieve values
 85	hasReplacements := false
 86	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] = value
 92			}
 93			replacements = append(replacements, placeholder, value)
 94			hasReplacements = true
 95		}
 96	}
 97
 98	// If no replacements were found, return the original layout
 99	if !hasReplacements {
100		return layout
101	}
102
103	// Create a strings.Replacer with all computed replacements
104	replacer := strings.NewReplacer(replacements...)
105	return replacer.Replace(layout)
106}
107
108// ClearCache clears all cached values, forcing re-computation on next Replace.
109func (r *Replacer) ClearCache() {
110	r.cachedValues = make(map[string]string)
111}