Skip to content

Commit

Permalink
add handlers, which provide the possibility to extend functionality a…
Browse files Browse the repository at this point in the history
…nd refactoring
  • Loading branch information
iagapie committed Jul 5, 2023
1 parent 9f85bf9 commit a0f7ae1
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 102 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Extends Template

[![Build Status](https://github.com/gowool/extends-template/workflows/Tests/badge.svg?branch=main)](https://github.com/gowool/extends-template/actions?query=branch%3Amain)
[![codecov](https://codecov.io/gh/gowool/extends-template/branch/main/graph/badge.svg)](https://codecov.io/gh/gowool/extends-template)
[![codecov](https://codecov.io/gh/gowool/extends-template/branch/main/graph/badge.svg?token=4MQRF2BQ9U)](https://codecov.io/gh/gowool/extends-template)
[![License](https://img.shields.io/dub/l/vibe-d.svg)](https://github.com/gowool/extends-template/blob/main/LICENSE)

"Extends Template" is a library that enhances template functionality by introducing an "extends" directive and automatically loading dependent HTML templates.
Expand Down
6 changes: 4 additions & 2 deletions environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type Environment struct {
left string
right string
loader Loader
handlers []Handler
reExtends *regexp.Regexp
reTemplates *regexp.Regexp
templates *sync.Map
Expand All @@ -30,8 +31,8 @@ type Environment struct {
mu sync.Mutex
}

func NewEnvironment(loader Loader) *Environment {
e := &Environment{loader: loader}
func NewEnvironment(loader Loader, handlers ...Handler) *Environment {
e := &Environment{loader: loader, handlers: handlers}

return e.Delims(leftDelim, rightDelim)
}
Expand Down Expand Up @@ -92,6 +93,7 @@ func (e *Environment) NewTemplateWrapper(name string) *TemplateWrapper {
return NewTemplateWrapper(
e.NewHTMLTemplate(name),
e.loader,
e.handlers,
e.reExtends,
e.reTemplates,
e.global...,
Expand Down
104 changes: 104 additions & 0 deletions node.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package et

import (
"bytes"
"context"
"html/template"
"path"
)

type Handler func(ctx context.Context, node *Node, namespace string) error

type Node struct {
name string
w *TemplateWrapper
Source *Source
Extends *Node
Successor *Node
Includes []*Node
}

func NewNode(name string, w *TemplateWrapper, successor *Node) *Node {
if w.ns != "" && '@' == w.ns[0] && '@' != name[0] {
name = w.ns + name
}

n := &Node{
name: name,
w: w,
Successor: successor,
}

if successor != nil {
successor.Extends = n
}

return n
}

func (n *Node) SelfParent() *Node {
if n.Extends == nil {
return n
}
return n.Extends.SelfParent()
}

func (n *Node) Init(ctx context.Context) (err error) {
if n.Source, err = n.w.loader.Get(ctx, n.name); err != nil {
return
}

n.w.names.Store(n.name, struct{}{})

if extends := n.w.reExtends.FindAllSubmatch(n.Source.Code, -1); len(extends) > 0 {
n.Source.Code = n.w.reExtends.ReplaceAll(n.Source.Code, []byte{})
if err = NewNode(toString(extends[0][1]), n.w, n).Init(ctx); err != nil {
return
}
}

if includes := n.w.reTemplates.FindAllSubmatch(n.Source.Code, -1); len(includes) > 0 {
for _, tpl := range includes {
include := NewNode(toString(tpl[1]), n.w, nil)
if err = include.Init(ctx); err != nil {
return
}
n.Includes = append(n.Includes, include)
if n.w.ns != "" && '@' == n.w.ns[0] && '@' != rune(tpl[1][0]) {
n.Source.Code = bytes.Replace(n.Source.Code, tpl[1], append(toBytes(n.w.ns), tpl[1]...), 1)
}
}
}

for _, h := range n.w.handlers {
if err = h(ctx, n, n.w.ns); err != nil {
return
}
}

return
}

func (n *Node) Parse(t *template.Template) error {
if _, err := t.Parse(toString(n.Source.Code)); err != nil {
return err
}

for _, include := range n.Includes {
if err := include.SelfParent().Parse(t.New(include.Source.Name)); err != nil {
return err
}
}

if n.Successor == nil {
return nil
}

name := n.Successor.Source.Name
if n.Successor.Successor == nil {
d, suffix := path.Split(name)
name = path.Join(d, "child_"+suffix)
}

return n.Successor.Parse(t.New(name))
}
126 changes: 27 additions & 99 deletions template_wrapper.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package et

import (
"bytes"
"context"
"golang.org/x/exp/slices"
"html/template"
"path"
"regexp"
"strings"
"sync"
Expand All @@ -22,149 +20,79 @@ type TemplateWrapper struct {
parsed atomic.Bool
unix atomic.Int64
loader Loader
handlers []Handler
global []string
ns string
}

func NewTemplateWrapper(
html *template.Template,
loader Loader,
handlers []Handler,
reExtends *regexp.Regexp,
reTemplates *regexp.Regexp,
global ...string,
) *TemplateWrapper {
t := &TemplateWrapper{
w := &TemplateWrapper{
HTML: html,
loader: loader,
handlers: handlers,
reExtends: reExtends,
reTemplates: reTemplates,
global: global,
}

if data := strings.SplitN(html.Name(), "/", 2); len(data) == 2 && '@' == data[0][0] {
t.ns = data[0] + "/"
w.ns = data[0] + "/"
}

return t
return w
}

func (t *TemplateWrapper) IsFresh(ctx context.Context) (ok bool) {
if !t.parsed.Load() {
if err := t.Parse(ctx); err != nil {
func (w *TemplateWrapper) IsFresh(ctx context.Context) (ok bool) {
if !w.parsed.Load() {
if err := w.Parse(ctx); err != nil {
return ok
}
}

unix := t.unix.Load()
t.names.Range(func(key, _ any) bool {
ok, _ = t.loader.IsFresh(ctx, key.(string), unix)
unix := w.unix.Load()
w.names.Range(func(key, _ any) bool {
ok, _ = w.loader.IsFresh(ctx, key.(string), unix)
return ok
})
return
}

func (t *TemplateWrapper) Parse(ctx context.Context) (err error) {
func (w *TemplateWrapper) Parse(ctx context.Context) (err error) {
defer func() {
t.parsed.Store(true)
t.unix.Store(time.Now().Unix())
w.parsed.Store(true)
w.unix.Store(time.Now().Unix())
}()

if t.orig == nil {
if t.orig, err = t.HTML.Clone(); err != nil {
if w.orig == nil {
if w.orig, err = w.HTML.Clone(); err != nil {
return
}
} else if t.HTML, err = t.orig.Clone(); err != nil {
} else if w.HTML, err = w.orig.Clone(); err != nil {
return
}

t.names = &sync.Map{}
w.names = &sync.Map{}

n := &node{}
if err = n.compile(ctx, t, t.HTML.Name()); err != nil {
node := NewNode(w.HTML.Name(), w, nil)
if err = node.Init(ctx); err != nil {
return
}
n = n.selfParent()
node = node.SelfParent()

for i := len(t.global) - 1; i >= 0; i-- {
s := &node{}
if err = s.compile(ctx, t, t.global[i]); err != nil {
for i := len(w.global) - 1; i >= 0; i-- {
globalNode := NewNode(w.global[i], w, nil)
if err = globalNode.Init(ctx); err != nil {
return
}
n.includes = slices.Insert(n.includes, 0, s)
node.Includes = slices.Insert(node.Includes, 0, globalNode)
}

return n.parse(t.HTML)
}

type node struct {
source *Source
parent *node
child *node
includes []*node
}

func (n *node) selfParent() *node {
if n.parent == nil {
return n
}
return n.parent.selfParent()
}

func (n *node) compile(ctx context.Context, w *TemplateWrapper, name string) (err error) {
if w.ns != "" && '@' == w.ns[0] && '@' != name[0] {
name = w.ns + name
}

if n.source, err = w.loader.Get(ctx, name); err != nil {
return
}

w.names.Store(name, struct{}{})

if extends := w.reExtends.FindAllSubmatch(n.source.Code, -1); len(extends) > 0 {
n.source.Code = w.reExtends.ReplaceAll(n.source.Code, []byte{})
n.parent = &node{child: n}
if err = n.parent.compile(ctx, w, toString(extends[0][1])); err != nil {
return
}
}

if includes := w.reTemplates.FindAllSubmatch(n.source.Code, -1); len(includes) > 0 {
for _, tpl := range includes {
include := &node{}
if err = include.compile(ctx, w, toString(tpl[1])); err != nil {
return
}
n.includes = append(n.includes, include)
if w.ns != "" && '@' == w.ns[0] && '@' != rune(tpl[1][0]) {
n.source.Code = bytes.Replace(n.source.Code, tpl[1], append(toBytes(w.ns), tpl[1]...), 1)
}
}
}

return
}

func (n *node) parse(t *template.Template) error {
if _, err := t.Parse(toString(n.source.Code)); err != nil {
return err
}

for _, include := range n.includes {
if err := include.selfParent().parse(t.New(include.source.Name)); err != nil {
return err
}
}

if n.child == nil {
return nil
}

name := n.child.source.Name
if n.child.child == nil {
d, suffix := path.Split(name)
name = path.Join(d, "child_"+suffix)
}

return n.child.parse(t.New(name))
return node.Parse(w.HTML)
}
2 changes: 2 additions & 0 deletions template_wrapper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ func TestTemplateWrapper_IsFresh(t *testing.T) {
wrapper := et.NewTemplateWrapper(
template.New(name),
wrapLoader{t: s.t},
nil,
et.ReExtends("{{", "}}"),
et.ReTemplate("{{", "}}"))

Expand All @@ -81,6 +82,7 @@ func TestTemplateWrapper_Parse(t *testing.T) {
wrapper := et.NewTemplateWrapper(
template.New(name),
wrapLoader{},
nil,
et.ReExtends("{{", "}}"),
et.ReTemplate("{{", "}}"))

Expand Down

0 comments on commit a0f7ae1

Please sign in to comment.