Documentation
¶
Overview ¶
Package tml is the TML language: a markup for terminal surfaces that a program, a plugin or an agent sends to a host such as tuios, which draws it in a pane, a popup or a dock cell.
A document is a tree of components from a fixed catalog, with a JSON state store. It has two encodings with a lossless mapping: TML text, a strict XML subset, for files and people, and TML JSON for the wire and for agents.
<tml version="1" title="Build">
<state>{"pct": 40, "step": "Compiling"}</state>
<card title="Build">
<text>Step: <b>{state.step}</b></text>
<progress value="{state.pct / 100}"/>
</card>
</tml>
The package parses and prints both encodings (ParseXML, ParseJSON, FormatXML, FormatJSON, Format), validates a tree against the catalog with errors that name the fix (Validate, Check), generates the JSON Schema of the JSON encoding (Schema), evaluates bindings with a step budget (ParseTemplate, ParseExpr), parses actions (ParseActions), and applies RFC 6902 JSON Patch to the state (Store, DecodePatch). Each expression records the state paths it reads (Expr.Deps), so a renderer can redraw only what a patch touches.
The host owns the frame. It gives every binding of one frame the same Budget; a nil Budget bounds each call alone. It makes the store of a document with StoreFor, so a patch cannot grow each, list or the rows past the limits. Every string and key that enters a store or a tree, or leaves an evaluation, has passed through Sanitize.
It draws nothing, and it imports only the standard library. No code from a document runs: expressions compute values and cannot loop, assign or call out.
Example ¶
package main
import (
"fmt"
"tuios.dev/tml"
)
func main() {
src := []byte(`<tml version="1" title="Build">
<state>{"pct": 40, "step": "Compiling"}</state>
<card title="Build">
<text>Step: <b>{state.step}</b></text>
<progress value="{state.pct / 100}"/>
</card>
</tml>`)
doc, _, err := tml.Check(src, nil)
if err != nil {
panic(err)
}
store, err := tml.StoreFor(doc, nil)
if err != nil {
panic(err)
}
// The owner sends a patch. Apply returns the state paths it changed.
ops, err := tml.DecodePatch([]byte(`[{"op":"replace","path":"/pct","value":80}]`), nil)
if err != nil {
panic(err)
}
changed, err := store.Apply(ops)
if err != nil {
panic(err)
}
fmt.Println(changed)
// A renderer evaluates the progress bar's binding with a budget.
bar := doc.At("/children/0/children/1")
v, _ := bar.Attr("value")
tmpl, err := tml.ParseTemplate(v.Text())
if err != nil {
panic(err)
}
value, err := tmpl.Eval(&tml.Env{Vars: map[string]any{"state": store.State()}, Budget: tml.NewBudget()})
fmt.Println(value, err)
}
Output: [/pct] 0.8 <nil>
Index ¶
- Constants
- Variables
- func ActionNames() []string
- func Check(src []byte, opts *ValidateOptions) (*Node, []Problem, error)
- func Format(src []byte, to Encoding, opts *ParseOptions) ([]byte, error)
- func FormatJSON(n *Node, indent bool) []byte
- func FormatXML(n *Node) []byte
- func Funcs() []string
- func IsJSON(src []byte) bool
- func PathPointer(path string) (string, error)
- func Roles() []string
- func Sanitize(s string) string
- func SanitizeJSON(v any) (any, error)
- func SanitizeValue(v any) any
- func Schema() []byte
- func Slots() []string
- func SplitPath(path string) (root, pointer string, err error)
- func Stringify(v any) string
- func Truthy(v any) bool
- type Action
- type Attr
- type AttrSpec
- type AttrType
- type Budget
- type Component
- type Content
- type Dep
- type Encoding
- type Env
- type Error
- type Expr
- type Kind
- type Limits
- type Node
- func (n *Node) At(pointer string) *Node
- func (n *Node) Attr(name string) (Value, bool)
- func (n *Node) AttrString(name string) string
- func (n *Node) Clone() *Node
- func (n *Node) Find(id string) (*Node, string)
- func (n *Node) ID() string
- func (n *Node) IsText() bool
- func (n *Node) SetAttr(name string, v Value)
- func (n *Node) State() (map[string]any, error)
- func (n *Node) Walk(fn func(node *Node, pointer string) bool)
- type Op
- type ParseOptions
- type PatchError
- type Pos
- type Problem
- type Severity
- type Store
- type SyntaxError
- type Template
- type TemplateError
- type ValidateOptions
- type Value
Examples ¶
Constants ¶
const ( CodeSyntax = "syntax" // the text or JSON does not parse CodeUnsupportedXML = "unsupported_xml" // DTD, entity, namespace, CDATA or processing instruction CodeUnknownComponent = "unknown_component" // a type that is not in the catalog CodeUnknownAttribute = "unknown_attribute" // an attribute the component does not have CodeMissingAttribute = "missing_attribute" // a required attribute is absent CodeBadValue = "bad_value" // a value of the wrong type or out of range CodeHexColor = "hex_color" // a colour written as hex instead of a role CodeBadChild = "bad_child" // a child the parent does not accept CodeTextNotAllowed = "text_not_allowed" // text where only components may go CodeChildCount = "child_count" // too few or too many children CodeBadExpression = "bad_expression" // an expression does not parse CodeUnknownFunction = "unknown_function" // a function that is not in the list CodeUnknownRoot = "unknown_path_root" // a path that starts with an unknown name CodeBindingMissing = "binding_missing" // a path the state does not have (warning) CodeBadAction = "bad_action" // an action call that is not allowed CodeDuplicateID = "duplicate_id" // two nodes with one id CodeMisplacedState = "misplaced_state" // state anywhere but the root CodePasswordRefused = "password_refused" // v1 has no password input CodeLimit = "limit" // over a size, depth or count limit CodeBadRoot = "bad_root" // the root is not a block component // Patch error codes. CodeTestFailed = "test_failed" // a test operation did not match: read the state again CodePathMissing = "path_missing" // a patch names a path the state does not have )
The problem codes. Each names one rule, so a program can act on it.
const ExprSteps = 10000
ExprSteps is the expression step budget of one surface for one frame.
const SchemaID = "https://tuios.dev/tml/tml.schema.json"
SchemaID is the $id of the generated JSON Schema.
Variables ¶
var ErrBudget = errors.New("tml: expression step budget exhausted")
ErrBudget is returned when an evaluation runs out of steps. A renderer shows the binding as "!".
Functions ¶
func Check ¶
func Check(src []byte, opts *ValidateOptions) (*Node, []Problem, error)
Check parses and validates a document in either encoding. A parse error comes back as the one problem.
Example ¶
package main
import (
"fmt"
"tuios.dev/tml"
)
func main() {
src := []byte(`<tml version="1" title="Seconds">
<state>{"secs": [41, 39, 44]}</state>
<chart-bar data="state.secs" h="4"/>
</tml>`)
_, problems, err := tml.Check(src, nil)
if err != nil {
for _, p := range problems {
fmt.Println(p)
}
}
}
Output: 3:3: <chart-bar> is not a TML component. (unknown_component at /children/0) Did you mean "bar"?
func Format ¶
func Format(src []byte, to Encoding, opts *ParseOptions) ([]byte, error)
Format parses a document in either encoding and prints it in canonical form. It does not validate the document.
func FormatJSON ¶
FormatJSON prints a tree in the JSON encoding. With indent set it puts one key per line, indented by two spaces.
func FormatXML ¶
FormatXML prints a tree as TML text. Block content puts each child on its own line, indented by two spaces. Inline content stays on one line. Raw content (code, markdown, diff, mermaid, kbd, state) is printed verbatim. White space that the parser would collapse is written as a character reference, so ParseXML(FormatXML(n)) equals n.
func PathPointer ¶
PathPointer turns a state path into a JSON Pointer: state.a.b[0] and a.b[0] both become /a/b/0. Only names and constant indexes are allowed.
func Roles ¶
func Roles() []string
Roles returns the theme roles a role attribute accepts. A host resolves each to a colour at the client's colour depth.
func Sanitize ¶
Sanitize removes what must never reach a terminal from text a document carries:
- C0 and C1 control characters other than tab and newline, and DEL
- the bidirectional marks, embeddings, overrides and isolates (U+061C, U+200E, U+200F, U+202A to U+202E, U+2066 to U+2069)
- the line and paragraph separators U+2028 and U+2029
- invisible characters: U+200B, U+2060 to U+2064, U+180E, U+FEFF, and the tag characters U+E0000 to U+E007F
It keeps the zero-width joiner and non-joiner (U+200D, U+200C), which emoji sequences and several scripts need. Invalid UTF-8 becomes U+FFFD. The parsers apply it to every text run and attribute, the store to every string and key it holds, and a template to every value it returns.
func SanitizeJSON ¶
SanitizeJSON is SanitizeValue for data that enters a store or a tree. It refuses two keys that become one after Sanitize, a number that is not finite, and any Go value outside the JSON model (other than int, int64 and float32, which become float64).
func SanitizeValue ¶
SanitizeValue applies Sanitize to every string and object key in a JSON value, as encoding/json decodes it into any. It returns v itself when v is already clean, so a clean store is not copied. Each value is visited once.
Two keys that become one after Sanitize keep the value of the key that sorts first, so the result does not depend on map order. A value that is not a JSON value becomes null. NewStore and the parsers refuse both cases instead; see SanitizeJSON.
func Schema ¶
func Schema() []byte
Schema returns the JSON Schema (draft 2020-12) of the JSON encoding, generated from the catalog, so the schema and the validator cannot drift. An agent can use it for constrained decoding. The validator checks more than the schema can say: bindings, state paths, actions and limits.
func Slots ¶
func Slots() []string
Slots returns the ANSI colours the root's accent may name as slot:NAME.
func SplitPath ¶
SplitPath reads a path of names and constant indexes, such as state.items[0].name or item.x, into its root name and a JSON Pointer below it.
Types ¶
type Action ¶
Action is one call in an action attribute such as on-press.
func ParseActions ¶
ParseActions parses an action attribute: calls separated by ;.
type AttrSpec ¶
type AttrSpec struct {
Name string
Type AttrType
Enum []string // TEnum: the accepted values
Required bool
// Bind reports whether the value may be a {binding}.
Bind bool
// Min and Max bound a TInt or TNumber when HasRange is set.
Min, Max float64
HasRange bool
// Multiline marks a text attribute that may hold line breaks. The
// parsers turn a tab or newline in any other text attribute into a space.
Multiline bool
Doc string
}
AttrSpec describes one attribute of a component.
func GlobalAttrs ¶
func GlobalAttrs() []AttrSpec
GlobalAttrs returns the attributes every component takes.
type AttrType ¶
type AttrType uint8
AttrType is the type an attribute value must have.
const ( TString AttrType = iota // free text TInt // a whole number TNumber // any number TBool // true or false TEnum // one of a fixed list TRole // a theme role, never a hex colour TAccent // a theme role or an ANSI slot, slot:magenta TSize // cells, 50%, fill or fill:2 TPad // one, two or four cell counts TData // a state path such as state.items, or a literal array TPath // a state path only, such as state.tab TExpr // an expression, with or without the braces TAction // one or more action calls separated by ; TDuration // 300ms, 5s, 2m THref // a file: or https: link TSrc // a file: path or a data:image URI TIdent // a name: letters, digits, - and _ TField // a field name of a row, or a comma list of them TSort // a field, optionally :asc or :desc TKey // a key name such as enter, esc, r or ctrl+b TPattern // an RE2 regular expression TObject // a JSON object (the root's state) )
The attribute types of the catalog.
func AttrTypeOf ¶
AttrTypeOf returns the type of an attribute of a component type, without copying the catalog entry.
type Budget ¶
type Budget struct {
Steps int
}
Budget is the step allowance of one surface for one frame. Every node an evaluation visits costs a step. Work that grows with its input costs more: a function pays a step per element it reads, and text pays a step per 32 bytes it reads or writes. Share one Budget across every binding of a frame.
type Component ¶
type Component struct {
Name string
Group string
Doc string
Attrs []AttrSpec // sorted by name; the global attributes are separate
Content Content
Children []string // ContentOnly: the accepted child types
// MinChildren and MaxChildren bound the number of child components.
// MaxChildren 0 means no bound.
MinChildren, MaxChildren int
// Inline marks a component that sits inside text: b, i, u, s, code, c,
// kbd and a.
Inline bool
// Block marks a component that may sit in block content.
Block bool
}
Component is one entry of the catalog. Lookup and Components return copies, so a caller cannot change the catalog.
func Components ¶
func Components() []Component
Components returns a copy of the catalog, sorted by group, then by name.
type Content ¶
type Content uint8
Content is what a component may hold.
type Dep ¶
Dep is one value an expression reads: a root name (state, item, form, event, view, value or row) and a JSON Pointer below it. An index computed at run time ends the pointer, so the dependency covers the whole array.
type Env ¶
type Env struct {
Vars map[string]any
Now func() time.Time // for ago(); time.Now when nil
Budget *Budget // nil means a fresh NewBudget for each call
}
Env is what an expression can read. Vars maps each root name (state, item, form, event, view, value, row) to its value. Values are JSON values as encoding/json decodes them into any: nil, bool, float64, string, []any and map[string]any.
type Error ¶
type Error struct {
Problems []Problem
}
Error is the error a parser or the validator returns. It marshals to the protocol's error object: {"error": {"code": "invalid_tml", ...}}.
func (*Error) MarshalJSON ¶
MarshalJSON writes the protocol's error object.
type Expr ¶
type Expr struct {
// contains filtered or unexported fields
}
Expr is a parsed expression. Expressions compute values: they cannot loop, assign, call out or keep state. Parse one with ParseExpr and evaluate it with Eval.
func ParseBinding ¶
ParseBinding parses an attribute that holds one expression, such as if, show or a column format: a bare expression such as form.canary, or one binding such as {state.cpu > 90}.
func (*Expr) Deps ¶
Deps returns the values the expression reads, in source order, without duplicates.
func (*Expr) Eval ¶
Eval evaluates an expression. Evaluation is total: a type mismatch, a missing path, a division by zero or a result that is not finite gives null. The only error is ErrBudget. Every string and key in the result passes through Sanitize.
type Limits ¶
type Limits struct {
DocBytes int // the source text or JSON
Nodes int // components and text runs, before each expands
Depth int // nesting of nodes
StateBytes int // the state store as compact JSON
Each int // nodes that each and list expand to, in all
Rows int // rows and data points of tables, lists, trees and charts, after each expands
PatchBytes int // one JSON Patch
Images int // image nodes in a document, after each expands
ImageBytes int // one decoded data: image
Problems int // problems reported in one pass; at least 1
}
Limits bound what one document may hold. Over a limit is a problem with the code limit, and the limit named in its message.
Every function takes a *Limits. A nil pointer means DefaultLimits, and a zero field means the default for that field, so a caller sets only what it changes.
func DefaultLimits ¶
func DefaultLimits() Limits
DefaultLimits are the limits for a document from a program, a plugin or an agent.
func PersonLimits ¶
func PersonLimits() Limits
PersonLimits are the limits for a document the person opens from their own command line: a larger document, the rest the same.
type Node ¶
type Node struct {
Type string
Attrs []Attr
Children []*Node
Text string
Pos Pos
// contains filtered or unexported fields
}
Node is one node of a TML tree. A node is either a component, with a Type, attributes and children, or a text run, with an empty Type and Text.
A component whose Type is not in the catalog is kept as it was written. The validator reports it, with a suggestion. Nothing is ever dropped silently.
func Parse ¶
func Parse(src []byte, opts *ParseOptions) (*Node, error)
Parse parses a document in either encoding. A document whose first character, after white space, is { is JSON. Anything else is TML text.
func ParseJSON ¶
func ParseJSON(src []byte, opts *ParseOptions) (*Node, error)
ParseJSON parses the JSON encoding of a document. A node is an object with a "type", its attributes beside it, and an optional "children" list in which a string is a text run.
The tree it returns is canonical, as ParseXML's is: attribute values are typed by the catalog, empty text runs are dropped and adjacent ones joined.
func ParseXML ¶
func ParseXML(src []byte, opts *ParseOptions) (*Node, error)
ParseXML parses TML text: a strict XML subset. It rejects a DTD, entities other than the five standard ones and numeric references, namespaces, CDATA and processing instructions. Comments are allowed and dropped.
The tree it returns is canonical: attribute values are typed by the catalog, white space in text is collapsed as described in the README, and the <state> element of the root becomes the root's state attribute.
func (*Node) At ¶
At returns the node a JSON Pointer of the form /children/0/children/2 names, or nil.
func (*Node) AttrString ¶
AttrString returns the named attribute as text, or "" when it is absent.
type Op ¶
type Op struct {
Op string `json:"op"`
Path string `json:"path"`
From string `json:"from,omitempty"`
Value json.RawMessage `json:"value,omitempty"`
}
Op is one operation of an RFC 6902 JSON Patch.
type ParseOptions ¶
type ParseOptions struct {
Limits *Limits
}
ParseOptions changes how a document is parsed. The zero value uses DefaultLimits.
type PatchError ¶
PatchError is a patch that cannot apply. Index is the operation that failed. A failed test is Code test_failed: the owner reads the state again.
func (*PatchError) Error ¶
func (e *PatchError) Error() string
type Pos ¶
Pos is a place in the source of a document. Line and Col count from 1. Col counts bytes, as Go error positions do. A zero Pos means the node was built in code and has no source.
type Problem ¶
type Problem struct {
At string `json:"at"`
Line int `json:"line,omitempty"`
Col int `json:"col,omitempty"`
Code string `json:"code"`
Message string `json:"message"`
Severity Severity `json:"severity,omitempty"`
DidYouMean string `json:"did_you_mean,omitempty"`
Accepted []string `json:"accepted,omitempty"`
Available []string `json:"available,omitempty"`
}
Problem is one thing wrong with a document. At is a JSON Pointer into the JSON form: /children/0/children/2 names a node, and /children/0/children/2/rows names one of its attributes.
func Validate ¶
func Validate(doc *Node, opts *ValidateOptions) ([]Problem, error)
Validate checks a tree against the catalog: component types, attributes and their types, required attributes, allowed children, theme roles, bindings, actions and limits. It returns every problem it found, up to Limits.Problems, errors and warnings both. The error is a *Error when at least one problem is an error. A document with warnings alone renders.
type Severity ¶
type Severity string
Severity tells an error from a warning. A document with warnings alone still renders.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store is the JSON state store of one surface. Every change goes through a JSON Patch, from the owner or from a local action, and returns the paths it changed, so a renderer can redraw only what depends on them. Every string and key that enters the store passes through Sanitize.
A Store is not safe for use by several goroutines.
func NewStore ¶
NewStore returns a store holding state. state may be nil. The store keeps a sanitized copy when state holds an unsafe character.
func StoreFor ¶
StoreFor returns the store of a document's state. The store keeps the tree, and Apply refuses a patch that would make each, list, the images or the rows of tables, lists, trees and charts pass their limits.
func (*Store) Apply ¶
Apply applies a JSON Patch. It is atomic: on an error the state is unchanged. The size limit is checked after every operation. It returns the JSON Pointers the patch changed.
func (*Store) Set ¶
Set writes value at a state path such as state.form.title or form.title, creating missing objects on the way. It is the set action. The value is sanitized, and a number that is not finite is refused.
type SyntaxError ¶
type SyntaxError struct {
Offset int
Message string
Suggest string // a likely fix, when there is one
}
SyntaxError is an expression that does not parse. Offset is a byte offset into the expression source.
func (*SyntaxError) Error ¶
func (e *SyntaxError) Error() string
type Template ¶
type Template struct {
// contains filtered or unexported fields
}
Template is text with {expr} bindings, as an attribute value or a text run holds it. {{ and }} write a literal brace.
func ParseTemplate ¶
ParseTemplate splits text into literal runs and bindings.
func (*Template) Eval ¶
Eval evaluates the template. A template that is one binding and nothing else gives the binding's value with its type. Any other template gives a string, at most 64 KiB. Every string and key passes through Sanitize, so state cannot carry an escape sequence to the screen.
type TemplateError ¶
TemplateError is a binding that does not parse. Offset is a byte offset into the template text.
func (*TemplateError) Error ¶
func (e *TemplateError) Error() string
type ValidateOptions ¶
type ValidateOptions struct {
Limits *Limits
// NoStateCheck skips the binding_missing warnings, for a document whose
// state arrives later in a patch.
NoStateCheck bool
}
ValidateOptions changes what Validate checks. The zero value uses DefaultLimits and checks bindings against the document's state.
type Value ¶
type Value struct {
Kind Kind
Str string // String, and the literal text of a Number
Bool bool // Bool
Raw json.RawMessage // Array and Object, compact
}
Value is an attribute value. A Number keeps its literal text, so a document prints back as it was written: 1.50 stays 1.50. An Array or Object keeps its JSON in compact form, with the keys in the order they were written.
func NumberValue ¶
NumberValue returns a number Value with the shortest text for f.
func (Value) Decode ¶
Decode returns the JSON value of an array or object attribute, such as a literal rows array. The parsers have already passed its strings and keys through Sanitize.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
internal
|
|
|
surface
Package surface holds the parts of a TML host that a renderer owns: view state, the local actions, dirty tracking and rate limits.
|
Package surface holds the parts of a TML host that a renderer owns: view state, the local actions, dirty tracking and rate limits. |