tpls

Extendable, Fast Template Engine for Go
git clone git://git.lair.cx/tpls
Log | Files | Refs | README | LICENSE

builder.go (2475B)


      1 package tplc
      2 
      3 import (
      4 	"go/format"
      5 	"io"
      6 	"log"
      7 	"os"
      8 
      9 	"github.com/pkg/errors"
     10 	"github.com/valyala/bytebufferpool"
     11 
     12 	"go.lair.cx/go-core/net/htmlx"
     13 )
     14 
     15 type TagHandler func(b *Builder, w *Writer, t *htmlx.Tokenizer) error
     16 
     17 type Builder struct {
     18 	tags map[string]TagHandler
     19 }
     20 
     21 func New() *Builder {
     22 	return &Builder{
     23 		tags: make(map[string]TagHandler),
     24 	}
     25 }
     26 
     27 // UseBuiltin enables built-in tags. if replaceStandard is true, it replaces
     28 // standard tag, such as title.
     29 func (b *Builder) UseBuiltin() *Builder {
     30 	b.AddTag("float", TagFloat)
     31 	b.AddTag("for", TagFor)
     32 	b.AddTag("if", TagIf)
     33 	b.AddTag("import", TagImport)
     34 	b.AddTag("integer", TagInteger)
     35 	b.AddTag("interface", TagInterface)
     36 	b.AddTag("module", TagModule)
     37 	b.AddTag("raw", TagRaw)
     38 	b.AddTag("string", TagString)
     39 	b.AddTag("struct", TagStruct)
     40 	b.AddTag("template", TagTemplate)
     41 	b.AddTag("title", TagTitle)
     42 
     43 	return b
     44 }
     45 
     46 func (b *Builder) AddTag(name string, tag TagHandler) *Builder {
     47 	b.tags[name] = tag
     48 	return b
     49 }
     50 
     51 func (b *Builder) Handler(name string) TagHandler {
     52 	handler, _ := b.tags[name]
     53 	return handler
     54 }
     55 
     56 func (b *Builder) render(r io.Reader, buf []byte) ([]byte, error) {
     57 	var w = &Writer{buf: buf, builder: b}
     58 
     59 	var (
     60 		t   = htmlx.NewTokenizer(r)
     61 		err error
     62 	)
     63 
     64 	for kind := t.Next(); kind != htmlx.ErrorToken; kind = t.Next() {
     65 		if kind != htmlx.StartTagToken {
     66 			continue
     67 		}
     68 
     69 		tagName, _ := t.TagName()
     70 
     71 		handler, ok := w.builder.tags[string(tagName)]
     72 		if !ok {
     73 			handler = TagFallback(string(tagName))
     74 		}
     75 
     76 		err = handler(b, w, t)
     77 		if err != nil {
     78 			return nil, err
     79 		}
     80 	}
     81 
     82 	err = t.Err()
     83 	if err != nil && err != io.EOF {
     84 		return nil, err
     85 	}
     86 
     87 	return w.Bytes(), nil
     88 }
     89 
     90 func (b *Builder) build(src, dst string, pkg string) error {
     91 	fp, err := os.Open(src)
     92 	if err != nil {
     93 		return errors.Wrap(err, "file open failed")
     94 	}
     95 	defer func(fp *os.File) {
     96 		_ = fp.Close()
     97 	}(fp)
     98 
     99 	buf := bytebufferpool.Get()
    100 	defer bytebufferpool.Put(buf)
    101 
    102 	// Package name
    103 	buf.B = append(buf.B, "// Code generated by tplc. DO NOT EDIT.\n\npackage "...)
    104 	buf.B = append(buf.B, pkg...)
    105 	buf.B = append(buf.B, "\n\nimport \"go.lair.cx/tpls\"\n\n"...)
    106 
    107 	buf.B, err = b.render(fp, buf.B)
    108 	if err != nil {
    109 		return errors.Wrap(err, "render error")
    110 	}
    111 
    112 	prettyCode, err := format.Source(buf.B)
    113 	if err != nil {
    114 		log.Println(string(buf.B))
    115 		return errors.Wrap(err, "format failed")
    116 	}
    117 
    118 	err = os.WriteFile(dst, prettyCode, 0644)
    119 	if err != nil {
    120 		return errors.Wrap(err, "write error")
    121 	}
    122 
    123 	return nil
    124 }