iris通用html,iris/html.go at master · kataras/iris · GitHub

这个博客介绍了如何使用 Go 语言创建一个 HTML 模板引擎。该引擎支持自定义文件系统、文件扩展名、模板重载、延迟加载、布局功能以及额外的模板选项。它还提供了用于布局和功能映射的方法,如 `yield`、`part`、`partial` 和 `render`。此外,还包含了错误处理和并发安全的缓冲池。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

package view

import (

"bytes"

"fmt"

"html/template"

"io"

"net/http"

"os"

"path/filepath"

"strings"

"sync"

)

// HTMLEngine contains the html view engine structure.

type HTMLEngine struct {

name string // the view engine's name, can be HTML, Ace or Pug.

// the file system to load from.

fs http.FileSystem

// files configuration

rootDir string

extension string

// if true, each time the ExecuteWriter is called the templates will be reloaded,

// each ExecuteWriter waits to be finished before writing to a new one.

reload bool

// parser configuration

options []string // (text) template options

left string

right string

layout string

rmu sync.RWMutex // locks for layoutFuncs and funcs

layoutFuncs template.FuncMap

funcs template.FuncMap

//

middleware func(name string, contents []byte) (string, error)

Templates *template.Template

customCache []customTmp // required to load them again if reload is true.

bufPool *sync.Pool

//

}

type customTmp struct {

name string

contents []byte

funcs template.FuncMap

}

var (

_ Engine = (*HTMLEngine)(nil)

_ EngineFuncer = (*HTMLEngine)(nil)

)

var emptyFuncs = template.FuncMap{

"yield": func(binding interface{}) (string, error) {

return "", fmt.Errorf("yield was called, yet no layout defined")

},

"part": func() (string, error) {

return "", fmt.Errorf("block was called, yet no layout defined")

},

"partial": func() (string, error) {

return "", fmt.Errorf("block was called, yet no layout defined")

},

"partial_r": func() (string, error) {

return "", fmt.Errorf("block was called, yet no layout defined")

},

"current": func() (string, error) {

return "", nil

},

"render": func() (string, error) {

return "", nil

},

}

// HTML creates and returns a new html view engine.

// The html engine used like the "html/template" standard go package

// but with a lot of extra features.

// The given "extension" MUST begin with a dot.

//

// Usage:

// HTML("./views", ".html") or

// HTML(iris.Dir("./views"), ".html") or

// HTML(AssetFile(), ".html") for embedded data.

func HTML(fs interface{}, extension string) *HTMLEngine {

s := &HTMLEngine{

name: "HTML",

fs: getFS(fs),

rootDir: "/",

extension: extension,

reload: false,

left: "{{",

right: "}}",

layout: "",

layoutFuncs: make(template.FuncMap),

funcs: make(template.FuncMap),

bufPool: &sync.Pool{New: func() interface{} {

return new(bytes.Buffer)

}},

}

return s

}

// RootDir sets the directory to be used as a starting point

// to load templates from the provided file system.

func (s *HTMLEngine) RootDir(root string) *HTMLEngine {

s.rootDir = filepath.ToSlash(root)

return s

}

// Name returns the engine's name.

func (s *HTMLEngine) Name() string {

return s.name

}

// Ext returns the file extension which this view engine is responsible to render.

// If the filename extension on ExecuteWriter is empty then this is appended.

func (s *HTMLEngine) Ext() string {

return s.extension

}

// Reload if set to true the templates are reloading on each render,

// use it when you're in development and you're boring of restarting

// the whole app when you edit a template file.

//

// Note that if `true` is passed then only one `View -> ExecuteWriter` will be render each time,

// no concurrent access across clients, use it only on development status.

// It's good to be used side by side with the https://github.com/kataras/rizla reloader for go source files.

func (s *HTMLEngine) Reload(developmentMode bool) *HTMLEngine {

s.reload = developmentMode

return s

}

// Option sets options for the template. Options are described by

// strings, either a simple string or "key=value". There can be at

// most one equals sign in an option string. If the option string

// is unrecognized or otherwise invalid, Option panics.

//

// Known options:

//

// missingkey: Control the behavior during execution if a map is

// indexed with a key that is not present in the map.

//"missingkey=default" or "missingkey=invalid"

//The default behavior: Do nothing and continue execution.

//If printed, the result of the index operation is the string

//"".

//"missingkey=zero"

