@@ -20,14 +20,14 @@ type Slugger struct {
2020 Substitutions map [string ]string // A map of string replacements to apply before generating the slug
2121 WithEmoji bool // If true, emojis will be included in a slug-friendly format
2222
23- init sync.Once // Controls initialization of the replacer
23+ mu sync.RWMutex // Guards substitutions and replacer; safe for concurrent Slug and updates
2424 replacer * strings.Replacer // Replacer used to handle substitutions in the input string
2525}
2626
2727// New creates and returns a new Slugger instance with optional substitutions and emoji handling.
2828func New (substitutions map [string ]string , withEmoji bool ) * Slugger {
2929 return & Slugger {
30- Substitutions : substitutions ,
30+ Substitutions : copyMap ( substitutions ) ,
3131 WithEmoji : withEmoji ,
3232 Separator : defaultSeparator ,
3333 }
@@ -50,9 +50,17 @@ func (sl *Slugger) Slug(s, separator string) string {
5050
5151 s = strings .ToLower (s )
5252
53- sl .init .Do (sl .initReplacer )
54- if sl .replacer != nil {
55- s = sl .replacer .Replace (s )
53+ sl .mu .RLock ()
54+ r := sl .replacer
55+ sl .mu .RUnlock ()
56+ if r == nil {
57+ sl .initReplacer ()
58+ sl .mu .RLock ()
59+ r = sl .replacer
60+ sl .mu .RUnlock ()
61+ }
62+ if r != nil {
63+ s = r .Replace (s )
5664 }
5765
5866 words := normalizeWordsToSafeASCII (s )
@@ -65,42 +73,67 @@ func (sl *Slugger) Slug(s, separator string) string {
6573
6674// AddSubstitution adds a new substitution to the Slugger and resets the replacer cache.
6775func (sl * Slugger ) AddSubstitution (key , value string ) {
76+ sl .mu .Lock ()
77+ defer sl .mu .Unlock ()
78+
6879 if sl .Substitutions == nil {
6980 sl .Substitutions = make (map [string ]string )
7081 }
7182
7283 sl .Substitutions [key ] = value
73- sl .init = sync. Once {}
84+ sl .replacer = nil
7485}
7586
7687// RemoveSubstitution deletes a substitution by key and resets the replacer cache.
7788func (sl * Slugger ) RemoveSubstitution (key string ) {
89+ sl .mu .Lock ()
90+ defer sl .mu .Unlock ()
91+
7892 if len (sl .Substitutions ) == 0 {
7993 return
8094 }
8195
8296 if _ , exists := sl .Substitutions [key ]; exists {
8397 delete (sl .Substitutions , key )
84- sl .init = sync. Once {}
98+ sl .replacer = nil
8599 }
86100}
87101
88102// ReplaceSubstitution updates the value of an existing substitution and resets the replacer cache.
89103func (sl * Slugger ) ReplaceSubstitution (key , newValue string ) {
104+ sl .mu .Lock ()
105+ defer sl .mu .Unlock ()
106+
90107 if len (sl .Substitutions ) == 0 {
91108 return
92109 }
93110
94111 if _ , exists := sl .Substitutions [key ]; exists {
95112 sl .Substitutions [key ] = newValue
96- sl .init = sync. Once {}
113+ sl .replacer = nil
97114 }
98115}
99116
100117// SetSubstitutions replaces all current substitutions with the provided map and resets the replacer cache.
101118func (sl * Slugger ) SetSubstitutions (substitutions map [string ]string ) {
102- sl .Substitutions = substitutions
103- sl .init = sync.Once {} // Reset the initialization to rebuild the replacer
119+ sl .mu .Lock ()
120+ defer sl .mu .Unlock ()
121+
122+ sl .Substitutions = copyMap (substitutions )
123+ sl .replacer = nil
124+ }
125+
126+ func copyMap (m map [string ]string ) map [string ]string {
127+ if m == nil {
128+ return make (map [string ]string )
129+ }
130+
131+ cp := make (map [string ]string , len (m ))
132+ for k , v := range m {
133+ cp [k ] = v
134+ }
135+
136+ return cp
104137}
105138
106139// ligatureReplacer is used to replace common ligatures with their ASCII equivalents.
@@ -140,6 +173,9 @@ func normalizeWordsToSafeASCII(s string) []string {
140173// initReplacer builds and caches a strings.Replacer from the current substitutions.
141174// Substitution keys are sorted by length (descending) to ensure longer matches are replaced first.
142175func (sl * Slugger ) initReplacer () {
176+ sl .mu .Lock ()
177+ defer sl .mu .Unlock ()
178+
143179 // Reset the replacer to nil so that it can be rebuilt with the latest substitutions
144180 sl .replacer = nil
145181
@@ -165,7 +201,7 @@ func (sl *Slugger) initReplacer() {
165201 }
166202
167203 slices .SortFunc (subsPairs , func (a , b subsKV ) int {
168- if la , lb := len (a .k ), len (b .k ); la != lb {
204+ if la , lb := len ([] rune ( a .k )) , len ([] rune ( b .k ) ); la != lb {
169205 return cmp .Compare (lb , la ) // sort by key length DESC
170206 }
171207
0 commit comments