gox

package module
v0.1.20 Latest Latest
Warning

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

Go to latest
Published: Mar 29, 2026 License: MIT Imports: 0 Imported by: 0

README

GoX

GoX — HTML templates as first-class Go expressions

GoX lets you write HTML-like templates in .gox files, compile them into .x.go files in the same Go package, and keep editor features working across both source and generated code. The generated code is plain Go, so the template layer stays inspectable, debuggable, and extensible from ordinary Go code.

Syntax guide: doors.dev/docs/template-syntax
This README focuses on installation, workflow, editor integration, and the rendering API behind GoX.


Install

Install the gox tool

The easiest path is the prebuilt binary from GitHub Releases.

To install from source:

make install

That builds the bundled Rust formatter and installs gox. Building from source requires Go, Cargo, and a working native toolchain.

Editor integration

Recommended: use the official VS Code or Neovim extension.

The bare gox command starts the GoX language server. gox srv is the explicit form of the same command.

If you are wiring an editor manually:

  1. Run gox or gox srv.
  2. Attach it to both .go and .gox buffers.
  3. Disable a separate workspace gopls client. GoX launches and proxies a gopls instance.
  4. Make sure gopls is on PATH, or pass -gopls /path/to/gopls.
  5. Install the tree-sitter-gox grammar.

Useful server flags:

  • -gopls to point at a specific gopls binary
  • -listen to expose the server over TCP or a Unix socket instead of stdio
  • -listen.timeout to stop an idle socket server after a timeout

GoX sits in front of gopls. It parses .gox, generates .x.go, keeps source and target positions mapped both ways, and forwards normal Go language features through a gopls instance.

Add the Go package

go get github.com/doors-dev/gox

Keep the installed gox tool and the Go module version reasonably in sync. Generated .x.go files carry a GoX version marker, and newer generated files require a tooling upgrade.


Workflow

File layout

A typical package can contain all three kinds of files:

main.go     # regular Go
page.gox    # source template
page.x.go   # generated Go

What you edit:

  • edit .gox
  • treat .x.go as generated output
  • keep normal .go files alongside both

What the tooling does:

  • the language server regenerates .x.go on save
  • gox gen does the same in batch mode
  • orphaned .x.go files are removed automatically

Rules worth following:

  • do not edit .x.go manually
  • do not use the .x.go suffix for hand-written files
  • if a generated file was produced by a newer GoX version, upgrade the tooling before continuing

Commands you actually use

gox                 # start the language server
gox gen             # generate .x.go files for the current directory
gox gen ./pkg       # generate a specific file or directory
gox fmt             # format .gox and .go files in the current directory
gox fmt ./internal  # format a specific directory
gox fmt ./main.go   # format a specific file
gox ver             # print the GoX version

By default, gox gen and gox fmt use the current directory and respect .gitignore. Both commands also accept an optional positional file or directory path. -no-ignore disables ignore handling, and -force skips target-file safety checks during generation.

The old -path flag is no longer supported.

What happens under the hood

The .gox parser produces a syntax tree. The assembler walks that tree and lowers template nodes into plain Go built around gox.Elem(func(cur gox.Cursor) error { ... }).

Alongside generation, GoX keeps a source-to-target translation map between .gox and .x.go. That mapping is used not only for diagnostics, but also for editor navigation and edits that need to round-trip through generated Go.

gox fmt formats .gox with the bundled Rust formatter and regular .go files with gofmt. Embedded <script> and <style> blocks are reformatted too.


Rendering Model

Generated .gox code ultimately works with four things:

  • render values such as Elem, Comp, and templ-compatible components
  • a Cursor that builds structure and emits rendering operations
  • an Attrs set attached to an open head before it is submitted
  • a stream of Job values consumed by a Printer

That is the useful mental model for GoX. Once you understand those pieces, generated .x.go files are easy to read and the advanced hooks stop feeling magical.

Render values

These are the core renderable types:

type Comp interface {
    Main() Elem
}

type Elem func(cur Cursor) error

Elem is the main render value in GoX. It is a function that renders through a Cursor.

