salix

package module
v0.0.0-...-bb9b6c8 Latest Latest
Warning

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

Go to latest
Published: Mar 20, 2024 License: MPL-2.0 Imports: 18 Imported by: 1

README

Salix Logo

 

Go Reference Go Report Card

Salix (pronounced say-lix) is a Go templating engine inspired by Leaf.

Salix's syntax is similar to Leaf and (in my opinion at least), it's much more fun to write than the Go template syntax. If you like this project, please star the repo. I hope you enjoy! :)

Table of contents

Examples

Template

<html>
    <head>
        <title>#(page.Title)</title>
    </head>
    <body>
        #for(i, user in users):
        <div>
            <h2>#(toLower(user.Name))</h2>
            <p>User ID: #(i)</p>
            #if(user.LoggedIn): <p>This user is logged in</p> #!if
            #if(user.IsAdmin): <p>This user is an admin!</p> #!if
            <p>Registered: #(user.RegisteredTime.Format("01-02-2006"))</p>
        </div>
        #!for
    </body>
</html>

API Usage

t, err := salix.New().ParseFile("example.salix.txt")
if err != nil {
  panic(err)
}

err = t.WithVarMap(vars).
    WithTagMap(tags).
    WithEscapeHTML(true).
    Execute(os.Stdout)
if err != nil {
  panic(err)
}

See the examples directory for more examples.

Tags

In Salix, tags have full control over the Abstract Syntax Tree (AST), which allows them to do things the language wouldn't ordinarily allow. Salix's if statements, for loops, macros, includes, etc. are implemented as tags.

Creating custom tags

You can extend the capabilities of Salix by creating custom tags. To create a custom tag, you need to implement the salix.Tag interface and add it to the tag map of your template or namespace using the WithTagMap method.

Salix tags follow a distinctive syntax pattern. They start with a pound sign (#), followed by a name and optional arguments. Tags can also enclose a block of content, and if they do, the block is terminated by an end tag with the same name. Here's an example of a template with both a macro tag and an include tag:

#macro("example"):
    Content
#!macro

#include("template.html")

In this example:

  • The macro tag has a block, indicated by the content enclosed between #macro("example"): and #!macro.
  • The include tag doesn't have a block; it simply includes the content of template.html.

for tag

Salix's for tag is used for iterating over slices, arrays, and maps. It can assign one or two variables depending on your needs. When using a single variable, it sets that variable to the current element in the case of slices or arrays, or the current value for maps. With two variables, it assigns the first to the index (in the case of slices or arrays) or the key (for maps), and the second to the element or value, respectively. Here's an example of the for tag in action:

#for(id, name in users):
	Name: #(name)
	ID:   #(id)
#!for

if tag

The if tag in Salix allows you to create conditional statements within your templates. It evaluates specified conditions and includes the enclosed content only if the condition is true. Here's an example:

#if(weather.Temp > 30):
	<p>It's a hot day!</p>
#elif(weather.Temp < 0):
	<p>It's freezing!</p>
#else:
	<p>The temperature is between 0 and 30</p<
#!if

include tag

The include tag allows you to import content from other templates in the namespace, into your current template, making it easier to manage complex templates. Here's an example of the include tag:

#include("header.html")
Using the include tag with extra arguments

The include tag can accept extra local variables as arguments. Here's an example with a title variable:

#include("header.html", title = "Home")

These local variables will then be defined in the included template.

macro tag

The macro tag is a powerful feature that allows you to define reusable template sections. These sections can be included later in the current template or in other templates that were included by the include tag. Here's an example of the macro tag:

#macro("content"): <!-- This defines a macro called "content" -->
    Content
#!macro

#macro("content") <!-- This inserts the content macro -->

When a macro tag has a block, it sets the macro's content. When it doesn't, it inserts the contents of the macro. In the above example, a macro is defined and then inserted.

Using the macro tag with extra arguments

Similar to the include tag, the macro tag can accept extra local variables as arguments. You can define these variables when including the macro. Here's an example:

#macro("content", x = 1, y = x + 2)

Functions

Functions used in a template can accept any number of arguments but are limited to returning a maximum of two values. When a function returns two values, the second one must be an error value.

Global Functions

Salix includes several useful global functions in all templates:

  • len(v any) int: Returns the length of the value passed in. If the length can't be found for the value passed in, it returns an error.
  • json(v any) string: Returns a JSON string for the value passed in.
  • toUpper(s string) string: Returns s, but with all characters mapped to their uppercase equivalents.
  • toLower(s string) string: Returns s, but with all characters mapped to their lowercase equivalents.
  • hasPrefix(s, prefix string) bool: Returns true if s starts with prefix.
  • trimPrefix(s, prefix string) string: Returns s, but with prefix removed from the beginning.
  • hasSuffix(s, suffix string) bool: Returns true if s ends with suffix.
  • trimSuffix(s, suffix string) string: Returns s, but with suffix removed from the end.
  • trimSpace(s string) string: Returns s, but with any whitespace characters removed from the beginning and end.
  • equalFold(s1, s2 string) bool: Returns true if s1 is equal to s2 under Unicode case-folding, which is a general form of case-insensitivity.
  • count(s, substr string) int: Returns the amount of times that substr appears in s.
  • split(s, sep string) []string: Returns a slice containing all substrings separated by sep.
  • join(ss []string, sep string) string: Returns a string with all substrings in ss joined by sep.
  • replace(s, old, new string, n int): Returns a string with n occurrences of old in s replaced with new.
  • replaceAll(s, old, new string): Returns a string with all occurrences of old in s replaced with new.

Adding Custom Functions

You can include custom functions as variables using the WithVarMap method on templates or namespaces. Methods that fit the above conditions can also be used as template functions.

Expressions

Salix's expressions mostly work like Go's, but there are some extra features worth mentioning.

Ignoring errors

If you'd like to ignore errors in an expression tag, you can do that by adding a question mark after the pound symbol.

<!-- This would return an error if example wasn't defined or if it didn't have an Example() method -->
#(example.Example())

<!-- This would ignore any error and keep executing the rest of the template -->
#?(example.Example())

Ternary Expressions

Salix supports ternary expressions, which allow you to choose a value based on whether a condition is true. For example:

#(len(matches) > 1 ? "several matches" : "one match")

This example returns "several matches" if the length of matches is greater than one. Otherwise, it returns "one match".

Coalescing operator

The coalescing operator allows you to return a default value if a variable isn't defined. Here's an example:

<title>#(title | "Home")</title>

In this case, the expression will return the content of the title variable if it's defined. If not, it will return "Home" as the default value.

The in operator

Salix's in operator allows you to check if a slice or array contains an element, if a map contains a key, or if a string contains a substring. Here's one example:

#("H" in "Hello") <!-- Returns true -->

Acknowledgements

  • Pigeon: Salix uses a PEG parser generated by pigeon. Salix would've been a lot more difficult to write without it.
  • Leaf: Leaf was the first templaing language I ever used, and it inspired a lot of the syntax I've implemented in Salix because I found it really fun to use.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrIncludeInvalidArgs = errors.New("include expects one string argument")
	ErrNoSuchTemplate     = errors.New("no such template")
)

