xtemplate

package module
v0.1.2 Latest Latest
Warning

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

Go to latest
Published: Oct 13, 2023 License: Apache-2.0 Imports: 34 Imported by: 6

README

XTemplate

xtemplate is a hypertext preprocessor that extends Go's html/template library to be capable enough to host an entire server-side web application using just template definitions. Designed with the htmx.org js library in mind, which makes server side rendered sites feel as interactive as a Single Page Apps.

⚠️ This project is still in development. ⚠️


Table of contents

Features

Query the database directly within template definitions
<ul>
  {{range .Query `SELECT id,name FROM contacts`}}
  <li><a href="/contact/{{.id}}">{{.name}}</a></li>
  {{end}}
</ul>

Note: The html/template library automatically sanitizes inputs, so you can rest easy from basic XSS attacks. Note: if you generate some html that you do trust, it's easy to inject if you intend to.

Define templates and import content from other files
<html>
  <title>Home</title>
  <!-- import the contents of another file -->
  {{template "/shared/_head.html" .}}

  <body>
    <!-- invoke a custom template defined anywhere -->
    {{template "navbar" .}}
    ...
  </body>
</html>
File-based routing

GET requests for a path with a matching template file will invoke the template file at that path, with the exception that files starting with _ are not routed. This allows defining templates that only other templates can invoke.

.
├── index.html          GET /
├── todos.html          GET /todos
├── admin
│   └── settings.html   GET /admin/settings
└── shared
    └── _head.html      (not routed)
Custom routes

Create custom route handlers for any http method and parametrized path by defining a template whose name matches the pattern <method> <path>. Uses httprouter syntax for path parameters and wildcards, which are made available in the template as values in the .Param key while serving a request.

<!-- match on path parameters -->
{{define "GET /contact/:id"}}
{{$contact := .QueryRow `SELECT name,phone FROM contacts WHERE id=?` (.Params.ByName "id")}}
<div>
  <span>Name: {{$contact.name}}</span>
  <span>Phone: {{$contact.phone}}</span>
</div>
{{end}}

<!-- match on any http method -->
{{define "DELETE /contact/:id"}}
{{$_ := .Exec `DELETE from contacts WHERE id=?` (.Params.ByName "id")}}
{{.RespStatus 204}}
{{end}}
Automatic reload

Templates are reloaded and validated automatically as soon as they are modified, no need to restart the server. If there's a syntax error it continues to serve the old version and prints the loading error out in the logs.

Ctrl+S > Alt+Tab > F5

Showcase

How to use

XTemplate is three Go packages in this repository:

  • github.com/infogulch/xtemplate, a library that loads template files and implements http.Handler, routing requests to templates. Use it in your own Go application by depending on it as a library and using the XTemplate struct struct.
  • github.com/infogulch/xtemplate/bin, a simple binary that configures XTemplate with CLI args and serves http requests with it. Build it yourself or download the binary from github releases.
  • github.com/infogulch/xtemplate/caddy, a caddy module that integrates xtemplate into the caddy web server.

Template syntax

Template syntax uses Go's html/template module, and extends it with custom functions and useful context.

Context values

The dot context {{.}} set on the main template handler provides request-specific data and stateful actions. See tplcontext.go for details.

  • Request and response related fields and fields
    • .Req is the current HTTP request struct, http.Request, which has various fields, including:
      • .Method - the method
      • .URL - the URL, which in turn has component fields (Scheme, Host, Path, etc.)
      • .Header - the header fields
      • .Host - the Host or :authority header of the request
    • .OriginalReq is the original, unmodified, un-rewritten request as it originally came in over the wire. Has the same fields as .Req.
    • .Params is a list of path parameters extracted from the url based on the current route, see custom routes. {{.Params.ByName "id"}}
    • .RemoteIP is the client's IP address. {{.RemoteIP}}
    • .Host is the hostname portion (no port) of the Host header of the HTTP request.
    • .Cookie Gets the value of a cookie by name. {{.Cookie "cookiename"}}
    • .Placeholder gets a caddy "placeholder variable". The braces ({}) have to be omitted.
    • .RespStatus Set the status code of the current response. {{.RespStatus 201}}
    • .RespHeader.Add Adds a header field to the HTTP response. {{.RespHeader.Add "Field-Name" "val"}}
    • .RespHeader.Set Sets a header field on the HTTP response, replacing any existing value.
    • .RespHeader.Del Deletes a header field on the HTTP response.
  • File related funcs. File operations are rooted at the directory specified by the context_root config option.
    • .ReadFile reads and returns the contents of a file, as-is. Note that the contents are NOT escaped, so you should only read trusted files.
    • .ListFiles returns a list of the files in the given directory.
    • .FileExists returns true if filename can be opened successfully.
    • .StatFile returns Stat of a filename.
  • Database related funcs. All funcs accept a query string and any number of parameters. Prefer using parameters over building the query string dynamically.
    • .Exec executes a statment
    • .QueryRows executes a query and returns all rows in a big []map[string]any.
    • .QueryRow executes a query, which must return one row, and returns the map[string]any.
    • .QueryVal executes a query, which must return one row and one column, and returns the value of the column.
  • Other
    • .Template evaluate the template name with the given context and return the result as a string.
    • .Funcs returns a list of all the custom FuncMap funcs that are available to call. Useful in combination with the try func.
    • .Config is a map of config strings set in the Caddyfile. See Config.
