tml

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 18, 2026 License: MIT Imports: 14 Imported by: 0

README

tml

Go Reference

tuios.dev/tml is the TML language: a markup for terminal surfaces. A program, a plugin or an agent sends a TML document to a host such as tuios. The host draws it in a pane, a popup, a dock cell or a cockpit card. A key press or a click goes back to the owner as an event, and the owner answers with a state patch.

The package is the language only. It draws nothing. It has:

  • The tree: a node type for each component of a fixed catalog.
  • Two encodings: TML text, a strict XML subset, and TML JSON. A parser and a printer for each. Both parsers give the same canonical tree, and a canonical tree prints and reads back unchanged in either encoding.
  • The validator. It checks the full catalog schema, and its errors name the fix.
  • The JSON Schema of TML JSON, generated from the catalog. Agents and MCP tools use it.
  • The state store, {state.path} bindings, and a small expression language with a step budget. Each expression records the state paths it reads.
  • The action parser, which checks the action attributes.
  • RFC 6902 JSON Patch, applied to the state, with the size limit checked after every operation.

The renderer, view state and dirty tracking come later, in tuios.dev/loom.

The package uses only the standard library. No code from a document runs: expressions compute values, and cannot loop, assign or call out.

Install

go get tuios.dev/tml

The go command reads the import path from tuios.dev. Go 1.25 or later can fetch it directly. With the default module proxy, any Go version can fetch it.

Example

<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 same document in TML JSON:

{"type": "tml", "version": 1, "title": "Build", "state": {"pct": 40, "step": "Compiling"},
 "children": [{"type": "card", "title": "Build", "children": [
   {"type": "text", "children": ["Step: ", {"type": "b", "children": ["{state.step}"]}]},
   {"type": "progress", "value": "{state.pct / 100}"}]}]}

Check a document:

_, problems, err := tml.Check(src, nil)
if err != nil {
	for _, p := range problems {
		fmt.Println(p)
	}
}

It prints:

3:3: <chart-bar> is not a TML component. (unknown_component at /children/0) Did you mean "bar"?

Apply a patch from the owner, then evaluate a binding:

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)

It prints [/pct], then 0.8 <nil>. The code is the body of Example in example_test.go, and a test keeps the two the same.

Notes for a host

  • Share one Budget per frame. Set Env.Budget to one NewBudget() for all the bindings of a surface's frame. A nil Budget gets a fresh budget for each call, which bounds one binding but not a frame.
  • The store sanitizes state. Node.State, NewStore, patch values and Store.Set pass every string and key through Sanitize. Expr.Eval and Template.Eval sanitize what they return. A renderer that reads the store directly, such as for table rows, gets clean strings.
  • Use StoreFor for a document's state. The store keeps the tree, and Apply refuses a patch that grows each, list, the images or the rows past their limits. The validator checks the same counts when a document arrives.

Syntax

A document is a tree of nodes. A node has a type, attributes and children.

  • TML text is for files, people and the playground. It is a strict XML subset. It has no DTD, no entities other than &lt; &gt; &amp; &quot; &apos; and numeric references such as &#10;, no namespaces, no CDATA and no processing instructions. The parser refuses each of them. Comments are allowed, and the parser drops them.
  • TML JSON is for the wire and for agents. type names the node. The attributes sit beside it. children is a list, and a string in it is a text run.

The rules that hold in both:

  • An attribute value is a string, a number or a boolean. The catalog gives each attribute a type, and both parsers read the value to that type. So version="1" in TML text and "version": 1 in JSON are the same tree.
  • A value with {...} is a binding: value="{state.pct / 100}". Write {{ and }} for literal braces.
  • <state> holds the state store as JSON. It must be the first child of the tml root. In JSON it is the state attribute of the root.
  • id names a node for patches, focus and events. key keeps identity in repeated children.
  • each="state.items" as="item" repeats a node once per array element.
  • if="expr" drops a node when false. show="expr" hides it and keeps its space.
  • A data attribute such as rows, data, values, items or options takes a state path without braces, such as state.items, or a literal array, such as [2, 5, 3].
  • The root is tml, or one block component for a small surface such as a dock cell.

