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}