multitemplate

package module
v0.4.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 2, 2014 License: BSD-2-Clause Imports: 9 Imported by: 3

README

Multitemplate

Build Status

While there are multiple template libraries for Go, I wanted one that would allow me to mix different styles of templates, without having to create some kind of lookup table to see which library a template was created in.

Multitemplate is a set of parsers, a html function library, template interoperation functions, and an implementation of a Template similar to the html/template Template struct in the standard library. The execution backend of this is the html/template package from the standard library, multitemplate is a set of functions, parsers, and glue that adds a larger set of functionality on top of the standard library.

Current documentation is available at godoc.org.

Current status is beta. The happy path works, but straying off the happy path may lead to big bad bears. Right now, I'm endeavoring to build real things with multitemplate, so I will be pushing new versions of multitemplate at arbitrary times to fix the issues I find. At some point in the next month or two I plan to start moving multitemplate to a release candidate state for the 0.5 release.

Bham documentation

I am continuing to add to the godoc documentation, this is a simple snippet from the booking examples written in bham.

  !!!
  %html
    %head
      = yield "head"
    %body
      = block "header"
        #header
          = if .user
            #options
              Connected as {{.user.Username}} |
              %a(href="{{ url "Hotels.Index" }}") Search
              |
              %a(href="{{ url "Hotels.Settings" }}") Settings
              |
              %a(href="{{ url "Application.Logout" }}") Logout
      = end_block
      = yield "content"

Terse Documentation

Terse is fairly well documented, but could always use some longer form examples. This is the same snippet as bham, but in terse

  !!
  html
    head
      @head
    body
      [header]
        #header
          ?.user
            #options
              Connected as {{.user.Username}} |
              a href=(url "Hotels.Index") Search
              |
              a href=(url "Hotels.Settings") Settings
              |
              a href=(url "Application.Logout") Logout
      @content

Using the library

Like database/sql, you can load in dialects (or parsers in this case) like the following

import (
  _ "github.com/acsellers/multitemplate/bham"
  "github.com/acsellers/multitemplate"
)

multitemplate uses extensions to detect which parser to use, a file named layout.bham.html would be shortened to layout.html as a template and parsed using the bham parser.

The simplest way to parse files, it to simply pass an array of file names to ParseFiles or Template.ParseFiles (which will determine parsers, remove extenstions for you), but you can also pass the name, source and parser name to the Parse function, but Parse will not remove extensions or detect parsers for you.

In addition to Execute and ExecuteTemplate, there is also an ExecuteContext, which it the way to configure layouts, and pre-set blocks and template to yield or output during execution.

Revel integration

You can find instruction on how to integrate multitemplate and revel at the godoc for github.com/acsellers/multitemplate/revel. Information on the replacement controller struct to use with the revel integration is at the godoc for github.com/acsellers/multitemplate/revel/app/controllers.

The sample in the revel folder is the booking sample from github.com/revel/revel, but with templates converted to use multitemplate languages.

Martini Integration

You can find instruction on how to integrate multitemplate and revel at the godoc for github.com/acsellers/multitemplate/martini. Information on the replacement controller struct to use with the revel integration is at the godoc for github.com/acsellers/multitemplate/martini.

The Sample in the martini folder is a port of a small Redis viewer I wrote a while ago, but with the templates all in the terse language.

Versioning

New versions are released when the features planned for that version are complete. If a feature looks to not be ready in the same timeframe as the other features earmarked for that version, then the longer features may get bumped.

Future Version Plans

1.1

  • New things, maybe a language, helpers module, or something from left field.

1.0

  • Stability, plan to keep same API at this point and add languages, subsystems

0.6

  • Mustache partial CMTS
  • More CMTS tests

0.5

  • Terse parser gets ported to bham
  • FormBuilder-like helper module
  • Bham spec
  • Common MultiTemplate Test Suite (set of tests that each language must pass)
  • Stdlib CMTS
  • Bham CMTS
  • Terse CMTS
  • Refactor mustache tests to use multitemplate.Template and Table Driven tests

Version History

0.4

  • New language (terse)
  • New sub-library for html helpers
  • FormTag, Link, General, Simple Asset helper modules
  • helpers modules that are enabled individually
  • Better parser construction (working in terse)
  • Martini integration (multirender)

0.3

  • Blocks know how they've been escaped and will check for escaping ruleset matches when being rendered (security)
  • Fix bugs in revel integration exposed by security fixes

0.2

  • Fix issues with bham around function parsing
  • Started documenting library
  • Various fixes