It also:

  • implements Comp by returning itself from Main()
  • renders directly with Render(ctx, w)
  • renders through custom pipelines with Print(ctx, printer)

GoX also defines a minimal Templ interface for values that render with Render(ctx, w), and Cursor.Any knows how to emit those too.

So in normal Go code, a template is just a value you can return, store, pass around, and render.

Cursor

Cursor is the low-level rendering state machine. It streams operations to a Printer and tracks active element heads so it can enforce correct ordering.

There are three head lifecycles:

  1. Regular element Init(tag) -> optional AttrSet / AttrMod -> Submit() -> child content -> Close()
  2. Void element InitVoid(tag) -> optional AttrSet / AttrMod -> Submit()
  3. Container InitContainer() -> child content -> Close()

Regular and void heads become HTML tags. Containers do not emit a tag, but they still create open and close jobs in the stream, which makes them useful for grouping and for render-time transformations.

The important state rule is:

  • before Submit(), you are still building a head and may mutate attributes
  • after Submit(), you may emit child content, but you may no longer mutate that head

cur.Context() returns the default context for jobs emitted through that cursor. cur.Send() forwards a prebuilt job directly to the underlying printer and bypasses cursor state validation.

Generated .x.go files are mostly straightforward cursor code, simular to:

func Badge(label string) gox.Elem {
    return func(cur gox.Cursor) error {
        err := cur.Init("span"); err != nil {
            return err
        }
        if err := cur.AttrSet("class", "badge"); err != nil {
            return err
        }
        if err := cur.Submit(); err != nil {
            return err
        }
        if err := cur.Text(label); err != nil {
            return err
        }
        return cur.Close()
    }
}

Most placeholder rendering ends up calling Cursor.Any or Cursor.Many.

Cursor.Any understands:

  • string and []string
  • Elem and []Elem
  • Comp and []Comp
  • Job and []Job
  • Editor
  • Templ
  • []interface{}

Anything else falls back to escaped fmt.Fprint.

Attributes

Attrs is the mutable attribute set attached to a head while that head is being built.

You mainly encounter attributes in three places:

  • when generated code or hand-written cursor code calls AttrSet
  • when code calls AttrMod to attach one or more render-time modifiers
  • when custom printers or proxies inspect JobHeadOpen.Attrs

Important details from the API:

  • attributes are stored sorted by name
  • names are case-sensitive
  • nil means "unset"
  • false means "unset"
  • any other non-nil value means "set"

The attribute system is also an extension point:

type Modify interface {
    Modify(ctx context.Context, tag string, attrs Attrs) error
}

type Mutate interface {
    Mutate(name string, value any) any
}

type Output interface {
    Output(w io.Writer) error
}

Modify is head-level, not value-level. It runs right before a head is rendered, receives the full Attrs set, and can inspect or change the final attributes for that element. This is the hook used by render-time attribute transformations.

Mutate is value-level. It lets a new attribute value depend on the previous value already stored under the same name.

Output lets a value control its own escaped output.

That makes attributes more than plain HTML metadata. They are also part of the rendering pipeline.

Extension points

GoX exposes three main render-time extension points:

  • Editor for code that needs direct cursor access
  • Proxy for wrapping or rebasing an element subtree before it renders
  • Printer for consuming and transforming the emitted job stream
Editor

Editor is the escape hatch for render-time behavior that needs direct cursor access.

Use it when rendering needs to:

  • emit low-level jobs manually
  • work directly with cur.Context()
  • integrate with a larger rendering runtime
  • do something more specific than "return another subtree"
Proxy

A Proxy wraps an Elem subtree before it renders.

You can do any type of transofrmation with proxy, for example:

  • change attributes
  • convert tags
  • render the subtree through a custom printer before forwarding it
  • basically any transormation

A common implementation pattern is a proxy printer: call elem.Print(cur.Context(), customPrinter), inspect the first *gox.JobHeadOpen or *gox.JobComp, adjust it, then forward the rest into the current cursor.

Printer

Rendering is a job stream.