//The operation returns the zero value for the map type's element.

//"missingkey=error"

//Execution stops immediately with an error.

//

func (s *HTMLEngine) Option(opt ...string) *HTMLEngine {

s.rmu.Lock()

s.options = append(s.options, opt...)

s.rmu.Unlock()

return s

}

// Delims sets the action delimiters to the specified strings, to be used in

// templates. An empty delimiter stands for the

// corresponding default: {{ or }}.

func (s *HTMLEngine) Delims(left, right string) *HTMLEngine {

s.left, s.right = left, right

return s

}

// Layout sets the layout template file which inside should use

// the {{ yield }} func to yield the main template file

// and optionally {{partial/partial_r/render}} to render other template files like headers and footers

//

// The 'tmplLayoutFile' is a relative path of the templates base directory,

// for the template file with its extension.

//

// Example: HTML("./templates", ".html").Layout("layouts/mainLayout.html")

// // mainLayout.html is inside: "./templates/layouts/".

//

// Note: Layout can be changed for a specific call

// action with the option: "layout" on the iris' context.Render function.

func (s *HTMLEngine) Layout(layoutFile string) *HTMLEngine {

s.layout = layoutFile

return s

}

// AddLayoutFunc adds the function to the template's layout-only function map.

// It is legal to overwrite elements of the default layout actions:

// - yield func() (template.HTML, error)

// - current func() (string, error)

// - partial func(partialName string) (template.HTML, error)

// - partial_r func(partialName string) (template.HTML, error)

// - render func(fullPartialName string) (template.HTML, error).

func (s *HTMLEngine) AddLayoutFunc(funcName string, funcBody interface{}) *HTMLEngine {

s.rmu.Lock()

s.layoutFuncs[funcName] = funcBody

s.rmu.Unlock()

return s

}

// AddFunc adds the function to the template's function map.

// It is legal to overwrite elements of the default actions:

// - url func(routeName string, args ...string) string

// - urlpath func(routeName string, args ...string) string

// - render func(fullPartialName string) (template.HTML, error).

// - tr func(lang, key string, args ...interface{}) string

func (s *HTMLEngine) AddFunc(funcName string, funcBody interface{}) {

s.rmu.Lock()

s.funcs[funcName] = funcBody

s.rmu.Unlock()

}

// SetFuncs overrides the template funcs with the given "funcMap".

func (s *HTMLEngine) SetFuncs(funcMap template.FuncMap) *HTMLEngine {

s.rmu.Lock()

s.funcs = funcMap

s.rmu.Unlock()

return s

}

// Funcs adds the elements of the argument map to the template's function map.

// It is legal to overwrite elements of the map. The return

// value is the template, so calls can be chained.

func (s *HTMLEngine) Funcs(funcMap template.FuncMap) *HTMLEngine {

s.rmu.Lock()

for k, v := range funcMap {

s.funcs[k] = v

}

s.rmu.Unlock()

return s

}

// Load parses the templates to the engine.

// It's also responsible to add the necessary global functions.

//

// Returns an error if something bad happens, caller is responsible to handle that.

func (s *HTMLEngine) Load() error {

s.rmu.Lock()

defer s.rmu.Unlock()

return s.load()

}

func (s *HTMLEngine) load() error {

if err := s.reloadCustomTemplates(); err != nil {

return err

}

return walk(s.fs, s.rootDir, func(path string, info os.FileInfo, err error) error {

if err != nil {

return err

}

if info == nil || info.IsDir() {

return nil

}

if s.extension != "" {

if !strings.HasSuffix(path, s.extension) {

return nil

}

}

buf, err := asset(s.fs, path)

if err != nil {

return fmt.Errorf("%s: %w", path, err)

}

return s.parseTemplate(path, buf, nil)

})

}

func (s *HTMLEngine) reloadCustomTemplates() error {

for _, tmpl := range s.customCache {

if err := s.parseTemplate(tmpl.name, tmpl.contents, tmpl.funcs); err != nil {

return err

}

}

return nil

}

// ParseTemplate adds a custom template to the root template.

func (s *HTMLEngine) ParseTemplate(name string, contents []byte, funcs template.FuncMap) (err error) {

s.rmu.Lock()

defer s.rmu.Unlock()

s.customCache = append(s.customCache, customTmp{

name: name,

contents: contents,

funcs: funcs,

})

return s.parseTemplate(name, contents, funcs)

}

