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 }