Useful concrete job types include:

  • *gox.JobHeadOpen
  • *gox.JobHeadClose
  • *gox.JobText
  • *gox.JobRaw
  • *gox.JobBytes
  • *gox.JobComp
  • *gox.JobTempl
  • *gox.JobFprint
  • *gox.JobError

Important behavior from the API:

  • open and close jobs for the same head share an ID
  • container head jobs emit no HTML, but still exist in the stream
  • the default printer from gox.NewPrinter checks j.Context().Err() before calling Output
  • jobs are pooled and single-use

Custom printers are where GoX opens up the most. They can buffer, transform, route, inspect, or reinterpret the stream instead of just writing HTML sequentially.

Helper adapters

The helpers in helpers.go keep the API lightweight when you want one-off implementations:

  • gox.EditorComp for values that should be both Editor and Comp
  • gox.EditorCompFunc
  • gox.EditorFunc
  • gox.ProxyFunc
  • gox.AttrModFunc
  • gox.PrinterFunc
  • gox.NewEscapedWriter

gox.NewEscapedWriter is useful when custom rendering code needs the same escaping rules as GoX text and attribute output.


Compatibility

gox.Elem implements both gox.Comp and a templ-style Render(ctx, w) method, so it can sit in existing render pipelines while still exposing lower-level hooks when you need them.


Disclaimer: GoX is an independent, third-party project and is not affiliated with, endorsed by, or sponsored by The Go Project, Google, or any official Go tooling.

Documentation

Overview

Package gox renders HTML-like templates as ordinary Go values.

In practice, templates are usually authored in `.gox` with HTML expressions or the `elem` form, and GoX compiles that syntax to Elem values.

Common forms:

// Regular Go function returning a template expression.
func Badge(label string) gox.Elem {
	return <span class="badge">~(label)</span>
}

// `elem` is shorthand for the same thing.
elem Badge(label string) {
	<span class="badge">~(label)</span>
}

// Generated/lowered form using Cursor directly.
func Badge(label string) gox.Elem {
	return func(cur gox.Cursor) error {
		if err := cur.Init("span"); err != nil {
			return err
		}
		if err := cur.AttrSet("class", "badge"); err != nil {
			return err
		}
		if err := cur.Submit(); err != nil {
			return err
		}
		if err := cur.Text(label); err != nil {
			return err
		}
		return cur.Close()
	}
}

Elem renders through Cursor under the hood. Cursor, Attrs, Job, and Printer are the lower-level APIs for custom rendering, stream transforms, and other integrations.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewEscapedWriter

func NewEscapedWriter(w io.Writer) io.Writer

NewEscapedWriter returns a writer that applies GoX's HTML escaping rules.

It is useful in custom printers or helpers that need the same escaping behavior as Text, Fprint, and attribute output.

func Release

func Release(r Releaser)

Release returns r to its pool.

Most callers never need Release because the standard Job implementations release themselves from Output.

Types

type Attr

type Attr = *attr

Attr is a handle to one attribute entry.

Attr values usually come from Attrs.Get, Attrs.Find, or Attrs.List.

func (Attr) IsSet

func (a Attr) IsSet() bool

IsSet reports whether this attribute should be rendered.

Rules:

  • nil Attr => false
  • stored value is bool => that bool value (true=set, false=unset)
  • otherwise => value != nil

func (Attr) Name

func (a Attr) Name() string

Name returns the attribute name.

func (Attr) OutputName

func (a Attr) OutputName(w io.Writer) error

OutputName writes only the attribute name to w.

This is a low-level helper for custom rendering pipelines.

func (Attr) OutputValue

func (a Attr) OutputValue(w io.Writer) error

OutputValue writes only the attribute value to w.

This is a low-level helper for custom rendering pipelines.

func (Attr) Set

func (a Attr) Set(value any)

Set stores value in the attribute.

If value implements Mutate, Set stores the result of value.Mutate(attributeName, currentValue). Nil unsets the attribute. A bool false is stored but still treated as unset by IsSet.

func (Attr) Unset

func (a Attr) Unset()