Functions

This section is empty.

Types

type FSTag

type FSTag struct {
	// FS is the filesystem that files will be loaded from.
	FS fs.FS

	// PathPrefix is joined to the path string before a file is read.
	PathPrefix string

	// Extension is appended to the end of the path string before a file is read.
	Extension string
}

FSTag writes files from an fs.FS to a template

No escaping is done on the files, so make sure to avoid user-generated data.

func (FSTag) Run

func (ft FSTag) Run(tc *TagContext, block, args []ast.Node) error

type HTML

type HTML string

HTML represents unescaped HTML strings

type NamedReader

type NamedReader interface {
	io.Reader
	Name() string
}

NamedReader is a reader with a name

type Namespace

type Namespace struct {

	// WhitespaceMutations enables postprocessing to remove whitespace where it isn't needed
	// to make the resulting document look better. Postprocessing is only done once when the
	// template is parsed, so it will not affect performance. (default: true)
	WhitespaceMutations bool
	// WriteOnSuccess indicates whether the output should only be written if generation fully succeeds.
	// This option buffers the output of the template, so it will use more memory. (default: false)
	WriteOnSuccess bool
	// NilToZero indictes whether nil pointer values should be converted to zero values of their underlying
	// types.
	NilToZero bool
	// contains filtered or unexported fields
}

Namespace represents a collection of templates that can include each other

func New

func New() *Namespace

New returns a new template namespace

func (*Namespace) ExecuteTemplate

func (n *Namespace) ExecuteTemplate(w io.Writer, name string, vars map[string]any) error

ExecuteTemplate gets and executes a template with the given name.

func (*Namespace) GetTemplate

func (n *Namespace) GetTemplate(name string) (Template, bool)

GetTemplate tries to get a template from the namespace's template map. If it finds the template, it returns the template and true. If it doesn't find it, it returns nil and false.

func (*Namespace) MustGetTemplate

func (n *Namespace) MustGetTemplate(name string) Template

MustGetTemplate is the same as GetTemplate but it panics if the template doesn't exist in the namespace.

func (*Namespace) Parse

func (n *Namespace) Parse(r NamedReader) (Template, error)

Parse parses a salix template from a NamedReader, which is an io.Reader with a Name method that returns a string. os.File implements NamedReader.

func (*Namespace) ParseBytes

func (t *Namespace) ParseBytes(filename string, tmpl []byte) (Template, error)

ParseString parses bytes using the given filename.

func (*Namespace) ParseFS

func (t *Namespace) ParseFS(fsys fs.FS, path string) (Template, error)

ParseFile parses a file at the given path in a filesystem. It uses the path as the name.