White space in TML text follows these rules. tml fmt prints a document so that it reads back to the same tree.

  • In text content, such as text, heading, button and the inline components, each run of white space becomes one space. The ends of a block are trimmed. Write &#32; for a space the parser must keep, and &#10; for a line break.
  • Between block components, white space does not count.
  • Raw content keeps its text as written: code, markdown, diff, mermaid, kbd and state. Bindings do not apply in raw content.

Sanitize removes what must never reach a terminal:

  • C0 and C1 control characters other than tab and newline, and DEL.
  • The bidirectional marks, embeddings, overrides and isolates.
  • 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.

It keeps the zero-width joiner and non-joiner, which emoji and several scripts need. The parsers apply it to every text run and attribute. A one-line text attribute, such as title, also has its tabs and line breaks turned into spaces.

Components

A tag or an attribute outside this catalog is an error with a suggestion. The parser keeps an unknown component in the tree, and the validator reports it.

Document
Component What it is Attributes (required in bold) Holds
tml The document root. accent on-key state title version block components
Layout
Component What it is Attributes (required in bold) Holds
col Lays children out top to bottom. align gap justify on-press block components
grid Wraps children into equal columns. cols gap block components
row Lays children out left to right. align gap justify on-press block components
scroll Scrolls its children. follow block components
spacer Takes the free space. none nothing
split Two children with a divider that a person can drag. dir ratio two block components
stack Layers children on top of each other. none block components
Text
Component What it is Attributes (required in bold) Holds
code Code, highlighted by lang. Also inline inside text. from lang lines text, kept as written
diff A unified diff. lang layout text, kept as written
heading A heading. level role text and inline components
markdown Markdown text. none text, kept as written
text A paragraph of text with inline styling. align on-press role wrap text and inline components
Inline
Component What it is Attributes (required in bold) Holds
a A link. It opens after the person confirms. href text and inline components
b Bold. none text and inline components
c Colours its text with a role. role text and inline components
i Italic. none text and inline components
kbd A key name. none text, kept as written
s Strikethrough. none text and inline components
u Underline. none text and inline components
Data
Component What it is Attributes (required in bold) Holds
column A column of a table. align field format sort title nothing
item One pair of a kv. k role v nothing
kv Key and value pairs in two aligned columns. none item only
list Repeats its children once per element of a state array. as items on-key on-select select block components
stat A labelled number, the KPI tile. delta label on-press role value nothing
table Rows of a state array, sorted, filtered and scrolled locally. filter key on-key on-select rows select sort column only
tree A tree of nested state objects. children items label on-select open select nothing
Chart
Component What it is Attributes (required in bold) Holds
area An area chart. data graphics role title x y nothing
bar A bar chart. data dir graphics role title x y nothing
gauge A value against a maximum. graphics label max role title value nothing
heatmap A grid of values as shades. data graphics role title v x y nothing
histogram Counts of values in bins. bins graphics role title values nothing
line A line chart. axis data graphics role title x y nothing
pie Shares of a whole. data graphics label role style title v nothing
sparkline A one-row trend. graphics role title values nothing
Media
Component What it is Attributes (required in bold) Holds
image An image. Without graphics it falls back to block art, then to alt. alt graphics on-press src nothing
mermaid A Mermaid diagram. Until the renderer lands, it shows the source. none text, kept as written
Status
Component What it is Attributes (required in bold) Holds
badge A short coloured tag. on-press role text and inline components
progress A progress bar. label role value nothing
spinner A busy mark. label nothing
status The tuios agent state glyph and word. label state nothing
Input
Component What it is Attributes (required in bold) Holds
button A button. disabled key kind on-press submit text and inline components
checkbox A checkbox. bind disabled label name on-change required value nothing
form Collects its fields into one value for submit. on-key on-submit block components
input A one-line text field. bind disabled label max min name numeric on-change pattern placeholder required value nothing
select Picks one value from a list. bind disabled label name on-change options required value nothing
slider A number on a track. bind disabled label max min name on-change required step value nothing
textarea A multiline text field. bind disabled label name on-change placeholder required rows value nothing
toggle An on and off switch. bind disabled label name on-change required value nothing
Container
Component What it is Attributes (required in bold) Holds
card A titled box, in the cockpit card style. fold meta on-press role title block components
modal A box over the surface that holds focus while open. open title block components
tab One tab of tabs. id title block components
tabs Shows one tab at a time. on-change value tab only
toast A short notice that goes away. role ttl text and inline components