Unset clears the attribute value.

func (Attr) Value

func (a Attr) Value() any

Value returns the stored value, which may be nil.

type AttrModFunc

type AttrModFunc func(ctx context.Context, tag string, attrs Attrs) error

AttrModFunc adapts a function into a Modify.

func (AttrModFunc) Modify

func (a AttrModFunc) Modify(ctx context.Context, tag string, attrs Attrs) error

Modify calls a(ctx, tag, attrs).

type Attrs

type Attrs = *attrs

Attrs stores the attributes attached to one element head.

Example:

attrs := gox.NewAttrs()
attrs.Get("class").Set("badge")
attrs.Get("hidden").Set(false)

Attrs keeps entries sorted by name for stable output and binary-search lookups. Names are case-sensitive, so "class" and "Class" are different attributes.

Presence follows these rules:

  • nil means unset
  • bool is set only when true
  • any other non-nil value is set

Attr handles returned by Get, Find, and List point into the owning Attrs. Attrs is not safe for concurrent use.

func NewAttrs

func NewAttrs() Attrs

NewAttrs returns an empty attribute set.

func (Attrs) AddMod

func (a Attrs) AddMod(m Modify)

AddMod queues m to run during ApplyMods.

func (Attrs) ApplyMods

func (a Attrs) ApplyMods(ctx context.Context, tag string) error

ApplyMods runs all queued modifiers on this attribute set.

Modifiers run in insertion order. If one returns an error, ApplyMods stops immediately. Modifiers that already ran are discarded.

func (Attrs) Clone

func (a Attrs) Clone() Attrs

Clone returns an independent copy of the attribute set.

Clone copies only attributes that are currently set. The modifier slice is copied too, but modifier values are shared.

func (Attrs) Find

func (a Attrs) Find(name string) (Attr, bool)

Find returns the set attribute named name.

Find does not create missing entries and returns false for unset ones.

func (Attrs) Get

func (a Attrs) Get(name string) Attr

Get returns the entry for name, creating it when needed.

The returned Attr is a live handle into this Attrs.

func (Attrs) Has

func (a Attrs) Has(name string) bool

Has reports whether name exists and is currently set.

func (Attrs) Inherit

func (a Attrs) Inherit(attrs Attrs)

Inherit copies all set attributes from attrs into a.

For each attribute in attrs:

  • if it is not set (per Attr.IsSet), it is ignored
  • otherwise, a.Get(name).Set(value) is performed

Note: because this uses Attr.Set, if the target attribute already has a value and the inherited value implements Mutate, the inherited value may be computed from the target’s previous value.

func (Attrs) List

func (a Attrs) List() []Attr

List returns a snapshot of all tracked attribute entries.

The slice itself is copied, but each Attr still points at the original entry. List includes unset attributes.

type Comp

type Comp interface {
	Main() Elem
}

Comp is anything that can produce a root Elem.

In practice, most components are written in `.gox` as `elem (T) Main() { ... }`. Elem itself also satisfies Comp, so APIs can accept either components or plain Elem values through one interface. Main may return nil to render nothing.

type Cursor

type Cursor = *cursor

Cursor builds output by streaming Jobs to a Printer.

Most `.gox` users never construct a Cursor directly because generated code does it for them. Reach for Cursor when you need manual rendering, custom editors, or proxy/printer integrations.

Example:

cur := gox.NewCursor(ctx, gox.NewPrinter(w))
_ = cur.Init("span")
_ = cur.AttrSet("class", "badge")
_ = cur.Submit()
_ = cur.Text("New")
_ = cur.Close()

Cursor maintains a stack of active heads to validate nesting and enforce a small state machine:

Regular element lifecycle:

  1. Init(tag)
  2. (optional) AttrSet / AttrMod
  3. Submit() // emits head-open job
  4. emit children jobs // Text/Comp/Any/etc.
  5. Close() // emits head-close job

Void element lifecycle:

  1. InitVoid(tag)
  2. (optional) AttrSet / AttrMod
  3. Submit() // emits head-open job; no children and no Close

