goxic

package module
v0.12.0 Latest Latest
Warning

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

Go to latest
Published: May 12, 2024 License: GPL-3.0 Imports: 12 Imported by: 3

README

goxic

Test Coverage Go Report Card GoDoc

import "git.fractalqb.de/fractalqb/goxic"


Intro

Template engine with templates that only have named placeholders – but no logic!

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 (TL;DR)

Short version is: A Template is nothing but static parts (text) interspersed with placeholders. Placeholders have a unique name and may appear several times within a template. One cannot generate output from a template only. Before output can be generated – aka "emitted" – all placeholders have to be bound to some Content. The bindings are held by a separate object of type BounT (short for "bound template"). This is important to be able to use a single template with different bindings. Note: A bound template itself is Content. – Be aware of infinite recursion! By the way, a function that writes output can be content too. This has quite some implications.

One can build a template through the Template API of the goxic packed. Though quite easy it is not very convenient to build templates this way. It is considered to be more common to build a template by parsing it from a file. The package provides a quite general Parser that also supports nesting of templates. Finally one can create new templates from bound templates where not every placeholder has been bound to content, i.e. to Fixate a BounT. This way one can build coarse grained templates from smaller ones without loosing efficiency.

The concepts described so far put a lot of control into the hands of the programmer. But there are things that might also be helpful, when controlled by the template writer:

  1. Escaping: This is a common problem with e.g. HTML templates where injection of content can result in security breaches.

  2. Selecting and Formatting Content: This is addressed in goxic with the Bind From Template (BFT) feature.

Both features can be used but don't need to – goxic has a layered design. What goxic still does ban from templates are control structures like conditionals and loops. Such things tend to be better be placed into code of a real programming language.

Escaping Content

This is a common problem with e.g. HTML templates where injection of content can result in security breaches. Here the situation is twofold: It depends on the position of the placeholder in the template whether or how content has to be escaped. This cannot be judged from the programmer's view. Sometimes ToDo…

Bind From Template

ToDo…

Documentation

Overview

Template engine that only has named placeholders – nothing more.

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

Short version is: A Template is nothing but static parts (text) interspersed with placeholders. Placeholders have a unique name and may appear several times withn a template. One cannot generate output from a template only. Before output can be generated – "emitted" in goxic – all placeholders have to be bound to some Content. The bindings are held by a separate object of type BounT (short for "bound template"). This way a single template can be used many times with different bindings. A bound template itself is Content. – Be aware of infinite recursion!

One can build a template through the Template API of the goxic packed. Though quite easy it is not very convenient to build templates this way. It is considered to be more common to build a template by parsing it from a file. The package provides a quite general Parser that also supports nesting of templates. Finally one can create new templates from bound templates where not every placeholder has been bound to content, i.e. to Fixate a BounT. This way one can build coarse grained templates from smaller ones without loosing efficiency.

Index

Examples

Constants

View Source
const (
	BftMarker  = "$"
	BftPathSep = "."
)
View Source
const Empty empty = 0

Constant Empty can be use as empty Content, i.e. nothing will be emitted as output.

View Source
const NameSep = ':'
View Source
const PathSep = '/'

Variables

This section is empty.

Functions

func IdName

func IdName(nm string) string

func IndexMap added in v0.11.0

func IndexMap(imap any, t *Template, complete bool, mapNames func(string) string) error

func IndexMaps added in v0.12.0

func IndexMaps(ts map[string]*Template, complete bool, mapNames func(string) string, maps ...any) error

func InitIndexMap

func InitIndexMap(imap any, tmpl *Template, mapNames func(string) string) (phCount int, err error)

func Must added in v0.9.0

func Must(n int64, err error) int64

func PrepTrimWS

func PrepTrimWS(line string) (trimmed string)

func RenameExists

func RenameExists(err error) bool

func RenameUnknown

func RenameUnknown(err error) bool

func StripPath

func StripPath(ph string) string

Types

type BounT

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

BounT keeps the placeholder bindings for one specific Template. Use Template's NewBounT or NewInitBounT methods to create such a binding object.

