init template
This commit is contained in:
24
.drone.yml
Normal file
24
.drone.yml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
kind: pipeline
|
||||||
|
name: default
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: golangci-lint
|
||||||
|
image: golangci/golangci-lint:v1.49
|
||||||
|
volumes:
|
||||||
|
- name: deps
|
||||||
|
path: /go/src/mod
|
||||||
|
commands:
|
||||||
|
- golangci-lint run --timeout 5m
|
||||||
|
|
||||||
|
- name: test
|
||||||
|
image: golang
|
||||||
|
volumes:
|
||||||
|
- name: deps
|
||||||
|
path: /go/src/mod
|
||||||
|
commands:
|
||||||
|
- go test ./...
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: deps
|
||||||
|
temp: {}
|
||||||
|
|
||||||
42
.golangci.yml
Normal file
42
.golangci.yml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
linters-settings:
|
||||||
|
dupl:
|
||||||
|
threshold: 100
|
||||||
|
funlen:
|
||||||
|
lines: 100
|
||||||
|
statements: 50
|
||||||
|
goconst:
|
||||||
|
min-len: 2
|
||||||
|
min-occurrences: 2
|
||||||
|
gocyclo:
|
||||||
|
min-complexity: 15
|
||||||
|
golint:
|
||||||
|
min-confidence: 0
|
||||||
|
govet:
|
||||||
|
check-shadowing: true
|
||||||
|
lll:
|
||||||
|
line-length: 140
|
||||||
|
maligned:
|
||||||
|
suggest-new: true
|
||||||
|
misspell:
|
||||||
|
locale: US
|
||||||
|
varnamelen:
|
||||||
|
min-name-length: 2
|
||||||
|
|
||||||
|
linters:
|
||||||
|
enable-all: true
|
||||||
|
disable:
|
||||||
|
- maligned
|
||||||
|
- exhaustivestruct
|
||||||
|
- golint
|
||||||
|
- structcheck
|
||||||
|
- varcheck
|
||||||
|
- interfacer
|
||||||
|
- deadcode
|
||||||
|
- nosnakecase
|
||||||
|
- scopelint
|
||||||
|
- ifshort
|
||||||
|
|
||||||
|
- gochecknoglobals
|
||||||
|
- ireturn
|
||||||
|
- exhaustruct
|
||||||
|
- gochecknoinits
|
||||||
38
engine/encode.go
Normal file
38
engine/encode.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package engine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/mime"
|
||||||
|
"gitoa.ru/go-4devs/templating/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewEncode(name string, encode func(w io.Writer, data interface{}) error, ext ...mime.Ext) Encode {
|
||||||
|
return Encode{
|
||||||
|
name: name,
|
||||||
|
encode: encode,
|
||||||
|
formats: ext,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Encode struct {
|
||||||
|
encode func(w io.Writer, data interface{}) error
|
||||||
|
name string
|
||||||
|
formats []mime.Ext
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Encode) Support(_ context.Context, reference render.Reference) bool {
|
||||||
|
return Support(reference, e.name, e.formats...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Encode) Load(context.Context, render.Reference) (render.Execute, error) {
|
||||||
|
return func(_ context.Context, wr io.Writer, data interface{}, _ render.Params) error {
|
||||||
|
if err := e.encode(wr, data); err != nil {
|
||||||
|
return fmt.Errorf("%s engine:%w", e.name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
27
engine/encode_test.go
Normal file
27
engine/encode_test.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
package engine_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gitoa.ru/go-4devs/templating/engine"
|
||||||
|
"gitoa.ru/go-4devs/templating/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEncodeLoad(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
buff := bytes.Buffer{}
|
||||||
|
|
||||||
|
exec, err := engine.NewEncode("json", func(w io.Writer, v interface{}) error {
|
||||||
|
return json.NewEncoder(w).Encode(v)
|
||||||
|
}).Load(ctx, render.NewReference("any"))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, exec(ctx, &buff, map[string]string{"name": "json data"}, nil))
|
||||||
|
require.Equal(t, "{\"name\":\"json data\"}\n", buff.String())
|
||||||
|
}
|
||||||
125
engine/engine.go
Normal file
125
engine/engine.go
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
package engine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/mime"
|
||||||
|
"gitoa.ru/go-4devs/templating/loader"
|
||||||
|
"gitoa.ru/go-4devs/templating/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ render.Engine = (*Engine)(nil)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNotSupport = errors.New("not support")
|
||||||
|
ErrDuplicate = errors.New("duplicate")
|
||||||
|
ErrNotFound = errors.New("not found")
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
Option func(*Engine)
|
||||||
|
Parse func(loader.Source) (Template, error)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Cache interface {
|
||||||
|
Set(ctx context.Context, tpl Template) error
|
||||||
|
Get(ctx context.Context, name string) (Template, error)
|
||||||
|
List(_ context.Context) []Template
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithTemplates(tpls ...Template) Option {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
return func(l *Engine) {
|
||||||
|
for _, tpl := range tpls {
|
||||||
|
_ = l.tpls.Set(ctx, tpl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithLoader(load loader.Loader) Option {
|
||||||
|
return func(l *Engine) {
|
||||||
|
l.load = load
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithFormats(formats ...mime.Ext) Option {
|
||||||
|
return func(e *Engine) {
|
||||||
|
e.formats = formats
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(name string, parse Parse, opts ...Option) *Engine {
|
||||||
|
engine := Engine{
|
||||||
|
name: name,
|
||||||
|
parce: parse,
|
||||||
|
tpls: NewTemplates(),
|
||||||
|
load: loader.Empty(),
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&engine)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &engine
|
||||||
|
}
|
||||||
|
|
||||||
|
type Engine struct {
|
||||||
|
name string
|
||||||
|
formats []mime.Ext
|
||||||
|
tpls Cache
|
||||||
|
load loader.Loader
|
||||||
|
parce Parse
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Engine) WithLoader(load loader.Loader) *Engine {
|
||||||
|
return New(l.name, l.parce, WithTemplates(l.tpls.List(context.Background())...), WithLoader(load))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Engine) Add(ctx context.Context, tpls ...Template) error {
|
||||||
|
for idx, tpl := range tpls {
|
||||||
|
if err := l.tpls.Set(ctx, tpl); err != nil {
|
||||||
|
return fmt.Errorf("engine add template[%d] with name %s: %w", idx, tpl.Name(), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Engine) Support(ctx context.Context, tpl render.Reference) bool {
|
||||||
|
return Support(tpl, l.name, l.formats...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l Engine) Load(ctx context.Context, reference render.Reference) (render.Execute, error) {
|
||||||
|
var (
|
||||||
|
tpl Template
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
tpl, err = l.tpls.Get(ctx, reference.Name())
|
||||||
|
if err == nil {
|
||||||
|
return tpl.Execute, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !errors.Is(err, ErrNotFound) {
|
||||||
|
return nil, fmt.Errorf("load get:%w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
source, err := l.load.Load(ctx, reference)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load source:%w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tpl, err = l.parce(source)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("load parce:%w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := l.tpls.Set(ctx, tpl); err != nil {
|
||||||
|
return nil, fmt.Errorf("load set:%w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tpl.Execute, nil
|
||||||
|
}
|
||||||
10
engine/suport.go
Normal file
10
engine/suport.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package engine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitoa.ru/go-4devs/mime"
|
||||||
|
"gitoa.ru/go-4devs/templating/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Support(reference render.Reference, name string, formats ...mime.Ext) bool {
|
||||||
|
return (reference.Engine != "" && reference.IsEngine(name)) || (len(formats) != 0 && reference.IsFromat(formats...))
|
||||||
|
}
|
||||||
80
engine/template.go
Normal file
80
engine/template.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package engine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/templating/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Template interface {
|
||||||
|
Name() string
|
||||||
|
Execute(ctx context.Context, w io.Writer, data interface{}, param render.Params) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTemplates() Templates {
|
||||||
|
return Templates{
|
||||||
|
list: make(map[string]Template),
|
||||||
|
mu: &sync.RWMutex{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Templates struct {
|
||||||
|
list map[string]Template
|
||||||
|
mu *sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Templates) Get(_ context.Context, name string) (Template, error) {
|
||||||
|
t.mu.RLock()
|
||||||
|
defer t.mu.RUnlock()
|
||||||
|
|
||||||
|
if tpl, ok := t.list[name]; ok {
|
||||||
|
return tpl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("templates get:%w", ErrNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Templates) Set(_ context.Context, tpl Template) error {
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
|
|
||||||
|
if _, ok := t.list[tpl.Name()]; ok {
|
||||||
|
return fmt.Errorf("templates set:%w", ErrDuplicate)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.list[tpl.Name()] = tpl
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t Templates) List(_ context.Context) []Template {
|
||||||
|
list := make([]Template, 0, len(t.list))
|
||||||
|
for _, tpl := range t.list {
|
||||||
|
list = append(list, tpl)
|
||||||
|
}
|
||||||
|
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTemplate(name string, execute func(w io.Writer, data interface{}) error) ExecTemplate {
|
||||||
|
return ExecTemplate{
|
||||||
|
name: name,
|
||||||
|
execute: execute,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExecTemplate struct {
|
||||||
|
name string
|
||||||
|
execute func(w io.Writer, data interface{}) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t ExecTemplate) Name() string {
|
||||||
|
return t.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t ExecTemplate) Execute(_ context.Context, w io.Writer, data interface{}, _ render.Params) error {
|
||||||
|
return t.execute(w, data)
|
||||||
|
}
|
||||||
38
execute.go
Normal file
38
execute.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package templating
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/templating/parser"
|
||||||
|
"gitoa.ru/go-4devs/templating/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
exec = render.New(parser.Name)
|
||||||
|
mu = sync.Mutex{}
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetParser(parser render.Parser) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
exec = exec.WithParser(parser)
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddEngine(engine ...render.Engine) {
|
||||||
|
mu.Lock()
|
||||||
|
defer mu.Unlock()
|
||||||
|
|
||||||
|
exec.Add(engine...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Execute(ctx context.Context, wr io.Writer, name string, data interface{}, opts ...render.Option) error {
|
||||||
|
if err := exec.Execute(ctx, wr, name, data, opts...); err != nil {
|
||||||
|
return fmt.Errorf("templating engine:%w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
75
execute_example_test.go
Normal file
75
execute_example_test.go
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
package templating_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
html "html/template"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
text "text/template"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/templating"
|
||||||
|
"gitoa.ru/go-4devs/templating/gohtml"
|
||||||
|
"gitoa.ru/go-4devs/templating/gotxt"
|
||||||
|
"gitoa.ru/go-4devs/templating/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Data struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleExecute() {
|
||||||
|
ctx := context.Background()
|
||||||
|
data := Data{Name: "andrey"}
|
||||||
|
|
||||||
|
gohtml.Must(html.New("example.html").Parse(`Hello {{ .Name }}!`))
|
||||||
|
|
||||||
|
err := templating.Execute(ctx, os.Stdout, "example.html.gohtml", data)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
// Output:
|
||||||
|
// Hello andrey!
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleExecute_withOption() {
|
||||||
|
ctx := context.Background()
|
||||||
|
data := Data{Name: "<b>world</b>"}
|
||||||
|
|
||||||
|
gohtml.Must(html.New("example_with_option.html").Parse(`Hello <s>{{ .Name }}</s>!`))
|
||||||
|
|
||||||
|
err := templating.Execute(ctx, os.Stdout, "example_with_option.html", data, render.WithEngine(gohtml.Name))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
// Output:
|
||||||
|
// Hello <s><b>world</b></s>!
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleExecute_text() {
|
||||||
|
ctx := context.Background()
|
||||||
|
data := Data{Name: "<u>text</u>"}
|
||||||
|
|
||||||
|
gotxt.Must(text.New("example.txt").Parse(`Hello {{ .Name }}!`))
|
||||||
|
|
||||||
|
err := templating.Execute(ctx, os.Stdout, "example.txt.gotxt", data)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
// Output:
|
||||||
|
// Hello <u>text</u>!
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleExecute_withoutEngine() {
|
||||||
|
ctx := context.Background()
|
||||||
|
data := Data{Name: "engine"}
|
||||||
|
|
||||||
|
gotxt.Must(text.New("example_engine.txt").Parse(`Text {{ .Name }}!`))
|
||||||
|
gohtml.Must(html.New("example_engine.html").Parse(`Html {{ .Name }}!`))
|
||||||
|
|
||||||
|
err := templating.Execute(ctx, os.Stdout, "example_engine.html", data)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
// Output:
|
||||||
|
// Html engine!
|
||||||
|
}
|
||||||
18
go.mod
Normal file
18
go.mod
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
module gitoa.ru/go-4devs/templating
|
||||||
|
|
||||||
|
go 1.17
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/stretchr/testify v1.8.0
|
||||||
|
gitoa.ru/go-4devs/encoding/json v0.0.1
|
||||||
|
gitoa.ru/go-4devs/mime v0.0.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/kr/pretty v0.1.0 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
gitoa.ru/go-4devs/encoding v0.0.1 // indirect
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
27
go.sum
Normal file
27
go.sum
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
gitoa.ru/go-4devs/encoding v0.0.1 h1:K93gf7fCwbjkl4yh+eVRDyQ/lNRJPH9Ryd4qWlTKMm4=
|
||||||
|
gitoa.ru/go-4devs/encoding v0.0.1/go.mod h1:7dsdRnHdwk82P+cH1qSBwT1dAhZVYnMmGHw+zc9nOAI=
|
||||||
|
gitoa.ru/go-4devs/encoding/json v0.0.1 h1:N6QHhTXaOzNTb3TnPVxGPiAI0vmWt5OPEBRBlU3PDmo=
|
||||||
|
gitoa.ru/go-4devs/encoding/json v0.0.1/go.mod h1:T+sKiDPkVJnUrmZJjbP8vQvF52lmCfyH92G3G2zNpMQ=
|
||||||
|
gitoa.ru/go-4devs/mime v0.0.1 h1:WKcEhe1g5l7gMHWuC7xxeir9zIPYmlNKRjmnOj0kciA=
|
||||||
|
gitoa.ru/go-4devs/mime v0.0.1/go.mod h1:t+UFgjBNqSdLMYzV3xbKoLWX7dZgcqxEzcjAFh4Y8Fs=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
25
gohtml/engine/engine.go
Normal file
25
gohtml/engine/engine.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package engine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
html "html/template"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/mime"
|
||||||
|
"gitoa.ru/go-4devs/templating/engine"
|
||||||
|
"gitoa.ru/go-4devs/templating/loader"
|
||||||
|
)
|
||||||
|
|
||||||
|
const Name = "gohtml"
|
||||||
|
|
||||||
|
func Parser(source loader.Source) (engine.Template, error) {
|
||||||
|
tpl, err := html.New(source.Name()).Parse(source.Data())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parse html:%w", engine.ErrNotSupport)
|
||||||
|
}
|
||||||
|
|
||||||
|
return engine.NewTemplate(tpl.Name(), tpl.Execute), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(opts ...engine.Option) *engine.Engine {
|
||||||
|
return engine.New(Name, Parser, append(opts, engine.WithFormats(mime.ExtHTML))...)
|
||||||
|
}
|
||||||
23
gohtml/engine/engine_test.go
Normal file
23
gohtml/engine/engine_test.go
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
package engine_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"gitoa.ru/go-4devs/templating/gohtml/engine"
|
||||||
|
"gitoa.ru/go-4devs/templating/loader"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParser(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
buff := bytes.Buffer{}
|
||||||
|
|
||||||
|
tpl, err := engine.Parser(loader.NewSource(t.Name(), `engine:{{.name}}`))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, tpl.Execute(ctx, &buff, map[string]string{"name": "gohtml"}, nil))
|
||||||
|
require.Equal(t, "engine:gohtml", buff.String())
|
||||||
|
}
|
||||||
43
gohtml/html.go
Normal file
43
gohtml/html.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package gohtml
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
html "html/template"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/templating"
|
||||||
|
"gitoa.ru/go-4devs/templating/engine"
|
||||||
|
gohtml "gitoa.ru/go-4devs/templating/gohtml/engine"
|
||||||
|
"gitoa.ru/go-4devs/templating/loader"
|
||||||
|
)
|
||||||
|
|
||||||
|
const Name = gohtml.Name
|
||||||
|
|
||||||
|
var htm = gohtml.New()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
templating.AddEngine(htm)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loader set new loader. This function is not thread-safe.
|
||||||
|
func Loader(in loader.Loader) {
|
||||||
|
htm = htm.WithLoader(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Must(template *html.Template, err error) {
|
||||||
|
MustRegister(html.Must(template, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustRegister(template *html.Template) {
|
||||||
|
if rerr := Register(template); rerr != nil {
|
||||||
|
panic(rerr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Register(template *html.Template) error {
|
||||||
|
if err := htm.Add(context.Background(), engine.NewTemplate(template.Name(), template.Execute)); err != nil {
|
||||||
|
return fmt.Errorf("gohtml loader:%w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
25
gotxt/engine/engine.go
Normal file
25
gotxt/engine/engine.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package engine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
text "text/template"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/mime"
|
||||||
|
"gitoa.ru/go-4devs/templating/engine"
|
||||||
|
"gitoa.ru/go-4devs/templating/loader"
|
||||||
|
)
|
||||||
|
|
||||||
|
const Name = "gotxt"
|
||||||
|
|
||||||
|
func Parser(source loader.Source) (engine.Template, error) {
|
||||||
|
tpl, err := text.New(source.Name()).Parse(source.Data())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parse text:%w", engine.ErrNotSupport)
|
||||||
|
}
|
||||||
|
|
||||||
|
return engine.NewTemplate(tpl.Name(), tpl.Execute), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(opts ...engine.Option) *engine.Engine {
|
||||||
|
return engine.New(Name, Parser, append(opts, engine.WithFormats(mime.ExtTxt, mime.ExtText))...)
|
||||||
|
}
|
||||||
43
gotxt/text.go
Normal file
43
gotxt/text.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package gotxt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
text "text/template"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/templating"
|
||||||
|
"gitoa.ru/go-4devs/templating/engine"
|
||||||
|
gotxt "gitoa.ru/go-4devs/templating/gotxt/engine"
|
||||||
|
"gitoa.ru/go-4devs/templating/loader"
|
||||||
|
)
|
||||||
|
|
||||||
|
const Name = gotxt.Name
|
||||||
|
|
||||||
|
var txt = gotxt.New()
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
templating.AddEngine(txt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loader set new loader. This function is not thread-safe.
|
||||||
|
func Loader(in loader.Loader) {
|
||||||
|
txt = txt.WithLoader(in)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Must(template *text.Template, err error) {
|
||||||
|
MustRegister(text.Must(template, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustRegister(template *text.Template) {
|
||||||
|
if rerr := Register(template); rerr != nil {
|
||||||
|
panic(rerr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Register(template *text.Template) error {
|
||||||
|
if err := txt.Add(context.Background(), engine.NewTemplate(template.Name(), template.Execute)); err != nil {
|
||||||
|
return fmt.Errorf("gotext loader:%w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
13
json/engine/engine.go
Normal file
13
json/engine/engine.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package engine
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitoa.ru/go-4devs/encoding/json"
|
||||||
|
"gitoa.ru/go-4devs/mime"
|
||||||
|
"gitoa.ru/go-4devs/templating/engine"
|
||||||
|
)
|
||||||
|
|
||||||
|
const Name = "json"
|
||||||
|
|
||||||
|
func New() engine.Encode {
|
||||||
|
return engine.NewEncode(Name, json.Encode, mime.ExtJSON)
|
||||||
|
}
|
||||||
12
json/json.go
Normal file
12
json/json.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gitoa.ru/go-4devs/templating"
|
||||||
|
json "gitoa.ru/go-4devs/templating/json/engine"
|
||||||
|
)
|
||||||
|
|
||||||
|
const Name = json.Name
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
templating.AddEngine(json.New())
|
||||||
|
}
|
||||||
29
loader/chain/loader.go
Normal file
29
loader/chain/loader.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package chain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/templating/loader"
|
||||||
|
"gitoa.ru/go-4devs/templating/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ loader.Loader = (Loaders)(nil)
|
||||||
|
|
||||||
|
type Loaders []loader.Loader
|
||||||
|
|
||||||
|
func (l Loaders) Load(ctx context.Context, template render.Reference) (loader.Source, error) {
|
||||||
|
for idx, load := range l {
|
||||||
|
sourse, err := load.Load(ctx, template)
|
||||||
|
if err == nil {
|
||||||
|
return sourse, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !errors.Is(err, loader.ErrNotFound) {
|
||||||
|
return loader.Source{}, fmt.Errorf("chain[%d]:%w", idx, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return loader.Source{}, fmt.Errorf("chains(%d):%w", len(l), loader.ErrNotFound)
|
||||||
|
}
|
||||||
20
loader/empty.go
Normal file
20
loader/empty.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package loader
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/templating/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _empty = empty{}
|
||||||
|
|
||||||
|
type empty struct{}
|
||||||
|
|
||||||
|
func (empty) Load(context.Context, render.Reference) (Source, error) {
|
||||||
|
return Source{}, fmt.Errorf("empty: %w", ErrNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Empty() Loader {
|
||||||
|
return _empty
|
||||||
|
}
|
||||||
34
loader/loader.go
Normal file
34
loader/loader.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package loader
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/templating/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrNotFound = errors.New("not found")
|
||||||
|
|
||||||
|
type Loader interface {
|
||||||
|
Load(ctx context.Context, r render.Reference) (Source, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSource(name, data string) Source {
|
||||||
|
return Source{
|
||||||
|
name: name,
|
||||||
|
data: data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Source struct {
|
||||||
|
name string
|
||||||
|
data string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Source) Name() string {
|
||||||
|
return s.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Source) Data() string {
|
||||||
|
return s.data
|
||||||
|
}
|
||||||
43
parser/name.go
Normal file
43
parser/name.go
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"gitoa.ru/go-4devs/mime"
|
||||||
|
"gitoa.ru/go-4devs/templating/render"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
nameWithExt = 2
|
||||||
|
nameWithEngine = 3
|
||||||
|
countOption = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
// Name parse name by <tpl name>.<format>.<engine> or <tpl name>.<format>.
|
||||||
|
func Name(_ context.Context, name string, opts ...render.Option) (render.Reference, error) {
|
||||||
|
options := make([]render.Option, 0, countOption)
|
||||||
|
tplName := strings.ToLower(name)
|
||||||
|
|
||||||
|
base := path.Base(tplName)
|
||||||
|
el := strings.SplitN(base, ".", nameWithEngine)
|
||||||
|
|
||||||
|
var ext mime.Ext
|
||||||
|
|
||||||
|
switch len(el) {
|
||||||
|
case nameWithEngine:
|
||||||
|
ext = mime.ExtFromString(el[1])
|
||||||
|
options = append(options, render.WithEngine(el[2]))
|
||||||
|
tplName = strings.TrimRight(tplName, "."+el[1]+"."+el[2])
|
||||||
|
case nameWithExt:
|
||||||
|
ext = mime.ExtFromString(el[1])
|
||||||
|
tplName = strings.TrimRight(tplName, "."+el[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ext.Is(mime.ExtUnrecognized) {
|
||||||
|
options = append(options, render.WithFormat(ext))
|
||||||
|
}
|
||||||
|
|
||||||
|
return render.NewReference(tplName, append(options, opts...)...), nil
|
||||||
|
}
|
||||||
78
render/reference.go
Normal file
78
render/reference.go
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
package render
|
||||||
|
|
||||||
|
import "gitoa.ru/go-4devs/mime"
|
||||||
|
|
||||||
|
type Option func(*Reference)
|
||||||
|
|
||||||
|
func WithParam(name string, val interface{}) Option {
|
||||||
|
return func(r *Reference) {
|
||||||
|
r.Params[name] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithEngine(name string) Option {
|
||||||
|
return func(r *Reference) {
|
||||||
|
r.Engine = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithFormat(ext mime.Ext) Option {
|
||||||
|
return func(r *Reference) {
|
||||||
|
r.Format = ext
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewReference(name string, opts ...Option) Reference {
|
||||||
|
ref := Reference{
|
||||||
|
name: name,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(&ref)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ref
|
||||||
|
}
|
||||||
|
|
||||||
|
type Reference struct {
|
||||||
|
name string
|
||||||
|
Engine string
|
||||||
|
Format mime.Ext
|
||||||
|
Params Params
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Reference) Name() string {
|
||||||
|
if !r.Format.Is(mime.ExtUnrecognized) {
|
||||||
|
return r.name + "." + r.Format.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Reference) String() string {
|
||||||
|
if r.Engine != "" {
|
||||||
|
return r.name + "." + r.Format.String() + "." + r.Engine
|
||||||
|
}
|
||||||
|
|
||||||
|
return r.name + "." + r.Format.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Reference) IsEngine(engines ...string) bool {
|
||||||
|
for _, engine := range engines {
|
||||||
|
if engine == r.Engine {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Reference) IsFromat(formats ...mime.Ext) bool {
|
||||||
|
return r.Format.Is(formats...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Params map[string]interface{}
|
||||||
|
|
||||||
|
func (p Params) Get(name string) interface{} {
|
||||||
|
return p[name]
|
||||||
|
}
|
||||||
77
render/render.go
Normal file
77
render/render.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package render
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrNotFound = errors.New("not found")
|
||||||
|
|
||||||
|
func New(parser Parser, engines ...Engine) *Render {
|
||||||
|
return &Render{
|
||||||
|
parser: parser,
|
||||||
|
engines: engines,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Render struct {
|
||||||
|
engines []Engine
|
||||||
|
parser Parser
|
||||||
|
}
|
||||||
|
|
||||||
|
type Execute func(ctx context.Context, wr io.Writer, data interface{}, params Params) error
|
||||||
|
|
||||||
|
type Engine interface {
|
||||||
|
Support(context.Context, Reference) bool
|
||||||
|
Load(ctx context.Context, reference Reference) (Execute, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Parser func(ctx context.Context, name string, opts ...Option) (Reference, error)
|
||||||
|
|
||||||
|
// Add add engine. This function is not thread-safe.
|
||||||
|
func (e *Render) Add(engine ...Engine) {
|
||||||
|
e.engines = append(e.engines, engine...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Render) WithParser(parser Parser) *Render {
|
||||||
|
return New(parser, e.engines...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Render) Load(ctx context.Context, reference Reference) (Execute, error) {
|
||||||
|
for _, engine := range e.engines {
|
||||||
|
if engine.Support(ctx, reference) {
|
||||||
|
exec, err := engine.Load(ctx, reference)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("render load:%w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return exec, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("loader:%w", ErrNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Render) Parse(ctx context.Context, name string, opts ...Option) (Reference, error) {
|
||||||
|
return e.parser(ctx, name, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Render) Execute(ctx context.Context, wr io.Writer, name string, data interface{}, opts ...Option) error {
|
||||||
|
reference, rerr := e.parser(ctx, name, opts...)
|
||||||
|
if rerr != nil {
|
||||||
|
return fmt.Errorf("parse: %w", rerr)
|
||||||
|
}
|
||||||
|
|
||||||
|
execute, err := e.Load(ctx, reference)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("engine load:%w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := execute(ctx, wr, data, reference.Params); err != nil {
|
||||||
|
return fmt.Errorf("render execute:%w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user