0.1

  • Move helpers, mussed, and bham into subdirectories of the same repo.
  • Write a buildable version of multitemplate.Template
  • Figure out how to set up Delims on standard library
  • Write tests on multitemplate.Template
  • Fix up yield function to take fallback template name
  • Content_for function with template name
  • Implement block and end_block
  • Write a revel connector

Documentation

Overview

Multitemplate is a library to allow you to write html templates in multiple languages, then allowing those templates to work with each other using either a Rails-like yield/content_for paradigm or a Django style block/extend paradigm. Multitemplate is built on html/template, so it gets all of the auto-escaping logic from that library.

Multitemplate at the moment has 3 languages, the standard Go template syntax, a simplified haml-like language called bham, and a simple mustache implementation. Multitemplate has an open interface for creating new syntaxes, so external languages can be easily used.

Terminology

Yield's are executing saved templates or blocks. You can add a fallback template to yields, but not fallback content. Yielding with a name will return the first template set for that name, or the content of the first block that had that name.

ContentFor will set a template to be executed on a name. This is similar to the template command built in to the go template library, but with a layer of indirection.

Blocks are template content that is executed, then saved for a later time. Blocks share names with ContentFor and yields, so a yield might outtput the content from a block, or a template set with ContentFor.

Inherited templates are templates that use the inherits function to define that after it is executed, then another template should be executed as well. These templates should only be made up of non-writing functions and blocks.

Context's are the way to use the more advanced features of the multitemplate library. With Context's, you can set two templates to be executed, a Main template (executed first), and a Layout template (executed after Main). You can also set Templates for Yields and Block content. Since you can't pass RenderArgs in the ExecuteContext, you should put your RenderArgs in the Dot variable.

Layouts are templates executed after a Main template. Context's are the way to define Layouts to be executed. The Main template should set up content that can then be yielded using the Main template. Yielding without a name will cause the main template's content to be output.

Integrations

While multitemplate is available to use as a library in all Go applications, it also includes integrations libraries that will integrate multitemplate into external frameworks without requiring the user to learn how to integrate this library.

The Revel integration is (as far as I know) a drop-in replacement for the Revel template library. Instructions on how to integrate are available in the godoc for the github.com/acsellers/multitemplate/revel subdirectory, while the integration code is in github.com/acsellers/multitemplate/revel/app/controllers due to how revel deals with modules.

Yield functionality

The following code demonstrates the common types of yield statements. The first yield will render the template assigned to the stylesheets key, or render the template "include/javascripts.html" if there is not a set argument. The second yield will render the template set on "stylesheets" with the .Stylesheets as the data. The third will render the template set for "sidebar" with the data originally put into ExecuteTemplate. The fourth yield will render the main template. This will be the template set for the Main attribute on the Context struct in this case.

app_controller.go

ctx := multitemplate.NewContext(renderArgs)
ctx.Yields["sidebar"] = "sidebars/admin.html"
ctx.Main = "app/main.html"
ctx.Layout = "layouts/main.html"
templates.ExecuteContext(writer, ctx)

layouts/main.html

<html>
  <head>
    {{ yield "javascript" (fallback "include/javascripts.html") }}
    {{ yield "stylesheets" .Stylesheets}}
  </head>
  <body>
    <div class="row">
      <div class="span3">
        {{ yield "sidebar" }}
      </div>
      <div class="span9">
        {{ yield }}
      </div>
    </div>
  </body>
</html>

Block functionality

The following code describes two templates that use the inherits function to utilize template inheritence. It works similarly to the yield example. Note that the inherits call should be the first call of the view, any code before the inherits call may be sent to the writer added to the Execute call.

In the interest of security, you should only enclose multiple continuous lines of similar types of content inside a block. The reason for this is that blocks may end up with different escaping rules when originally executed and saved versus where they are output. While multitemplate will catch many cases where a block is asked to be output to a location that is under different escaping rules than the block's original rules, you should still be careful.

app_controller.go

templates.ExecuteTemplate(writer, "app/index.html", renderArgs)

layouts/main.html

<html>
  <head>
    {{ block "javascript" }}
      <script src="/assets/app.js" type="text/javascript">
    {{ end_block }}
    <link rel="stylesheet" href="/assets/app.css">
  </head>
  <body>
    <div class="row">
      <div class="span3">
        {{ block "sidebar" }}
          <ul>
            <li class="home">Home</li>
          </ul>
        {{ end_block }}
      </div>
      <div class="span9">
        {{ block "content" }}
          Could not find your content.
        {{ end_block }}
      </div>
    </div>
  </body>
</html>

app/index.html

{{ inherits "layouts/main.html" }}
{{ block "content" }}
  Amazing content!
{{ end_block }}

Yield and Block functionality