Container lifecycle:

  1. InitContainer() // emits container head-open job immediately
  2. emit children jobs
  3. Close() // emits container head-close job

Content state

Several methods require the cursor to be in a content state, meaning:

  • no element head is active (top-level), OR
  • the current element/container head has already been submitted with Submit, and may accept children.

Cursor is not safe for concurrent use.

func NewCursor

func NewCursor(ctx context.Context, printer Printer) Cursor

NewCursor returns a Cursor that emits jobs to printer.

ctx becomes the default context for jobs created through this cursor. The returned cursor starts at top level, so callers may emit content immediately or begin a new head with Init, InitVoid, or InitContainer.

func (Cursor) Any

func (c Cursor) Any(any any) error

Any renders a value using GoX's default dynamic dispatch.

Supported cases include:

  • string / []string
  • Elem / []Elem
  • Comp / []Comp
  • Job / []Job
  • Editor
  • Templ
  • []any (treated as a variadic list)

Nil interface values are ignored. Everything else falls back to Fprint.

func (Cursor) AttrMod

func (c Cursor) AttrMod(mods ...Modify) error

AttrMod adds one or more modifiers to the current head.

AttrMod may be used only after Init or InitVoid and before Submit. Modifiers run right before rendering and may inspect, leave unchanged, or mutate the full attribute set.

func (Cursor) AttrSet

func (c Cursor) AttrSet(name string, value any) error

AttrSet sets an attribute on the current head.

AttrSet may be used only after Init or InitVoid and before Submit.

func (Cursor) Bytes

func (c Cursor) Bytes(data []byte) error

Bytes emits raw bytes at the current cursor position.

func (Cursor) Close

func (c Cursor) Close() error

Close closes the current regular or container head.

The current head must already be submitted. Void elements must not be closed.

func (Cursor) Comp

func (c Cursor) Comp(comp Comp) error

Comp emits comp at the current cursor position.

func (Cursor) CompCtx

func (c Cursor) CompCtx(ctx context.Context, comp Comp) error

CompCtx is like Comp but uses ctx for the emitted job.

func (Cursor) Context

func (c Cursor) Context() context.Context

Context returns the default context for jobs emitted by this cursor.

func (Cursor) Editor

func (c Cursor) Editor(editor Editor) error

Editor applies editor to this cursor.

func (Cursor) Fprint

func (c Cursor) Fprint(any any) error

Fprint renders any with fmt.Fprint and GoX escaping.

func (Cursor) Init

func (c Cursor) Init(tag string) error

Init starts a regular element head.

After Init, callers may set attributes with AttrSet or AttrMod. Child content must wait until Submit succeeds.

func (Cursor) InitContainer

func (c Cursor) InitContainer() error

InitContainer starts a synthetic container head and submits it immediately.

Containers do not emit an HTML tag. They group a range of child jobs under a shared head id and must still be closed with Close.

func (Cursor) InitVoid

func (c Cursor) InitVoid(tag string) error

InitVoid starts a void element head.

Void heads may receive attributes before Submit, but they never accept children and must not be closed.

func (Cursor) Many

func (c Cursor) Many(many ...any) error

Many renders each value in order using Any.

Many is a convenient way to emit mixed values without switching manually.

func (Cursor) NewID

func (c Cursor) NewID() uint64

NewID returns a process-unique id for correlating render-time state.

IDs increase monotonically within one cursor.

func (Cursor) Raw

func (c Cursor) Raw(text string) error

Raw emits unescaped text at the current cursor position.

func (Cursor) Send

func (c Cursor) Send(job Job) error

Send forwards job directly to the underlying Printer.

Send skips cursor state validation, so callers must preserve any ordering and nesting guarantees they need.

func (Cursor) Submit

func (c Cursor) Submit() error

Submit emits the current head's opening job.

After Submit, the current head is open for child content and no longer accepts attribute mutation.

func (Cursor) Templ

func (c Cursor) Templ(templ Templ) error

Templ emits a templ-compatible component at the current cursor position.

func (Cursor) TemplCtx