Every component also takes these attributes:

Attribute Meaning
id Names the node for patches, focus and events.
key Keeps identity in repeated children.
each, as Repeats the node once per element of a state array.
if Drops the node when false.
show Hides the node when false and keeps its space.
w, h Width and height: cells (12), a percentage (50%), fill or fill:2.
min-w, max-w, min-h, max-h Size bounds.
pad Padding: one, two or four cell counts.
grow Share of free space.
Theme roles

A colour is a theme role, never a hex value. The host picks the colour from its theme, at the colour depth of the terminal. A hex value is an error that lists the roles.

fg dim mute accent accent-bright success warning error info attention busy edge canvas panel surface card accent-tint success-tint warning-tint error-tint series1 to series6

The root's accent can also name an ANSI slot, such as slot:magenta.

Expressions

A binding holds one expression. The language is small and total: every expression gives a value, and a mismatch gives null.

  • Literals: numbers, strings in single or double quotes, true, false, null, arrays and objects.
  • Paths: state.a.b, state.items[0], item.x inside each, form.tag inside a form, event.value inside an action, value and row in a column format, and view.width.
  • Operators: + - * / %, == != < <= > >=, && || !, a ? b : c, and ?? for null.
  • No loops, user functions, assignment or regular expressions.
Function Result
len(x) The length of a string, an array or an object.
sum(xs), avg(xs), min(xs), max(xs) Over an array, or sum(rows, 'field'), or over several numbers.
round(x, digits), abs(x) Rounding and absolute value.
pct(part, whole) part / whole * 100. pct(x) is x * 100.
fmt(x, digits), also format A number with thousands separators: 1,234.5.
dur(x, unit) A duration from ns, us, ms (the default), s, m or h: 840ms, 2.21s, 4m 5s.
bytes(x) A byte count: 512B, 3.1G.
ago(t) How long ago a Unix time or RFC 3339 time was: 3m ago.
upper(s), lower(s), trim(s) Case and spaces.
join(xs, sep) The elements as text, joined.
contains(a, b), starts(s, p) Substring, element and prefix tests.
pick(rows, 'field') One field of each row.
count(rows, 'field', value) The rows whose field equals value.

Each surface has a budget of 10,000 steps per frame. A node costs one step, and a function costs one step per element it reads. Past the budget, a binding renders !.

Actions

An action attribute, such as on-press or on-submit, holds one or more calls separated by ;.

Action Runs Effect
set('path', expr) Locally Writes document state.
toggle('path') Locally Flips a boolean.
tab('id') Locally Opens a tab. With tabs value="state.tab" it writes the state.
sort('field'), sort('table', 'field') Locally Sorts a table. The same field again flips the order.
filter('id', expr) Locally Filters a table or a list.
fold('id'), focus('id') Locally Folds a card, moves focus.
emit('name', expr) Owner Sends an event.
close(expr) Owner Closes the surface with a value.
copy(expr) Host Puts text on the clipboard.
open('file:path') Host Opens a file under the owner's files folder.
pane('verb', {...}) Host Runs focus, new-window or send-text under the owner's grants.

ParseActions parses and checks an action attribute. The renderer runs the local actions and sends the rest to the owner or the host.

