gax

package module
v0.3.1 Latest Latest
Warning

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

Go to latest
Published: Jun 19, 2023 License: Unlicense Imports: 6 Imported by: 0

README

Overview

Simple system for writing HTML/XML as Go code. Better-performing replacement for html/template and text/template. Vaguely inspired by JS library https://github.com/mitranim/prax.

Advantages over string-based templating:

  • No weird special language to learn.
  • Normal Go code.
  • Normal Go conditionals.
  • Normal Go loops.
  • Normal Go functions.
  • Normal Go static typing.
  • Normal Go code analysis.
  • Much better performance.

Other features / benefits:

  • Tiny and dependency-free (only stdlib).

TOC

Usage

API docs: https://pkg.go.dev/github.com/mitranim/gax.

package main

import (
  "fmt"

  gax "github.com/mitranim/gax"
)

var (
  E  = gax.E
  AP = gax.AP
)

func main() {
  fmt.Println(Page(mockDat))
  // <!doctype html><html lang="en"><head><meta charset="utf-8"><link rel="icon" href="data:;base64,="><title>Posts</title></head><body><h1 class="title">Posts</h1><h2>Post0</h2><h2>Post1</h2></body></html>
}

func Page(dat Dat) gax.Bui {
  return gax.F(
    gax.Str(gax.Doctype),
    E(`html`, AP(`lang`, `en`),
      E(`head`, nil,
        E(`meta`, AP(`charset`, `utf-8`)),
        E(`link`, AP(`rel`, `icon`, `href`, `data:;base64,=`)),

        // Use normal Go conditionals.
        func(bui *gax.Bui) {
          if dat.Title != `` {
            bui.E(`title`, nil, dat.Title)
          } else {
            bui.E(`title`, nil, `test markup`)
          }
        },
      ),

      E(`body`, nil,
        E(`h1`, AP(`class`, `title`), `Posts`),

        // Use normal Go loops.
        func(bui *gax.Bui) {
          for _, post := range dat.Posts {
            bui.E(`h2`, nil, post)
          }
        },
      ),
    ),
  )
}

var mockDat = Dat{
  Title: `Posts`,
  Posts: []string{`Post0`, `Post1`},
}

type Dat struct {
  Title string
  Posts []string
}

Performance

Gax easily beats text/template and html/template. The more complex a template is, the better it gets.

The static benchmark is "unfair" because the Gax version renders just once into a global variable. This is recommended for all completely static markup. Prerendering is also possible with text/template and html/template, but syntactically inconvenient and usually avoided. With Gax it's syntactically convenient and easily done, and the benchmark reflects that.

The dynamic benchmark is intentionally naive, avoiding some Gax optimizations such as static prerender, to mimic simple user code.

go test -bench . -benchmem
cpu: Intel(R) Core(TM) i9-8950HK CPU @ 2.90GHz
Benchmark_template_static-12   17002192    67.31 ns/op     48 B/op     1 allocs/op
Benchmark_gax_static-12        640101200   1.845 ns/op      0 B/op     0 allocs/op
Benchmark_template_dynamic-12      9205   130812 ns/op  51811 B/op  1370 allocs/op
Benchmark_gax_dynamic-12          70465    17090 ns/op  10376 B/op   169 allocs/op

Changelog

v0.3.1

Added various attribute-manipulating methods to Attr, Attrs, Elem.

v0.3.0

  • Renamed .Append to .AppendTo for consistency with other libraries.
  • Elem with empty .Tag no longer renders anything. As a result, zero value of Elem is the same as nil. This can be convenient for functions that return Elem.
  • Added LinkBlank.
  • Require Go 1.20.

v0.2.1

Child rendering now supports walking []Ren and []T where T is Ren.

v0.2.0

API revision. Now supports both the append-only style via Bui.E and the expression style via free E. Mix and match for simpler code.

v0.1.4

Bui.Child also supports func(E).

v0.1.3

Added Bui.With and Ebui.

v0.1.2

Minor syntactic bugfix.

v0.1.1

