blocks

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Sep 13, 2026 License: MIT Imports: 24 Imported by: 11

README

Blocks

build status report card godocs

Blocks is a view engine for Go, built on html/template. You write a layout once, write each page as a plain file, and render any page inside any layout.

  • Layouts, blocks and partials
  • Templates from a directory, an embed.FS, any fs.FS, or memory
  • Markdown pages, through goldmark
  • Reload while you edit, safely, with requests in flight
  • Template functions per engine, per layout, or shared across every engine
  • HTML comments stripped before parsing
  • Loads under a context, for a slow file system

Requires Go 1.27 or later.

Installation

go get github.com/kataras/blocks@latest

Getting started

Create a ./views directory:

│   main.go
└───views
    │   index.html
    ├───layouts
    │       main.html
    └───partials
            footer.html

./views/layouts/main.html is the frame every page renders inside:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{ if .Title }}{{ .Title }}{{ else }}Default Title{{ end }}</title>
</head>
<body>
    {{ template "content" . }}

    <footer>{{ partial "partials/footer" . }}</footer>
</body>
</html>

template "content" is where the page goes. {{ yield . }} is shorthand for the same thing. partial renders another template in place, and the dot passes the current data to it.

./views/index.html is a page. It needs no wrapper:

<h1>Index Body</h1>

./main.go loads the directory once and renders from a handler:

package main

import (
	"log"
	"net/http"

	"github.com/kataras/blocks"
)

var views = blocks.New("./views")

