Browse Source

templates: Add 'import' action (#4321)

Related to (closed) Issue #2094 on template inheritance. This PR adds a new function called "import" which works like "include", except it only takes one argument and passes it to the referenced file to be used as "." in that file.

* Update tplcontext.go

Add {{ render "/path/to/file.ext" $data }} via funcRender

* Update tplcontext.go

* Refactor funcInclude, add funcImport to enable {{block}} and {{template}}

* Fix funcImport return of nil showing up in html

* Update godocs for  and
master
Tim Culverhouse 3 years ago
committed by GitHub
parent
commit
5fda9610f9
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 18
      modules/caddyhttp/templates/templates.go
  2. 75
      modules/caddyhttp/templates/tplcontext.go

18
modules/caddyhttp/templates/templates.go

@ -91,9 +91,25 @@ func init() {
// {{httpInclude "/foo/bar?q=val"}} // {{httpInclude "/foo/bar?q=val"}}
// ``` // ```
// //
// ##### `import`
//
// Imports the contents of another file and adds any template definitions to the template stack. If there are no defitions, the filepath will be the defition name. Any {{ define }} blocks will be accessible by {{ template }} or {{ block }}. Imports must happen before the template or block action is called
//
// **filename.html**
// ```
// {{ define "main" }}
// content
// {{ end }}
//
// **index.html**
// ```
// {{ import "/path/to/file.html" }}
// {{ template "main" }}
// ```
//
// ##### `include` // ##### `include`
// //
// Includes the contents of another file. Optionally can pass key-value pairs as arguments to be accessed by the included file. // Includes the contents of another file and renders in-place. Optionally can pass key-value pairs as arguments to be accessed by the included file.
// //
// ``` // ```
// {{include "path/to/file.html"}} // no arguments // {{include "path/to/file.html"}} // no arguments

75
modules/caddyhttp/templates/tplcontext.go

@ -46,24 +46,26 @@ type TemplateContext struct {
RespHeader WrappedHeader RespHeader WrappedHeader
config *Templates config *Templates
tpl *template.Template
} }
// NewTemplate returns a new template intended to be evaluated with this // NewTemplate returns a new template intended to be evaluated with this
// context, as it is initialized with configuration from this context. // context, as it is initialized with configuration from this context.
func (c TemplateContext) NewTemplate(tplName string) *template.Template { func (c *TemplateContext) NewTemplate(tplName string) *template.Template {
tpl := template.New(tplName) c.tpl = template.New(tplName)
// customize delimiters, if applicable // customize delimiters, if applicable
if c.config != nil && len(c.config.Delimiters) == 2 { if c.config != nil && len(c.config.Delimiters) == 2 {
tpl.Delims(c.config.Delimiters[0], c.config.Delimiters[1]) c.tpl.Delims(c.config.Delimiters[0], c.config.Delimiters[1])
} }
// add sprig library // add sprig library
tpl.Funcs(sprigFuncMap) c.tpl.Funcs(sprigFuncMap)
// add our own library // add our own library
tpl.Funcs(template.FuncMap{ c.tpl.Funcs(template.FuncMap{
"include": c.funcInclude, "include": c.funcInclude,
"import": c.funcImport,
"httpInclude": c.funcHTTPInclude, "httpInclude": c.funcHTTPInclude,
"stripHTML": c.funcStripHTML, "stripHTML": c.funcStripHTML,
"markdown": c.funcMarkdown, "markdown": c.funcMarkdown,
@ -74,8 +76,7 @@ func (c TemplateContext) NewTemplate(tplName string) *template.Template {
"fileExists": c.funcFileExists, "fileExists": c.funcFileExists,
"httpError": c.funcHTTPError, "httpError": c.funcHTTPError,
}) })
return c.tpl
return tpl
} }
// OriginalReq returns the original, unmodified, un-rewritten request as // OriginalReq returns the original, unmodified, un-rewritten request as
@ -85,18 +86,36 @@ func (c TemplateContext) OriginalReq() http.Request {
return or return or
} }
// funcInclude returns the contents of filename relative to the site root. // funcInclude returns the contents of filename relative to the site root and renders it in place.
// Note that included files are NOT escaped, so you should only include // Note that included files are NOT escaped, so you should only include
// trusted files. If it is not trusted, be sure to use escaping functions // trusted files. If it is not trusted, be sure to use escaping functions
// in your template. // in your template.
func (c TemplateContext) funcInclude(filename string, args ...interface{}) (string, error) { func (c TemplateContext) funcInclude(filename string, args ...interface{}) (string, error) {
bodyBuf, err := c.readFileToBuffer(filename)
if err != nil {
return "", err
}
c.Args = args
err = c.executeTemplateInBuffer(filename, bodyBuf)
if err != nil {
return "", err
}
return bodyBuf.String(), nil
}
// readFileToBuffer returns the contents of filename relative to root as a buffer
func (c TemplateContext) readFileToBuffer(filename string) (*bytes.Buffer, error) {
if c.Root == nil { if c.Root == nil {
return "", fmt.Errorf("root file system not specified") return nil, fmt.Errorf("root file system not specified")
} }
file, err := c.Root.Open(filename) file, err := c.Root.Open(filename)
if err != nil { if err != nil {
return "", err return nil, err
} }
defer file.Close() defer file.Close()
@ -106,17 +125,10 @@ func (c TemplateContext) funcInclude(filename string, args ...interface{}) (stri
_, err = io.Copy(bodyBuf, file) _, err = io.Copy(bodyBuf, file)
if err != nil { if err != nil {
return "", err return nil, err
}
c.Args = args
err = c.executeTemplateInBuffer(filename, bodyBuf)
if err != nil {
return "", err
} }
return bodyBuf.String(), nil return bodyBuf, nil
} }
// funcHTTPInclude returns the body of a virtual (lightweight) request // funcHTTPInclude returns the body of a virtual (lightweight) request
@ -167,17 +179,34 @@ func (c TemplateContext) funcHTTPInclude(uri string) (string, error) {
return buf.String(), nil return buf.String(), nil
} }
func (c TemplateContext) executeTemplateInBuffer(tplName string, buf *bytes.Buffer) error { // funcImport parses the filename into the current template stack. The imported
tpl := c.NewTemplate(tplName) // file will be rendered within the current template by calling {{ block }} or
// {{ template }} from the standard template library. If the imported file has
// no {{ define }} blocks, the name of the import will be the path
func (c *TemplateContext) funcImport(filename string) (string, error) {
bodyBuf, err := c.readFileToBuffer(filename)
if err != nil {
return "", err
}
_, err = c.tpl.Parse(bodyBuf.String())
if err != nil {
return "", err
}
return "", nil
}
func (c *TemplateContext) executeTemplateInBuffer(tplName string, buf *bytes.Buffer) error {
c.NewTemplate(tplName)
parsedTpl, err := tpl.Parse(buf.String()) _, err := c.tpl.Parse(buf.String())
if err != nil { if err != nil {
return err return err
} }
buf.Reset() // reuse buffer for output buf.Reset() // reuse buffer for output
return parsedTpl.Execute(buf, c) return c.tpl.Execute(buf, c)
} }
func (c TemplateContext) funcPlaceholder(name string) string { func (c TemplateContext) funcPlaceholder(name string) string {

Loading…
Cancel
Save