Converted methods .WriteTo(*[]byte) methods to .Append([]byte) []byte for better compliance with established interfaces.

v0.1.0

Init.

License

https://unlicense.org

Misc

I'm receptive to suggestions. If this library almost satisfies you but needs changes, open an issue or chat me up. Contacts: https://mitranim.com/#contacts

Documentation

Overview

Simple system for writing HTML as Go code. Better-performing replacement for `html/template` and `text/template`; see benchmarks in readme.

Vaguely inspired by JS library https://github.com/mitranim/prax.

Features / benefits:

  • No weird special language to learn.
  • Use actual Go code.
  • Use normal Go conditionals.
  • Use normal Go loops.
  • Use normal Go functions.
  • Benefit from static typing.
  • Benefit from Go code analysis.
  • Benefit from Go performance.
  • Tiny and dependency-free.

The API is bloated with "just in case" public exports, but 99% of what you want is `E`, `F`, `Bui`, and `Bui.E`. See the `Bui` example below.

Index

Examples

Constants

View Source
const Doctype = `<!doctype html>`

Shortcut for prepending HTML doctype. Use `Bui(Doctype)` to create a document-level HTML builder, or `Str(Doctype)` to prepend this in `F`.

Variables

View Source
var Bool = newStringSet(
	`allowfullscreen`, `allowpaymentrequest`, `async`, `autofocus`, `autoplay`,
	`checked`, `controls`, `default`, `disabled`, `formnovalidate`, `hidden`,
	`ismap`, `itemscope`, `loop`, `multiple`, `muted`, `nomodule`, `novalidate`,
	`open`, `playsinline`, `readonly`, `required`, `reversed`, `selected`,
	`truespeed`,
)

Set of known HTML boolean attributes. Can be modified via `Bool.Add` and `Bool.Del`. The specification postulates the concept, but where's the standard list? Taken from non-authoritative sources. Reference:

https://www.w3.org/TR/html52/infrastructure.html#boolean-attribute
View Source
var Void = newStringSet(
	`area`, `base`, `br`, `col`, `embed`, `hr`, `img`, `input`, `link`, `meta`,
	`param`, `source`, `track`, `wbr`,
)

Set of known HTML void elements, also known as self-closing tags. Can be modified via `Void.Add` and `Void.Del`. Reference:

https://www.w3.org/TR/html52/
https://www.w3.org/TR/html52/syntax.html#writing-html-documents-elements

Functions

func Vac added in v0.2.0

func Vac(val any) any

Short for "vacate", "vacuum", "vacuous". Takes a "child" intended for `E` or `F`. If the child is empty, returns `nil`, otherwise returns the child as-is. Empty is defined as containing only nils. Just like `E` and `F`, this recursively walks `[]any`.

Types

type Attr

type Attr [2]string

Represents an arbitrary HTML/XML attribute. Usually part of `Attrs{}`. An empty/zero attr (equal to `Attr{}`) is ignored during encoding.

Example
package main

import (
	"fmt"

	x "github.com/mitranim/gax"
)

func main() {
	fmt.Println(x.Attr{`class`, `some-class`})
}
Output:

class="some-class"

func (Attr) Add added in v0.3.1

func (self Attr) Add(val string) Attr

Returns a modified version where the given input is appended to `.Value`, space-separated if both values are non-empty.

func (Attr) AppendTo added in v0.3.0

func (self Attr) AppendTo(buf []byte) []byte

Mostly for internal use.

func (Attr) Name

func (self Attr) Name() string

Attribute name. If the attr is not equal to `Attr{}`, the name is validated during encoding. Using an invalid name causes a panic.

func (Attr) Set added in v0.3.1

func (self Attr) Set(val string) Attr

Returns a modified version with `.Value` replaced with the given input.

func (Attr) SetName added in v0.3.1

func (self Attr) SetName(val string) Attr

Returns a modified version with `.Name` replaced with the given input.

func (Attr) String

func (self Attr) String() string

Implement `fmt.Stringer` for debug purposes. Not used by builder methods.

func (Attr) Value

func (self Attr) Value() string