Errors that name the fix

Validate and Check report up to 20 problems in one pass. Each problem has a JSON Pointer into the JSON form, a line and column, a code, and the closest legal value. Warnings alone do not stop a document.

{"error": {"code": "invalid_tml", "message": "1 problem in the document", "problems": [
  {"at": "/children/0/children/2/sortBy", "line": 5, "col": 48, "code": "unknown_attribute",
   "message": "table has no attribute 'sortBy'.", "severity": "error", "did_you_mean": "sort",
   "accepted": ["filter", "key", "on-key", "on-select", "rows", "select", "sort", "..."]}]}}

The codes are syntax, unsupported_xml, unknown_component, unknown_attribute, missing_attribute, bad_value, hex_color, bad_child, text_not_allowed, child_count, bad_expression, unknown_function, unknown_path_root, binding_missing (a warning), bad_action, duplicate_id, misplaced_state, password_refused, limit and bad_root.

Limits

Limit Value
Document 256 KiB, 1 MiB from the person's own command line
Nodes 5,000
Depth 32
State 1 MiB
Nodes that each and list expand to 2,000
Rows and data points of tables, lists, trees and charts, after each expands 50,000
Patch 64 KiB, 100 a second per surface
Images 4 per document after each expands, 4 MiB each
Problems in one report 20

Over a limit is a problem with the code limit. The report keeps its last slot for an error, so warnings cannot hide one.

Validate, format and describe

The standalone tml command will live in tuios.dev/loom/cmd/tml. Until then, these functions do its work:

Function Does
Check(src, opts) Parses and validates a document in either encoding.
Format(src, to, opts) Prints a document in canonical TML text or TML JSON.
Schema() Returns the JSON Schema of TML JSON.

The JSON Schema is also in this folder as tml.schema.json. go generate writes it again after a catalog change, and a test fails while the two differ.

Tests

  • Every example of the design is a fixture under testdata/examples. Each one validates, prints to its golden TML text and JSON, and each golden reads back to the other.
  • Each broken document under testdata/errors has a golden list of its problems.
  • FuzzParseXML, FuzzParseJSON, FuzzExpr and FuzzPatch run their seeds in the ordinary suite. The parser targets assert the limits, the character filter, and that every parsed tree prints and reads back to itself through either encoding.
  • review_test.go holds the inputs of the wave 1 security review: patch growth, escapes in state, the problem cap, nested each, step charging, quadratic parsing, and the medium findings.
go test ./...
go test -run XXX -fuzz FuzzParseXML -fuzztime 5m

Versions

The module is at v0. The API can change between minor versions. Its tags have the folder name as a prefix, such as tml/v0.1.0. The source is in the tml folder of the tuios repository, under the MIT licence.

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

Examples

Constants

View Source
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.

View Source
const ExprSteps = 10000

ExprSteps is the expression step budget of one surface for one frame.

View Source
const SchemaID = "https://tuios.dev/tml/tml.schema.json"

SchemaID is the $id of the generated JSON Schema.

Variables

View Source
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 ActionNames

func ActionNames() []string

ActionNames lists the actions, sorted.

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

func FormatJSON(n *Node, indent bool) []byte

FormatJSON prints a tree in the JSON encoding. With indent set it puts one key per line, indented by two spaces.

func FormatXML

func FormatXML(n *Node) []byte

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 Funcs

func Funcs() []string

Funcs lists the functions an expression may call, sorted.

func IsJSON

func IsJSON(src []byte) bool

IsJSON reports whether src looks like the JSON encoding.

func PathPointer

func PathPointer(path string) (string, error)

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

func Sanitize(s string) string

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

func SanitizeJSON(v any) (any, error)

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

func SanitizeValue(v any) any

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

func SplitPath(path string) (root, pointer string, err error)

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.

func Stringify

func Stringify(v any) string

Stringify formats a value as text: null is empty, a whole number has no decimals, and an array or object is JSON with sorted keys. The result stops at 64 KiB.