Example
tmpl := NewTemplate("").
	Ph("foo").
	AddStr("<thisisfix1>").
	Ph("foo").
	AddStr("<thisisfix2>").
	Ph("bar")
bnt := tmpl.NewBounT(nil)
bnt.BindName(gxc.P("FOO"), "foo")
bnt.BindName(gxc.P("BAR"), "bar")
bnt.WriteTo(os.Stdout)
Output:

FOO<thisisfix1>FOO<thisisfix2>BAR

func (*BounT) Bind

func (bt *BounT) Bind(cnt Content, phIdxs ...int) (anonymous int)

Bind returns the number of "anonymous binds", i.e. placeholders with empty names that got a binding.

func (*BounT) BindIfName

func (bt *BounT) BindIfName(cnt Content, name string)

BindName binds the content cnt to the placeholders by name. If no placeholder with name exists nothings is bound.

func (*BounT) BindMatch

func (bt *BounT) BindMatch(cnt Content, pattern *regexp.Regexp)

func (*BounT) BindName

func (bt *BounT) BindName(cnt Content, name string) error

BindName binds the content cnt to the placeholders by name. If no placeholder with name exists an error is returned.

func (*BounT) BindTemplates added in v0.11.0

func (bt *BounT) BindTemplates(ts map[string]*Template, selectOnly bool, phSelect map[string]string)

BindTemplates fills the placeholders of bt with new BounTs created for the respective templates from ts.

func (*BounT) Fill

func (bt *BounT) Fill(data any, overwrite bool) (missed int, err error)

Fill the placeholders using the bind from template (BFT) feature. The BFT feature allows template authors to specify a 'path' that reaches into the Go data object passed to Fill, i.e. the binding is determined by the template not the program. BFT uses a special notation for placeholder names to achieve this. TODO …

Example
package main

import (
	"fmt"
	"os"
)

type bftAddr struct {
	Street string
	No     int
}

type bftData struct {
	Name  string
	Addrs []bftAddr
}

func main() {
	tmpl := NewTemplate("bft example").
		AddStr("Name: ").Ph("$Name").
		AddStr("\nLast Address: ").Ph("$Addrs.-1.Street").
		AddStr(" ").Ph("$%05d Addrs.-1.No")
	data := bftData{
		Name: "John Doe",
		Addrs: []bftAddr{
			bftAddr{Street: "Cansas Lane", No: 1},
			bftAddr{Street: "Yellow-Brick-Road", No: 33},
		},
	}
	bt := tmpl.NewBounT(nil)
	miss, err := bt.Fill(data, true)
	if err != nil {
		panic(err)
	}
	if miss != 0 {
		fmt.Printf("missed %d placeholders\n", miss)
	}
	_, err = bt.WriteTo(os.Stdout)
	if err != nil {
		fmt.Println(err)
	}
}
Output:

Name: John Doe
Last Address: Yellow-Brick-Road 00033

func (*BounT) Fixate

func (bt *BounT) Fixate() (*Template, error)

Returns nil if there is nothing to fixate.

func (*BounT) HasUnbound

func (bt *BounT) HasUnbound(unbound map[string]PhIdxs) (res bool)

HasUnbound checks if there are placehoders that are not yet bound to content. When a map is passed to HasUnbound all placeholder names are put into the map along with the indices of the yet unbound placeholders.

func (*BounT) String added in v0.9.1

func (bt *BounT) String() string

func (*BounT) Template

func (bt *BounT) Template() *Template

Template returns the template that bt is the binding for.

func (*BounT) Wrap

func (bt *BounT) Wrap(wrapper ContentXformer)

Wrap all bound content into the transformer 'wrapper'.

func (*BounT) WriteTo added in v0.9.0

func (bt *BounT) WriteTo(wr io.Writer) (res int64, err error)

WriteTo writes the BounT content to wr.

type Content

type Content = io.WriterTo

Content will write the content to an io.Writer.

type ContentXformer added in v0.11.0

type ContentXformer func(Content) (wrapped Content)

func CXFChain added in v0.11.0

func CXFChain(cxs ...ContentXformer) ContentXformer

CntXChain creates a content transformer from a list of content transgformers applying them in the order they were passed in cxs.