func (c Cursor) TemplCtx(ctx context.Context, templ Templ) error

TemplCtx is like Templ but uses ctx for the emitted job.

func (Cursor) Text

func (c Cursor) Text(text string) error

Text emits escaped text at the current cursor position.

type Editor

type Editor interface {
	Edit(cur Cursor) error
}

Editor renders by operating on a Cursor directly.

Use Editor when returning another Elem is not enough and the implementation needs low-level access to cursor methods or custom jobs.

type EditorComp

type EditorComp interface {
	Editor
	Comp
}

EditorComp is both a low-level Editor and a regular Comp.

It is useful for values that should plug into component-based APIs while still exposing direct cursor control.

type EditorCompFunc

type EditorCompFunc func(cur Cursor) error

EditorCompFunc adapts a function into an EditorComp.

It is useful for small helpers that need direct cursor access but should also satisfy component-based APIs.

func (EditorCompFunc) Edit

func (e EditorCompFunc) Edit(cur Cursor) error

Edit calls e(cur).

func (EditorCompFunc) Main

func (e EditorCompFunc) Main() Elem

Main makes EditorCompFunc satisfy Comp by returning it as an Elem.

type EditorFunc

type EditorFunc func(cur Cursor) error

EditorFunc adapts a function into an Editor.

func (EditorFunc) Edit

func (e EditorFunc) Edit(cur Cursor) error

Edit calls e(cur).

type Elem

type Elem func(cur Cursor) error

Elem is the runtime value produced by GoX template syntax.

In practice, Elem is usually authored in `.gox` as an HTML expression or with the `elem` form.

Example:

var badge gox.Elem = <span class="badge">New</span>

elem Badge(label string) {
	<span class="badge">~(label)</span>
}

Generated code lowers Elem to Cursor operations. Elem also implements Comp and templ-style rendering through Render.

func (Elem) Main

func (e Elem) Main() Elem

Main makes Elem satisfy Comp by returning itself.

func (Elem) Print

func (e Elem) Print(ctx context.Context, printer Printer) error

Print renders e through printer using ctx as the default job context.

Print creates a fresh Cursor, executes e, and streams the resulting jobs to printer. If e is nil, Print returns nil.

func (Elem) Render

func (e Elem) Render(ctx context.Context, w io.Writer) error

Render writes e to w with GoX's default Printer.

Render is the usual way to turn an Elem into HTML and also makes Elem usable in templ-style rendering pipelines. If e is nil, Render returns nil.

type HeadError

type HeadError string

HeadError reports an invalid Cursor state transition.

Examples include emitting child content before Submit or mutating attributes after a head has already been submitted.

func (HeadError) Error

func (e HeadError) Error() string

type HeadKind

type HeadKind int

HeadKind identifies what kind of head Cursor is building.

The kind controls whether the head emits a real tag and whether it may have children.

const (
	// KindContainer is a synthetic head used to group a sequence of jobs without
	// emitting an actual HTML tag. It is submitted immediately.
	KindContainer HeadKind = iota

	// KindRegular is a normal, non-void HTML element.
	KindRegular

	// KindVoid is a void/self-closing HTML element (e.g. <input>, <br>, etc.).
	// Void heads are submitted as an open job and then removed from the stack;
	// they never accept children and must not be closed.
	KindVoid
)

type Job

type Job interface {
	// Context returns the context associated with this job.
	Context() context.Context
	Output
}

Job is a single render operation emitted by Cursor.

Concrete jobs such as JobHeadOpen, JobText, and JobComp let custom printers observe or transform the stream while still sharing one cancellation context.

type JobBytes

type JobBytes struct {
	Ctx   context.Context
	Bytes []byte
}

JobBytes writes bytes without escaping.

func NewJobBytes

func NewJobBytes(ctx context.Context, b []byte) *JobBytes

NewJobBytes returns a pooled JobBytes.

func (*JobBytes) Context

func (j *JobBytes) Context() context.Context

Context returns the context associated with this job.

func (*JobBytes) Output

func (j *JobBytes) Output(w io.Writer) error