func Truthy

func Truthy(v any) bool

Truthy reports whether a value counts as true: not null, false, 0, "", an empty array or an empty object.

Types

type Action

type Action struct {
	Name string
	Args []*Expr
	Off  int // byte offset of the call in the attribute
}

Action is one call in an action attribute such as on-press.

func ParseActions

func ParseActions(s string) ([]Action, error)

ParseActions parses an action attribute: calls separated by ;.

func (Action) Literal

func (a Action) Literal(i int) string

Literal returns the argument as a string literal.

type Attr

type Attr struct {
	Name  string
	Value Value
	Pos   Pos
}

Attr is one attribute of a node, in the order the source wrote it.

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

func AttrTypeOf(component, attr string) (AttrType, bool)

AttrTypeOf returns the type of an attribute of a component type, without copying the catalog entry.

func (AttrType) String

func (t AttrType) String() string

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.

func NewBudget

func NewBudget() *Budget

NewBudget returns a Budget of ExprSteps.

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.

func Lookup

func Lookup(name string) (Component, bool)

Lookup returns a copy of the catalog entry for a component type.

func (Component) Accepts

func (c Component) Accepts(child string) bool

Accepts reports whether the component may hold a child of type child.

func (Component) AttrNames

func (c Component) AttrNames() []string

AttrNames returns the attributes a component accepts, its own first, then the global ones, each sorted.

func (Component) Spec

func (c Component) Spec(name string) (AttrSpec, bool)

Spec returns the spec of the named attribute, own or global.

type Content

type Content uint8

Content is what a component may hold.

const (
	ContentNone   Content = iota // no children
	ContentBlocks                // block components
	ContentInline                // text runs and inline components
	ContentRaw                   // one text run, kept verbatim
	ContentOnly                  // only the components Children names
)

The content models.

func ContentOf

func ContentOf(component string) Content

ContentOf returns the content model of a component type. An unknown type holds blocks.

type Dep

type Dep struct {
	Root string
	Path string
}

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.

func (Dep) String

func (d Dep) String() string

type Encoding

type Encoding uint8

Encoding names a TML encoding for Format.

const (
	SameEncoding Encoding = iota // the encoding of the input
	TextEncoding                 // TML text
	JSONEncoding                 // TML JSON, indented
)

The encodings Format can print.

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) Error

func (e *Error) Error() string

Error implements error.

func (*Error) MarshalJSON

func (e *Error) MarshalJSON() ([]byte, error)

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

func ParseBinding(s string) (*Expr, error)

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 ParseExpr

func ParseExpr(src string) (*Expr, error)

ParseExpr parses one expression.

func (*Expr) Deps

func (e *Expr) Deps() []Dep

Deps returns the values the expression reads, in source order, without duplicates.

func (*Expr) Eval

func (e *Expr) Eval(env *Env) (any, error)

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.

func (*Expr) Funcs

func (e *Expr) Funcs() []string

Funcs returns the functions the expression calls.

func (*Expr) Source

func (e *Expr) Source() string

Source returns the text the expression was parsed from.

type Kind

type Kind uint8

Kind is the type of an attribute value.

const (
	KindString Kind = iota
	KindNumber
	KindBool
	KindArray
	KindObject
)

The kinds of attribute value. String, Number and Bool are the scalar kinds both encodings share. Array and Object hold JSON: the root's state, and a literal array in a data attribute such as sparkline values.

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 TextNode

func TextNode(s string) *Node

TextNode returns a text run.

func (*Node) At

func (n *Node) At(pointer string) *Node

At returns the node a JSON Pointer of the form /children/0/children/2 names, or nil.

func (*Node) Attr

func (n *Node) Attr(name string) (Value, bool)

Attr returns the value of the named attribute.

func (*Node) AttrString

func (n *Node) AttrString(name string) string

AttrString returns the named attribute as text, or "" when it is absent.

func (*Node) Clone

