Skip to content

Commit

Permalink
✨ feat: Simple string templates
Browse files Browse the repository at this point in the history
  • Loading branch information
sohaha committed Jan 2, 2024
1 parent 8acbf86 commit 1c66253
Show file tree
Hide file tree
Showing 2 changed files with 161 additions and 0 deletions.
93 changes: 93 additions & 0 deletions zstring/template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package zstring

import (
"bytes"
"fmt"
"io"
)

type Template struct {
template string
startTag []byte
endTag []byte

texts [][]byte
tags []string
}

func NewTemplate(template, startTag, endTag string) (*Template, error) {
t := Template{
startTag: String2Bytes(startTag),
endTag: String2Bytes(endTag),
}

err := t.ResetTemplate(template)

return &t, err
}

func (t *Template) ResetTemplate(template string) error {
t.template = template
t.tags = t.tags[:0]
t.texts = t.texts[:0]

s := String2Bytes(template)

tagsCount := bytes.Count(s, t.startTag)
if tagsCount == 0 {
return nil
}

if tagsCount+1 > cap(t.texts) {
t.texts = make([][]byte, 0, tagsCount+1)
}
if tagsCount > cap(t.tags) {
t.tags = make([]string, 0, tagsCount)
}

for {
n := bytes.Index(s, t.startTag)
if n < 0 {
t.texts = append(t.texts, s)
break
}
t.texts = append(t.texts, s[:n])

s = s[n+len(t.startTag):]
n = bytes.Index(s, t.endTag)
if n < 0 {
return fmt.Errorf("cannot find end tag=%q in the template=%q starting from %q", Bytes2String(t.endTag), template, s)
}

t.tags = append(t.tags, Bytes2String(s[:n]))
s = s[n+len(t.endTag):]
}

return nil
}

func (t *Template) Process(w io.Writer, fn func(w io.Writer, tag string) (int, error)) (int64, error) {
var nn int64
n := len(t.texts) - 1
if n == -1 {
ni, err := w.Write(String2Bytes(t.template))
return int64(ni), err
}

for i := 0; i < n; i++ {
ni, err := w.Write(t.texts[i])
nn += int64(ni)
if err != nil {
return nn, err
}

ni, err = fn(w, t.tags[i])
nn += int64(ni)
if err != nil {
return nn, err
}
}
ni, _ := w.Write(t.texts[n])
nn += int64(ni)
return nn, nil
}
68 changes: 68 additions & 0 deletions zstring/template_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package zstring_test

import (
"io"
"strings"
"testing"

zls "github.com/sohaha/zlsgo"
"github.com/sohaha/zlsgo/zstring"
"github.com/sohaha/zlsgo/zutil"
)

func TestNewTemplate(t *testing.T) {
tt := zls.NewTest(t)

tmpl, err := zstring.NewTemplate("hello {name}", "{", "}")
tt.NoError(err)

w := zutil.GetBuff()
defer zutil.PutBuff(w)

_, err = tmpl.Process(w, func(w io.Writer, tag string) (int, error) {
return w.Write([]byte("Go"))
})
tt.NoError(err)
tt.Equal("hello Go", w.String())

err = tmpl.ResetTemplate("The best {n} {say}")
tt.NoError(err)

w.Reset()
_, err = tmpl.Process(w, func(w io.Writer, tag string) (int, error) {
switch tag {
case "say":
return w.Write([]byte("!!!"))
}
return w.Write([]byte("Go"))
})
tt.NoError(err)
tt.Equal("The best Go !!!", w.String())
}

func BenchmarkTemplate(b *testing.B) {
tmpl, _ := zstring.NewTemplate("hello {name}", "{", "}")

b.Run("Buffer", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
var w strings.Builder
_, _ = tmpl.Process(&w, func(w io.Writer, tag string) (int, error) {
return w.Write([]byte("Go"))
})
}
})
})

b.Run("GetBuff", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
w := zutil.GetBuff()
_, _ = tmpl.Process(w, func(w io.Writer, tag string) (int, error) {
return w.Write([]byte("Go"))
})
zutil.PutBuff(w)
}
})
})
}

0 comments on commit 1c66253

Please sign in to comment.