mirror of
https://github.com/therootcompany/golib.git
synced 2026-01-27 23:18:05 +00:00
feat: add text/textvars for cutting preceding character when var is empty
This commit is contained in:
parent
160e26623b
commit
65b6438f6d
@ -1,76 +0,0 @@
|
||||
package smstmpl
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/therootcompany/golib/net/smsgw/smscsv"
|
||||
)
|
||||
|
||||
var reUnmatchedVars = regexp.MustCompile(`(\{[^}]+\})`)
|
||||
|
||||
func RenderAll(messages []smscsv.Message) ([]smscsv.Message, error) {
|
||||
for i, message := range messages {
|
||||
rowIndex := i + 1
|
||||
|
||||
message.SetText(ReplaceVar(message.Template(), "Name", message.Name()))
|
||||
keyIter := maps.Keys(message.Vars)
|
||||
keys := slices.Sorted(keyIter)
|
||||
for _, key := range keys {
|
||||
val := message.Vars[key]
|
||||
message.SetText(ReplaceVar(message.Text(), key, val))
|
||||
}
|
||||
|
||||
if tmpls := reUnmatchedVars.FindAllString(message.Text(), -1); len(tmpls) != 0 {
|
||||
return nil, &smscsv.CSVWarn{
|
||||
Index: rowIndex,
|
||||
Code: "UnmatchedVars",
|
||||
Message: fmt.Sprintf(
|
||||
"failing due to row %d (%s): leftover template variable(s): %s",
|
||||
rowIndex, message.Name(), strings.Join(tmpls, " "),
|
||||
),
|
||||
// Record: rec,
|
||||
}
|
||||
}
|
||||
|
||||
messages[i] = message
|
||||
}
|
||||
|
||||
// TODO XXX AJ makes sure the copy retains its Text
|
||||
return messages, nil
|
||||
}
|
||||
|
||||
func ReplaceVar(text, key, val string) string {
|
||||
if val != "" {
|
||||
// No special treatment:
|
||||
// "Hey {+Name}," => "Hey Doe,"
|
||||
// "Bob,{Name}" => "Bob,Doe"
|
||||
// "{Name-},Joe" => "Doe,Joe"
|
||||
// "Hi {-Name-}, Joe" => "Hi Doe, Joe"
|
||||
var reHasVar = regexp.MustCompile(fmt.Sprintf(`\{\+?%s-?\}`, regexp.QuoteMeta(key)))
|
||||
return reHasVar.ReplaceAllString(text, val)
|
||||
}
|
||||
|
||||
var metaKey = regexp.QuoteMeta(key)
|
||||
|
||||
// "Hey {+Name}," => "Hey ,"
|
||||
var reEatNone = regexp.MustCompile(fmt.Sprintf(`\{\+%s\}`, metaKey))
|
||||
text = reEatNone.ReplaceAllString(text, val)
|
||||
|
||||
// "Bob,{Name};" => "Bob;"
|
||||
var reEatOneLeft = regexp.MustCompile(fmt.Sprintf(`.?\{%s\}`, metaKey))
|
||||
text = reEatOneLeft.ReplaceAllString(text, val)
|
||||
|
||||
// ",{Name-};Joe" => ",Joe"
|
||||
var reEatOneRight = regexp.MustCompile(fmt.Sprintf(`\{%s-\}.?`, metaKey))
|
||||
text = reEatOneRight.ReplaceAllString(text, val)
|
||||
|
||||
// "Hi {-Name-}, Joe" => "Hi Joe"
|
||||
var reEatOneBoth = regexp.MustCompile(fmt.Sprintf(`.?\{-%s-\}.?`, metaKey))
|
||||
text = reEatOneBoth.ReplaceAllString(text, val)
|
||||
|
||||
return text
|
||||
}
|
||||
66
text/textvars/README.md
Normal file
66
text/textvars/README.md
Normal file
@ -0,0 +1,66 @@
|
||||
# [textvars](https://github.com/therootcompany/golib/tree/main/text/textvars)
|
||||
|
||||
[](https://pkg.go.dev/github.com/therootcompany/golib/text/textvars)
|
||||
|
||||
Text replacement functions that handle the empty string / trailing comma problem in a sane way: \
|
||||
(cuts the character to the left when empty)
|
||||
|
||||
Example: Leading space:
|
||||
|
||||
```go
|
||||
textvars.ReplaceVar(`Hey {Name}!`, "Name", "Joe")
|
||||
// "Hey Joe!"
|
||||
|
||||
textvars.ReplaceVar(`Hey {Name}!`, "Name", "")
|
||||
// "Hey!" 👍
|
||||
|
||||
strings.ReplaceAll(`Hey {Name}!`, "{Name}", "")
|
||||
// "Hey !" 🫤
|
||||
```
|
||||
|
||||
Example: Leading comma:
|
||||
|
||||
```go
|
||||
textvars.ReplaceVar(`Apples,{Fruit},Bananas`, "Fruit", "Oranges")
|
||||
// "Apples,Oranges,Bananas"
|
||||
|
||||
textvars.ReplaceVar(`Apples,{Fruit},Bananas`, "Fruit", "")
|
||||
// "Apples,Bananas" 👍
|
||||
|
||||
strings.ReplaceAll(`Apples,{Fruit},Bananas`, "{Fruit}", "")
|
||||
// "Apples,,Bananas" 🫤
|
||||
```
|
||||
|
||||
Example: Multiple Vars
|
||||
|
||||
```go
|
||||
tmpl := `{#}. {Name}`
|
||||
vars := map[string]string{
|
||||
"#": "1",
|
||||
"Name": "Joe",
|
||||
}
|
||||
text, err := textvars.ReplaceVars(tmpl, vars)
|
||||
// "1. Joe"
|
||||
// errors if any {...} are left over
|
||||
```
|
||||
|
||||
**Note**: This is the sort of thing that's it's probably better to copy and paste rather than to have as a dependency, but I wanted to have it for myself as a convenience in my own repo of tools, so here it is.
|
||||
|
||||
## Other Uses
|
||||
|
||||
It seemed like an okay idea at the time, so I also baked in some other uses:
|
||||
|
||||
| Syntax | Example | "Joe" | Empty ("") | Comment |
|
||||
| ---------- | -------------- | ----------- | ---------- | ----------------------------- |
|
||||
| `{Name}` | `Hey {Name}!` | `Hey Joe!` | `Hey!` | cuts left character if empty |
|
||||
| `{Name-}` | `1,{Name-},3` | `1,Joe,3` | `1,3` | cuts right character if empty |
|
||||
| `{-Name-}` | `Hey! {Name}!` | `Hey! Joe!` | `Hey!` | cuts left and right if empty |
|
||||
| `{+Name}` | `Name:{+Name}` | `Name:Joe` | `Name:` | keeps left character always |
|
||||
|
||||
However, I haven't actually had the use case for those yet and you probably won't either... so don't use what you don't need. 🙃
|
||||
|
||||
I DO NOT plan on making a robust template system. I was only interested in solving the _leading space_ / _trailing comma_ problem for [sendsms](https://github.com/therootcompany/golib/tree/main/cmd/sendsms).
|
||||
|
||||
# Legal
|
||||
|
||||
CC0-1.0 (Public Domain)
|
||||
3
text/textvars/go.mod
Normal file
3
text/textvars/go.mod
Normal file
@ -0,0 +1,3 @@
|
||||
module github.com/therootcompany/golib/text/textvars
|
||||
|
||||
go 1.25.4
|
||||
72
text/textvars/textvars.go
Normal file
72
text/textvars/textvars.go
Normal file
@ -0,0 +1,72 @@
|
||||
// Authored in 2026 by AJ ONeal <aj@therootcompany.com> (https://therootcompany.com)
|
||||
//
|
||||
// To the extent possible under law, the author(s) have dedicated all copyright
|
||||
// and related and neighboring rights to this software to the public domain
|
||||
// worldwide. This software is distributed without any warranty.
|
||||
//
|
||||
// You should have received a copy of the CC0 Public Domain Dedication along with
|
||||
// this software. If not, see <https://creativecommons.org/publicdomain/zero/1.0/>.
|
||||
//
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
package textvars
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"regexp"
|
||||
"slices"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var reUnmatchedVars = regexp.MustCompile(`(\{[^}]+\})`)
|
||||
|
||||
func GetPlaceholders(tmpl string) []string {
|
||||
return reUnmatchedVars.FindAllString(tmpl, -1)
|
||||
}
|
||||
|
||||
func ReplaceVars(text string, vars map[string]string) (string, error) {
|
||||
keyIter := maps.Keys(vars)
|
||||
keys := slices.Sorted(keyIter)
|
||||
for _, key := range keys {
|
||||
val := vars[key]
|
||||
text = ReplaceVar(text, key, val)
|
||||
}
|
||||
|
||||
if tmpls := GetPlaceholders(text); len(tmpls) != 0 {
|
||||
return "", fmt.Errorf("leftover template variable(s): %s", strings.Join(tmpls, " "))
|
||||
}
|
||||
|
||||
return text, nil
|
||||
}
|
||||
|
||||
func ReplaceVar(text, key, val string) string {
|
||||
if val != "" {
|
||||
// No special treatment:
|
||||
// "Hey {+Name}," => "Hey Doe,"
|
||||
// "Bob,{Name}" => "Bob,Doe"
|
||||
// "{Name-},Joe" => "Doe,Joe"
|
||||
// "Hi {-Name-}, Joe" => "Hi Doe, Joe"
|
||||
var reHasVar = regexp.MustCompile(fmt.Sprintf(`\{\+?%s-?\}`, regexp.QuoteMeta(key)))
|
||||
return reHasVar.ReplaceAllString(text, val)
|
||||
}
|
||||
|
||||
var metaKey = regexp.QuoteMeta(key)
|
||||
|
||||
// "Hey {+Name}," => "Hey ,"
|
||||
text = strings.ReplaceAll(text, `{+`+key+`}`, val)
|
||||
|
||||
// "Bob,{Name};" => "Bob;"
|
||||
var reEatOneLeft = regexp.MustCompile(fmt.Sprintf(`.?\{%s\}`, metaKey))
|
||||
text = reEatOneLeft.ReplaceAllString(text, val)
|
||||
|
||||
// ",{Name-};Joe" => ",Joe"
|
||||
var reEatOneRight = regexp.MustCompile(fmt.Sprintf(`\{%s-\}.?`, metaKey))
|
||||
text = reEatOneRight.ReplaceAllString(text, val)
|
||||
|
||||
// "Hi {-Name-}, Joe" => "Hi Joe"
|
||||
var reEatOneBoth = regexp.MustCompile(fmt.Sprintf(`.?\{-%s-\}.?`, metaKey))
|
||||
text = reEatOneBoth.ReplaceAllString(text, val)
|
||||
|
||||
return text
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user