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