Improve make_elements.py and fix some issues.

This commit is contained in:
luk3yx 2021-02-04 22:29:50 +13:00
parent 649acafff3
commit 9f28d8cae3
5 changed files with 577 additions and 24 deletions

View File

@ -40,13 +40,25 @@ A Minetest mod library to make modifying formspecs easier.
The AST is similar (and generated from) [the formspec element list], however
all attributes are lowercase.
[the formspec element list]: https://github.com/minetest/minetest/blob/dee2210/doc/lua_api.txt#L1959
[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.
#### 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`.
`name` when unparsing formspecs still works, however parsed formspecs
always use `selectors`.
### Special cases

File diff suppressed because one or more lines are too long

465
elements.yaml Normal file
View File

@ -0,0 +1,465 @@
#
# This file is automatically generated by make_elements.py and isn't actually
# used by formspec_ast, however it is useful for comparing changes across
# lua_api versions.
#
anchor:
- - - [x, number]
- [y, number]
animated_image:
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [name, string]
- [texture_name, string]
- [frame_count, number]
- [frame_duration, number]
- [frame_start, number]
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [name, string]
- [texture_name, string]
- [frame_count, number]
- [frame_duration, number]
background:
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [texture_name, string]
- [auto_clip, boolean]
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [texture_name, string]
background9:
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [texture_name, string]
- [auto_clip, boolean]
- - [middle_x, number]
- [middle_y, number]
- [middle_x2, number]
- [middle_y2, number]
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [texture_name, string]
- [auto_clip, boolean]
- - [middle_x, number]
- [middle_y, number]
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [texture_name, string]
- [auto_clip, boolean]
- - [middle_x, number]
bgcolor:
- - [bgcolor, string]
- [fullscreen, boolean]
- [fbgcolor, string]
- - [bgcolor, string]
- [fullscreen, boolean]
- - [bgcolor, string]
box:
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [color, string]
button:
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [name, string]
- [label, string]
button_exit:
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [name, string]
- [label, string]
checkbox:
- - - [x, number]
- [y, number]
- [name, string]
- [label, string]
- [selected, boolean]
- - - [x, number]
- [y, number]
- [name, string]
- [label, string]
container:
- - - [x, number]
- [y, number]
container_end:
- []
dropdown:
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [name, string]
- - - [item, string]
- '...'
- [selected_idx, number]
- [index_event, string]
- - - [x, number]
- [y, number]
- [w, number]
- [name, string]
- - - [item, string]
- '...'
- [selected_idx, number]
- [index_event, string]
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [name, string]
- - - [item, string]
- '...'
- [selected_idx, number]
- - - [x, number]
- [y, number]
- [w, number]
- [name, string]
- - - [item, string]
- '...'
- [selected_idx, number]
field:
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [name, string]
- [label, string]
- [default, string]
- - [name, string]
- [label, string]
- [default, string]
field_close_on_enter:
- - [name, string]
- [close_on_enter, boolean]
formspec_version:
- - [version, number]
hypertext:
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [name, string]
- [text, string]
image:
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [texture_name, string]
image_button:
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [texture_name, string]
- [name, string]
- [label, string]
- [noclip, boolean]
- [drawborder, boolean]
- [pressed_texture_name, string]
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [texture_name, string]
- [name, string]
- [label, string]
image_button_exit:
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [texture_name, string]
- [name, string]
- [label, string]
item_image:
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [item_name, string]
item_image_button:
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [item_name, string]
- [name, string]
- [label, string]
label:
- - - [x, number]
- [y, number]
- [label, string]
list:
- - [inventory_location, string]
- [list_name, string]
- - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- ['', 'null']
- - [inventory_location, string]
- [list_name, string]
- - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [starting_item_index, number]
listcolors:
- - [slot_bg_normal, string]
- [slot_bg_hover, string]
- [slot_border, string]
- [tooltip_bgcolor, string]
- [tooltip_fontcolor, string]
- - [slot_bg_normal, string]
- [slot_bg_hover, string]
- [slot_border, string]
- - [slot_bg_normal, string]
- [slot_bg_hover, string]
listring:
- - [inventory_location, string]
- [list_name, string]
- []
no_prepend:
- []
position:
- - - [x, number]
- [y, number]
pwdfield:
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [name, string]
- [label, string]
real_coordinates:
- - [bool, boolean]
scroll_container:
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [scrollbar_name, string]
- [orientation, string]
- [scroll_factor, number]
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [scrollbar_name, string]
- [orientation, string]
scroll_container_end:
- []
scrollbar:
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [orientation, string]
- [name, string]
- [value, string]
scrollbaroptions:
- - - [opt, table]
- '...'
set_focus:
- - [name, string]
- [force, boolean]
- - [name, string]
size:
- - - [w, number]
- [h, number]
- - - [w, number]
- [h, number]
- [fixed_size, boolean]
style:
- - - - [selectors, string]
- '...'
- - [prop, table]
- '...'
- - - [name, string]
- - [prop, table]
- '...'
style_type:
- - - - [selectors, string]
- '...'
- - [prop, table]
- '...'
- - - [name, string]
- - [prop, table]
- '...'
tabheader:
- - - [x, number]
- [y, number]
- [h, number]
- [name, string]
- - - [caption, string]
- '...'
- [current_tab, string]
- [transparent, boolean]
- [draw_border, boolean]
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [name, string]
- - - [caption, string]
- '...'
- [current_tab, string]
- [transparent, boolean]
- [draw_border, boolean]
- - - [x, number]
- [y, number]
- [name, string]
- - - [caption, string]
- '...'
- [current_tab, string]
- [transparent, boolean]
- [draw_border, boolean]
- - - [x, number]
- [y, number]
- [h, number]
- [name, string]
- - - [caption, string]
- '...'
- [current_tab, string]
- [transparent, boolean]
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [name, string]
- - - [caption, string]
- '...'
- [current_tab, string]
- [transparent, boolean]
- - - [x, number]
- [y, number]
- [name, string]
- - - [caption, string]
- '...'
- [current_tab, string]
- [transparent, boolean]
- - - [x, number]
- [y, number]
- [h, number]
- [name, string]
- - - [caption, string]
- '...'
- [current_tab, string]
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [name, string]
- - - [caption, string]
- '...'
- [current_tab, string]
- - - [x, number]
- [y, number]
- [name, string]
- - - [caption, string]
- '...'
- [current_tab, string]
table:
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [name, string]
- - - [cells, string]
- '...'
- [selected_idx, number]
tablecolumns:
- - - - [type, string]
- - [opt, table]
- '...'
- '...'
tableoptions:
- - - [opt, table]
- '...'
textarea:
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [name, string]
- [label, string]
- [default, string]
textlist:
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [name, string]
- - - [listelem, string]
- '...'
- [selected_idx, number]
- [transparent, boolean]
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [name, string]
- - - [listelem, string]
- '...'
- [selected_idx, number]
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [name, string]
- - - [listelem, string]
- '...'
tooltip:
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [tooltip_text, string]
- [bgcolor, string]
- [fontcolor, string]
- - [gui_element_name, string]
- [tooltip_text, string]
- [bgcolor, string]
- [fontcolor, string]
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [tooltip_text, string]
- [bgcolor, string]
- - [gui_element_name, string]
- [tooltip_text, string]
- [bgcolor, string]
- - - [x, number]
- [y, number]
- - [w, number]
- [h, number]
- [tooltip_text, string]
- - [gui_element_name, string]
- [tooltip_text, string]
vertlabel:
- - - [x, number]
- [y, number]
- [label, string]

View File

@ -1,9 +1,10 @@
#!/usr/bin/env python3
#
# A primitive script to parse lua_api.txt for formspec elements.
# This script needs Python 3.8+ and ruamel.yaml to work.
#
import copy, lua_dump, os, urllib.request
import copy, json, lua_dump, os, re, ruamel.yaml, urllib.request
def _make_known(**kwargs):
known = {}
@ -14,9 +15,11 @@ def _make_known(**kwargs):
_known = _make_known(
number=('x', 'y', 'w', 'h', 'selected_idx', 'version',
'starting_item_index', 'scroll_factor'),
'starting_item_index', 'scroll_factor', 'frame_count',
'frame_duration', 'frame_start'),
boolean=('auto_clip', 'fixed_size', 'transparent', 'draw_border', 'bool',
'fullscreen', 'noclip', 'drawborder', 'selected', 'force'),
'fullscreen', 'noclip', 'drawborder', 'selected', 'force',
'close_on_enter'),
table=('param', 'opt', 'prop'),
null=('',),
)
@ -31,12 +34,16 @@ _aliases = {
'cell': 'cells',
}
def _fix_param_name(param):
param = param.lower().strip().strip('<>').replace(' ', '_')
param = _aliases.get(param, param)
assert param != '...'
return param
def _fix_param(param):
if isinstance(param, str):
if ',' not in param:
param = param.lower().strip().strip('<>').replace(' ', '_')
param = _aliases.get(param, param)
assert param != '...'
param = _fix_param_name(param)
return (param, _known.get(param, 'string'))
param = param.split(',')
@ -111,12 +118,6 @@ def _style_hook(params):
params[0] = [(('selectors', 'string'), '...')]
yield params
# Fix scroll_container
@hook('scroll_container')
def _scroll_container_hook(params):
yield params
yield params[:4]
# Fix dropdown
@hook('dropdown')
def _scroll_container_hook(params):
@ -127,14 +128,29 @@ def _scroll_container_hook(params):
yield params[:5]
yield params
# Fix textlist
@hook('textlist')
def _textlist_hook(params):
if len(params) > 5:
yield params[:5]
yield params
_param_re = re.compile(r'^\* `([^`]+)`(?: and `([^`]+)`)?:? ')
def _raw_parse(data):
# Get everything from the elements heading to the end of the next heading
data = data.split('\nElements\n--------\n', 1)[-1].split('\n----', 1)[0]
for line in data.split('\n'):
if not line.startswith('### `') or not line.endswith('`'):
# Remove the next heading
data = data.rsplit('\n', 1)[0]
# Get element data
for elem_data in data.split('\n### '):
lines = elem_data.split('\n')
raw_elem = lines.pop(0)
if not raw_elem.startswith('`') or not raw_elem.endswith('`'):
continue
elem = line[5:-2]
name, params = elem.split('[', 1)
name, params = raw_elem[1:-2].split('[', 1)
if params:
params = _fix_param(params.split(';'))
else:
@ -143,9 +159,37 @@ def _raw_parse(data):
if name in _hooks:
for p in reversed(tuple(map(copy.deepcopy, _hooks[name](params)))):
yield name, p
else:
continue
# Optional parameters
optional_params = set()
for line in lines:
match = _param_re.match(line)
if not match or 'optional' not in line.lower():
continue
optional_params.add(_fix_param_name(match.group(1)))
if p2 := match.group(2):
optional_params.add(_fix_param_name(p2))
# if optional_params:
# print('Optional', name, optional_params)
# Convert the optional parameters into a format formspec_ast can
# understand without major changes
while True:
yield name, params
if not params:
break
last_param = params[-1]
if (not isinstance(last_param, tuple) or
not isinstance(last_param[0], str) or
last_param[0] not in optional_params):
break
# print('Optional', name, last_param)
params = params[:-1]
def parse(data):
"""
Returns a dict:
@ -167,7 +211,8 @@ def parse(data):
return res
URL = 'https://github.com/minetest/minetest/raw/master/doc/lua_api.txt'
# TODO: Fix model[] parsing then switch this back to the master branch
URL = 'https://github.com/minetest/minetest/raw/050964b/doc/lua_api.txt'
def fetch_and_parse(*, url=URL):
with urllib.request.urlopen(url) as f:
raw = f.read()
@ -181,13 +226,33 @@ _comment = """
"""
_yaml_comment = """
#
# This file is automatically generated by make_elements.py and isn't actually
# used by formspec_ast, however it is useful for comparing changes across
# lua_api versions.
#
"""
def main():
dirname = os.path.dirname(__file__)
filename = os.path.join(dirname, 'elements.lua')
data = fetch_and_parse()
# Horrible code to create elements.yaml
filename2 = os.path.join(dirname, 'elements.yaml')
print('Writing to ' + filename2 + '...')
with open(filename2, 'w') as f:
f.write(_yaml_comment.lstrip())
# Yuck - Unparsing then re-parsing the data as JSON is the easiest way
# I can think of to convert tuples to lists.
ruamel.yaml.dump(json.loads(json.dumps(data)), f)
print('Writing to ' + filename + '...')
with open(filename, 'w') as f:
f.write(_comment.lstrip())
f.write(lua_dump.serialize(fetch_and_parse()))
f.write(lua_dump.serialize(data))
# elems = fetch_and_parse()
# for elem in sorted(elems):
# for def_ in elems[elem]:

View File

@ -80,6 +80,8 @@ local fs = [[
image[0,1;1,1;air.png]
set_focus[name;true]
dropdown[0,0;1;test;abc,def,ghi,jkl;2]
field_close_on_enter[my-field;false]
bgcolor[blue]
]]
fs = ('\n' .. fs):gsub('\n[ \n]*', '')
@ -161,7 +163,16 @@ test_parse_unparse(fs, {
name = "test",
item = {"abc", "def", "ghi", "jkl"},
selected_idx = 2,
}
},
{
type = "field_close_on_enter",
name = "my-field",
close_on_enter = false,
},
{
type = "bgcolor",
bgcolor = "blue",
},
})