# formspec_ast

A Minetest mod library to make modifying formspecs easier.

## API

 - `formspec_ast.parse(formspec_string)`: Parses `formspec_string` and returns
    an AST.
 - `formspec_ast.unparse(tree)`: Unparses the abstract syntax tree provided
    and returns a formspec string.
 - `formspec_ast.register_element('modname:element', function())`: Registers
    a custom element. This function may be removed in a future release.
 - `formspec_ast.interpret(string_or_tree)`: Returns a formspec string after
    (optionally parsing) and unparsing the formspec provided.
 - `formspec_ast.walk(tree)`: Returns an iterator (use this directly in a for
    loop) that will return all nodes in a tree, including ones inside
    containers.
 - `formspec_ast.find(tree, node_type)`: Similar to `walk(tree)`, however only
    returns `node_type` nodes.
 - `formspec_ast.get_element_by_name(tree, name)`: Returns the first element in
    the tree with the name `name`.
 - `formspec_ast.get_elements_by_name(tree, name)`: Returns a list of all the
    elements with the name `name`.
 - `formspec_ast.apply_offset(tree, x, y)`: Shifts all elements in `tree`.
    Similar to `container`.
 - `formspec_ast.flatten(tree)`: Removes all containers and offsets elements
    that were in containers accordingly. **The use of this function is
    discouraged as `scroll_container[]` elements are not flattened.**
 - `formspec_ast.show_formspec(player_or_name, formname, formspec)`: Similar
    to `minetest.show_formspec`, however also accepts player objects and will
    pass `formspec` through `formspec_ast.interpret` first.
 - `formspec_ast.safe_parse(string_or_tree)`: Similar to `formspec_ast.parse`,
    however will delete any elements that may crash the client (or any I
    haven't added to the safe element list).
 - `formspec_ast.formspec_escape(text)`: The same as `minetest.formspec_escape`,
    should only be used when formspec_ast is being embedded outside of Minetest.

## AST

The AST is similar (and generated from) [the formspec element list], however
all attributes are lowercase.

[the formspec element list]: https://minetest.gitlab.io/minetest/formspec/#elements

### Recent backwards incompatibilities

While I try to reduce backwards incompatibilities, sometimes they are necessary
to either fix bugs in formspec_ast or for implementing new formspec features.

#### March 2021

 - The `index_event` value for `dropdown` is now a boolean instead of a string.

#### February 2021

 - The `close_on_enter` value for `field_close_on_enter` is now a boolean
   instead of a string.
 - The `frame_count`, `frame_duration` and `frame_start` values in
   `animated_image` are now numbers.

#### September 2020

 - The `style[]` element has a `selectors` field instead of `name`. Using
   `name` when unparsing formspecs still works, however parsed formspecs
   always use `selectors`.

### Special cases

 - `formspec_version` (provided it is the first element) is moved to
    `tree.formspec_version` (`1` by default).

### `formspec_ast.parse` example

*Note that the whitespace in the formspec is optional and exists for
readability. Non-numeric table items in the `dump()` output are re-ordered for
readability.*

```lua
> tree = formspec_ast.parse('size[5,2] '
>>     .. 'style[name;bgcolor=blue;textcolor=yellow]'
>>     .. 'container[1,1]'
>>     .. '    label[0,0;Containers are fun]'
>>     .. '    container[-1,-1]'
>>     .. '        button[0.5,0;4,1;name;Label]'
>>     .. '    container_end[]'
>>     .. '    label[0,1;Nested containers work too.]'
>>     .. 'container_end[]'
>>     .. ' image[0,1;1,1;air.png]')
> print(dump(tree))
{
    formspec_version = 1,
    {
        type = "size",
        w = 5,
        h = 2,
    },
    {
        type = "style",
        selectors = {"name"},
        props = {
            bgcolor = "blue",
            textcolor = "yellow",
        },
    },
    {
        type = "container",
        x = 1,
        y = 1,
        {
            type = "label",
            x = 0,
            y = 0,
            label = "Containers are fun",
        },
        {
            type = "container",
            x = -1,
            y = -1,
            {
                type = "button",
                x = 0.5,
                y = 0,
                w = 4,
                h = 1,
                name = "name",
                label = "Label",
            },
        },
        {
            type = "label",
            x = 0,
            y = 1,
            label = "Nested containers work too.",
        },
    },
    {
        type = "image",
        x = 0,
        y = 1,
        w = 1,
        h = 1,
        texture_name = "air.png",
    },
}

```

### `formspec_ast.flatten` example

```lua
> print(dump(formspec_ast.flatten(tree)))
{
    formspec_version = 1,
    {
        type = "size",
        w = 5,
        h = 2,
    },
    {
        type = "style",
        selectors = {"name"},
        props = {
            bgcolor = "blue",
            textcolor = "yellow",
        },
    },
    {
        type = "label",
        x = 1,
        y = 1,
        label = "Containers are fun",
    },
    {
        type = "button",
        x = 0.5,
        y = 0,
        w = 4
        h = 1,
        name = "name",
        label = "Label",
    },
    {
        type = "label",
        x = 1,
        y = 2,
        label = "Nested containers work too.",
    },
    {
        type = "image",
        x = 0,
        y = 1,
        w = 1,
        h = 1,
        texture_name = "air.png",
    },
}
```

### `formspec_ast.unparse` example

```lua
> print(formspec_ast.unparse(tree))
size[5,2,]style[name;textcolor=yellow;bgcolor=blue]container[1,1]label[0,0;Containers are fun]container[-1,-1]button[0.5,0;4,1;name;Label]container_end[]label[0,1;Nested containers work too.]container_end[]image[0,1;1,1;air.png]

> print(formspec_ast.unparse(formspec_ast.flatten(tree)))
size[5,2,]style[name;textcolor=yellow;bgcolor=blue]label[1,1;Containers are fun]button[0.5,0;4,1;name;Label]label[1,2;Nested containers work too.]image[0,1;1,1;air.png]
```