Output writes Bytes directly to w.

type JobComp

type JobComp struct {
	Comp Comp
	Ctx  context.Context
}

JobComp renders a Comp.

Output calls Comp.Main and, when it returns a non-nil Elem, renders that Elem into the output stream.

func NewJobComp

func NewJobComp(ctx context.Context, comp Comp) *JobComp

NewJobComp returns a pooled JobComp.

func (*JobComp) Context

func (j *JobComp) Context() context.Context

Context returns the context associated with this job.

func (*JobComp) Output

func (j *JobComp) Output(w io.Writer) error

Output renders the component's root element (if any) into w.

type JobError

type JobError struct {
	Ctx context.Context
	Err error
}

JobError fails rendering with a stored error.

func NewJobError

func NewJobError(ctx context.Context, err error) *JobError

NewJobError returns a pooled JobError.

func (*JobError) Context

func (j *JobError) Context() context.Context

Context returns the context associated with this job.

func (*JobError) Output

func (j *JobError) Output(w io.Writer) error

Output returns the stored error.

type JobFprint

type JobFprint struct {
	Ctx context.Context
	Any any
}

JobFprint formats a value with fmt.Fprint and GoX escaping.

It is the default fallback for values that do not have specialized handling in Cursor.Any.

func NewJobFprint

func NewJobFprint(ctx context.Context, v any) *JobFprint

NewJobFprint returns a pooled JobFprint.

func (*JobFprint) Context

func (j *JobFprint) Context() context.Context

Context returns the context associated with this job.

func (*JobFprint) Output

func (j *JobFprint) Output(w io.Writer) error

Output writes Any to w using fmt.Fprint with escaping applied.

type JobHeadClose

type JobHeadClose struct {
	// ID is the head identifier associated with this element/container.
	// The opening and closing jobs for the same head share the same ID.
	ID uint64

	// Kind describes how this head should be rendered (regular/void/container).
	Kind HeadKind

	// Tag is the element tag name. It must be non-empty for regular heads.
	Tag string

	// Ctx is the context associated with this job.
	Ctx context.Context
}

JobHeadClose writes the closing half of a head.

Regular heads emit `</tag>`. Container heads emit no HTML. Closing a void head is an error.

func NewJobHeadClose

func NewJobHeadClose(ctx context.Context, id uint64, kind HeadKind, tag string) *JobHeadClose

NewJobHeadClose returns a pooled JobHeadClose.

func (*JobHeadClose) Context

func (j *JobHeadClose) Context() context.Context

Context returns the context associated with this job.

func (*JobHeadClose) Output

func (j *JobHeadClose) Output(w io.Writer) error

Output writes the closing tag to w.

Behavior by kind:

  • KindContainer: writes nothing and returns nil
  • KindVoid: returns an error (void elements cannot be closed)
  • KindRegular: requires Tag to be non-empty and writes `</tag>`

type JobHeadOpen

type JobHeadOpen struct {
	// ID is the head identifier associated with this element/container.
	// The opening and closing jobs for the same head share the same ID.
	ID uint64

	// Kind describes how this head should be rendered (regular/void/container).
	Kind HeadKind

	// Tag is the element tag name. It must be non-empty for regular/void heads.
	Tag string

	// Ctx is the context used for attribute modifiers and downstream render hooks.
	Ctx context.Context

	// Attrs is the attribute set associated with this head.
	Attrs Attrs
}

JobHeadOpen writes the opening half of a head.

Regular and void heads emit `<tag ...>`. Container heads emit no HTML.

func NewJobHeadOpen

func NewJobHeadOpen(ctx context.Context, id uint64, kind HeadKind, tag string, attrs Attrs) *JobHeadOpen

NewJobHeadOpen returns a pooled JobHeadOpen.

The returned job is single-use and is usually sent straight to a Printer.

func (*JobHeadOpen) Context

func (j *JobHeadOpen) Context() context.Context

Context returns the context associated with this job.

func (*JobHeadOpen) Output

func (j *JobHeadOpen) Output(w io.Writer) error