Functions

There are built-in functions that perform actions that are unrelated to a specific request. See funcs.go for details.

Go stdlib template functions

See text/template#Functions.

Expand for a stdlib funcs documentation.
  • and Returns the boolean AND of its arguments by returning the first empty argument or the last argument. That is, "and x y" behaves as "if x then y else x." Evaluation proceeds through the arguments left to right and returns when the result is determined.
  • call Returns the result of calling the first argument, which must be a function, with the remaining arguments as parameters. Thus "call .X.Y 1 2" is, in Go notation, dot.X.Y(1, 2) where Y is a func-valued field, map entry, or the like. The first argument must be the result of an evaluation that yields a value of function type (as distinct from a predefined function such as print). The function must return either one or two result values, the second of which is of type error. If the arguments don't match the function or the returned error value is non-nil, execution stops.
  • html Returns the escaped HTML equivalent of the textual representation of its arguments. This function is unavailable in html/template, with a few exceptions.
  • index Returns the result of indexing its first argument by the following arguments. Thus "index x 1 2 3" is, in Go syntax, x[1][2][3]. Each indexed item must be a map, slice, or array.
  • slice slice returns the result of slicing its first argument by the remaining arguments. Thus "slice x 1 2" is, in Go syntax, x[1:2], while "slice x" is x[:], "slice x 1" is x[1:], and "slice x 1 2 3" is x[1:2:3]. The first argument must be a string, slice, or array.
  • js Returns the escaped JavaScript equivalent of the textual representation of its arguments.
  • len Returns the integer length of its argument.
  • not Returns the boolean negation of its single argument.
  • or Returns the boolean OR of its arguments by returning the first non-empty argument or the last argument, that is, "or x y" behaves as "if x then x else y". Evaluation proceeds through the arguments left to right and returns when the result is determined.
  • print An alias for fmt.Sprint
  • printf An alias for fmt.Sprintf
  • println An alias for fmt.Sprintln
  • urlquery Returns the escaped value of the textual representation of its arguments in a form suitable for embedding in a URL query. This function is unavailable in html/template, with a few exceptions.
Sprig library template functions

See the Sprig documentation for details: Sprig Function Documentation.

Expand for a listing of Sprig funcs.
xtemplate functions
  • markdown Renders the given Markdown text as HTML and returns it. This uses the Goldmark library, which is CommonMark compliant. It also has these extensions enabled: Github Flavored Markdown, Footnote, and syntax highlighting provided by Chroma.
  • splitFrontMatter Splits front matter out from the body. Front matter is metadata that appears at the very beginning of a file or string. Front matter can be in YAML, TOML, or JSON formats.
    • .Meta to access the metadata fields, for example: {{$parsed.Meta.title}}
    • .Body to access the body after the front matter, for example: {{markdown $parsed.Body}}
  • sanitizeHtml Uses bluemonday to sanitize strings with html content. {{sanitizeHtml "strict" "Shows <b>only</b> text content"}}
    • First parameter is the name of the chosen sanitization policy. "strict" = StrictPolicy(), "ugc" = UGCPolicy() for 'user generated content', "externalugc" = UGCPolicy() + disallow relative urls + add target=_blank to urls.
    • Second parameter is the content to sanitize.
    • Returns the string as a template.HTML type which can be output directly into the document without trustHtml.
  • humanize Transforms size and time inputs to a human readable format using the go-humanize library. Call with two parameters, the format type and the value to format. Format types are:
    • size which turns an integer amount of bytes into a string like 2.3 MB, for example: {{humanize "size" "2048000"}}
    • time which turns a time string into a relative time string like 2 weeks ago, for example: {{humanize "time" "Fri, 05 May 2022 15:04:05 +0200"}}
  • ksuid returns a 'K-Sortable Globally Unique ID' using segmentio/ksuid
  • idx gets an item from a list, similar to the built-in index, but with reversed args: index first, then array. This is useful to use index in a pipeline, for example: {{generate-list | idx 5}}
  • try takes a function that returns an error in the first argument and calls it with the values from the remaining arguments, and returns the result including any error as struct fields. This enables template authors to handle funcs that return errors within the template definition. Example: {{ $result := try .QueryVal "SELECT 'oops' WHERE 1=0" }}{{if $result.OK}}{{$result.Value}}{{else}}QueryVal requires exactly one row. Error: {{$result.Error}}{{end}}

Project history and license

The idea for this project started as infogulch/go-htmx (now archived), which included the first implementations of template-name-based routing, exposing sql db functions to templates, and a persistent templates instance shared across requests and reloaded when template files changed.

