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 gox.Elem(func(cur gox.Cursor) error {
if err := cur.Init("span"); err != nil {
return err
}
if err := cur.Set("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 ¶
- func NewEscapedWriter(w io.Writer) io.Writer
- func Release(r Releaser)
- type Attr
- type Attrs
- func (a Attrs) AddMod(m Modify)
- func (a Attrs) ApplyMods(ctx context.Context, tag string) error
- func (a Attrs) Clone() Attrs
- func (a Attrs) Find(name string) (Attr, bool)
- func (a Attrs) Get(name string) Attr
- func (a Attrs) Has(name string) bool
- func (a Attrs) Inherit(attrs Attrs)
- func (a Attrs) List() []Attr
- type Comp
- type Cursor
- func (c Cursor) Any(any any) error
- func (c Cursor) AttrMod(mods ...Modify) errordeprecated
- func (c Cursor) AttrSet(name string, value any) errordeprecated
- func (c Cursor) Bytes(data []byte) error
- func (c Cursor) Close() error
- func (c Cursor) Comp(comp Comp) error
- func (c Cursor) CompCtx(ctx context.Context, comp Comp) error
- func (c Cursor) Context() context.Context
- func (c Cursor) Editor(editor Editor) error
- func (c Cursor) Fprint(any any) error
- func (c Cursor) Init(tag string) error
- func (c Cursor) InitContainer() error
- func (c Cursor) InitVoid(tag string) error
- func (c Cursor) Many(many ...any) error
- func (c Cursor) Modify(mods ...Modify) error
- func (c Cursor) NewID() uint64
- func (c Cursor) Printer() Printer
- func (c Cursor) Raw(text string) error
- func (c Cursor) Send(job Job) errordeprecated
- func (c Cursor) Set(name string, value any) error
- func (c Cursor) Submit() error
- func (c Cursor) Templ(templ Templ) error
- func (c Cursor) TemplCtx(ctx context.Context, templ Templ) error
- func (c Cursor) Text(text string) error
- type Editor
- type EditorComp
- type EditorCompFunc
- type EditorFunc
- type Elem
- type HeadError
- type HeadKind
- type Job
- type JobBytes
- type JobComp
- type JobError
- type JobFprint
- type JobHeadClose
- type JobHeadOpen
- type JobRaw
- type JobTempl
- type JobText
- type Modify
- type ModifyFunc
- type Mutate
- type Output
- type OutputError
- type Printer
- type PrinterFunc
- type Proxy
- type ProxyFunc
- type Releaser
- type Templ
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func NewEscapedWriter ¶ added in v0.0.41
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.
Writes replace &, <, >, " and ' with entities and NUL with U+FFFD, and pass everything else through. That covers element text and quoted attribute values; it is not sufficient for JavaScript, CSS, or URL contexts.
func Release ¶ added in v0.0.5
func Release(r Releaser)
Release returns r to its pool.
Most callers never need Release because the standard Job implementations release themselves from Output, including when Output returns an error. Release is for the opposite case: a custom printer that drops a job instead of outputting it. Never do both: a value released twice can be handed out to two owners at once.
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 ¶
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) OutputName ¶ added in v0.0.38
OutputName writes only the attribute name to w.
This is a low-level helper for custom rendering pipelines.
func (Attr) OutputValue ¶ added in v0.0.38
OutputValue writes only the attribute value to w, HTML-escaped and without the surrounding quotes.
This is a low-level helper for custom rendering pipelines. A value that implements Output writes itself through an escaping writer; any other value is formatted with fmt.Fprint and escaped.
OutputValue returns an error, and writes nothing, when the value is nil or a bool: an unset attribute has no value, and a bool attribute renders as a bare name. Check IsSet and the bool case before calling it.
func (Attr) Set ¶
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 ¶ added in v0.0.37
func (a Attr) Unset()
Unset clears the attribute's value: the attribute stops rendering, IsSet reports false, and Value returns nil.
Set(nil) has the same effect. Set(false) also stops the attribute from rendering, though Value then reports false. Set("") does not: an empty string is still set and renders as name="".
The entry itself stays in the owning Attrs under its name, so Attrs.List still reports it, and a later Set gives it a value again.
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 values are pooled. Passing one to NewJobHeadOpen transfers ownership: that job's Output releases the attribute set, and every Attr handle it renders, back to the pools, so neither the Attrs nor any Attr handle from it may be used afterwards, and one attribute set must not back two heads. Use Clone to keep an independent copy.
Attrs is not safe for concurrent use.
func (Attrs) ApplyMods ¶ added in v0.0.14
ApplyMods runs all queued modifiers on this attribute set. It is a no-op on a nil Attrs.
Modifiers run in insertion order and are dequeued before they run, so no queued modifier runs twice. A modifier may queue more modifiers with AddMod; those run in the same pass, after the ones already queued, and the pass ends only when the queue drains.
If a modifier returns an error, ApplyMods stops immediately and returns it. The modifiers that have not run yet stay queued and run on the next ApplyMods call, including the one performed during rendering.
func (Attrs) Clone ¶
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 ¶ added in v0.0.37
Find returns the set attribute named name.
Find does not create missing entries and returns false for unset ones.
func (Attrs) Get ¶
Get returns the entry for name, creating it when needed.
The returned Attr is a live handle into this Attrs.
func (Attrs) Inherit ¶ added in v0.0.23
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.
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.Set("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:
- Init(tag)
- (optional) Set / Modify
- Submit() // emits head-open job
- emit children jobs // Text/Comp/Any/etc.
- Close() // emits head-close job
Void element lifecycle:
- InitVoid(tag)
- (optional) Set / Modify
- Submit() // emits head-open job; no children and no Close
Container lifecycle:
- InitContainer() // emits container head-open job immediately
- emit children jobs
- 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 ¶
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 ¶ added in v0.0.7
Any renders a value using GoX's default dynamic dispatch.
Cases are tried in this order, so a value that satisfies several of them is handled by the first match (an EditorComp, for example, is applied as an Editor rather than rendered as a Comp):
- string / []string
- Elem / []Elem
- Editor
- Comp / []Comp
- Job / []Job
- Templ
- []any (treated as a variadic list)
Nil interface values are ignored. Job and []Job are handed straight to the Printer, skipping the cursor state validation the other cases perform. Everything else falls back to Fprint.
func (Cursor) AttrMod
deprecated
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.
Deprecated: use Modify instead.
func (Cursor) Bytes ¶ added in v0.0.21
Bytes emits data at the current cursor position, unescaped and byte for byte.
The emitted job keeps a reference to data instead of copying it, so callers must not modify data until the job has been output. With a Printer that buffers or defers jobs, that can be well after Bytes returns.
func (Cursor) Close ¶ added in v0.0.7
Close emits the current head's closing job and removes it from the stack.
The head must already be submitted; Close fails and emits nothing when the current head is still pending or when no head is open. Close does not check which tag it is closing, so a stray Close is reported only when the stack is empty: after a void element, which Submit already removed from the stack, an extra Close silently closes the enclosing head and misnests the output.
func (Cursor) Comp ¶ added in v0.0.7
Comp emits comp at the current cursor position.
The component is emitted as one JobComp rather than expanded onto this cursor: the default Printer renders that job through a fresh default Printer and Cursor, so the component's head stack is independent of this one and its jobs never reach this cursor's Printer. A nil comp renders nothing.
func (Cursor) Context ¶ added in v0.0.19
Context returns the default context for jobs emitted by this cursor.
func (Cursor) Editor ¶ added in v0.0.26
Editor applies editor to this cursor.
Unlike Comp, the editor runs directly against this cursor and its head stack, and Editor performs no cursor state validation of its own: an editor may set attributes on a head that is still pending, or open and close heads that outlive the call. A nil editor panics.
func (Cursor) Init ¶ added in v0.0.7
Init starts a regular element head.
After Init, callers may set attributes with Set or Modify. Child content must wait until Submit succeeds.
func (Cursor) InitContainer ¶ added in v0.0.12
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 ¶ added in v0.0.7
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 ¶ added in v0.0.9
Many renders each value in order using Any.
Many is a convenient way to emit mixed values without switching manually.
func (Cursor) Modify ¶ added in v0.1.28
Modify adds one or more modifiers to the current head.
Modify 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) NewID ¶ added in v0.0.22
NewID returns a process-unique id for correlating render-time state.
IDs increase monotonically within one cursor.
func (Cursor) Printer ¶ added in v0.1.29
Printer returns the underlying Printer for direct job emission.
Jobs sent this way skip cursor state validation and are not recorded on the head stack, so callers must preserve any ordering and nesting guarantees themselves.
func (Cursor) Set ¶ added in v0.1.28
Set sets attribute name on the current head.
Set may be used only after Init or InitVoid and before Submit; otherwise it returns a HeadError and stores nothing.
Values follow Attr.Set rules: a later Set replaces the value from an earlier one, nil and false leave the attribute unset so it does not render, true renders it as a bare name, and an empty string still renders as name="". A value implementing Mutate is the exception to replacement: Set stores the result of value.Mutate(name, currentValue) instead.
func (Cursor) Submit ¶ added in v0.0.7
Submit emits the current head's opening job.
After Submit the head no longer accepts attributes: Set and Modify fail from this point on. A regular or container head stays on the stack, open for child content until Close. A void head is complete once submitted and is removed from the stack immediately, so the cursor returns to the enclosing head's content state: following content belongs to that enclosing head, and the next Close closes it, not the void element.
Submit fails when there is no pending head or the head was already submitted.
func (Cursor) Templ ¶ added in v0.0.7
Templ emits a templ-compatible component at the current cursor position.
type Editor ¶ added in v0.0.25
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 ¶ added in v0.1.17
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 ¶ added in v0.1.17
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 ¶ added in v0.1.17
func (e EditorCompFunc) Edit(cur Cursor) error
func (EditorCompFunc) Main ¶ added in v0.1.17
func (e EditorCompFunc) Main() Elem
type EditorFunc ¶ added in v0.0.38
EditorFunc adapts a function into an Editor.
func (EditorFunc) Edit ¶ added in v0.0.38
func (e EditorFunc) Edit(cur Cursor) error
type Elem ¶
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) Print ¶
Print sends e to printer as a single root JobComp carrying ctx.
Print does not render e: the printer receives exactly one job and nothing runs until the printer acts on it. Calling Job.Output on that job renders the subtree through a fresh default Printer and Cursor writing to Output's writer, so the nested jobs never reach the custom printer; a custom printer that needs those jobs must expand the component itself, by running Comp.Main's Elem against a Cursor bound to that printer instead of calling Output. If e is nil, Print sends nothing and returns nil.
type HeadError ¶
type HeadError string
HeadError reports an invalid Cursor state transition.
Cursor returns HeadError from Init, InitVoid, InitContainer and from methods that require a content state when the current head has not been submitted yet, and from Set and Modify when it already has been. Submit and Close report their own misuse (submitting twice, closing a head that is still pending, closing with no head open) with plain errors, so a type check for HeadError does not catch every state error.
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. Each job carries its own context: the cursor's context by default, or the one passed to Cursor.CompCtx, Cursor.TemplCtx, or a NewJob* constructor, so the jobs of one stream do not necessarily share a cancellation signal.
The jobs GoX emits are pooled and single-use: Output returns the job to its pool and clears its fields, so a job may be output at most once and must not be inspected or resent afterwards.
type JobBytes ¶ added in v0.0.21
JobBytes writes bytes without escaping.
It is the []byte counterpart of JobRaw, so the caller is responsible for the safety of the content. NewJobBytes keeps the caller's slice instead of copying it: the slice must stay unmodified until the job is output, which a Printer that buffers jobs may defer well past Send.
func NewJobBytes ¶ added in v0.0.21
NewJobBytes returns a pooled JobBytes.
type JobComp ¶
JobComp renders a Comp.
Cursor emits one for every Comp or Elem value passed to Cursor.Comp, Cursor.CompCtx, or Cursor.Any, and Elem.Print sends one as the root job. Output calls Comp.Main and, when it returns a non-nil Elem, renders that Elem through a fresh default Printer and Cursor writing to w, so the jobs produced inside the component never reach the Printer that received this JobComp. A custom printer that needs those jobs must expand the component itself, by running Comp.Main's Elem against a Cursor bound to that printer instead of calling Output. A nil Comp or a nil Main result renders nothing.
func NewJobComp ¶ added in v0.0.8
NewJobComp returns a pooled JobComp.
type JobError ¶
JobError fails rendering with a stored error.
func NewJobError ¶ added in v0.0.20
NewJobError returns a pooled JobError.
type JobFprint ¶
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 ¶ added in v0.0.8
NewJobFprint returns a pooled JobFprint.
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 ¶
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. It takes ownership of attrs: outputting or releasing the job also releases the attribute set, and Output additionally releases the Attr handles it renders, so the caller must not use them afterwards. attrs may be nil, in which case the job writes the tag with no attributes.
func (*JobHeadOpen) Context ¶
func (j *JobHeadOpen) Context() context.Context
Context returns the context associated with this job.
type JobRaw ¶
JobRaw writes unescaped text.
type JobTempl ¶
JobTempl renders a templ-compatible value.
func NewJobTempl ¶ added in v0.0.8
NewJobTempl returns a pooled JobTempl.
type JobText ¶
JobText writes escaped text.
func NewJobText ¶ added in v0.0.8
NewJobText returns a pooled JobText.
type Modify ¶ added in v0.1.6
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 ModifyFunc ¶ added in v0.1.21
ModifyFunc adapts a function into a Modify.
type Mutate ¶ added in v0.0.37
Mutate computes a new attribute value from the previous one.
Attr.Set detects Mutate values and stores the result of value.Mutate(attributeName, currentValue). The current value is nil whenever the attribute has no value yet (never set, or unset), so implementations must handle a nil value.
The result is stored as-is: returning nil unsets the attribute, and a returned value that itself implements Mutate is not applied again.
type Output ¶ added in v0.0.37
Output is the low-level write contract:
Output(w io.Writer) error
Jobs implement it to write themselves to the underlying writer. It is also the per-value attribute hook: an attribute value that implements Output is serialized by calling its Output with an escaping writer instead of being formatted with fmt.Fprint, so the value chooses its own bytes but still cannot emit raw markup.
type OutputError ¶ added in v0.0.44
type OutputError string
OutputError reports invalid job state during rendering.
func (OutputError) Error ¶ added in v0.0.44
func (e OutputError) Error() string
type Printer ¶
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 ¶
NewPrinter returns the default Printer that writes jobs to w in order.
Send checks the job's context first: when it is already canceled or expired, the job is skipped, nothing is written, and Send returns that context's error, which surfaces as the error from Elem.Render or the enclosing Elem.
type PrinterFunc ¶ added in v0.1.10
PrinterFunc adapts a function into a Printer.
func (PrinterFunc) Send ¶ added in v0.1.10
func (p PrinterFunc) Send(j Job) error
type Proxy ¶
Proxy wraps an Elem before it is rendered.
Generated code for `~>(p) <div>...</div>` calls p.Proxy(cur, el), where el holds the wrapped subtree. Rendering el is the implementation's responsibility: a Proxy that returns without calling el(cur) (or cur.Comp(el)) drops the subtree, and running el against a different Cursor is what reroutes it. The error Proxy returns becomes the render error.
Proxies are useful when a subtree needs cross-cutting behavior such as instrumentation, attribute injection, conditional rendering, or rerouting through a custom Printer.