Attribute value. Automatically escaped and quoted when encoding the attr. For known HTML boolean attrs, listed in `Bool`, the value may be tweaked for better spec compliance, or the attr may be omitted entirely.

type AttrWri

type AttrWri []byte

Short for "attribute writer". Mostly for internal use. Writes text as if it were inside an HTML/XML attribute, without enclosing quotes, escaping as necessary. For escaping rules, see:

https://www.w3.org/TR/html52/syntax.html#escaping-a-string

func (AttrWri) String

func (self AttrWri) String() string

Similar to `strings.Builder.String`. Free cast with no allocation.

func (*AttrWri) Write

func (self *AttrWri) Write(val []byte) (int, error)

Implement `io.Writer`. Similar to `strings.Builder.Write`, but escapes special chars. Technically not compliant with `io.Writer`: the returned count of written bytes may exceed the size of the provided chunk.

func (*AttrWri) WriteRune

func (self *AttrWri) WriteRune(val rune) (int, error)

Similar to `strings.Builder.WriteRune`, but escapes special chars.

func (*AttrWri) WriteString

func (self *AttrWri) WriteString(val string) (size int, _ error)

Implement `io.StringWriter`. Similar to `strings.Builder.WriteString`, but escapes special chars.

type Attrs added in v0.2.0

type Attrs []Attr

Short for "attributes". List of arbitrary HTML/XML attributes. Used by `Elem`. Usually passed to `E` or `Bui.E`.

Example
package main

import (
	"fmt"

	x "github.com/mitranim/gax"
)

func main() {
	attrs := x.Attrs{
		{`class`, `some-class`},
		{`style`, `some: style`},
	}
	fmt.Println(attrs)
}
Output:

class="some-class" style="some: style"

func A

func A(vals ...Attr) Attrs

Short for "attributes". Same as the `Attrs{}` constructor, but uses parentheses, which is sometimes more convenient. Symmetric with `Attrs.A`.

Example
package main

import (
	"fmt"

	x "github.com/mitranim/gax"
)

func main() {
	attrs := x.A(
		x.Attr{`class`, `some-class`},
		x.Attr{`style`, `some: style`},
	)
	fmt.Println(attrs)
}
Output:

class="some-class" style="some: style"

func AP added in v0.2.0

func AP(pairs ...string) Attrs

Short for "attributes from pairs". Recommended way to write attributes, due to its brevity. Symmetric with `Attrs.AP`.

Example
package main

import (
	"fmt"

	x "github.com/mitranim/gax"
)

func main() {
	fmt.Println(
		x.AP(
			`href`, `/`,
			`aria-current`, `page`,
			`class`, `some-class`,
		),
	)
}
Output:

href="/" aria-current="page" class="some-class"

func LinkBlank added in v0.3.0

func LinkBlank() Attrs

Shortcut for `target="_blank" rel="noopener noreferrer"`, which must always be used together, which is easy to forget.

func (Attrs) A added in v0.2.0

func (self Attrs) A(vals ...Attr) Attrs

Shortcut for appending more attributes. Useful when combining attributes from hardcoded pairs (via `AP`) with attributes created as `Attr`. For example, you can write a function that generates a specific attribute, and use this to append the result.

Example
package main

import (
	"fmt"

	x "github.com/mitranim/gax"
)

func main() {
	cur := func() x.Attr { return x.Attr{`aria-current`, `page`} }
	bg := func() x.Attr { return x.Attr{`style`, `background-image: url(...)`} }

	fmt.Println(
		x.AP(`class`, `some-class`).A(cur(), bg()),
	)
	// class="some-class" aria-current="page" style="background-image: url(...)"
}
Output:

func (Attrs) AP added in v0.2.0

func (self Attrs) AP(pairs ...string) Attrs

Shortcut for appending more attributes from pairs, as if by calling `AP`. Panics if the argument count is not even.

Example
package main

import (
	"fmt"

	x "github.com/mitranim/gax"
)