func (n *Node) Clone() *Node

Clone returns a deep copy of n.

func (*Node) Find

func (n *Node) Find(id string) (*Node, string)

Find returns the node with the given id, and its JSON Pointer.

func (*Node) ID

func (n *Node) ID() string

ID returns the node's id attribute.

func (*Node) IsText

func (n *Node) IsText() bool

IsText reports whether n is a text run.

func (*Node) SetAttr

func (n *Node) SetAttr(name string, v Value)

SetAttr sets the named attribute, keeping its place when it exists.

func (*Node) State

func (n *Node) State() (map[string]any, error)

State returns the decoded state store of a document: the state attribute of the root. A document without one has an empty object. Every string and key passes through Sanitize, so a JSON escape such as \u001b cannot reach a renderer.

func (*Node) Walk

func (n *Node) Walk(fn func(node *Node, pointer string) bool)

Walk calls fn for n and every node below it, depth first, with the JSON Pointer of each node relative to n. When fn returns false, Walk skips the children of that node.

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.

func DecodePatch

func DecodePatch(src []byte, limits *Limits) ([]Op, error)

DecodePatch decodes a JSON Patch document and checks its size and shape.

type ParseOptions

type ParseOptions struct {
	Limits *Limits
}

ParseOptions changes how a document is parsed. The zero value uses DefaultLimits.

type PatchError

type PatchError struct {
	Index   int
	Code    string
	Message string
}

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

type Pos struct {
	Line   int `json:"line"`
	Col    int `json:"col"`
	Offset int `json:"-"`
}

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.

func (Problem) String

func (p Problem) String() string

String formats a problem as line:col: message (code), with the suggestion.

type Severity

type Severity string

Severity tells an error from a warning. A document with warnings alone still renders.

const (
	SevError   Severity = "error"
	SevWarning Severity = "warning"
)

The severities.

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

func NewStore(state map[string]any, limits *Limits) (*Store, error)

NewStore returns a store holding state. state may be nil. The store keeps a sanitized copy when state holds an unsafe character.

func StoreFor

func StoreFor(doc *Node, lim *Limits) (*Store, error)

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

func (s *Store) Apply(ops []Op) ([]string, error)

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) Get

func (s *Store) Get(pointer string) any

Get returns the value at a JSON Pointer, or nil.

func (*Store) Set

func (s *Store) Set(path string, value any) ([]string, error)

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.

func (*Store) Size

func (s *Store) Size() int

Size is the state's size as compact JSON.

func (*Store) State

func (s *Store) State() map[string]any

State returns the state. The caller must not change it.

func (*Store) Version

func (s *Store) Version() uint64

Version counts the changes applied. It starts at 0.

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

func ParseTemplate(s string) (*Template, error)

ParseTemplate splits text into literal runs and bindings.

func (*Template) Eval

func (t *Template) Eval(env *Env) (any, error)

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.

func (*Template) Exprs

func (t *Template) Exprs() []*Expr

Exprs returns the bindings of the template.

func (*Template) HasExpr

func (t *Template) HasExpr() bool

HasExpr reports whether the template holds a binding.

func (*Template) Render

func (t *Template) Render(env *Env) string

Render evaluates the template to text. A binding that runs out of budget renders as "!".

type TemplateError

type TemplateError struct {
	Offset  int
	Message string
}

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 BoolValue

func BoolValue(b bool) Value

BoolValue returns a boolean Value.

func NumberValue

func NumberValue(f float64) Value

NumberValue returns a number Value with the shortest text for f.

func StringValue

func StringValue(s string) Value

StringValue returns a string Value.

func (Value) Decode

func (v Value) Decode() (any, error)

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.

func (Value) Float

func (v Value) Float() (float64, bool)

Float returns a Number as a float64. The second result is false for any other kind, and for a number that does not parse.

func (Value) Text

func (v Value) Text() string

Text returns the value as it is written in an XML attribute.

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.

Jump to

Keyboard shortcuts

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