type ContentXformers added in v0.11.0

type ContentXformers = map[string]ContentXformer

type DuplicateTemplates

type DuplicateTemplates map[string]*Template

func (DuplicateTemplates) Error

func (err DuplicateTemplates) Error() string

type EscGoTmpl

type EscGoTmpl struct {
	To  io.Writer
	Esc func(io.Writer, []byte)
}

func (EscGoTmpl) Write

func (ew EscGoTmpl) Write(p []byte) (n int, err error)

type IdxMapParser added in v0.12.0

type IdxMapParser struct {
	Parser  *Parser
	MapName func(string) string
}

func (IdxMapParser) Parse added in v0.12.0

func (p IdxMapParser) Parse(rd io.Reader, rootName string, complete bool, maps ...any) error

func (IdxMapParser) ParseFile added in v0.12.0

func (p IdxMapParser) ParseFile(templateFile, rootName string, complete bool, maps ...any) error

func (IdxMapParser) ParseString added in v0.12.0

func (p IdxMapParser) ParseString(s, rootName string, complete bool, maps ...any) error

type Parser

type Parser struct {
	// String that starts an inline placeholder
	StartInlinePh string
	// String that terminates an inline placeholder
	EndInlinePh string

	// Regular expression that matches a block placeholder line
	BlockPh *regexp.Regexp
	// Index of the submatch with the placeholder's name
	PhNameSubmatch int
	// Index of the submatch for the marker with the preceding line break marker
	PhPreBrkSubmatch int
	// Index of the submatch for the marker with the following line break marker
	PhPostBrkSubmatch int

	// Regular expression that matches the start of a sub-template
	StartSubTemplate    *regexp.Regexp
	StartNameSubmatch   int
	StartPreBrkSubmatch int

	// Regular expression that matches the end of a sub-template
	EndSubTemplate     *regexp.Regexp
	EndNameSubmatch    int
	EndPostBrkSubmatch int

	Endl     string
	PrepLine func([]byte) []byte
	// When not nil, placeholders of the form <name>\<tag> will get the content
	// wrapper CXFMap[<tag>] for the placeholder <name>.
	CXFMap map[string]ContentXformer
}
Example
rd := strings.NewReader(`1st line of TL template
<!-- >>> subt1 >>> -->
1st line of subt1
<!-- >>> sub1ph <<< -->
<!-- <<< subt1 <<< \-->
last line of TL template

`)
p := newTestParser()
ts := make(map[string]*Template)
if err := p.Parse(rd, "", ts); err != nil {
	fmt.Printf("parsing failed: %s\n", err)
} else {
	tlt := ts[""]
	if _, err := tlt.NewBounT(nil).WriteTo(os.Stdout); err != nil {
		fmt.Println(err)
	}
	sbt := ts["subt1"]
	sbbt := sbt.NewBounT(nil)
	sbbt.BindName(gxc.P("SUB1PH"), "sub1ph")
	if _, err := sbbt.WriteTo(os.Stdout); err != nil {
		fmt.Println(err)
	}
}
Output:

1st line of TL template
last line of TL template
1st line of subt1
SUB1PH
Example (Cxf_off)
rd := strings.NewReader("<html>`foo\\xml`</html>")
p := newTestParser()
ts := make(map[string]*Template)
err := p.Parse(rd, "cxf_off", ts)
if err != nil {
	fmt.Println(err)
}
bt := ts[""].NewBounT(nil)
bt.BindName(gxc.P("THIS"), "foo\\xml")
bt.WriteTo(os.Stdout)
Output:

<html>THIS</html>
Example (Cxf_ok)
rd := strings.NewReader("<html>`foo\\xml` and `bar`</html>")
p := newTestParser()
p.CXFMap = map[string]ContentXformer{
	"xml": gxc.StringWrapper("<[", "]>"),
}
ts := make(map[string]*Template)
err := p.Parse(rd, "cxf_ok", ts)
if err != nil {
	fmt.Println(err)
}
bt := ts[""].NewBounT(nil)
bt.BindName(gxc.P("THIS"), "foo")
bt.BindName(gxc.P("THAT"), "bar")
bt.BindName(gxc.P("DINGENS"), "baz")
bt.WriteTo(os.Stdout)
Output:

<html><[THIS]> and THAT</html>

func NewParser

func NewParser(inlineStart, inlineEnd, lcomStart, lcomEnd string) *Parser

func (*Parser) Parse

func (p *Parser) Parse(rd io.Reader, rootName string, into map[string]*Template) error

func (*Parser) ParseFile

func (p *Parser) ParseFile(templateFile, rootName string, into map[string]*Template) error

func (*Parser) ParseString added in v0.9.0

func (p *Parser) ParseString(s, rootName string, into map[string]*Template) error

type PhIdxs

type PhIdxs = []int

type PhWriteMode added in v0.11.0

type PhWriteMode int
const (
	PhWriteInline  PhWriteMode = 1
	PhWriteBlock   PhWriteMode = 2
	PhStripPreBrk  PhWriteMode = (1 << 2)
	PhStripPostBrk PhWriteMode = (1 << 3)
)

func (PhWriteMode) All added in v0.11.0

func (pwm PhWriteMode) All(test PhWriteMode) bool

type Template

type Template struct {
	Name string
	// contains filtered or unexported fields
}

Template holds a sequence of fixed (dstatic) content fragment that have to be emitted verbatim. These fragments are intermixed with named placeholders. The content to fill in the placeholders generally is computed dynamically. A placeholder can appear several times in different posotions within a template. A template can start or end either with a placeholder or with fixed content.

To bind cotent to placeholders one first has to crate a bound template (BounT) to hold the bindings. When all placeholders ar bound the resulting content can be emitted.

func MergedTemplates

func MergedTemplates(
	into *Template,
	ts map[string]*Template,
	selectOnly bool,
	phSelect map[string]string,
) (*Template, error)

MergeTemplates binds temlpates into the template into accoriding to ph names

func NewTemplate

func NewTemplate(name string) *Template

func (*Template) AddFix

func (t *Template) AddFix(fixFragment []byte) *Template

AddFix adds a new piece of static content to the end of the template. Note that static context is merged to preceeding static content as long as no placholder was added before.

func (*Template) AddStr

func (t *Template) AddStr(str string) *Template

AddStr adds a string as static content to the end of the temlate.

func (*Template) FixAt

func (t *Template) FixAt(idx int) []byte

FixAt returns the piece of static content with the index idx (indices are zero-based).

func (*Template) FixCount

func (t *Template) FixCount() int

FixCount returns the number of pieces of static content in the template.

func (*Template) FlattenPhs

func (t *Template) FlattenPhs(merge bool) error

func (*Template) ForeachPh

func (t *Template) ForeachPh(r func(name string, idxs []int))

func (*Template) NewBounT

func (t *Template) NewBounT(reuse *BounT) *BounT

NewBounT initializes a new binding objekt for template t. When nil is passed a new BounT object is allocated on the heap. Otherwise the passed BounT object is reused.

func (*Template) NewInitBounT

func (t *Template) NewInitBounT(cnt Content, reuse *BounT) *BounT

NewInitBounT first uses NewBounT to initialize a BounT object for temlate t. All placeholders are then initially bound to content cnt.

func (*Template) Pack

func (t *Template) Pack() (self *Template)

Pack compacts memory used for the fix parts and the placeholder indices to improve locality. This may also release unused memory when the fix parts are slices into some bigger and otherwise unused piece of memory.

func (*Template) Ph

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

Ph adds a new placeholder to the end of the template.

func (*Template) PhAt

func (t *Template) PhAt(idx int) string

PlaceholderAt returns the placeholder that will be emitted between static content idx-1 and static content idx. Note that PlaceholderAt(0) will be emitted before the first piece of static content. This placeholder is optional.

func (*Template) PhCount added in v0.9.2

func (t *Template) PhCount() int

PlaceholderNum returns the number of placeholders defined in the template.

func (*Template) PhIdxs

func (t *Template) PhIdxs(name string) []int

PlaceholderIdxs returns the positions in which one placeholder will be emitted. Note that placeholder index 0 is – if define – emitted before the first piece of fixed content.