func (*Namespace) ParseFSGlob

func (t *Namespace) ParseFSGlob(fsys fs.FS, glob string) error

ParseGlob parses all the files in the filesystem that were matched by the given glob and adds them to the namespace.

func (*Namespace) ParseFile

func (t *Namespace) ParseFile(path string) (Template, error)

ParseFile parses the file at path as a salix template. It uses the path as the name.

func (*Namespace) ParseGlob

func (t *Namespace) ParseGlob(glob string) error

ParseGlob parses all the files that were matched by the given glob nd adds them to the namespace.

func (*Namespace) ParseString

func (t *Namespace) ParseString(filename, tmpl string) (Template, error)

ParseString parses a string using the given filename.

func (*Namespace) ParseWithName

func (n *Namespace) ParseWithName(name string, r io.Reader) (Template, error)

ParseWithFilename parses a salix template from r, using the given name.

func (*Namespace) WithEscapeHTML

func (n *Namespace) WithEscapeHTML(b bool) *Namespace

WithEscapeHTML turns HTML escaping on or off for the namespace

func (*Namespace) WithNilToZero

func (n *Namespace) WithNilToZero(b bool) *Namespace

WithNilToZero enables or disables conversion of nil values to zero values for the namespace

func (*Namespace) WithTagMap

func (n *Namespace) WithTagMap(m map[string]Tag) *Namespace

WithTagMap sets the namespace's tag map to m

func (*Namespace) WithVarMap

func (n *Namespace) WithVarMap(m map[string]any) *Namespace

WithVarMap sets the namespace's variable map to m

func (*Namespace) WithWhitespaceMutations

func (n *Namespace) WithWhitespaceMutations(b bool) *Namespace

WithWhitespaceMutations turns whitespace mutations on or off for the namespace

func (*Namespace) WithWriteOnSuccess

func (n *Namespace) WithWriteOnSuccess(b bool) *Namespace

WithWriteOnSuccess enables or disables only writing if generation fully succeeds.

type Tag

type Tag interface {
	Run(tc *TagContext, block, args []ast.Node) error
}

Tag represents a tag in a Salix template

type TagContext

type TagContext struct {
	Tag ast.Tag
	// contains filtered or unexported fields
}

TagContext is passed to Tag implementations to allow them to control the interpreter

func (*TagContext) Execute

func (tc *TagContext) Execute(nodes []ast.Node, local map[string]any) error

Execute runs the interpreter on the given AST nodes, with the given local variables.

func (*TagContext) ExecuteToMemory

func (tc *TagContext) ExecuteToMemory(nodes []ast.Node, local map[string]any) ([]byte, error)

ExecuteToMemory runs the interpreter on the given AST nodes, with the given local variables, and returns the resulting bytes rather than writing them out.

func (*TagContext) GetValue

func (tc *TagContext) GetValue(node ast.Node, local map[string]any) (any, error)

GetValue evaluates the given AST node using the given local variables.

func (*TagContext) NodeToString

func (tc *TagContext) NodeToString(node ast.Node) string

NodeToString returns a textual representation of the given AST node for users to see, such as in error messages. This does not directly correlate to Salix source code.

func (*TagContext) PosError

func (tc *TagContext) PosError(node ast.Node, format string, v ...any) error

PosError returns an error with the file position prepended. This should be used for errors wherever possible, to make it easier for users to find errors.

func (*TagContext) Write

func (tc *TagContext) Write(b []byte) (int, error)

Write writes b to the underlying writer. It implements the io.Writer interface.

type Template

type Template struct {

	// WriteOnSuccess indicates whether the output should only be written if generation fully succeeds.
	// This option buffers the output of the template, so it will use more memory. (default: false)
	WriteOnSuccess bool
	NilToZero      bool
	// contains filtered or unexported fields
}

Template represents a Salix template

func (Template) Execute

func (t Template) Execute(w io.Writer) error

Execute executes a parsed template and writes the result to w.

func (Template) WithEscapeHTML

func (t Template) WithEscapeHTML(b bool) Template

WithEscapeHTML returns a copy of the template with HTML escaping enabled or disabled. The HTML escaping functionality is NOT context-aware. Using the HTML type allows you to get around the escaping if needed.

func (Template) WithNilToZero

func (t Template) WithNilToZero(b bool) Template

WithNilToZero enables or disables conversion of nil values to zero values.

func (Template) WithTagMap

func (t Template) WithTagMap(m map[string]Tag) Template

WithTagMap returns a copy of the template with its tag map set to m.

func (Template) WithVarMap

func (t Template) WithVarMap(m map[string]any) Template

WithVarMap returns a copy of the template with its variable map set to m.

func (Template) WithWriteOnSuccess

func (t Template) WithWriteOnSuccess(b bool) Template

WithWriteOnSuccess enables or disables only writing if generation fully succeeds.

Directories

Path Synopsis
examples

Jump to

Keyboard shortcuts

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