func (s *HTMLEngine) parseTemplate(name string, contents []byte, funcs template.FuncMap) (err error) {

s.initRootTmpl()

name = strings.TrimPrefix(name, "/")

tmpl := s.Templates.New(name)

tmpl.Option(s.options...)

var text string

if s.middleware != nil {

text, err = s.middleware(name, contents)

if err != nil {

return

}

} else {

text = string(contents)

}

tmpl.Funcs(emptyFuncs).Funcs(s.funcs)

if len(funcs) > 0 {

tmpl.Funcs(funcs) // custom for this template.

}

_, err = tmpl.Parse(text)

return

}

func (s *HTMLEngine) initRootTmpl() { // protected by the caller.

if s.Templates == nil {

// the root template should be the same,

// no matter how many reloads as the

// following unexported fields cannot be modified.

// However, on reload they should be cleared otherwise we get an error.

s.Templates = template.New(s.rootDir)

s.Templates.Delims(s.left, s.right)

}

}

func (s *HTMLEngine) executeTemplateBuf(name string, binding interface{}) (string, error) {

buf := s.bufPool.Get().(*bytes.Buffer)

buf.Reset()

err := s.Templates.ExecuteTemplate(buf, name, binding)

result := buf.String()

s.bufPool.Put(buf)

return result, err

}

func (s *HTMLEngine) layoutFuncsFor(lt *template.Template, name string, binding interface{}) {

s.runtimeFuncsFor(lt, name, binding)

funcs := template.FuncMap{

"yield": func() (template.HTML, error) {

result, err := s.executeTemplateBuf(name, binding)

// Return safe HTML here since we are rendering our own template.

return template.HTML(result), err

},

}

for k, v := range s.layoutFuncs {

funcs[k] = v

}

lt.Funcs(funcs)

}

func (s *HTMLEngine) runtimeFuncsFor(t *template.Template, name string, binding interface{}) {

funcs := template.FuncMap{

"part": func(partName string) (template.HTML, error) {

nameTemp := strings.ReplaceAll(name, s.extension, "")

fullPartName := fmt.Sprintf("%s-%s", nameTemp, partName)

result, err := s.executeTemplateBuf(fullPartName, binding)

if err != nil {

return "", nil

}

return template.HTML(result), err

},

"current": func() (string, error) {

return name, nil

},

"partial": func(partialName string) (template.HTML, error) {

fullPartialName := fmt.Sprintf("%s-%s", partialName, name)

if s.Templates.Lookup(fullPartialName) != nil {

result, err := s.executeTemplateBuf(fullPartialName, binding)

return template.HTML(result), err

}

return "", nil

},

// partial related to current page,

// it would be easier for adding pages' style/script inline

// for example when using partial_r '.script' in layout.html

// templates/users/index.html would load templates/users/index.script.html

"partial_r": func(partialName string) (template.HTML, error) {

ext := filepath.Ext(name)

root := name[:len(name)-len(ext)]

fullPartialName := fmt.Sprintf("%s%s%s", root, partialName, ext)

if s.Templates.Lookup(fullPartialName) != nil {

result, err := s.executeTemplateBuf(fullPartialName, binding)

return template.HTML(result), err

}

return "", nil

},

"render": func(fullPartialName string) (template.HTML, error) {

result, err := s.executeTemplateBuf(fullPartialName, binding)

return template.HTML(result), err

},

}

t.Funcs(funcs)

}

// ExecuteWriter executes a template and writes its result to the w writer.

func (s *HTMLEngine) ExecuteWriter(w io.Writer, name string, layout string, bindingData interface{}) error {

// re-parse the templates if reload is enabled.

if s.reload {

s.rmu.Lock()

defer s.rmu.Unlock()

s.Templates = nil

// we lose the templates parsed manually, so store them when it's called

// in order for load to take care of them too.

if err := s.load(); err != nil {

return err

}

}

t := s.Templates.Lookup(name)

if t == nil {

return ErrNotExist{name, false, bindingData}

}

s.runtimeFuncsFor(t, name, bindingData)

if layout = getLayout(layout, s.layout); layout != "" {

lt := s.Templates.Lookup(layout)

if lt == nil {

return ErrNotExist{layout, true, bindingData}

}

s.layoutFuncsFor(lt, name, bindingData)

return lt.Execute(w, bindingData)

}

return t.Execute(w, bindingData)

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值