func main() {
	fmt.Println(
		x.AP(`class`, `some-class`).AP(`href`, `/`),
	)
	// class="some-class" href="/"
}
Output:

func (Attrs) Add added in v0.3.1

func (self Attrs) Add(key, val string) Attrs

Returns a modified version where each attribute with the matching key is modified via `Attr.Add` to append the given value to the previous value, space-separated. If no matching attribute is found, appends the given key-value as a new attribute. As a special case, if the key is empty, returns self as-is.

func (Attrs) AppendTo added in v0.3.0

func (self Attrs) AppendTo(buf []byte) []byte

Mostly for internal use.

func (Attrs) GoString added in v0.2.0

func (self Attrs) GoString() string

Implement `fmt.GoStringer` for debug purposes. Not used by builder methods. Represents itself as a call to `AP`, which is the recommended way to write this.

func (Attrs) Set added in v0.3.1

func (self Attrs) Set(key, val string) Attrs

Returns a modified version where each attribute with the matching key is modified via `Attr.Set` to have the given value. If no matching attribute is found, appends the given key-value as a new attribute. As a special case, if the key is empty, returns self as-is.

func (Attrs) String added in v0.2.0

func (self Attrs) String() string

Implement `fmt.Stringer` for debug purposes. Not used by builder methods.

type Bui

type Bui []byte

Short for "builder" or "builder for UI". Has methods for generating HTML/XML markup, declarative but efficient. See `E`, `F`, and `Bui.E` for 99% of the API you will use.

When used as a child (see `Bui.E`, `Bui.F`, `Bui.Child`), this also indicates pre-escaped markup, appending itself to another `Bui` without HTML/XML escaping. For strings, see `Str`.

Example
package main

import (
	"fmt"

	x "github.com/mitranim/gax"
)

func main() {
	var (
		E  = x.E
		AP = x.AP
	)

	type Dat struct {
		Title string
		Posts []string
	}

	dat := Dat{
		Title: `Posts`,
		Posts: []string{`Post0`, `Post1`},
	}

	bui := x.F(
		x.Str(x.Doctype),
		E(`html`, AP(`lang`, `en`),
			E(`head`, nil,
				E(`meta`, AP(`charset`, `utf-8`)),
				E(`link`, AP(`rel`, `icon`, `href`, `data:;base64,=`)),

				// Use normal Go conditionals.
				func(b *x.Bui) {
					if dat.Title != "" {
						b.E(`title`, nil, dat.Title)
					} else {
						b.E(`title`, nil, `test markup`)
					}
				},
			),

			E(`body`, nil,
				E(`h1`, AP(`class`, `title`), `Posts`),

				// Use normal Go loops.
				func(b *x.Bui) {
					for _, post := range dat.Posts {
						b.E(`h2`, nil, post)
					}
				},
			),
		),
	)

	fmt.Println(bui)
}
Output:

<!doctype html><html lang="en"><head><meta charset="utf-8"><link rel="icon" href="data:;base64,="><title>Posts</title></head><body><h1 class="title">Posts</h1><h2>Post0</h2><h2>Post1</h2></body></html>

func F added in v0.2.0

func F(vals ...any) (bui Bui)

Short for "fragment" or "document fragment". Shortcut for making `Bui` with these children.

Example
package main

import (
	"fmt"

	x "github.com/mitranim/gax"
)

func main() {
	var doc = x.F(
		x.Str(x.Doctype),
		x.E(`html`, nil),
	)

	fmt.Println(doc)
}
Output:

<!doctype html><html></html>

func (*Bui) Attr

func (self *Bui) Attr(val Attr)

Mostly for internal use. Writes an HTML/XML attribute, preceded with a space. Supports HTML bool attrs: if `Bool.Has(key)`, the attribute value may be adjusted for spec compliance. Automatically escapes the attribute value.

Sanity-checks the attribute name. Using an invalid name causes a panic.

func (*Bui) Attrs

func (self *Bui) Attrs(vals ...Attr)

Mostly for internal use. Writes HTML/XML attributes. Supports HTML special cases; see `Bui.Attr`.

func (*Bui) Begin

