tml

The TML language as a Go library. Parse, print and validate documents for terminal surfaces, evaluate their bindings, run local actions and apply JSON Patch to their state.

tuios.dev/tml is the TML language. A TML document is a tree of components from a fixed catalog, with a JSON state store. A program, a plugin or an agent sends one to a host, and the host draws it in a pane, a popup or a dock cell. This library is the language. It draws nothing.

go get tuios.dev/tml

The library uses only the standard library. The API reference is on pkg.go.dev.

tuios does not show TML surfaces yet. The renderer, the tuios commands and the standalone tml command come in later releases.

What is in it

  • Two encodings. TML text is a strict XML subset for files and people. TML JSON is for the wire and for agents. Each has a parser and a printer. Both parsers give the same canonical tree, and a canonical tree prints and reads back unchanged in either encoding. The first parse normalizes some input: it types attributes by the catalog, joins adjacent text and moves the state to the end of the root.
  • The validator. It checks the full catalog: attributes and their types, required attributes, allowed children, theme roles, bindings, actions and limits. Each problem names the fix.
  • The JSON Schema of TML JSON, generated from the catalog. An agent can use it for constrained output.
  • State and bindings. A JSON store, {state.path} bindings, and a small expression language with a step budget. Expressions cannot loop, assign or call out.
  • Actions. The parser checks the action attributes, such as on-press="emit('save', form)". The renderer runs them.
  • JSON Patch. RFC 6902, atomic, applied to the state. The size limit is checked after every operation.
  • Dependencies. Each expression records the state paths it reads, so a renderer can redraw only what a patch touches.

A document

<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}"}]}]}

A colour is always a theme role, such as success or dim. A hex colour is an error, so every surface follows the theme.

Check a document

doc, problems, err := tml.Check(src, nil)
if err != nil {
	for _, p := range problems {
		// 3:3: <chart-bar> is not a TML component. (unknown_component at /children/0) Did you mean "bar"?
		fmt.Println(p)
	}
	return
}

Each problem has a JSON Pointer to the node, a line and a column, a code, and the closest legal value. *tml.Error marshals to the error object a host sends back:

{"error": {"code": "invalid_tml", "message": "1 problem in the document", "problems": [
  {"at": "/children/0", "line": 3, "col": 3, "code": "unknown_component",
   "message": "<chart-bar> is not a TML component.", "severity": "error", "did_you_mean": "bar"}]}}

Apply a patch

store, err := tml.StoreFor(doc, nil) // nil means the default limits
if err != nil {
	panic(err)
}
ops, err := tml.DecodePatch([]byte(`[{"op":"replace","path":"/pct","value":80}]`), nil)
if err != nil {
	panic(err)
}
changed, err := store.Apply(ops)
fmt.Println(changed, err) // [/pct] <nil>

Notes for a host

  • Set a budget. Give Env.Budget one tml.NewBudget() for all the bindings of a frame. Every node, value and 32 bytes of text an expression reads costs steps. Past the budget a binding renders !. A nil budget gets a fresh one for each call, which bounds one binding but not a frame.
  • The store sanitizes state. The store removes control, bidi and invisible characters from every string and key it takes in: the document's state, patch values and Set. It refuses keys that become one after this, and Go values outside the JSON model. Literal data arrays are sanitized when the document is parsed. Evaluated bindings are sanitized too. A renderer that reads the store directly, such as for table rows, gets clean strings.
  • Make the store with StoreFor. It keeps the tree, and refuses a patch that grows each, list, the images or the rows of tables and charts past their limits.

Validate, format and describe

tml.Check validates a document in either encoding. tml.Format prints it in canonical TML text or JSON. tml.Schema returns the JSON Schema of TML JSON. The standalone tml command will come with the renderer.

The README lists every component, attribute, function, action and limit.

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.

On this page