As both yields and blocks are built on the same underlying mechanisms, they can be combined in interesting ways. Implementation wise, blocks are like yields that have embedded fallback content, while yields have to have separate template fallbacks. Both Layouts and the Main template can extend other templates.

app_controller.go

templates.ExecuteTemplate(writer, "app/index.html", renderArgs)

app/index.html

{{ extend "layouts/main.html" }}

{{ block "javascript" }}
  <script src="/assets/app.js">
{{ end_block }}

{{ content_for "more_stylesheets" "assets/beta-css.html" }}

layouts/main.html

<html>
  <head>
    {{ yield "javascript" }}
    {{ block "stylesheets" }}
      {{ yield "more_stylesheets" }}
      <style>
        body { margin-left: 40px }
      </style>
    {{ end_block }}
  </head>
  <body>
    {{ block "content" }}
      Content goes here
    {{ end_block }}
  </body>
</html>

Functions Reference

yield allows for rendering template aliases or simply rendering nothing. Rendering the Main template without a Main template set is an error

// yield the main template with the original RenderArgs
{{ yield }}

// Yield the main template with specific RenderArgs
{{ yield .CurrentObject }}

// Yield a pre-set template with original RenderArgs,
// or render nothing
{{ yield "hero_module" }}

// Yield a pre-set template, or a fallback template
// if the pre-set template is not present
{{ yield "blurb" (fallback "demo/lorem_blurb.html" }}

// Yield a pre-set template with specific RenderArgs
{{ yield "carousel" .CarouselImages }}

// Yield a pre-set template, or a fallback template, both
// with specific RenderArgs
{{ yield "results" .CurrentObject (fallback "errors/undefined.html") }}

content_for allows you to set a template to be rendered in a block or yield from within a template.

// Set the sidebar key to be a specific template
{{ content_for "sidebar" .Current.Sidebar }}

block saves content inside the current template to a key. That key can be recalled using yield or another block with the same key in the final template with a block of that key. Keys are claimed by the first block to render to them.

{{ block "sidebar" }}
  <ul>
    <li>One</li>
    <li>Two</li>
    <li>Three</li>
  </ul>
{{ end_block }}

end_block ends the content are started by block

extend marks that the current template is made up of blocks that will be executed in the context of another template. Template inheritence can be carried to arbitrary levels, you are not limited to using extend only once in template execution.

{{ extend "include/main.html" }}

root_dot is the orignal RenderArgs passed in to the ExecuteTemplate call

{{ $title = root_dot.Title }}

exec execute an arbitrary template with the passed name and data

{{ exec .Header.Path . }}

fallback sets a specific template to be rendered in the case that a yield call finds that there is no content set for the key of the yield.

{{ yield "footer" . (fallback "include/old_footer.html") }}

Things to know about

yield . is ambiguous when dot is currently a string. It could be either a request to output a pre-set template or block, or to render the main template with the dot as the data.

Assigning the same key in a Context for both Yields and Content means that the Content will be ignored. Calling content_for and block (in templates) with the same key has lock-out protection within the template functions. In this case, we will use the template named in the Yields map. Within the templates, the rule is the first to claim the key, wins. Any integrations that hide the Context, will operate under the assumption that the last claim before template execution should win.

Getting an error about a stack overflow during template execution is most likely a template that is yielding itself.

The block function has two related functions, define_block and exec_block. If you need to define a block, even when it would normally execute the block (for instance, if you are in the main layout, and which to ensure a block exists before yielding or executing a template), define_block will save the content of the block and not output the content. This will not override any content already saved for that block name. exec_block is the reverse function, it will force the block to be executed. If you need to start the main template with a block, and you are using a template, exec_block will cause the block to be executed correctly. You should use the standard block call in nearly all situations.

There are tests in integration_test.go that spell out how yields and block interact in all the situations that I could think of. The test cases are spelled out with minimal templates, names for each test case, and a description of what the situation that the case is testing.

Bham is a beta-quality library. I've tried to fix the bugs that I'm aware of, but I'm sure that there's more lurking out there.

The Mustache implementation here is alpha quality. It's low on the totem pole for improvements.

Version plans

First release is 0.1, which has bham and html/templates available as first-class languages. Blocks and yields are supported, along with layouts.

Second release is 0.5, which adds the helpers library, and the super_block and main_block functions. Also a whole bunch of new tests for yields, blocks and their interactions.

Third release will be either a 0.6 or a 1.0, adding things I forgot, fixing bugs discovered and things that need to be fixed. Mustache will get integration tests, function calling, blocks. If there were relatively few bugs to fix, then this will be 1.0.

Releases after 1.0, will be adding functions or languages. Template languages I'm interested in investigating adding are: jade/slim, full haml, some sort of lispy thing, handlebars, jinja2, and Razor.

Index

Constants

This section is empty.

Variables

View Source
var GoLeftDelim, GoRightDelim string

Delimeters for the standard Go template parser

View Source
var LoadedFuncs = template.FuncMap{}

LoadedFuncs is the place to load functions to be loaded.

View Source
var Parsers = make(map[string]Parser)

To Register a parser, set it in this map for each extension that would correspond to it.

View Source
var StaticFuncs = template.FuncMap{
	"fallback": func(s string) fallback {
		return fallback(s)
	},
}

Functions that are not tied to a context, but are part of the core multitemplate system

Functions

This section is empty.

Types

type Context

type Context struct {
	// Main template to be rendered, not layout
	Main string

	// Layout for rendering
	Layout string

	// Templates set for yields
	Yields map[string]string
	// Blocks (pre-rendered HTML)
	Blocks map[string]RenderedBlock
	// Base RenderArgs for the template
	Dot interface{}
	// contains filtered or unexported fields
}

A Context allows you to setup more specialized template executions, like those involving layouts

func NewContext

func NewContext(data interface{}) *Context

func (*Context) Close

func (c *Context) Close(w io.Writer) error

type Parser

type Parser interface {
	ParseTemplate(name, src string, funcs template.FuncMap) (map[string]*parse.Tree, error)
	String() string
}

The interface you must have to implement a Parser

type RenderedBlock added in v0.3.1

type RenderedBlock struct {
	Content template.HTML
	Type    Ruleset
}

type Ruleset added in v0.3.1

type Ruleset string
const (
	User Ruleset = ""
	HTML Ruleset = "&lt;&#34;&#39;."
	CSS  Ruleset = "ZgotmplZ"
	JS   Ruleset = `"\u003c\"'."`
)

type Template

type Template struct {
	Tmpl *template.Template
	Base string
	// contains filtered or unexported fields
}

func Must

func Must(t *Template, err error) *Template

func New

func New(name string) *Template

func ParseFiles

func ParseFiles(filenames ...string) (*Template, error)

func ParseGlob

func ParseGlob(pattern string) (*Template, error)

func (*Template) AddParseTree

func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error)