func (self *Bui) Begin(tag string, attrs Attrs)

Mostly for internal use. Writes the beginning of an HTML/XML element, with optional attrs. Supports HTML special cases; see `Bui.Attrs`. Sanity-checks the tag. Using an invalid tag causes a panic.

func (Bui) Bytes

func (self Bui) Bytes() []byte

Free cast to `[]byte`.

func (*Bui) C added in v0.2.0

func (self *Bui) C(val any)

Shorter alias for `Bui.Child`.

func (*Bui) Child

func (self *Bui) Child(val any)

Mostly for internal use. Writes an arbitrary child. See `Bui.E` for the list of special rules.

func (*Bui) E

func (self *Bui) E(tag string, attrs Attrs, children ...any)

One of the primary APIs. Counterpart to the function `E`. Short for "element" or "HTML element". Writes an HTML/XML tag, with attributes and inner content.

For a runnable example, see the definition of `Bui`.

Special rules for children:

  • `nil` is ignored.
  • `func()`, `func(*Bui)`, or `Ren.Render` is called for side effects.
  • `[]any` is recursively walked.
  • `[]Ren` is walked, calling `Ren.Render` on each val.
  • `[]T` where `T` implements `Ren` is walked, calling `Ren.Render` on each val.
  • Other values are stringified and escaped via `TextWri`.

To write text without escaping, use `Str` for strings and `Bui` for byte slices.

func (*Bui) End

func (self *Bui) End(tag string)

Mostly for internal use. Writes the end of an HTML/XML element. Supports HTML void elements, also known as self-closing tags: if `Void.Has(tag)`, this method is a nop. Sanity-checks the tag. Using an invalid tag causes a panic.

func (*Bui) EscBytes

func (self *Bui) EscBytes(val []byte)

Writes text content, escaping if necessary. For writing `string`, see `Bui.EscString`.

func (*Bui) EscString

func (self *Bui) EscString(val string)

Writes text content, escaping if necessary. For writing `[]byte`, see `Bui.EscBytes`.

func (*Bui) F added in v0.2.0

func (self *Bui) F(vals ...any)

Writes multiple children via `Bui.Child`. Like the "tail part" of `Bui.E`. Counterpart to the function `F`.

func (*Bui) NonEscBytes

func (self *Bui) NonEscBytes(val []byte)

Mostly for internal use. Writes without escaping. Intended for markup. For writing `string`, see `Bui.NonEscString`. For escaping, see `Bui.EscBytes`.

func (*Bui) NonEscString

func (self *Bui) NonEscString(val string)

Mostly for internal use. Writes without escaping. Intended for markup. For writing `[]byte`, see `Bui.NonEscBytes`. For escaping, see `Bui.EscString`.

func (Bui) Render added in v0.2.0

func (self Bui) Render(bui *Bui)

Implement `Ren`. Appends itself without HTML/XML escaping.

Example
package main

import (
	"fmt"

	x "github.com/mitranim/gax"
)

func main() {
	var bui x.Bui
	bui.E(`div`, nil, x.Bui(`<script>alert('hacked!')</script>`))

	fmt.Println(bui)
}
Output:

<div><script>alert('hacked!')</script></div>

func (Bui) String

func (self Bui) String() string

Free cast to `string`.

func (*Bui) T added in v0.2.0

func (self *Bui) T(val string)

Shorter alias for `Bui.EscString`.

func (*Bui) Text added in v0.3.0

func (self *Bui) Text(val string)

Shorter alias for `Bui.EscString`.

type Elem added in v0.2.0

type Elem struct {
	Tag   string
	Attrs Attrs
	Child any
}

Represents an HTML element. Usually created via `E`. Can render itself, or be passed as a child to `F` or `Bui.E`.

func E

func E(tag string, attrs Attrs, child ...any) Elem

Primary API. Short for "element" or "HTML element". Expresses an HTML/XML tag, with attributes and inner content. Creates an instance of `Elem`, which implements the `Ren` interface. It can render itself as HTML/XML, or be passed as a child to `F`, `E`, `Bui.E`.