func main() {
	if err := views.Load(); err != nil {
		log.Fatal(err)
	}

	http.HandleFunc("/", index)

	log.Println("Listening on http://localhost:8080")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

func index(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/html; charset=utf-8")

	data := map[string]any{"Title": "Index Title"}
	if err := views.ExecuteTemplate(w, "index", "main", data); err != nil {
		http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		log.Println(err)
	}
}

Load parses every file. Do it once at startup, before serving. An engine is safe to use from many goroutines after that.

Send the error text to your log, not to the browser. A parse error names the file that failed.

Embedding the templates

embed puts the templates inside the binary, so the program has no directory to ship:

import "embed"

//go:embed views
var embedded embed.FS

var views = blocks.New(embedded).RootDir("views")

RootDir picks the directory inside the file system and is trimmed from every name, so the page above stays index rather than views/index. Any fs.FS works the same way, including what fs.Sub returns.

Names

A template is named by its path under the root, without the extension.

File Name
views/index.html index
views/admin/users.html admin/users
views/layouts/main.html main, the layout
views/notes.md notes.md

The extension is optional when you render: index and index.html are the same page. A layout may be named with or without its directory, so main, main.html and layouts/main.html all work. Markdown keeps its extension in the name, because notes.md and notes.html are two different files.

Blocks

A layout can leave a hole with a default in it, and a page can replace it:

<!-- layouts/main.html -->
<title>{{ block "title" . }}Default Title{{ end }}</title>
<!-- index.html -->
{{ define "title" }}Index Title{{ end }}
{{ define "content" }}<h1>Index Body</h1>{{ end }}

A page that defines nothing keeps the layout defaults. Note the shape of that second file: once a page defines anything, it has to define content too, because the wrapper is only added to a page that defines nothing.

Template functions

views := blocks.New("./views").
	Funcs(template.FuncMap{
		"year": func() int { return time.Now().Year() },
	})

Funcs covers pages and layouts. LayoutFuncs adds functions only layouts can call. blocks.Register adds functions to every engine in the program, which is how a package of shared helpers ships:

package myfuncs

func init() {
	blocks.Register(template.FuncMap{"markdownify": markdownify})
}

Functions are read when Load runs. Add them first.

Markdown

A .md file is rendered to HTML and then parsed as a template, so a markdown page can use actions and render inside a layout. Tables, strikethrough, task lists, definition lists, bare links and raw HTML blocks all pass through.

One sharp edge: the file is a template after the markdown is converted, so {{ inside a code block is still a template action. Escape it, or call Extensions(".md", yourParser) with a parser of your own.

Reloading while you edit

views := blocks.New("./views").Reload(true)

Every render reparses the directory. A reload builds a new set of templates and swaps it in, so a request already rendering finishes on the set it started with, and a file with a syntax error leaves the last working templates in place and returns the error. Turn it off in production: it reparses every file on every request.

Options

All of these return the engine, and all of them come before Load.

Method What it does
RootDir(dir) The directory inside the file system that holds the templates
LayoutDir(dir) Where layouts live, layouts by default
DefaultLayout(name) The layout used when you pass an empty one
Extension(ext) The template extension, .html by default
Extensions(ext, parser) A parser for another extension, or nil to ignore it
Delims(left, right) The action delimiters, {{ and }} by default
Option(opts...) The html/template options, such as missingkey=error
Funcs, LayoutFuncs, AddFunc Template functions
Reload(bool) Reparse on every render

Templates built at runtime

MemoryFileSystem holds templates with no files on disk, which suits generated pages and tests:

mfs := blocks.NewMemoryFileSystem()
mfs.ParseTemplate("layouts/main.html", []byte(`<main>{{ yield . }}</main>`), nil)
mfs.ParseTemplate("index.html", []byte(`<p>{{ greet .Name }}</p>`),
	template.FuncMap{"greet": func(s string) string { return "Hello, " + s }})

views := blocks.New(mfs)
err := views.Load()

The function map is optional. When you pass one, those functions are added to the engine at the next Load.

Examples

The _examples directory has a runnable program for each shape: a plain directory, embedded templates, several engines in one program, shared function collections, and a static site generator.

Upgrading

See CHANGELOG.md. This release requires Go 1.27, replaces the Templates and Layouts fields with Lookup and the name iterators, and drops go-bindata support in favor of embed.

License

This software is licensed under the MIT License.

Documentation

Overview

Package blocks renders HTML pages built from a layout and a content template, on top of the standard html/template.

A page is one file. A layout is a file that says where the page goes, with the yield action or with the content template action:

<!-- views/layouts/main.html -->
<html>
<head><title>{{ .Title }}</title></head>
<body>{{ yield . }}</body>
</html>

<!-- views/index.html -->
<h1>{{ .Title }}</h1>

Every page is parsed into every layout, so any page can render under any layout:

views := blocks.New("./views")
if err := views.Load(); err != nil {
	return err
}

err := views.ExecuteTemplate(w, "index", "main", map[string]any{"Title": "Home"})

Where templates come from

New accepts a directory name, any fs.FS (an embed.FS included), or a MemoryFileSystem for templates built at runtime. Embedding is what most programs want, because the binary then carries its own templates:

//go:embed views
var embedded embed.FS

views := blocks.New(embedded).RootDir("views")

Names

A template is named by its path under the root directory, without the extension, so "views/admin/users.html" is "admin/users". Layouts live in "layouts" by default, and that directory is trimmed from the name: "views/layouts/main.html" is "main". Both names may be given with the extension, and a layout with its directory, so "layouts/main.html" works too.

Blocks, partials and functions

A layout can leave a hole with the block action, and a page can fill it:

<title>{{ block "title" . }}Default{{ end }}</title>   in the layout
{{ define "title" }}Specific{{ end }}                  in the page

The partial function renders another template in place, which is how a footer or a card is reused:

<footer>{{ partial "partials/footer" . }}</footer>

Funcs adds functions for one engine, LayoutFuncs adds functions only layouts can call, and Register adds functions every engine gets. A function map is read when Load runs, so add the functions first.

What happens to a file before it is parsed

Leading and trailing whitespace is trimmed. HTML comments are removed, which has to happen because the template parser reads a define inside a comment as a real one. A file with the .md extension is rendered from markdown to HTML first, and other extensions can be handled by registering a parser with Extensions.

Loading and reloading

Load parses everything once. Reload(true) reparses on every render, for development. A reload builds a new set of templates and swaps it in, so a render already in flight keeps the set it started with, and a reparse that fails leaves the working templates in place and returns the error.

An engine is safe for concurrent use once loaded. Configure it, load it, then serve.

Example

Loading templates from an embed.FS. RootDir selects the directory inside it, so the names come out as "index" and "about" rather than "testdata/views/index".

package main

import (
	"embed"
	"log"
	"os"

	"github.com/kataras/blocks"
)

// The templates under testdata/views are compiled into the test binary, which is how a
// program that ships as one file carries its templates.
//
//go:embed testdata/views
var embedded embed.FS

func main() {
	views := blocks.New(embedded).RootDir("testdata/views")
	if err := views.Load(); err != nil {
		log.Fatal(err)
	}

	data := map[string]any{"Title": "Home", "Name": "Gopher", "Year": 2026}
	if err := views.ExecuteTemplate(os.Stdout, "index", "main", data); err != nil {
		log.Fatal(err)
	}

}
Output:
<!DOCTYPE html>
<html lang="en">
<head><title>Home</title></head>
<body>
<h1>Home</h1>
<p>Welcome, Gopher.</p>
<footer><small>2026 Blocks</small></footer>
</body>
</html>

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrNoTemplates = errors.New("blocks: no template files found")

ErrNoTemplates is returned by Load when the file system holds no file the engine can use, which usually means the directory or the extension is wrong.

Functions

func Register

func Register(funcMap template.FuncMap)

Register adds functions that every engine can use, the way database/sql.Register adds a driver. Call it from an init function, before the engines are created.

A value is usually a plain function, as html/template wants. Two other forms are accepted, for a function that needs the engine it belongs to:

func(*Blocks) any              // returns the function to register
func(*Blocks) template.FuncMap // returns a whole map, resolved the same way

Overwriting a name that is already registered is allowed, including the built-in partial. The functions are picked up by the next Load of each engine.

Usage:

package myfuncs

func init() {
	blocks.Register(template.FuncMap{"year": func() int { return time.Now().Year() }})
}

The program then imports that package for its side effect, and every engine has the function:

import _ "myfuncs"

func Set

func Set(v *Blocks) func(http.Handler) http.Handler

Set returns middleware that puts this engine in the request's context, for a program that serves different groups of routes from different engines.

See Get.

Example

Different engines for different groups of routes, reached through the request context.

package main

import (
	"embed"
	"fmt"
	"log"
	"net/http"
	"net/http/httptest"

	"github.com/kataras/blocks"
)

// The templates under testdata/views are compiled into the test binary, which is how a
// program that ships as one file carries its templates.
//
//go:embed testdata/views
var embedded embed.FS

func main() {
	admin := blocks.New(embedded).RootDir("testdata/views")
	if err := admin.Load(); err != nil {
		log.Fatal(err)
	}

	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		views := blocks.Get(r)

		data := map[string]any{"Title": "Admin", "Year": 2026}
		if err := views.ExecuteTemplate(w, "about", "main", data); err != nil {
			http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		}
	})

	w := httptest.NewRecorder()
	blocks.Set(admin)(handler).ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/admin", nil))
	fmt.Println(w.Code)

}
Output:
200

Types

type Blocks

type Blocks struct {
	// contains filtered or unexported fields
}

Blocks parses a set of template files and renders them, on their own or inside a layout. Create one with New, configure it, then call Load once before rendering.

A Blocks is safe for concurrent use once loaded, and Reload(true) stays safe: a reload builds a new set of templates and swaps it in, so a render already in flight keeps the set it started with.

func Get

func Get(r *http.Request) *Blocks

Get returns the engine Set put in the request's context, or nil when there is none.

func New

func New(fsOrDir any) *Blocks

New returns an engine that loads templates from fsOrDir, which may be:

  • a directory name, such as "./views"
  • an fs.FS, including an embed.FS and anything fs.Sub returns
  • a *MemoryFileSystem, for templates built at runtime

Embedding is the usual choice for a program that ships as a single binary:

//go:embed views
var embedded embed.FS

views := blocks.New(embedded).RootDir("views")

The engine is configured through its methods, which return the engine so calls can be chained, and which must be called before Load. Load parses the files, and nothing renders until it has run.

Functions registered with the package level Register are inherited by every engine. Use Funcs for functions this engine alone should have.

It panics if fsOrDir is none of the types above.

Example (Directory)

A directory on disk, the shape most programs start with. DefaultLayout means ExecuteTemplate can be called with an empty layout name.

package main

import (
	"log"
	"os"

	"github.com/kataras/blocks"
)

func main() {
	views := blocks.New("./testdata/views").DefaultLayout("main")
	if err := views.Load(); err != nil {
		log.Fatal(err)
	}

	data := map[string]any{"Title": "About", "Year": 2026}
	if err := views.ExecuteTemplate(os.Stdout, "about", "", data); err != nil {
		log.Fatal(err)
	}

}
Output:
<!DOCTYPE html>
<html lang="en">
<head><title>About</title></head>
<body>
<h1>About</h1>
<footer><small>2026 Blocks</small></footer>
</body>
</html>

func (*Blocks) AddFunc added in v0.0.10

func (v *Blocks) AddFunc(funcName string, fn any)

AddFunc adds one function, the way Funcs adds a map. It takes effect at the next Load.

func (*Blocks) DefaultLayout

func (v *Blocks) DefaultLayout(layoutName string) *Blocks

DefaultLayout sets the layout ExecuteTemplate uses when it is given an empty one.

func (*Blocks) Delims

func (v *Blocks) Delims(left, right string) *Blocks

Delims sets the action delimiters, which default to "{{" and "}}". An empty delimiter keeps its default. Layout detection follows the delimiters, so a layout written with custom ones is still recognized.

It panics if a delimiter is not valid UTF-8. The delimiters become part of the expressions that read each file, and an expression cannot hold an invalid rune.

func (*Blocks) ExecuteTemplate

func (v *Blocks) ExecuteTemplate(w io.Writer, tmplName, layoutName string, data any) error

ExecuteTemplate renders the template named tmplName into w, inside the layout named layoutName, with data as the template's dot.

An empty layoutName falls back to the one given to DefaultLayout, and when that is empty too the template renders on its own. Either name may be given with or without the file extension, and a layout may be named with or without its directory.

The error is an ErrNotExist when a name matches nothing loaded. Output may already have been written to w when an execution error comes back.

It is safe to call from many goroutines. If they share a w, their output interleaves.

Example

Rendering from an HTTP handler. Set the content type yourself: the engine writes the body and nothing else.

package main

import (
	"embed"
	"fmt"
	"log"
	"net/http"
	"net/http/httptest"

	"github.com/kataras/blocks"
)

// The templates under testdata/views are compiled into the test binary, which is how a
// program that ships as one file carries its templates.
//
//go:embed testdata/views
var embedded embed.FS

func main() {
	views := blocks.New(embedded).RootDir("testdata/views")
	if err := views.Load(); err != nil {
		log.Fatal(err)
	}

	handler := func(w http.ResponseWriter, _ *http.Request) {
		w.Header().Set("Content-Type", "text/html; charset=utf-8")

		data := map[string]any{"Title": "Home", "Name": "Gopher", "Year": 2026}
		if err := views.ExecuteTemplate(w, "index", "main", data); err != nil {
			http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		}
	}

	w := httptest.NewRecorder()
	handler(w, httptest.NewRequest(http.MethodGet, "/", nil))
	fmt.Println(w.Code, w.Header().Get("Content-Type"))

}
Output:
200 text/html; charset=utf-8

func (*Blocks) Ext added in v0.0.10

func (v *Blocks) Ext() string

Ext returns the template file extension, with its dot.

func (*Blocks) Extension

func (v *Blocks) Extension(ext string) *Blocks

Extension sets the template file extension, with its dot. It defaults to ".html".

func (*Blocks) Extensions

func (v *Blocks) Extensions(ext string, parser ExtensionParser) *Blocks

Extensions registers a parser that runs on a file's contents before they are parsed as a template. The extension starts with a dot, such as ".md".

One entry is registered by default, ".md", which renders markdown to HTML. Pass a nil parser to drop an extension, after which files carrying it are ignored unless it is the engine's own extension.

func (*Blocks) Funcs

func (v *Blocks) Funcs(funcMap template.FuncMap) *Blocks

Funcs adds functions that every template can call, layouts included. The map is copied, so the caller may keep using it. Overwriting a name that is already there is allowed.

The functions take effect at the next Load: templates that are already parsed keep the functions they were parsed with.

It panics if a value is not a function with a return type html/template accepts.

func (*Blocks) LayoutDir

func (v *Blocks) LayoutDir(relToDirLayoutDir string) *Blocks

LayoutDir sets the directory that holds the layouts, relative to the root one. It defaults to "layouts".

The directory is trimmed from a layout's name, so the file "layouts/main.html" is the layout "main". Naming it in full works too.

func (*Blocks) LayoutFuncs

func (v *Blocks) LayoutFuncs(funcMap template.FuncMap) *Blocks

LayoutFuncs adds functions that only layouts can call. The map is copied.

A name added here can be overridden by Funcs, which is applied after it.

It panics if a value is not a function with a return type html/template accepts.

func (*Blocks) LayoutNames added in v0.1.0

func (v *Blocks) LayoutNames() iter.Seq[string]

LayoutNames iterates the loaded layout names in order.

func (*Blocks) Load

func (v *Blocks) Load() error

Load parses the templates, including the layouts, into the engine. Call it once after configuring the engine and before rendering anything.

If it fails, the templates from the previous successful Load keep working.

func (*Blocks) LoadWithContext

func (v *Blocks) LoadWithContext(ctx context.Context) error

LoadWithContext is Load with a context, for a file system slow enough to be worth cancelling: a network mount, or a very large directory.

func (*Blocks) Lookup added in v0.1.0

func (v *Blocks) Lookup(tmplName, layoutName string) *template.Template

Lookup returns the parsed template for a name, or nil when there is none. Give an empty layoutName for the template on its own. Names are normalized the way ExecuteTemplate normalizes them.

The returned template is the live one. Read it and execute it, but do not reparse or reconfigure it: it is shared with every other render, and the next Load replaces it.

Example

Lookup reaches the parsed template, for rendering one block on its own. Treat what it returns as read-only.

package main

import (
	"embed"
	"log"
	"os"

	"github.com/kataras/blocks"
)

// The templates under testdata/views are compiled into the test binary, which is how a
// program that ships as one file carries its templates.
//
//go:embed testdata/views
var embedded embed.FS

func main() {
	views := blocks.New(embedded).RootDir("testdata/views")
	if err := views.Load(); err != nil {
		log.Fatal(err)
	}

	tmpl := views.Lookup("partials/footer", "")
	if err := tmpl.ExecuteTemplate(os.Stdout, "content", map[string]any{"Year": 2026}); err != nil {
		log.Fatal(err)
	}

}
Output:
<small>2026 Blocks</small>

func (*Blocks) Option

func (v *Blocks) Option(opt ...string) *Blocks

Option sets options for the templates, each a plain string or "key=value". The options reach every template, layouts included.

The one option html/template defines is missingkey, which says what happens when a map is indexed with a key it does not hold:

"missingkey=default" or "missingkey=invalid"
	Do nothing and continue. A printed value becomes "<no value>".
"missingkey=zero"
	Use the zero value for the map's element type.
"missingkey=error"
	Stop execution with an error.

It panics if an option string is not recognized.

func (*Blocks) PartialFunc

func (v *Blocks) PartialFunc(partialName string, data any) (template.HTML, error)

PartialFunc renders a template's content block and returns it as HTML, which is what the partial template function calls.

func (*Blocks) Reload

func (v *Blocks) Reload(b bool) *Blocks

Reload makes ExecuteTemplate reparse the templates on every call, which is what you want while editing them and not in production. A failed reparse keeps the templates that already work and returns the error.

func (*Blocks) RootDir added in v0.0.3

func (v *Blocks) RootDir(root string) *Blocks

RootDir sets the directory inside the file system that holds the templates. Everything outside it is ignored, and template names are relative to it.

The directory is a slash separated path. A leading slash, a leading "./" and a trailing slash are all accepted, so "views", "/views", "./views" and "views/" name the same directory. An empty value, "/" or "." means the whole file system.

It panics if the path cannot name a directory, such as one that climbs out of the file system with "..".

func (*Blocks) TemplateNames added in v0.1.0

func (v *Blocks) TemplateNames() iter.Seq[string]

TemplateNames iterates the loaded template names in order.

Example

What was loaded, by name.

package main

import (
	"embed"
	"fmt"
	"log"

	"github.com/kataras/blocks"
)

// The templates under testdata/views are compiled into the test binary, which is how a
// program that ships as one file carries its templates.
//
//go:embed testdata/views
var embedded embed.FS

func main() {
	views := blocks.New(embedded).RootDir("testdata/views")
	if err := views.Load(); err != nil {
		log.Fatal(err)
	}

	for name := range views.TemplateNames() {
		fmt.Println(name)
	}
	for name := range views.LayoutNames() {
		fmt.Println("layout:", name)
	}

}
Output:
about
index
partials/footer
layout: main

func (*Blocks) TemplateString added in v0.0.11

func (v *Blocks) TemplateString(tmplName, layoutName string, data any) (string, error)

TemplateString renders a template and returns the result, the way ExecuteTemplate renders it into a writer.

It does not reload in Reload mode. Call Load first when the files may have changed.

type ContextKeyType

type ContextKeyType struct{}

ContextKeyType is the type of the request context value that carries an engine.

See Set and Get.

var ContextKey ContextKeyType

ContextKey is the request context key an engine is stored under.

See Set and Get.

type ErrNotExist

type ErrNotExist struct {
	// Name is the template that was asked for.
	Name string
	// Layout is the layout that was asked for, empty when there was none.
	Layout string
	// IsLayout reports whether the layout is the part that is missing.
	IsLayout bool
}

ErrNotExist reports that a template or a layout is not among the parsed ones. It matches fs.ErrNotExist, so errors.Is(err, fs.ErrNotExist) is true.

func (ErrNotExist) Error

func (e ErrNotExist) Error() string

Error implements the error interface.

func (ErrNotExist) Is added in v0.1.0

func (e ErrNotExist) Is(target error) bool

Is reports that this error matches fs.ErrNotExist.

type ExtensionParser

type ExtensionParser func([]byte) ([]byte, error)

ExtensionParser converts a file's contents before they are parsed as a template. It is how a file that is not HTML, such as markdown, becomes one.

type FuncMapper added in v0.1.0

type FuncMapper interface {
	TemplateFuncs() template.FuncMap
}

FuncMapper is implemented by a file system that carries template functions of its own. An engine asks for them at every Load and adds them to the function map, under anything given to Funcs. MemoryFileSystem implements it, which is how the function map passed to ParseTemplate reaches the templates.

type LoadError added in v0.1.0

type LoadError struct {
	// File is the name of the file that failed, as it appears in the file system.
	File string
	// Err is the underlying parse error.
	Err error
}

LoadError reports that one file could not be parsed. It names the file and wraps the error from html/template.

It never carries the file's contents. A caller that hands err.Error() to an HTTP response would otherwise publish the template source.

func (*LoadError) Error added in v0.1.0

func (e *LoadError) Error() string

Error implements the error interface.

func (*LoadError) Unwrap added in v0.1.0

func (e *LoadError) Unwrap() error

Unwrap returns the underlying parse error.

type MemoryFileSystem added in v0.0.11

type MemoryFileSystem struct {
	// contains filtered or unexported fields
}

MemoryFileSystem holds template files in memory. It implements fs.FS, so it can be passed to New like any other file system, and it is safe for concurrent use.

Use it when the templates are built at runtime, or when a test would rather not write files to disk.

mfs := blocks.NewMemoryFileSystem()
err := mfs.ParseTemplate("index.html", []byte("<h1>Hello, {{ .Name }}!</h1>"), nil)
views := blocks.New(mfs)
err = views.Load()

func NewMemoryFileSystem added in v0.0.11

func NewMemoryFileSystem() *MemoryFileSystem

NewMemoryFileSystem returns an empty file system. Add files to it with ParseTemplate.

Example

Templates built in memory, with no files on disk. The function map given to ParseTemplate is added to the engine, so every template can call greet.

package main

import (
	"fmt"
	"html/template"
	"log"

	"github.com/kataras/blocks"
)

func main() {
	mfs := blocks.NewMemoryFileSystem()

	err := mfs.ParseTemplate("layouts/main.html",
		[]byte(`<main>{{ yield . }}</main>`), nil)
	if err != nil {
		log.Fatal(err)
	}

	err = mfs.ParseTemplate("index.html", []byte(`<p>{{ greet .Name }}</p>`),
		template.FuncMap{"greet": func(name string) string { return "Hello, " + name }})
	if err != nil {
		log.Fatal(err)
	}

	views := blocks.New(mfs)
	if err = views.Load(); err != nil {
		log.Fatal(err)
	}

	out, err := views.TemplateString("index", "main", map[string]any{"Name": "Gopher"})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(out)

}
Output:
<main><p>Hello, Gopher</p></main>

func (*MemoryFileSystem) Open added in v0.0.11

func (mfs *MemoryFileSystem) Open(name string) (fs.File, error)

Open implements fs.FS. Each call returns an independent reader, so one file can be read by many goroutines at once.

func (*MemoryFileSystem) ParseTemplate added in v0.0.11

func (mfs *MemoryFileSystem) ParseTemplate(name string, contents []byte, funcMap template.FuncMap) error

ParseTemplate stores a template file under the given name, which is a slash separated path such as "layouts/main.html". A name may be given with a leading slash or with backslashes, and both are normalized. It returns an error if the name escapes the file system, or if a value in funcMap is not a function usable by html/template.

The contents are copied, so the caller may reuse the slice.

The functions in funcMap are added to the engine's function map the next time the engine loads, which makes them available to every template rather than to this one alone. A nil funcMap adds nothing. Calling ParseTemplate again with the same name replaces the file.

func (*MemoryFileSystem) ReadDir added in v0.0.11

func (mfs *MemoryFileSystem) ReadDir(name string) ([]fs.DirEntry, error)

ReadDir implements fs.ReadDirFS. The entries are sorted by file name.

func (*MemoryFileSystem) ReadFile added in v0.1.0

func (mfs *MemoryFileSystem) ReadFile(name string) ([]byte, error)

ReadFile implements fs.ReadFileFS. The returned slice is a copy.

func (*MemoryFileSystem) Stat added in v0.1.0

func (mfs *MemoryFileSystem) Stat(name string) (fs.FileInfo, error)

Stat implements fs.StatFS.

func (*MemoryFileSystem) TemplateFuncs added in v0.1.0

func (mfs *MemoryFileSystem) TemplateFuncs() template.FuncMap

TemplateFuncs returns the functions collected from every ParseTemplate call, or nil if there are none. An engine calls it at Load.

Directories

Path Synopsis
_examples
basic command
embedded command
funcs command
funcs/mycollection
Package mycollection contains a template.FuncMap that should be registered across all Blocks view engines.
Package mycollection contains a template.FuncMap that should be registered across all Blocks view engines.
multiple command

Jump to

Keyboard shortcuts

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