func (*Template) Clone

func (t *Template) Clone() (*Template, error)

func (*Template) Context

func (t *Template) Context(ctx *Context) (*Template, error)

func (*Template) Execute

func (t *Template) Execute(w io.Writer, data interface{}) error

func (*Template) ExecuteContext

func (t *Template) ExecuteContext(w io.Writer, ctx *Context) error

func (*Template) ExecuteTemplate

func (t *Template) ExecuteTemplate(w io.Writer, name string, data interface{}) error

func (*Template) Funcs

func (t *Template) Funcs(fm template.FuncMap) *Template

func (*Template) Lookup

func (t *Template) Lookup(name string) *Template

func (*Template) Name

func (t *Template) Name() string

func (*Template) Parse

func (t *Template) Parse(name, src, parser string) (*Template, error)

func (*Template) ParseFiles

func (t *Template) ParseFiles(filenames ...string) (*Template, error)

func (*Template) ParseGlob

func (t *Template) ParseGlob(pattern string) (*Template, error)

func (*Template) Templates

func (t *Template) Templates() []*Template

Directories

Path Synopsis
bham (block-based html abstraction markup) Is a restricted subset of haml that brings the usefulness of haml nesting to multitemplate.
bham (block-based html abstraction markup) Is a restricted subset of haml that brings the usefulness of haml nesting to multitemplate.
Helpers is a package that adds a set of functions to multitemplate to simplify various common html operations.
Helpers is a package that adds a set of functions to multitemplate to simplify various common html operations.
Package multirender is a middleware for Martini that provides HTML templates through multitemplate, it like to imitate and build on the render package from martini-contrib.
Package multirender is a middleware for Martini that provides HTML templates through multitemplate, it like to imitate and build on the render package from martini-contrib.
sample
This example is converted from my redup package github.com/acsellers/redup
This example is converted from my redup package github.com/acsellers/redup
mustache is a package to parse the mustache template format into the Tree format needed by multitemplate.
mustache is a package to parse the mustache template format into the Tree format needed by multitemplate.
multitemplate is a package for executing templates from multiple template languages.
multitemplate is a package for executing templates from multiple template languages.
app/controllers
multitemplate is a package that allows multiple templates written in multiple template languages to call between each other.
multitemplate is a package that allows multiple templates written in multiple template languages to call between each other.
Terse is an html templating language inspired by slim, but written for the features of multitemplate.
Terse is an html templating language inspired by slim, but written for the features of multitemplate.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL