github.com/sohaha/zlsgo@v1.7.13-0.20240501141223-10dd1a906f76/znet/template_go.go (about)

     1  package znet
     2  
     3  import (
     4  	"fmt"
     5  	"html/template"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  	"sync"
    11  
    12  	"github.com/sohaha/zlsgo/zfile"
    13  	"github.com/sohaha/zlsgo/zlog"
    14  	"github.com/sohaha/zlsgo/zstring"
    15  )
    16  
    17  type htmlEngine struct {
    18  	log       *zlog.Logger
    19  	funcmap   map[string]interface{}
    20  	Templates *template.Template
    21  	directory string
    22  	options   TemplateOptions
    23  	mutex     sync.RWMutex
    24  	loaded    bool
    25  }
    26  
    27  type TemplateOptions struct {
    28  	Extension  string
    29  	Layout     string
    30  	DelimLeft  string
    31  	DelimRight string
    32  	Reload     bool
    33  	Debug      bool
    34  }
    35  
    36  func getTemplateOptions(debug bool, opt ...func(o *TemplateOptions)) TemplateOptions {
    37  	o := TemplateOptions{
    38  		Extension:  ".html",
    39  		DelimLeft:  "{{",
    40  		DelimRight: "}}",
    41  		Layout:     "slot",
    42  	}
    43  	if debug {
    44  		o.Debug = true
    45  		o.Reload = true
    46  	}
    47  	for _, f := range opt {
    48  		f(&o)
    49  	}
    50  	return o
    51  }
    52  
    53  var _ Template = &htmlEngine{}
    54  
    55  func newGoTemplate(e *Engine, directory string, opt ...func(o *TemplateOptions)) *htmlEngine {
    56  	h := &htmlEngine{
    57  		directory: directory,
    58  		funcmap:   make(map[string]interface{}),
    59  	}
    60  	if e != nil {
    61  		h.log = e.Log
    62  		h.options = getTemplateOptions(e.IsDebug(), opt...)
    63  	} else {
    64  		h.log = zlog.New()
    65  		h.log.ResetFlags(zlog.BitLevel)
    66  		h.options = getTemplateOptions(true, opt...)
    67  	}
    68  	return h
    69  }
    70  
    71  func (e *htmlEngine) AddFunc(name string, fn interface{}) *htmlEngine {
    72  	e.mutex.Lock()
    73  	e.funcmap[name] = fn
    74  	e.mutex.Unlock()
    75  	return e
    76  }
    77  
    78  func (e *htmlEngine) SetFuncMap(m map[string]interface{}) *htmlEngine {
    79  	e.funcmap = m
    80  	return e
    81  }
    82  
    83  func (e *htmlEngine) Load() error {
    84  	if e.loaded && !e.options.Reload {
    85  		return nil
    86  	}
    87  
    88  	e.mutex.Lock()
    89  	defer e.mutex.Unlock()
    90  
    91  	e.Templates = template.New(e.directory)
    92  	e.Templates.Delims(e.options.DelimLeft, e.options.DelimRight)
    93  	e.Templates.Funcs(e.funcmap)
    94  
    95  	total := 0
    96  	tip := zstring.Buffer()
    97  	err := filepath.Walk(e.directory, func(path string, info os.FileInfo, err error) error {
    98  		if err != nil {
    99  			return err
   100  		}
   101  
   102  		if info == nil || info.IsDir() {
   103  			return nil
   104  		}
   105  
   106  		if len(e.options.Extension) >= len(path) || path[len(path)-len(e.options.Extension):] != e.options.Extension {
   107  			return nil
   108  		}
   109  
   110  		rel, err := filepath.Rel(e.directory, path)
   111  		if err != nil {
   112  			return err
   113  		}
   114  		name := filepath.ToSlash(rel)
   115  		name = strings.TrimSuffix(name, e.options.Extension)
   116  
   117  		buf, err := zfile.ReadFile(path)
   118  		if err != nil {
   119  			return err
   120  		}
   121  
   122  		_, err = e.Templates.New(name).Parse(string(buf))
   123  		if err == nil {
   124  			if e.options.Debug {
   125  				total++
   126  				tip.WriteString("\t    - " + name + "\n")
   127  			}
   128  		}
   129  
   130  		return err
   131  	})
   132  	if err == nil && !e.loaded && e.options.Debug {
   133  		e.log.Debugf("Loaded HTML Templates (%d): \n%s", total, tip.String())
   134  	}
   135  
   136  	e.loaded = true
   137  	return err
   138  }
   139  
   140  func (e *htmlEngine) Render(out io.Writer, template string, data interface{}, layout ...string) error {
   141  	if !e.loaded || e.options.Reload {
   142  		if err := e.Load(); err != nil {
   143  			return err
   144  		}
   145  	}
   146  
   147  	tmpl := e.Templates.Lookup(template)
   148  	if tmpl == nil {
   149  		return fmt.Errorf("template %s does not exist", template)
   150  	}
   151  
   152  	if len(layout) > 0 && layout[0] != "" {
   153  		lay := e.Templates.Lookup(layout[0])
   154  		if lay == nil {
   155  			return fmt.Errorf("layout %s does not exist", layout[0])
   156  		}
   157  		e.mutex.Lock()
   158  		defer e.mutex.Unlock()
   159  		lay.Funcs(map[string]interface{}{
   160  			e.options.Layout: func() error {
   161  				return tmpl.Execute(out, data)
   162  			},
   163  		})
   164  		return lay.Execute(out, data)
   165  	}
   166  
   167  	return tmpl.Execute(out, data)
   168  }