func (*Template) PhModeAt added in v0.11.0

func (t *Template) PhModeAt(idx int) PhWriteMode

func (*Template) PhWrap

func (t *Template) PhWrap(name string, wrapper ContentXformer) *Template

func (*Template) PhWrite added in v0.11.0

func (t *Template) PhWrite(name string, mode PhWriteMode) *Template

func (*Template) PhWriteWrap added in v0.11.0

func (t *Template) PhWriteWrap(name string, wrMode PhWriteMode, wrapper ContentXformer) *Template

func (*Template) Phs

func (t *Template) Phs() []string

Placeholders returns all placeholders – more precisely placeholder names – defined in the template.

func (*Template) RenamePh

func (t *Template) RenamePh(current, newName string, merge bool) error

RenamePh renames the current placeholder to a new name. If merge is true the current placeholder will be renamed even if a placeholder with the newName already exists. Otherwise an error is retrned. Renaming a placeholder that does not exists also result in an error.

func (*Template) RenamePhs

func (t *Template) RenamePhs(current, newNames []string, merge bool) error

RenamePhs renames each placeholder current[i] to have the name newNames[i] afterwards.

This is different from RenamePh(current[i],newName[i],merge).

func (*Template) Static

func (t *Template) Static() []byte

Static returns the fixed part as byte slice if there are no placeholders. Otherwise, i.e. the template is not static, it returns nil.

func (*Template) StaticWith

func (t *Template) StaticWith(fill Content) ([]byte, error)

func (*Template) Wrap

func (t *Template) Wrap(wrapper ContentXformer, idxs ...int)
Example
tmpl := NewTemplate("embrace").AddStr("foo").Ph("bar").AddStr("baz")
tmpl.Wrap(gxc.StringWrapper("<[", "]>"), tmpl.PhIdxs("bar")...)
bt := tmpl.NewInitBounT(gxc.P("BAR"), nil)
bt.WriteTo(os.Stdout)
Output:

foo<[BAR]>baz

func (*Template) WrapAt

func (t *Template) WrapAt(idx int) ContentXformer

func (*Template) Write

func (t *Template) Write(wr io.Writer, fmtPh func(name string, mode PhWriteMode) string) (err error)
Example
t := NewTemplate("Write Example")
t.AddStr("line ").Ph("p1").AddStr(" 1\n").
	AddStr("line ").Ph("p2").AddStr("\n2\n").
	AddStr("line\n").Ph("p3").AddStr(" 3\n").
	AddStr("line\n").Ph("p4").AddStr("\n4")
t.Write(os.Stdout, func(name string, mode PhWriteMode) string {
	if !mode.All(PhWriteBlock) {
		return fmt.Sprintf("<%s>", name)
	}
	var sb strings.Builder
	sb.WriteByte('#')
	if mode.All(PhStripPreBrk) {
		sb.WriteByte('\\')
	}
	fmt.Fprintf(&sb, " >>> %s <<<", name)
	if mode.All(PhStripPostBrk) {
		sb.WriteString(" \\")
	}
	return sb.String()
})
Output:

line <p1> 1
line <p2>
2
line
<p3> 3
line
# >>> p4 <<<
4

func (*Template) XformPhs

func (t *Template) XformPhs(merge bool, x func(string) string) error
Example
tmpl := NewTemplate("rename")
tmpl.AddStr("AB")
tmpl.Ph("a")
tmpl.AddStr("CD")
tmpl.Ph("b")
tmpl.AddStr("EF")
tmpl.Ph("c")
tmpl.AddStr("GH")
tmpl.Ph("d")
tmpl.AddStr("IJ")
tmpl.XformPhs(true, func(pi string) string {
	switch pi {
	case "a":
		return "d"
	case "d":
		return "a"
	default:
		return "b"
	}
})
bt := tmpl.NewBounT(nil)
bt.BindName(gxc.P("<A>"), "a")
bt.BindName(gxc.P("<B>"), "b")
bt.BindName(gxc.P("<D>"), "d")
bt.WriteTo(os.Stdout)
Output:

AB<D>CD<B>EF<B>GH<A>IJ

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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