For special rules regarding child encoding, see `Bui.E`.

Example
package main

import (
	"fmt"

	x "github.com/mitranim/gax"
)

func main() {
	var (
		E  = x.E
		AP = x.AP
	)

	fmt.Println(
		E(`span`, AP(`aria-hidden`, `true`), `🔥`),
	)
}
Output:

<span aria-hidden="true">🔥</span>

func (Elem) AttrAdd added in v0.3.1

func (self Elem) AttrAdd(key, val string) Elem

func (Elem) AttrSet added in v0.3.1

func (self Elem) AttrSet(key, val string) Elem

Returns a modified version where `.Attrs` contain an attribute with

func (Elem) GoString added in v0.2.0

func (self Elem) GoString() string

Implement `fmt.GoStringer` for debug purposes. Not used by builder methods. Represents itself as a call to `E`, which is the recommended way to write this.

func (Elem) Render added in v0.2.0

func (self Elem) Render(b *Bui)

Implement `Ren`. This allows `Elem` to be passed as a child to the various rendering functions like `E`, `F`, `Bui.E`. As a special case, `Elem` with an empty `.Tag` does not render anything.

func (Elem) String added in v0.2.0

func (self Elem) String() string

Implement `fmt.Stringer` for debug purposes. Not used by builder methods.

type NonEscWri

type NonEscWri []byte

Short for "non-escaping writer". Mostly for internal use. Similar to `bytes.Buffer` or `strings.Builder`, but simpler and more flexible, because it's just a typedef of `[]byte`.

func (NonEscWri) String

func (self NonEscWri) String() string

Similar to `strings.Builder.String`. Free cast with no allocation.

func (*NonEscWri) Write

func (self *NonEscWri) Write(val []byte) (int, error)

Implement `io.Writer`. Similar to `strings.Builder.Write`.

func (*NonEscWri) WriteRune

func (self *NonEscWri) WriteRune(val rune) (int, error)

Similar to `strings.Builder.WriteRune`.

func (*NonEscWri) WriteString

func (self *NonEscWri) WriteString(val string) (int, error)

Implement `io.StringWriter`. Similar to `strings.Builder.WriteString`.

type Ren added in v0.2.0

type Ren interface{ Render(*Bui) }

Short for "renderer". On children implementing this interface, the `Render` method is called for side effects, instead of stringifying the child.

type Str added in v0.2.0

type Str string

Indicates pre-escaped markup. Children of this type are written as-is without additional HTML/XML escaping. For bytes, use `Bui`.

func (Str) Render added in v0.2.0

func (self Str) Render(bui *Bui)

Implement `Ren`. Appends itself without HTML/XML escaping.

Example
package main

import (
	"fmt"

	x "github.com/mitranim/gax"
)

func main() {
	var bui x.Bui
	bui.E(`div`, nil, x.Str(`<script>alert('hacked!')</script>`))

	fmt.Println(bui)
}
Output:

<div><script>alert('hacked!')</script></div>

type TextWri

type TextWri []byte

Short for "text writer". Mostly for internal use. Writes text as if it were inside an HTML/XML element, escaping as necessary. For escaping rules, see:

https://www.w3.org/TR/html52/syntax.html#escaping-a-string

func (TextWri) String

func (self TextWri) String() string

Similar to `strings.Builder.String`. Free cast with no allocation.

func (*TextWri) Write

func (self *TextWri) Write(val []byte) (int, error)

Implement `io.Writer`. Similar to `strings.Builder.Write`, but escapes special chars. Technically not compliant with `io.Writer`: the returned count of written bytes may exceed the size of the provided chunk.

func (*TextWri) WriteRune

func (self *TextWri) WriteRune(val rune) (int, error)

Similar to `strings.Builder.WriteRune`, but escapes special chars.

func (*TextWri) WriteString

func (self *TextWri) WriteString(val string) (size int, _ error)

Implement `io.StringWriter`. Similar to `strings.Builder.WriteString`, but escapes special chars.

Jump to

Keyboard shortcuts

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