Output writes the opening tag and attributes to w.

Behavior by kind:

  • KindContainer: writes nothing and returns nil
  • KindRegular / KindVoid: requires Tag to be non-empty and writes `<tag ...>`

type JobRaw

type JobRaw struct {
	Ctx  context.Context
	Text string
}

JobRaw writes unescaped text.

func NewJobRaw

func NewJobRaw(ctx context.Context, text string) *JobRaw

NewJobRaw returns a pooled JobRaw.

func (*JobRaw) Context

func (j *JobRaw) Context() context.Context

Context returns the context associated with this job.

func (*JobRaw) Output

func (j *JobRaw) Output(w io.Writer) error

Output writes Text to w without escaping.

type JobTempl

type JobTempl struct {
	Ctx   context.Context
	Templ Templ
}

JobTempl renders a templ-compatible value.

func NewJobTempl

func NewJobTempl(ctx context.Context, templ Templ) *JobTempl

NewJobTempl returns a pooled JobTempl.

func (*JobTempl) Context

func (j *JobTempl) Context() context.Context

Context returns the context associated with this job.

func (*JobTempl) Output

func (j *JobTempl) Output(w io.Writer) error

Output renders the templ component into w.

type JobText

type JobText struct {
	Ctx  context.Context
	Text string
}

JobText writes escaped text.

func NewJobText

func NewJobText(ctx context.Context, text string) *JobText

NewJobText returns a pooled JobText.

func (*JobText) Context

func (j *JobText) Context() context.Context

Context returns the context associated with this job.

func (*JobText) Output

func (j *JobText) Output(w io.Writer) error

Output writes Text to w with HTML escaping applied.

type Modify

type Modify interface {
	Modify(ctx context.Context, tag string, attrs Attrs) error
}

Modify can inspect and mutate an element's full attribute set right before rendering.

Modifiers run in the order they were added. They are one-shot: after ApplyMods succeeds, the queue is cleared.

type Mutate

type Mutate interface {
	Mutate(name string, value any) any
}

Mutate computes a new attribute value from the previous one.

Attr.Set detects Mutate values and stores the result of value.Mutate(attributeName, currentValue).

type Output

type Output = utils.Output

Output is the low-level write contract implemented by Jobs.

Output is aliased from an internal utility type.

type OutputError

type OutputError string

OutputError reports invalid job state during rendering.

func (OutputError) Error

func (e OutputError) Error() string

type Printer

type Printer interface {
	Send(j Job) error
}

Printer consumes the Job stream produced during rendering.

The default Printer from NewPrinter writes HTML sequentially to an io.Writer. Custom printers can inspect, buffer, rewrite, or reroute jobs before final output. Printer implementations are not required to be safe for concurrent use unless they document otherwise.

func NewPrinter

func NewPrinter(w io.Writer) Printer

NewPrinter returns the default Printer that writes jobs to w in order.

type PrinterFunc

type PrinterFunc func(j Job) error

PrinterFunc adapts a function into a Printer.

func (PrinterFunc) Send

func (p PrinterFunc) Send(j Job) error

Send calls p(j).

type Proxy

type Proxy interface {
	Proxy(cur Cursor, elem Elem) error
}

Proxy wraps an Elem before it is rendered.

Proxies are useful when a subtree needs cross-cutting behavior such as instrumentation, attribute injection, conditional rendering, or rerouting through a custom Printer.

type ProxyFunc

type ProxyFunc func(cur Cursor, elem Elem) error

ProxyFunc adapts a function into a Proxy.

func (ProxyFunc) Proxy

func (p ProxyFunc) Proxy(cur Cursor, elem Elem) error

Proxy calls p(cur, elem).

type Releaser

type Releaser interface {
	// contains filtered or unexported methods
}

Releaser returns a pooled value to its internal pool.

Released values are single-use and must not be touched again.

type Templ

type Templ interface {
	Render(ctx context.Context, w io.Writer) error
}

Templ is the minimal templ-compatible rendering interface.

Elem implements Templ via Render.

Jump to

Keyboard shortcuts

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