Documentation
¶
Overview ¶
Package goxic implements the fractal[qb] toxic template engine concept for the Go programming language. For details on the idea behind the toxic template engine see: https://fractalqb.de/toxic/index.html
Designing good programming languages is hard, even for DSLs. As a developer, I also want to minimise the number of DSLs that I have to master. For this reason alone, it makes sense to control the logic of a template from the host programming language. In addition, the host language usually provides more sophisticated tool support – in the language itself, in the IDE and in the tests.
Example ¶
package main import ( "io" "os" ) /* NO ERROR HANDLING FOR BREVITY */ // The template to be parsed – often one parses templates from a files const template = `<!DOCTYPE html> <html lang="{lang}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>goxic Example</title> </head> <body> <h1>Personen</h1> <table> <!-- \ >>> rows <<< \ --> <!-- >>> table-row >>> --> <tr> <td>{given-name}</td> <td>{familyname}</td> </tr> <!-- \ <<< table-row <<< --> </table> </body> </html>` // Index maps for efficient field access for table-rows var imapRow struct { *Template GivenName Fields `goxic:"given-name"` Familyname Fields `goxic:"familyname"` } // Some data to be used in the template type person struct { givenName string familyname string } var persons = []person{ {"Liese", "Müller"}, {"Max", "Mustermann"}, } func main() { // Cerate a simple parser that can handle HTML templates (insecure, no escaping) parser := Parser{ CommentLine: NewBlockComment("<!--", "-->").Line, InlineStart: '{', // Repeat runes to put them InlineEnd: '}', // into normal template text. } // Parse the template; root template gets map key "" tmpl, _ := parser.ParseString(template) // Initialise index map for table-rows MapIndices(&imapRow, tmpl["table-row"], MapAll) // Bind root template fields without index map rootBind := tmpl[""].NewBindings(nil) // Bindings for root template rootBind.BindName("lang", Str("de")) rowBind := imapRow.NewBindings(nil) // Bindings for row template (reused) // Bind a function that generates all rows rootBind.BindName("rows", Func(func(w io.Writer) (n int64, err error) { for _, p := range persons { imapRow.GivenName.Bind(rowBind, Str(p.givenName)) imapRow.Familyname.Bind(rowBind, Str(p.familyname)) m, err := rowBind.WriteTo(w) rowBind.Reset() n += m if err != nil { return n, err } } return n, nil })) // Write template with bindings to stdout rootBind.WriteTo(os.Stdout) }
Output: <!DOCTYPE html> <html lang="de"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>goxic Example</title> </head> <body> <h1>Personen</h1> <table> <tr> <td>Liese</td> <td>Müller</td> </tr> <tr> <td>Max</td> <td>Mustermann</td> </tr> </table> </body> </html>
Index ¶
- Constants
- Variables
- func BindByName(b *Bindings, nmap []Named, content ByNamer)
- func Field(name string, esc ...Escaper) appendField
- func Fmt(format string, args ...any) io.WriterTo
- func MapIndices(m any, t *Template, mode MapMode) error
- func MultiMapIndices(ts map[string]*Template, mode MapMode, idxMaps map[string]any) error
- func NamesFields(t *Template, anon bool) map[string]Named
- func NamesIndices(t *Template, anon bool) map[string]Fields
- func Print(v any) io.WriterTo
- func Seq[S iter.Seq[E], E any](seq S, b *Bindings, bind func(int, E, *Bindings)) io.WriterTo
- func Slice[S ~[]E, E any](es S, b *Bindings, bind func(int, E, *Bindings)) io.WriterTo
- func Time(t time.Time, layout string) io.WriterTo
- func ToStr(s fmt.Stringer) io.WriterTo
- type Bindings
- func (b *Bindings) Bind(field int, content io.WriterTo) error
- func (b *Bindings) BindAll(content io.WriterTo)
- func (b *Bindings) BindName(name string, content io.WriterTo) error
- func (b *Bindings) BindOrName(field int, content io.WriterTo) error
- func (bds *Bindings) Binding(field int) io.WriterTo
- func (b *Bindings) Binds(content io.WriterTo, fields ...int) error
- func (bds *Bindings) Fixate(keepAnon bool, name func(path, name string) string) (*Template, error)
- func (b *Bindings) NumField() int
- func (bds *Bindings) Reset() *Bindings
- func (bds *Bindings) String() string
- func (b *Bindings) Template() *Template
- func (bds *Bindings) WriteTo(w io.Writer) (n int64, err error)
- type BlockComment
- type ByNameFunc
- type ByNamer
- type Bytes
- type EscapeChain
- type EscapeFunc
- type EscapeWriter
- type Escaper
- type Fields
- type Func
- type Int
- type LineComment
- type MapMode
- type Named
- type Parser
- func (p *Parser) Parse(r io.Reader) (tmpls map[string]*Template, err error)
- func (p *Parser) ParseFS(fs fs.FS, name string) (tmpls map[string]*Template, err error)
- func (p *Parser) ParseFile(name string) (tmpls map[string]*Template, err error)
- func (p *Parser) ParseString(s string) (tmpls map[string]*Template, err error)
- type Separator
- type Str
- type Template
- func (t *Template) Append(ff ...any) error
- func (t *Template) AppendField(name string, esc Escaper) error
- func (t *Template) AppendFix(fix []byte) error
- func (t *Template) AppendFixString(fix string) error
- func (t *Template) Field(field int) (string, Escaper)
- func (t *Template) Fields(name string) iter.Seq[int]
- func (t *Template) Fix(i int) []byte
- func (t *Template) ListNames(anon bool) (ls []string)
- func (t *Template) MustStatic() Bytes
- func (t *Template) Names() map[string]int
- func (t *Template) NewBindings(reuse *Bindings) *Bindings
- func (t *Template) NumFields() int
- func (t *Template) NumFix() int
- func (t *Template) Pack()
- func (t *Template) SetField(field int, name string, esc Escaper) error
- func (t *Template) SetFix(i int, fix []byte) error
- func (t *Template) SetFixString(i int, fix string) error
- func (t *Template) Static() []byte
- type Uint
- type UnknownField
Examples ¶
Constants ¶
const ( MapAllNamedFields MapMode = (1 << iota) MapAllAnonFields MapAllIndices MapFields = MapAllNamedFields | MapAllAnonFields MapAll = MapAllNamedFields | MapAllIndices MapComplete = MapAll | MapAllAnonFields )
const NamePath = Separator(".")
Variables ¶
var Empty empty
Functions ¶
func BindByName ¶ added in v0.13.1
func Field ¶ added in v0.13.0
Field is used with Template.Append to build templates.
func MapIndices ¶ added in v0.13.0
MapIndices fills the index map m from the Template t. An index map is a struct with an anonymous embedded Template pointer and exported fields of type Fields.
Example ¶
// Explicitly build template, parsing is simpler var htmlTmpl Template htmlTmpl.AppendFixString("<html>\n") htmlTmpl.AppendFixString(" <title>") htmlTmpl.AppendField("title", nil) // !No escaper htmlTmpl.AppendFixString("</title>\n <body>\n <h1>") htmlTmpl.AppendField("title", nil) // !No escaper htmlTmpl.AppendFixString("</h1>\n") htmlTmpl.AppendField("Body", nil) // !No escaper htmlTmpl.AppendFixString("\n </body>\n</html>\n") // The index map: var htmlMap struct { *Template // Embed the template pointer Title Fields `goxic:"title"` // Set template field name Body Named // Template field name is the same: "Body" Ignore Fields `goxic:"-"` // Exclude from index mapping } // Fill htmlMap from template MapIndices(&htmlMap, &htmlTmpl, MapAll) b := htmlMap.NewBindings(nil) // Use the embedded template pointer htmlMap.Title.Bind(b, Str("Index Map Example")) // Syntactic sugar b.Binds(Str("…"), htmlMap.Title...) htmlMap.Body.Bind(b, Str(`<script>alert("Use Escapers to avoid injection attacks!")</script>`)) b.WriteTo(os.Stdout)
Output: <html> <title>Index Map Example</title> <body> <h1>Index Map Example</h1> <script>alert("Use Escapers to avoid injection attacks!")</script> </body> </html>
func MultiMapIndices ¶ added in v0.13.0
func NamesFields ¶ added in v0.13.1
NamesIndices returns a map of all distinct field names along with their respective indices.
Template t must not be changed afterwards.
func NamesIndices ¶ added in v0.13.1
NamesIndices returns a map of all distinct field names along with their respective indices.
Template t must not be changed afterwards.
Types ¶
type Bindings ¶ added in v0.13.0
type Bindings struct {
// contains filtered or unexported fields
}
TODO: It is quite common to bind the same content to more than one field when filling in templates. Therefore, fields can be given names and then content can be bound to all fields with the same name.
Example ¶
var t Template t.SetFix(0, []byte("FIX")) t.SetField(0, "foo", nil) b := t.NewBindings(nil) b.Bind(0, Str("bar")) b.Bind(1, Print(4711)) b.WriteTo(os.Stdout)
Output: barFIX4711
func NewBindings ¶ added in v0.13.0
func (*Bindings) Bind ¶ added in v0.13.0
binding (*Bindings)(nil) is same as binding nil -> Template.NewBindings
func (*Bindings) BindName ¶ added in v0.13.0
BindName is a convenience method that collects the field indices of name and then binds all fields to content. When repeatedly binding to the same template field, it is more efficient to use Template.Fields once and then use Fields.Bind or Bindings.Binds repeatedly. MapIndices helps to manage templates and their indices in a structured way with so-called index maps.
func (*Bindings) BindOrName ¶ added in v0.13.0
BindOrName binds field i to the name of the field if w is nil. Otherwiese it binds w to the field. For an example see Fields.BindOrName.
func (*Bindings) Fixate ¶ added in v0.13.0
Example ¶
t0 := must.Ret(NewTemplate( "x0.0", Field("n0.0"), "x0.1", Field("n0.1", embraceString("[", "]")), "x0.2", )) t1 := must.Ret(NewTemplate( "x1.0", Field("n1.0", embraceString("(", ")")), "x1.1", Field("n1.1"), "x1.2", )) b1 := t1.NewBindings(nil) must.Do(b1.BindName("n1.1", Str("S1.1"))) b0 := t0.NewBindings(nil) must.Do(b0.BindName("n0.0", Str("S0.0"))) must.Do(b0.BindName("n0.1", b1)) tf := must.Ret(b0.Fixate(false, nil)) bf := tf.NewBindings(nil) must.Do(bf.BindName("n1.0", Str("S1.0"))) bf.WriteTo(os.Stdout)
Output: x0.0S0.0x0.1[x1.0][(S1.0)][x1.1][S1.1][x1.2]x0.2
type BlockComment ¶ added in v0.13.0
type BlockComment struct {
// contains filtered or unexported fields
}
func NewBlockComment ¶ added in v0.13.0
func NewBlockComment(start, end string) BlockComment
func (BlockComment) Line ¶ added in v0.13.0
func (lb BlockComment) Line(line []byte) []byte
type ByNameFunc ¶ added in v0.13.1
func (ByNameFunc) GoxicByName ¶ added in v0.13.1
func (f ByNameFunc) GoxicByName(name string) io.WriterTo
type EscapeChain ¶ added in v0.13.0
type EscapeChain []Escaper // TODO better encapsulation
func (EscapeChain) Escape ¶ added in v0.13.0
func (ec EscapeChain) Escape(w io.Writer) EscapeWriter
type EscapeFunc ¶ added in v0.13.0
type EscapeFunc func(io.Writer) EscapeWriter
func (EscapeFunc) Escape ¶ added in v0.13.0
func (ef EscapeFunc) Escape(w io.Writer) EscapeWriter
type EscapeWriter ¶ added in v0.13.0
type Escaper ¶ added in v0.13.0
type Escaper interface {
Escape(io.Writer) EscapeWriter
}
func ChainEscape ¶ added in v0.13.0
type Fields ¶ added in v0.13.1
type Fields []int
func NameIndices ¶ added in v0.13.1
Template t must not be changed afterwards.
func (Fields) BindOrName ¶ added in v0.13.1
BindOrName binds fields with their names if content is nil. Otherwise it binds w to the fields. See also Bindings.BindOrName.
Example ¶
const fieldName = "Einfach eine Überschrift" tmpl := must.Ret(NewTemplate("<h1>", Field(fieldName), "</h1>")) idxs := NameIndices(tmpl, fieldName) b := tmpl.NewBindings(nil) fmt.Println("Empty:", b) idxs.BindOrName(b, nil) fmt.Println("Name :", b) idxs.BindOrName(b, Str("Yet Another Heading")) fmt.Println("Or :", b)
Output: Empty: <h1></h1> Name : <h1>Einfach eine Überschrift</h1> Or : <h1>Yet Another Heading</h1>
type LineComment ¶ added in v0.13.0
type LineComment string
func (LineComment) Line ¶ added in v0.13.0
func (le LineComment) Line(line []byte) []byte
type Named ¶ added in v0.13.1
type Named struct {
// contains filtered or unexported fields
}
func NameFields ¶ added in v0.13.1
Template t must not be changed afterwards.
func (Named) BindByName ¶ added in v0.13.1
Example ¶
tmpl := must.Ret(NewTemplate( "User ID : ", Field("ID"), "\n", "Username: ", Field("Name"), "\n", )) var idxmap struct { *Template ID Named Name Named } must.Do(MapIndices(&idxmap, tmpl, MapAll)) tbn := testByNamer{ ID: 4711, Name: "John Doe", } b := idxmap.NewBindings(nil) idxmap.ID.BindByName(b, &tbn) idxmap.Name.BindByName(b, &tbn) b.WriteTo(os.Stdout)
Output: User ID : 4711 Username: John Doe
func (Named) BindOrName ¶ added in v0.13.1
BindOrName binds fields with their names if content is nil. Otherwise it binds w to the fields. See also Bindings.BindOrName.
func (Named) Name ¶ added in v0.13.1
TODO Panics if Named.Template returns nil?
type Parser ¶
type Parser struct { CommentLine func(line []byte) []byte InlineStart, InlineEnd rune FieldName func(string) error Escape map[string]Escaper PrepLine func([]byte) []byte Newline string // contains filtered or unexported fields }
type Template ¶
A template is a sequence of fixed data, preceded, interspersed and followed by fields that can be dynamically filled with content. In the simplest case, fields and fixed data are addressed by indices. Field 0 is before fix data 0 and field 1 follows fix data 0 and is before fix data 1 and so on. The last part is field N for a template with N fixed data parts:
[ Field 0 | Fix 0 | Field 1 | Fix 1 | … | Fix N-1 | Field N ]
It is perfectly possible to define templates using its methods. However, it is far more convenient to read in text templates from a file using the Parser.
To fill in fields, you must first create a Bindings object for the template with Template.NewBindings. This can then be used to bind content to fields. After binding, the complete content is written to an io.Writer using Bindings.WriteTo.
A zero Template is ready for use. It must not be copied or changed after creating the first Bindings for the template.
func NewTemplate ¶
func (*Template) AppendField ¶ added in v0.13.0
AppendField sets the next field that follows either another field or a fixed part, whichever is last.
func (*Template) AppendFix ¶ added in v0.13.0
AppendFix sets the next fixed part that follows either another fixed part or a field, whichever is last. It may assume ownership of the memory held by fix.
func (*Template) AppendFixString ¶ added in v0.13.0
func (*Template) Fields ¶ added in v0.13.0
Fields returns the field indices of all fields with the given name.
func (*Template) MustStatic ¶ added in v0.13.0
func (*Template) NewBindings ¶ added in v0.13.0
TODO returns reuse unchangeds if t is nil -> tmpls["no-exists"].NewBindins()
func (*Template) SetFix ¶ added in v0.13.0
SetFix sets the fix part with index i to fix. The template assumes ownership of the memory held by fix. If the caller wants to keep ownership e.g. use bytes.Clone to pass a copy.
func (*Template) SetFixString ¶ added in v0.13.0
type UnknownField ¶ added in v0.13.0
type UnknownField string
func (UnknownField) Error ¶ added in v0.13.0
func (err UnknownField) Error() string