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