go-htmx was refactored and rebased on top of the templates module from the Caddy server to create caddy-xtemplate to add some extra features including reading files directly and built-in funcs for markdown conversion, and to get a jump start on supporting the broad array of web server features without having to implement them from scratch.

xtemplate is licensed under the Apache 2.0 license. See LICENSE

Documentation

Overview

xtemplate extends Go's html/template to be capable enough to define an entire server-side application with just templates.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type HandlerError

type HandlerError interface {
	Error() string
	ServeHTTP(w http.ResponseWriter, r *http.Request)
}

HandlerError is a special error that hijacks the normal response handling and passes response handling off to the ServeHTTP method on this error value.

func NewHandlerError

func NewHandlerError(name string, fn func(w http.ResponseWriter, r *http.Request)) HandlerError

NewHandlerError returns a new HandlerError based on a string and a function that matches the ServeHTTP signature.

type ReturnError

type ReturnError struct{}

ReturnError is a sentinel value returned by the `return` template func/keyword that indicates a successful/normal exit but allows the template to exit early.

func (ReturnError) Error

func (ReturnError) Error() string

type TemplateContext

type TemplateContext struct {
	Req     *http.Request
	Params  pathmatcher.Params
	Headers WrappedHeader
	Config  map[string]string
	// contains filtered or unexported fields
}

TemplateContext is the TemplateContext with which HTTP templates are executed.

func (*TemplateContext) Cookie

func (c *TemplateContext) Cookie(name string) string

Cookie gets the value of a cookie with name name.

func (*TemplateContext) Exec

func (c *TemplateContext) Exec(query string, params ...any) (result sql.Result, err error)

func (*TemplateContext) FileExists

func (c *TemplateContext) FileExists(filename string) (bool, error)

funcFileExists returns true if filename can be opened successfully.

func (*TemplateContext) Funcs

func (c *TemplateContext) Funcs() template.FuncMap

func (*TemplateContext) Host

func (c *TemplateContext) Host() (string, error)

Host returns the hostname portion of the Host header from the HTTP request.

func (*TemplateContext) ListFiles

func (c *TemplateContext) ListFiles(name string) ([]string, error)

ListFiles reads and returns a slice of names from the given directory relative to the root of c.

func (*TemplateContext) QueryRow

func (c *TemplateContext) QueryRow(query string, params ...any) (map[string]any, error)

func (*TemplateContext) QueryRows

func (c *TemplateContext) QueryRows(query string, params ...any) (rows []map[string]any, err error)

func (*TemplateContext) QueryStats

func (c *TemplateContext) QueryStats() struct {
	Count         int
	TotalDuration time.Duration
}

func (*TemplateContext) QueryVal

func (c *TemplateContext) QueryVal(query string, params ...any) (any, error)

func (*TemplateContext) ReadFile

func (c *TemplateContext) ReadFile(filename string) (string, error)

ReadFile returns the contents of a filename relative to the site root. 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 in your template.

func (*TemplateContext) RemoteIP

func (c *TemplateContext) RemoteIP() string

RemoteIP gets the IP address of the client making the request.

func (*TemplateContext) StatFile

func (c *TemplateContext) StatFile(filename string) (fs.FileInfo, error)

StatFile returns Stat of a filename

func (*TemplateContext) Status added in v0.1.2

func (c *TemplateContext) Status(status int) string

func (*TemplateContext) Template

func (c *TemplateContext) Template(name string, context any) (string, error)

func (*TemplateContext) WithVars

func (c *TemplateContext) WithVars(vars map[string]any) TemplateContextVars

type TemplateContextVars

type TemplateContextVars struct {
	*TemplateContext
	Vars map[string]any
}

type WrappedHeader

type WrappedHeader struct{ http.Header }

WrappedHeader wraps niladic functions so that they can be used in templates. (Template functions must return a value.)

func (WrappedHeader) Add

func (h WrappedHeader) Add(field, val string) string

Add adds a header field value, appending val to existing values for that field. It returns an empty string.

func (WrappedHeader) Del

func (h WrappedHeader) Del(field string) string

Del deletes a header field. It returns an empty string.

func (WrappedHeader) Set

func (h WrappedHeader) Set(field, val string) string

Set sets a header field value, overwriting any other values for that field. It returns an empty string.

type XTemplate

type XTemplate struct {
	TemplateFS fs.FS
	ContextFS  fs.FS
	ExtraFuncs []template.FuncMap
	DB         *sql.DB
	Config     map[string]string
	Delims     struct{ L, R string }
	Log        *slog.Logger
	// contains filtered or unexported fields
}

func (*XTemplate) Reload

func (t *XTemplate) Reload() error

func (*XTemplate) ServeHTTP

func (t *XTemplate) ServeHTTP(w http.ResponseWriter, r *http.Request)

Directories

Path Synopsis
app module
caddy module
cmd module
internal module
providers
nats module
register module
test module

Jump to

Keyboard shortcuts

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