mirror of
https://github.com/lvgl/lvgl.git
synced 2025-01-14 06:42:58 +08:00
1649 lines
52 KiB
Python
1649 lines
52 KiB
Python
"""
|
|
This file opens the XML `index.xml` file generated by Doxygen
|
|
which has the following structure:
|
|
|
|
<doxygenindex ...>
|
|
<compound ... >
|
|
</compound>
|
|
...
|
|
</doxygenindex>
|
|
|
|
Each <compound> element has a 'kind' attribute that is one of the following:
|
|
|
|
- define
|
|
- dir
|
|
- enum
|
|
- enumvalue
|
|
- example
|
|
- file
|
|
- function
|
|
- page
|
|
- struct
|
|
- typedef
|
|
- union
|
|
- variable
|
|
|
|
Most <compound> elements have child <member> elements with their own contents
|
|
depending on the 'kind' of <compound> element they are in.
|
|
|
|
This file defines classes for each of these except for
|
|
|
|
- dir
|
|
- page
|
|
- example
|
|
|
|
The remaining 'kind' values are:
|
|
|
|
- define
|
|
- enum
|
|
- enumvalue
|
|
- file
|
|
- function
|
|
- struct
|
|
- typedef
|
|
- union
|
|
- variable
|
|
|
|
The list of classes is:
|
|
|
|
Class | Adds Self to Dictionary in __init__()
|
|
--------- | --------------------------------------
|
|
DEFINE => `defines`
|
|
ENUM => `enums`
|
|
VARIABLE => `variables`
|
|
NAMESPACE => `namespaces`
|
|
STRUCT => `structures`
|
|
UNION appears to have a different purpose
|
|
TYPEDEF => `typedefs`
|
|
FUNCTION => `functions`
|
|
GROUP => `groups`
|
|
FILE => `files`
|
|
CLASS => `classes`
|
|
|
|
Additional classes:
|
|
|
|
- NAMESPACE(object):
|
|
- FUNC_ARG(object): (becomes members of FUNCTION objects)
|
|
- STRUCT_FIELD(object): (becomes members of STRUCT objects)
|
|
- GROUP(object):
|
|
- CLASS(object):
|
|
- XMLSearch(object):
|
|
|
|
Each of the above Dictionary variables has entries with
|
|
|
|
- keys = actual name of the code elements Doxygen found in the .H files.
|
|
- values = XML node generated by `xml.etree::ElementTree`
|
|
|
|
Samples:
|
|
|
|
'defines': {'ZERO_MEM_SENTINEL': <doc_builder.DEFINE object at 0x000001FB5D866420>,
|
|
'LV_GLOBAL_DEFAULT': <doc_builder.DEFINE object at 0x000001FB5D866210>,
|
|
'LV_ASSERT_OBJ': <doc_builder.DEFINE object at 0x000001FB5D1EC080>,
|
|
'LV_TRACE_OBJ_CREATE': <doc_builder.DEFINE object at 0x000001FB5D8660F0>,
|
|
|
|
'enums': {'lv_key_t': <doc_builder.ENUM object at 0x000001FB5D1EEB40>,
|
|
'lv_group_refocus_policy_t': <doc_builder.ENUM object at 0x000001FB5D1E3DA0>,
|
|
'lv_obj_flag_t': <doc_builder.ENUM object at 0x000001FB5D29F830>,
|
|
'lv_obj_class_editable_t': <doc_builder.ENUM object at 0x000001FB5D29E300>,
|
|
|
|
'variables': {'lv_global': <doc_builder.VARIABLE object at 0x000001FB5D1E3FE0>,
|
|
'lv_obj_class': <doc_builder.VARIABLE object at 0x000001FB5D1EE1E0>,
|
|
'lv_font_montserrat_8': <doc_builder.VARIABLE object at 0x000001FB5DAB41A0>,
|
|
'lv_font_montserrat_10': <doc_builder.VARIABLE object at 0x000001FB5D99D040>,
|
|
|
|
'namespaces': {},
|
|
|
|
'structures': {'_lv_anim_t::_lv_anim_path_para_t': <doc_builder.UNION object at 0x000001FB5C4240E0>,
|
|
'_lv_anim_t': <doc_builder.STRUCT object at 0x000001FB5C45F680>,
|
|
'_lv_animimg_t': <doc_builder.STRUCT object at 0x000001FB5C4FE390>,
|
|
'_lv_arc_t': <doc_builder.STRUCT object at 0x000001FB59D350A0>,
|
|
|
|
'unions': {},
|
|
|
|
'typedefs': {'lv_global_t': <doc_builder.TYPEDEF object at 0x000001FB5D1EFFE0>,
|
|
'lv_group_focus_cb_t': <doc_builder.TYPEDEF object at 0x000001FB5D1F1CA0>,
|
|
'lv_group_edge_cb_t': <doc_builder.TYPEDEF object at 0x000001FB5D1EE7E0>,
|
|
|
|
'functions': {'lv_group_create': <doc_builder.FUNCTION object at 0x000001FB5D1E0470>,
|
|
'lv_group_delete': <doc_builder.FUNCTION object at 0x000001FB5D1F3800>,
|
|
'lv_group_set_default': <doc_builder.FUNCTION object at 0x000001FB5D1ECAA0>,
|
|
|
|
Additional dictionaries:
|
|
'files': {'lv_global.h': <doc_builder.FILE object at 0x000001FB5D864E00>,
|
|
'lv_group.h': <doc_builder.FILE object at 0x000001FB5D1EFD40>,
|
|
'lv_group_private.h': <doc_builder.FILE object at 0x000001FB5D0D7DD0>,
|
|
|
|
'html_files': {'lvgl': 'lvgl.html',
|
|
'lv_api_map_v8': 'lv_api_map_v8.html',
|
|
'lv_api_map_v9_0': 'lv_api_map_v9_0.html',
|
|
'lv_api_map_v9_1': 'lv_api_map_v9_1.html',
|
|
|
|
"""
|
|
import os
|
|
import sys
|
|
from xml.etree import ElementTree as ET
|
|
|
|
base_path = ''
|
|
xml_path = ''
|
|
|
|
EMIT_WARNINGS = True
|
|
DOXYGEN_OUTPUT = True
|
|
|
|
MISSING_FUNC = 'MissingFunctionDoc'
|
|
MISSING_FUNC_ARG = 'MissingFunctionArgDoc'
|
|
MISSING_FUNC_RETURN = 'MissingFunctionReturnDoc'
|
|
MISSING_FUNC_ARG_MISMATCH = 'FunctionArgMissing'
|
|
MISSING_STRUCT = 'MissingStructureDoc'
|
|
MISSING_STRUCT_FIELD = 'MissingStructureFieldDoc'
|
|
MISSING_UNION = 'MissingUnionDoc'
|
|
MISSING_UNION_FIELD = 'MissingUnionFieldDoc'
|
|
MISSING_ENUM = 'MissingEnumDoc'
|
|
MISSING_ENUM_ITEM = 'MissingEnumItemDoc'
|
|
MISSING_TYPEDEF = 'MissingTypedefDoc'
|
|
MISSING_VARIABLE = 'MissingVariableDoc'
|
|
MISSING_MACRO = 'MissingMacroDoc'
|
|
|
|
|
|
def warn(warning_type, *args):
|
|
if EMIT_WARNINGS:
|
|
args = ' '.join(str(arg) for arg in args)
|
|
|
|
if warning_type is None:
|
|
output = f'\033[31;1m {args}\033[0m\n'
|
|
else:
|
|
output = f'\033[31;1m{warning_type}: {args}\033[0m\n'
|
|
|
|
sys.stdout.write(output)
|
|
sys.stdout.flush()
|
|
|
|
|
|
def build_docstring(element):
|
|
docstring = None
|
|
if element.tag == 'parameterlist':
|
|
return None
|
|
|
|
if element.text:
|
|
docstring = element.text.strip()
|
|
|
|
for item in element:
|
|
ds = build_docstring(item)
|
|
if ds:
|
|
if docstring:
|
|
docstring += ' ' + ds
|
|
else:
|
|
docstring = ds.strip()
|
|
|
|
if element.tag == 'para':
|
|
if docstring:
|
|
docstring = '\n\n' + docstring
|
|
|
|
if element.tag == 'ref':
|
|
docstring = f':ref:`{docstring}`'
|
|
|
|
if element.tail:
|
|
if docstring:
|
|
docstring += ' ' + element.tail.strip()
|
|
else:
|
|
docstring = element.tail.strip()
|
|
|
|
return docstring
|
|
|
|
|
|
def read_as_xml(d):
|
|
try:
|
|
return ET.fromstring(d)
|
|
except: # NOQA
|
|
return None
|
|
|
|
|
|
def load_xml(fle):
|
|
fle = os.path.join(xml_path, fle + '.xml')
|
|
|
|
with open(fle, 'rb') as f:
|
|
d = f.read().decode('utf-8')
|
|
|
|
# This code is to correct a bug in Doxygen. That bug incorrectly parses
|
|
# a typedef and it causes an error to occur building the docs. The Error
|
|
# doesn't stop the documentation from being generated, I just don't want
|
|
# to see the ugly red output.
|
|
#
|
|
# if 'typedef void() lv_lru_free_t(void *v)' in d:
|
|
# d = d.replace(
|
|
# '<type>void()</type>\n '
|
|
# '<definition>typedef void() lv_lru_free_t(void *v)</definition>',
|
|
# '<type>void</type>\n '
|
|
# '<definition>typedef void(lv_lru_free_t)(void *v)</definition>'
|
|
# )
|
|
# with open(fle, 'wb') as f:
|
|
# f.write(d.encode('utf-8'))
|
|
|
|
return ET.fromstring(d)
|
|
|
|
|
|
structures = {}
|
|
functions = {}
|
|
enums = {}
|
|
typedefs = {}
|
|
variables = {}
|
|
unions = {}
|
|
namespaces = {}
|
|
files = {}
|
|
|
|
|
|
# things to remove from description
|
|
# <para> </para>
|
|
|
|
|
|
class STRUCT_FIELD(object):
|
|
|
|
def __init__(self, name, type, description, file_name, line_no):
|
|
self.name = name
|
|
self.type = type
|
|
self.description = description
|
|
self.file_name = file_name
|
|
self.line_no = line_no
|
|
|
|
|
|
class STRUCT(object):
|
|
_missing = MISSING_STRUCT
|
|
_missing_field = MISSING_STRUCT_FIELD
|
|
|
|
template = '''\
|
|
.. doxygenstruct:: {name}
|
|
:project: lvgl
|
|
:members:
|
|
:protected-members:
|
|
:private-members:
|
|
:undoc-members:
|
|
'''
|
|
|
|
def __init__(self, parent, refid, name, **_):
|
|
if name in structures:
|
|
self.__dict__.update(structures[name].__dict__)
|
|
else:
|
|
structures[name] = self
|
|
self.parent = parent
|
|
self.refid = refid
|
|
self.name = name
|
|
self.types = set()
|
|
self._deps = None
|
|
self.header_file = ''
|
|
self.description = None
|
|
self.fields = []
|
|
self.file_name = None
|
|
self.line_no = None
|
|
|
|
if parent and refid:
|
|
root = load_xml(refid)
|
|
|
|
for compounddef in root:
|
|
if compounddef.attrib['id'] != self.refid:
|
|
continue
|
|
|
|
for child in compounddef:
|
|
if child.tag == 'includes':
|
|
self.header_file = os.path.splitext(child.text)[0]
|
|
continue
|
|
|
|
elif child.tag == 'location':
|
|
self.file_name = child.attrib['file']
|
|
self.line_no = child.attrib['line']
|
|
|
|
elif child.tag == 'detaileddescription':
|
|
self.description = build_docstring(child)
|
|
|
|
elif child.tag == 'sectiondef':
|
|
for memberdef in child:
|
|
t = get_type(memberdef)
|
|
description = None
|
|
name = ''
|
|
file_name = None
|
|
line_no = None
|
|
|
|
for element in memberdef:
|
|
if element.tag == 'location':
|
|
file_name = element.attrib['file']
|
|
line_no = element.attrib['line']
|
|
|
|
elif element.tag == 'name':
|
|
name = element.text
|
|
|
|
elif element.tag == 'detaileddescription':
|
|
description = build_docstring(element)
|
|
|
|
field = STRUCT_FIELD(name, t, description, file_name, line_no)
|
|
self.fields.append(field)
|
|
|
|
if t is None:
|
|
continue
|
|
|
|
self.types.add(t)
|
|
|
|
if not self.description:
|
|
warn(self._missing, self.name)
|
|
warn(None, 'FILE:', self.file_name)
|
|
warn(None, 'LINE:', self.line_no)
|
|
warn(None)
|
|
|
|
for field in self.fields:
|
|
if not field.description:
|
|
warn(self._missing_field, self.name)
|
|
warn(None, 'FIELD:', field.name)
|
|
warn(None, 'FILE:', field.file_name)
|
|
warn(None, 'LINE:', field.line_no)
|
|
warn(None)
|
|
|
|
def get_field(self, name):
|
|
for field in self.fields:
|
|
if field.name == name:
|
|
return field
|
|
|
|
@property
|
|
def deps(self):
|
|
if self._deps is None:
|
|
self._deps = dict(
|
|
typedefs=set(),
|
|
functions=set(),
|
|
enums=set(),
|
|
structures=set(),
|
|
unions=set(),
|
|
namespaces=set(),
|
|
variables=set(),
|
|
)
|
|
for type_ in self.types:
|
|
if type_ in typedefs:
|
|
self._deps['typedefs'].add(typedefs[type_])
|
|
elif type_ in structures:
|
|
self._deps['structures'].add(structures[type_])
|
|
elif type_ in unions:
|
|
self._deps['unions'].add(unions[type_])
|
|
elif type_ in enums:
|
|
self._deps['enums'].add(enums[type_])
|
|
elif type_ in functions:
|
|
self._deps['functions'].add(functions[type_])
|
|
elif type_ in variables:
|
|
self._deps['variables'].add(variables[type_])
|
|
elif type_ in namespaces:
|
|
self._deps['namespaces'].add(namespaces[type_])
|
|
return self._deps
|
|
|
|
def __str__(self):
|
|
return self.template.format(name=self.name)
|
|
|
|
|
|
class UNION(STRUCT):
|
|
_missing = MISSING_UNION
|
|
_missing_field = MISSING_UNION_FIELD
|
|
|
|
template = '''\
|
|
.. doxygenunion:: {name}
|
|
:project: lvgl
|
|
'''
|
|
|
|
|
|
def get_type(node):
|
|
def gt(n):
|
|
for c in n:
|
|
if c.tag == 'ref':
|
|
t = c.text.strip()
|
|
break
|
|
else:
|
|
t = node.text.strip()
|
|
|
|
return t.replace('*', '').replace('(', '').replace(')', '').strip()
|
|
|
|
for child in node:
|
|
if child.tag == 'type':
|
|
return gt(child)
|
|
|
|
|
|
class VARIABLE(object):
|
|
template = '''\
|
|
.. doxygenvariable:: {name}
|
|
:project: lvgl
|
|
'''
|
|
|
|
def __init__(self, parent, refid, name, **_):
|
|
if name in variables:
|
|
self.__dict__.update(variables[name].__dict__)
|
|
else:
|
|
variables[name] = self
|
|
self.parent = parent
|
|
self.refid = refid
|
|
self.name = name
|
|
self.description = None
|
|
self.type = ''
|
|
self.file_name = None
|
|
self.line_no = None
|
|
|
|
if parent is not None:
|
|
root = load_xml(parent.refid)
|
|
|
|
for compounddef in root:
|
|
if compounddef.attrib['id'] != parent.refid:
|
|
continue
|
|
|
|
for child in compounddef:
|
|
if (
|
|
child.tag == 'sectiondef' and
|
|
child.attrib['kind'] == 'var'
|
|
):
|
|
for memberdef in child:
|
|
if memberdef.attrib['id'] == refid:
|
|
break
|
|
else:
|
|
continue
|
|
|
|
self.type = get_type(memberdef)
|
|
|
|
for element in memberdef:
|
|
if element.tag == 'location':
|
|
self.file_name = element.attrib['file']
|
|
self.line_no = element.attrib['line']
|
|
elif element.tag == 'detaileddescription':
|
|
self.description = build_docstring(element)
|
|
|
|
if not self.description:
|
|
warn(MISSING_VARIABLE, self.name)
|
|
warn(None, 'FILE:', self.file_name)
|
|
warn(None, 'LINE:', self.line_no)
|
|
warn(None)
|
|
|
|
def __str__(self):
|
|
return self.template.format(name=self.name)
|
|
|
|
|
|
class NAMESPACE(object):
|
|
template = '''\
|
|
.. doxygennamespace:: {name}
|
|
:project: lvgl
|
|
:members:
|
|
:protected-members:
|
|
:private-members:
|
|
:undoc-members:
|
|
'''
|
|
|
|
def __init__(self, parent, refid, name, **_):
|
|
if name in namespaces:
|
|
self.__dict__.update(namespaces[name].__dict__)
|
|
else:
|
|
namespaces[name] = self
|
|
self.parent = parent
|
|
self.refid = refid
|
|
self.name = name
|
|
self.description = None
|
|
self.line_no = None
|
|
self.file_name = None
|
|
self.enums = []
|
|
self.funcs = []
|
|
self.vars = []
|
|
self.typedefs = []
|
|
self.structs = []
|
|
self.unions = []
|
|
self.classes = []
|
|
|
|
# root = load_xml(refid)
|
|
#
|
|
# for compounddef in root:
|
|
# if compounddef.attrib['id'] != refid:
|
|
# continue
|
|
#
|
|
# for sectiondef in compounddef:
|
|
# if sectiondef.tag != 'sectiondef':
|
|
# continue
|
|
#
|
|
# enum
|
|
# typedef
|
|
# func
|
|
# struct
|
|
# union
|
|
#
|
|
#
|
|
# cls = globals()[sectiondef.attrib['kind'].upper()]
|
|
# if cls == ENUM:
|
|
# if sectiondef[0].text:
|
|
# sectiondef.attrib['name'] = sectiondef[0].text.strip()
|
|
# enums_.append(cls(self, **sectiondef.attrib))
|
|
# else:
|
|
# sectiondef.attrib['name'] = None
|
|
# enums_.append(cls(self, **sectiondef.attrib))
|
|
#
|
|
# elif cls == ENUMVALUE:
|
|
# if enums_[-1].is_member(sectiondef):
|
|
# enums_[-1].add_member(sectiondef)
|
|
#
|
|
# else:
|
|
# sectiondef.attrib['name'] = sectiondef[0].text.strip()
|
|
# cls(self, **sectiondef.attrib)
|
|
|
|
def __str__(self):
|
|
return self.template.format(name=self.name)
|
|
|
|
|
|
class FUNC_ARG(object):
|
|
|
|
def __init__(self, name, type):
|
|
self.name = name
|
|
self.type = type
|
|
self.description = None
|
|
|
|
|
|
groups = {}
|
|
|
|
|
|
class GROUP(object):
|
|
template = '''\
|
|
.. doxygengroup:: {name}
|
|
:project: lvgl
|
|
'''
|
|
|
|
def __init__(self, parent, refid, name, **_):
|
|
if name in groups:
|
|
self.__dict__.update(functions[name].__dict__)
|
|
else:
|
|
functions[name] = self
|
|
self.parent = parent
|
|
self.refid = refid
|
|
self.name = name
|
|
self.description = None
|
|
|
|
def __str__(self):
|
|
return self.template.format(name=self.name)
|
|
|
|
|
|
|
|
class FUNCTION(object):
|
|
template = '''\
|
|
.. doxygenfunction:: {name}
|
|
:project: lvgl
|
|
'''
|
|
|
|
def __init__(self, parent, refid, name, **_):
|
|
if name in functions:
|
|
self.__dict__.update(functions[name].__dict__)
|
|
else:
|
|
functions[name] = self
|
|
self.parent = parent
|
|
self.refid = refid
|
|
self.name = name
|
|
self.types = set()
|
|
self.restype = None
|
|
self.args = []
|
|
self._deps = None
|
|
self.description = None
|
|
self.res_description = None
|
|
self.file_name = None
|
|
self.line_no = None
|
|
self.void_return = False
|
|
|
|
if parent is not None:
|
|
root = load_xml(parent.refid)
|
|
|
|
for compounddef in root:
|
|
if compounddef.attrib['id'] != parent.refid:
|
|
continue
|
|
|
|
for child in compounddef:
|
|
if child.tag != 'sectiondef':
|
|
continue
|
|
|
|
if child.attrib['kind'] != 'func':
|
|
continue
|
|
|
|
for memberdef in child:
|
|
if 'id' not in memberdef.attrib:
|
|
continue
|
|
|
|
if memberdef.attrib['id'] == refid:
|
|
break
|
|
else:
|
|
continue
|
|
|
|
break
|
|
else:
|
|
continue
|
|
|
|
break
|
|
else:
|
|
return
|
|
|
|
self.restype = get_type(memberdef)
|
|
|
|
for child in memberdef:
|
|
if child.tag == 'type':
|
|
if child.text and child.text.strip() == 'void':
|
|
self.void_return = True
|
|
|
|
if child.tag == 'param':
|
|
t = get_type(child)
|
|
if t is not None:
|
|
self.types.add(t)
|
|
|
|
for element in child:
|
|
if element.tag == 'declname':
|
|
arg = FUNC_ARG(element.text, t)
|
|
self.args.append(arg)
|
|
|
|
for child in memberdef:
|
|
if child.tag == 'location':
|
|
self.file_name = child.attrib['file']
|
|
self.line_no = child.attrib['line']
|
|
|
|
elif child.tag == 'detaileddescription':
|
|
self.description = build_docstring(child)
|
|
for element in child:
|
|
if element.tag != 'para':
|
|
continue
|
|
|
|
for desc_element in element:
|
|
if desc_element.tag == 'simplesect' and desc_element.attrib['kind'] == 'return':
|
|
self.res_description = build_docstring(desc_element)
|
|
|
|
if desc_element.tag != 'parameterlist':
|
|
continue
|
|
|
|
for parameter_item in desc_element:
|
|
parameternamelist = parameter_item[0]
|
|
if parameternamelist.tag != 'parameternamelist':
|
|
continue
|
|
|
|
parameter_name = parameternamelist[0].text
|
|
|
|
try:
|
|
parameterdescription = parameter_item[1]
|
|
if parameterdescription.tag == 'parameterdescription':
|
|
parameter_description = build_docstring(parameterdescription)
|
|
else:
|
|
parameter_description = None
|
|
except IndexError:
|
|
parameter_description = None
|
|
|
|
if parameter_name is not None:
|
|
for arg in self.args:
|
|
if arg.name != parameter_name:
|
|
continue
|
|
|
|
arg.description = parameter_description
|
|
break
|
|
else:
|
|
warn(MISSING_FUNC_ARG_MISMATCH, self.name)
|
|
warn(None, 'ARG:', parameter_name)
|
|
warn(None, 'FILE:', self.file_name)
|
|
warn(None, 'LINE:', self.line_no)
|
|
warn(None)
|
|
|
|
if not self.description:
|
|
warn(MISSING_FUNC, self.name)
|
|
warn(None, 'FILE:', self.file_name)
|
|
warn(None, 'LINE:', self.line_no)
|
|
warn(None)
|
|
else:
|
|
for arg in self.args:
|
|
if not arg.description:
|
|
warn(MISSING_FUNC_ARG, self.name)
|
|
warn(None, 'ARG:', arg.name)
|
|
warn(None, 'FILE:', self.file_name)
|
|
warn(None, 'LINE:', self.line_no)
|
|
warn(None)
|
|
|
|
if not self.res_description and not self.void_return:
|
|
warn(MISSING_FUNC_RETURN, self.name)
|
|
warn(None, 'FILE:', self.file_name)
|
|
warn(None, 'LINE:', self.line_no)
|
|
warn(None)
|
|
|
|
if self.restype in self.types:
|
|
self.restype = None
|
|
|
|
@property
|
|
def deps(self):
|
|
if self._deps is None:
|
|
self._deps = dict(
|
|
typedefs=set(),
|
|
functions=set(),
|
|
enums=set(),
|
|
structures=set(),
|
|
unions=set(),
|
|
namespaces=set(),
|
|
variables=set(),
|
|
)
|
|
if self.restype is not None:
|
|
self.types.add(self.restype)
|
|
|
|
for type_ in self.types:
|
|
if type_ in typedefs:
|
|
self._deps['typedefs'].add(typedefs[type_])
|
|
elif type_ in structures:
|
|
self._deps['structures'].add(structures[type_])
|
|
elif type_ in unions:
|
|
self._deps['unions'].add(unions[type_])
|
|
elif type_ in enums:
|
|
self._deps['enums'].add(enums[type_])
|
|
elif type_ in functions:
|
|
self._deps['functions'].add(functions[type_])
|
|
elif type_ in variables:
|
|
self._deps['variables'].add(variables[type_])
|
|
elif type_ in namespaces:
|
|
self._deps['namespaces'].add(namespaces[type_])
|
|
return self._deps
|
|
|
|
def __str__(self):
|
|
return self.template.format(name=self.name)
|
|
|
|
|
|
class FILE(object):
|
|
|
|
def __init__(self, _, refid, name, node, **__):
|
|
if name in files:
|
|
self.__dict__.update(files[name].__dict__)
|
|
return
|
|
|
|
files[name] = self
|
|
|
|
self.refid = refid
|
|
self.name = name
|
|
self.header_file = os.path.splitext(name)[0]
|
|
|
|
enums_ = []
|
|
|
|
for member in node:
|
|
if member.tag != 'member':
|
|
continue
|
|
|
|
cls = globals()[member.attrib['kind'].upper()]
|
|
if cls == ENUM:
|
|
if member[0].text:
|
|
member.attrib['name'] = member[0].text.strip()
|
|
enums_.append(cls(self, **member.attrib))
|
|
else:
|
|
member.attrib['name'] = None
|
|
enums_.append(cls(self, **member.attrib))
|
|
|
|
elif cls == ENUMVALUE:
|
|
if enums_[-1].is_member(member):
|
|
enums_[-1].add_member(member)
|
|
|
|
else:
|
|
member.attrib['name'] = member[0].text.strip()
|
|
cls(self, **member.attrib)
|
|
|
|
|
|
class ENUM(object):
|
|
template = '''\
|
|
.. doxygenenum:: {name}
|
|
:project: lvgl
|
|
'''
|
|
|
|
def __init__(self, parent, refid, name, **_):
|
|
if name in enums:
|
|
self.__dict__.update(enums[name].__dict__)
|
|
else:
|
|
|
|
enums[name] = self
|
|
|
|
self.parent = parent
|
|
self.refid = refid
|
|
self.name = name
|
|
self.members = []
|
|
self.description = None
|
|
self.file_name = None
|
|
self.line_no = None
|
|
|
|
if parent is not None:
|
|
root = load_xml(parent.refid)
|
|
|
|
for compounddef in root:
|
|
if compounddef.attrib['id'] != parent.refid:
|
|
continue
|
|
|
|
for child in compounddef:
|
|
if child.tag != 'sectiondef':
|
|
continue
|
|
|
|
if child.attrib['kind'] != 'enum':
|
|
continue
|
|
|
|
for memberdef in child:
|
|
if 'id' not in memberdef.attrib:
|
|
continue
|
|
|
|
if memberdef.attrib['id'] == refid:
|
|
break
|
|
else:
|
|
continue
|
|
|
|
break
|
|
else:
|
|
continue
|
|
|
|
break
|
|
else:
|
|
return
|
|
# raise RuntimeError(f'not able to locate enum {name} ({refid})')
|
|
|
|
for element in memberdef:
|
|
if element.tag == 'location':
|
|
self.file_name = element.attrib['file']
|
|
self.line_no = element.attrib['line']
|
|
|
|
if element.tag == 'detaileddescription':
|
|
self.description = build_docstring(element)
|
|
elif element.tag == 'enumvalue':
|
|
item_name = None
|
|
item_description = None
|
|
item_file_name = None
|
|
item_line_no = None
|
|
|
|
for s_element in element:
|
|
if s_element.tag == 'name':
|
|
item_name = s_element.text
|
|
elif s_element.tag == 'detaileddescription':
|
|
item_description = build_docstring(s_element)
|
|
|
|
elif s_element.tag == 'location':
|
|
item_file_name = child.attrib['file']
|
|
item_line_no = child.attrib['line']
|
|
|
|
if item_name is not None:
|
|
for ev in self.members:
|
|
if ev.name != item_name:
|
|
continue
|
|
break
|
|
else:
|
|
ev = ENUMVALUE(
|
|
self,
|
|
element.attrib['id'],
|
|
item_name
|
|
)
|
|
|
|
self.members.append(ev)
|
|
|
|
ev.description = item_description
|
|
|
|
if not self.description:
|
|
warn(MISSING_ENUM, self.name)
|
|
warn(None, 'FILE:', self.file_name)
|
|
warn(None, 'LINE:', self.line_no)
|
|
warn(None)
|
|
|
|
for member in self.members:
|
|
if not member.description:
|
|
warn(MISSING_ENUM_ITEM, self.name)
|
|
warn(None, 'MEMBER:', member.name)
|
|
warn(None, 'FILE:', self.file_name)
|
|
warn(None, 'LINE:', self.line_no)
|
|
warn(None)
|
|
|
|
def is_member(self, member):
|
|
return (
|
|
member.attrib['kind'] == 'enumvalue' and
|
|
member.attrib['refid'].startswith(self.refid)
|
|
)
|
|
|
|
def add_member(self, member):
|
|
name = member[0].text.strip()
|
|
for ev in self.members:
|
|
if ev.name == name:
|
|
return
|
|
|
|
self.members.append(
|
|
ENUMVALUE(
|
|
self,
|
|
member.attrib['refid'],
|
|
name
|
|
)
|
|
)
|
|
|
|
def __str__(self):
|
|
template = [self.template.format(name=self.name)]
|
|
template.extend(list(str(member) for member in self.members))
|
|
|
|
return '\n'.join(template)
|
|
|
|
|
|
defines = {}
|
|
|
|
|
|
def build_define(element):
|
|
define = None
|
|
|
|
if element.text:
|
|
define = element.text.strip()
|
|
|
|
for item in element:
|
|
ds = build_define(item)
|
|
if ds:
|
|
if define:
|
|
define += ' ' + ds
|
|
else:
|
|
define = ds.strip()
|
|
|
|
if element.tail:
|
|
if define:
|
|
define += ' ' + element.tail.strip()
|
|
else:
|
|
define = element.tail.strip()
|
|
|
|
return define
|
|
|
|
|
|
class DEFINE(object):
|
|
template = '''\
|
|
.. doxygendefine:: {name}
|
|
:project: lvgl
|
|
'''
|
|
|
|
def __init__(self, parent, refid, name, **_):
|
|
if name in defines:
|
|
self.__dict__.update(defines[name].__dict__)
|
|
else:
|
|
defines[name] = self
|
|
|
|
self.parent = parent
|
|
self.refid = refid
|
|
self.name = name
|
|
self.description = None
|
|
self.file_name = None
|
|
self.line_no = None
|
|
self.params = None
|
|
self.initializer = None
|
|
|
|
if parent is not None:
|
|
root = load_xml(parent.refid)
|
|
|
|
for compounddef in root:
|
|
if compounddef.attrib['id'] != parent.refid:
|
|
continue
|
|
|
|
for child in compounddef:
|
|
if child.tag != 'sectiondef':
|
|
continue
|
|
|
|
if child.attrib['kind'] != 'define':
|
|
continue
|
|
|
|
for memberdef in child:
|
|
if memberdef.attrib['id'] == refid:
|
|
break
|
|
else:
|
|
continue
|
|
|
|
break
|
|
else:
|
|
continue
|
|
|
|
break
|
|
else:
|
|
return
|
|
|
|
for element in memberdef:
|
|
if element.tag == 'location':
|
|
self.file_name = element.attrib['file']
|
|
self.line_no = element.attrib['line']
|
|
|
|
elif element.tag == 'detaileddescription':
|
|
self.description = build_docstring(element)
|
|
|
|
elif element.tag == 'param':
|
|
for child in element:
|
|
if child.tag == 'defname':
|
|
if self.params is None:
|
|
self.params = []
|
|
|
|
if child.text:
|
|
self.params.append(child.text)
|
|
|
|
elif element.tag == 'initializer':
|
|
initializer = build_define(element)
|
|
if initializer is None:
|
|
self.initializer = ''
|
|
else:
|
|
self.initializer = initializer
|
|
|
|
if not self.description:
|
|
warn(MISSING_MACRO, self.name)
|
|
warn(None, 'FILE:', self.file_name)
|
|
warn(None, 'LINE:', self.line_no)
|
|
warn(None)
|
|
|
|
def __str__(self):
|
|
return self.template.format(name=self.name)
|
|
|
|
|
|
class ENUMVALUE(object):
|
|
template = '''\
|
|
.. doxygenenumvalue:: {name}
|
|
:project: lvgl
|
|
'''
|
|
|
|
def __init__(self, parent, refid, name, **_):
|
|
self.parent = parent
|
|
self.refid = refid
|
|
self.name = name
|
|
self.description = None
|
|
self.file_name = None
|
|
self.line_no = None
|
|
|
|
def __str__(self):
|
|
return self.template.format(name=self.name)
|
|
|
|
|
|
class TYPEDEF(object):
|
|
template = '''\
|
|
.. doxygentypedef:: {name}
|
|
:project: lvgl
|
|
'''
|
|
|
|
def __init__(self, parent, refid, name, **_):
|
|
if name in typedefs:
|
|
self.__dict__.update(typedefs[name].__dict__)
|
|
else:
|
|
typedefs[name] = self
|
|
|
|
self.parent = parent
|
|
self.refid = refid
|
|
self.name = name
|
|
self.type = None
|
|
self._deps = None
|
|
self.description = None
|
|
self.file_name = None
|
|
self.line_no = None
|
|
|
|
if parent is not None:
|
|
root = load_xml(parent.refid)
|
|
|
|
for compounddef in root:
|
|
if compounddef.attrib['id'] != parent.refid:
|
|
continue
|
|
|
|
for child in compounddef:
|
|
if child.tag != 'sectiondef':
|
|
continue
|
|
if child.attrib['kind'] != 'typedef':
|
|
continue
|
|
|
|
for memberdef in child:
|
|
if 'id' not in memberdef.attrib:
|
|
continue
|
|
|
|
if memberdef.attrib['id'] == refid:
|
|
break
|
|
else:
|
|
continue
|
|
|
|
break
|
|
else:
|
|
continue
|
|
|
|
break
|
|
else:
|
|
return
|
|
|
|
for element in memberdef:
|
|
if element.tag == 'location':
|
|
self.file_name = element.attrib['file']
|
|
self.line_no = element.attrib['line']
|
|
|
|
if element.tag == 'detaileddescription':
|
|
self.description = build_docstring(element)
|
|
|
|
if not self.description:
|
|
warn(MISSING_TYPEDEF, self.name)
|
|
warn(None, 'FILE:', self.file_name)
|
|
warn(None, 'LINE:', self.line_no)
|
|
warn(None)
|
|
|
|
self.type = get_type(memberdef)
|
|
|
|
@property
|
|
def deps(self):
|
|
if self._deps is None:
|
|
self._deps = dict(
|
|
typedefs=set(),
|
|
functions=set(),
|
|
enums=set(),
|
|
structures=set(),
|
|
unions=set(),
|
|
namespaces=set(),
|
|
variables=set(),
|
|
)
|
|
if self.type is not None:
|
|
type_ = self.type
|
|
|
|
if type_ in typedefs:
|
|
self._deps['typedefs'].add(typedefs[type_])
|
|
elif type_ in structures:
|
|
self._deps['structures'].add(structures[type_])
|
|
elif type_ in unions:
|
|
self._deps['unions'].add(unions[type_])
|
|
elif type_ in enums:
|
|
self._deps['enums'].add(enums[type_])
|
|
elif type_ in functions:
|
|
self._deps['functions'].add(functions[type_])
|
|
elif type_ in variables:
|
|
self._deps['variables'].add(variables[type_])
|
|
elif type_ in namespaces:
|
|
self._deps['namespaces'].add(namespaces[type_])
|
|
|
|
return self._deps
|
|
|
|
def __str__(self):
|
|
return self.template.format(name=self.name)
|
|
|
|
|
|
classes = {}
|
|
|
|
|
|
class CLASS(object):
|
|
|
|
def __init__(self, _, refid, name, node, **__):
|
|
if name in classes:
|
|
self.__dict__.update(classes[name].__dict__)
|
|
return
|
|
|
|
classes[name] = self
|
|
|
|
self.refid = refid
|
|
self.name = name
|
|
|
|
enums_ = []
|
|
|
|
for member in node:
|
|
if member.tag != 'member':
|
|
continue
|
|
|
|
cls = globals()[member.attrib['kind'].upper()]
|
|
if cls == ENUM:
|
|
member.attrib['name'] = member[0].text.strip()
|
|
enums_.append(cls(self, **member.attrib))
|
|
elif cls == ENUMVALUE:
|
|
if enums_[-1].is_member(member):
|
|
enums_[-1].add_member(member)
|
|
|
|
else:
|
|
member.attrib['name'] = member[0].text.strip()
|
|
cls(self, **member.attrib)
|
|
|
|
|
|
lvgl_src_path = ''
|
|
api_path = ''
|
|
html_files = {}
|
|
|
|
|
|
def iter_src(n, p):
|
|
if p:
|
|
out_path = os.path.join(api_path, p)
|
|
else:
|
|
out_path = api_path
|
|
|
|
index_file = None
|
|
|
|
if p:
|
|
src_path = os.path.join(lvgl_src_path, p)
|
|
else:
|
|
src_path = lvgl_src_path
|
|
|
|
folders = []
|
|
|
|
for file in os.listdir(src_path):
|
|
if 'private' in file:
|
|
continue
|
|
|
|
if os.path.isdir(os.path.join(src_path, file)):
|
|
folders.append((file, os.path.join(p, file)))
|
|
continue
|
|
|
|
if not file.endswith('.h'):
|
|
continue
|
|
|
|
if not os.path.exists(out_path):
|
|
os.makedirs(out_path)
|
|
|
|
if index_file is None:
|
|
index_file = open(os.path.join(out_path, 'index.rst'), 'w')
|
|
if n:
|
|
index_file.write('=' * len(n))
|
|
index_file.write('\n' + n + '\n')
|
|
index_file.write('=' * len(n))
|
|
index_file.write('\n\n\n')
|
|
|
|
index_file.write('.. toctree::\n :maxdepth: 2\n\n')
|
|
|
|
name = os.path.splitext(file)[0]
|
|
index_file.write(' ' + name + '\n')
|
|
|
|
rst_file = os.path.join(out_path, name + '.rst')
|
|
html_file = os.path.join(p, name + '.html')
|
|
html_files[name] = html_file
|
|
|
|
with open(rst_file, 'w') as f:
|
|
f.write('.. _{0}_h:'.format(name))
|
|
f.write('\n\n')
|
|
f.write('=' * len(file))
|
|
f.write('\n')
|
|
f.write(file)
|
|
f.write('\n')
|
|
f.write('=' * len(file))
|
|
f.write('\n\n\n')
|
|
|
|
f.write('.. doxygenfile:: ' + file)
|
|
f.write('\n')
|
|
f.write(' :project: lvgl')
|
|
f.write('\n\n')
|
|
|
|
for name, folder in folders:
|
|
if iter_src(name, folder):
|
|
if index_file is None:
|
|
index_file = open(os.path.join(out_path, 'index.rst'), 'w')
|
|
|
|
if n:
|
|
index_file.write('=' * len(n))
|
|
index_file.write('\n' + n + '\n')
|
|
index_file.write('=' * len(n))
|
|
index_file.write('\n\n\n')
|
|
|
|
index_file.write('.. toctree::\n :maxdepth: 2\n\n')
|
|
|
|
index_file.write(' ' + os.path.split(folder)[-1] + '/index\n')
|
|
|
|
if index_file is not None:
|
|
index_file.write('\n')
|
|
index_file.close()
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def clean_name(nme):
|
|
# Handle error:
|
|
# AttributeError: 'NoneType' object has no attribute 'startswith'
|
|
if nme is None:
|
|
return nme
|
|
|
|
if nme.startswith('_lv_'):
|
|
nme = nme[4:]
|
|
elif nme.startswith('lv_'):
|
|
nme = nme[3:]
|
|
|
|
if nme.endswith('_t'):
|
|
nme = nme[:-2]
|
|
|
|
return nme
|
|
|
|
|
|
# Definitions:
|
|
# - "section" => The name "abc_def" has 2 sections.
|
|
# - N = number of sections in `item_name`.
|
|
# After removing leading '_lv_', 'lv_' and trailing '_t' from `obj_name`,
|
|
# do the remaining first N "sections" of `obj_name` match `item_name`
|
|
# (case sensitive)?
|
|
def is_name_match(item_name, obj_name):
|
|
# Handle error:
|
|
# AttributeError: 'NoneType' object has no attribute 'split'
|
|
if obj_name is None:
|
|
return False
|
|
|
|
u_num = item_name.count('_') + 1
|
|
|
|
obj_name = obj_name.split('_')
|
|
|
|
# Reject (False) if `obj_name` doesn't have as many sections as `item_name`.
|
|
if len(obj_name) < u_num:
|
|
return False
|
|
|
|
obj_name = '_'.join(obj_name[:u_num])
|
|
|
|
return item_name == obj_name
|
|
|
|
|
|
def get_includes(name1, name2, obj, includes):
|
|
name2 = clean_name(name2)
|
|
|
|
if not is_name_match(name1, name2):
|
|
return
|
|
|
|
if obj.parent is not None and hasattr(obj.parent, 'header_file'):
|
|
header_file = obj.parent.header_file
|
|
elif hasattr(obj, 'header_file'):
|
|
header_file = obj.header_file
|
|
else:
|
|
return
|
|
|
|
if not header_file:
|
|
return
|
|
|
|
if header_file not in html_files:
|
|
return
|
|
|
|
includes.add((header_file, html_files[header_file]))
|
|
|
|
|
|
class XMLSearch(object):
|
|
|
|
def __init__(self, temp_directory):
|
|
global xml_path
|
|
import subprocess
|
|
import re
|
|
import sys
|
|
|
|
bp = os.path.abspath(os.path.dirname(__file__))
|
|
|
|
lvgl_path = os.path.join(temp_directory, 'lvgl')
|
|
src_path = os.path.join(lvgl_path, 'src')
|
|
|
|
doxy_path = os.path.join(bp, 'Doxyfile')
|
|
|
|
with open(doxy_path, 'rb') as f:
|
|
data = f.read().decode('utf-8')
|
|
|
|
data = data.replace(
|
|
'#*#*LV_CONF_PATH*#*#',
|
|
os.path.join(temp_directory, 'lv_conf.h')
|
|
)
|
|
data = data.replace('*#*#SRC#*#*', '"{0}"'.format(src_path))
|
|
|
|
with open(os.path.join(temp_directory, 'Doxyfile'), 'wb') as f:
|
|
f.write(data.encode('utf-8'))
|
|
|
|
# -----------------------------------------------------------------
|
|
# Populate LVGL_URLPATH and LVGL_GITCOMMIT environment variables:
|
|
# - LVGL_URLPATH <= 'master' or '8.4' '9.2' etc.
|
|
# - LVGL_GITCOMMIT <= commit hash of HEAD.
|
|
# The previous version of this was populating LVGL_URLPATH with
|
|
# the multi-line list of all existing branches in the repository,
|
|
# which was not what was intended.
|
|
# -----------------------------------------------------------------
|
|
status, branch = subprocess.getstatusoutput("git branch --show-current")
|
|
_, gitcommit = subprocess.getstatusoutput("git rev-parse HEAD")
|
|
|
|
# If above failed (i.e. `branch` not valid), default to 'master'.
|
|
if status != 0:
|
|
branch = 'master'
|
|
elif branch == 'master':
|
|
# Expected in most cases. Nothing to change.
|
|
pass
|
|
else:
|
|
# `branch` is valid. Capture release version if in a 'release/' branch.
|
|
if branch.startswith('release/'):
|
|
branch = branch[8:]
|
|
else:
|
|
# Default to 'master'.
|
|
branch = 'master'
|
|
|
|
os.environ['LVGL_URLPATH'] = branch
|
|
os.environ['LVGL_GITCOMMIT'] = gitcommit
|
|
|
|
# ---------------------------------------------------------------------
|
|
# Provide a way to run an external command and abort build on error.
|
|
#
|
|
# This is necessary because when tempdir created by tempfile.mkdtemp()`
|
|
# is on a different drive, the "cd tmpdir && doxygen Doxyfile" syntax
|
|
# fails because of the different semantics of the `cd` command on
|
|
# Windows: it doesn't change the default DRIVE if `cd` is executed
|
|
# from a different drive. The result, when this is the case, is that
|
|
# Doxygen runs in the current working directory instead of in the
|
|
# temporary directory as was intended.
|
|
# ---------------------------------------------------------------------
|
|
def cmd(cmd_str, start_dir=None):
|
|
if start_dir is None:
|
|
start_dir = os.getcwd()
|
|
|
|
saved_dir = os.getcwd()
|
|
os.chdir(start_dir)
|
|
|
|
# This method of running Doxygen is used because if it
|
|
# succeeds, we do not want anything going to STDOUT.
|
|
# Running it via `os.system()` would send its output
|
|
# to STDOUT.
|
|
p = subprocess.Popen(
|
|
cmd_str,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE,
|
|
shell=True
|
|
)
|
|
|
|
out, err = p.communicate()
|
|
if p.returncode:
|
|
if out:
|
|
# Note the `.decode("utf-8")` is required here
|
|
# because `sys.stdout.write()` requires a string,
|
|
# and `out` by itself is a byte array -- it causes
|
|
# it to generate an exception and abort the script.
|
|
sys.stdout.write(out.decode("utf-8"))
|
|
sys.stdout.flush()
|
|
if err:
|
|
sys.stderr.write(err.decode("utf-8"))
|
|
sys.stdout.flush()
|
|
|
|
sys.exit(p.returncode)
|
|
|
|
# If execution arrived here, Doxygen exited with code 0.
|
|
os.chdir(saved_dir)
|
|
|
|
# -----------------------------------------------------------------
|
|
# Run Doxygen in temporary directory.
|
|
# -----------------------------------------------------------------
|
|
cmd('doxygen Doxyfile', temp_directory)
|
|
|
|
xml_path = os.path.join(temp_directory, 'xml')
|
|
|
|
self.index = load_xml('index')
|
|
|
|
for compound in self.index:
|
|
compound.attrib['name'] = compound[0].text.strip()
|
|
if compound.attrib['kind'] in ('example', 'page', 'dir'):
|
|
continue
|
|
|
|
globals()[compound.attrib['kind'].upper()](
|
|
None,
|
|
node=compound,
|
|
**compound.attrib
|
|
)
|
|
|
|
def get_macros(self):
|
|
return list(defines.values())
|
|
|
|
def get_enum_item(self, e_name):
|
|
for enum, obj in enums.items():
|
|
for enum_item in obj.members:
|
|
if enum_item.name == e_name:
|
|
return enum_item
|
|
|
|
def get_enum(self, e_name):
|
|
return enums.get(e_name, None)
|
|
|
|
def get_function(self, f_name):
|
|
return functions.get(f_name, None)
|
|
|
|
def get_variable(self, v_name):
|
|
return variables.get(v_name, None)
|
|
|
|
def get_union(self, u_name):
|
|
return unions.get(u_name, None)
|
|
|
|
def get_structure(self, s_name):
|
|
return structures.get(s_name, None)
|
|
|
|
def get_typedef(self, t_name):
|
|
return typedefs.get(t_name, None)
|
|
|
|
def get_macro(self, m_name):
|
|
return defines.get(m_name, None)
|
|
|
|
|
|
def announce(*args):
|
|
args = ' '.join(repr(arg) for arg in args)
|
|
print(f'{os.path.basename(__file__)}: ', args)
|
|
|
|
|
|
def run(project_path, temp_directory, *doc_paths):
|
|
"""
|
|
This function does 2 things:
|
|
1. Generates .RST files for the LVGL header files that will have API
|
|
pages generated for them. It places these in <tmp_dir>/API/...
|
|
following the <project_path>/src/ directory structure.
|
|
2. Add Sphinx hyperlinks to the end of source .RST files found
|
|
in the `doc_paths` array directories, whose file-name stems
|
|
match code-element names found by Doxygen.
|
|
|
|
:param project_path: platform-appropriate path to LVGL root directory
|
|
:param temp_directory: platform-appropriate path to temp dir being operated on
|
|
:param doc_paths: list of platform-appropriate paths to find source .RST files.
|
|
:return: n/a
|
|
"""
|
|
global base_path
|
|
global xml_path
|
|
global lvgl_src_path
|
|
global api_path
|
|
|
|
base_path = temp_directory
|
|
xml_path = os.path.join(base_path, 'xml')
|
|
api_path = os.path.join(base_path, 'API')
|
|
lvgl_src_path = os.path.join(project_path, 'src')
|
|
|
|
announce("Generating API documentation .RST files...")
|
|
|
|
if not os.path.exists(api_path):
|
|
os.makedirs(api_path)
|
|
|
|
# Generate .RST files for API pages.
|
|
iter_src('API', '')
|
|
# Load index.xml -- core of what was generated by Doxygen.
|
|
index = load_xml('index')
|
|
|
|
# Populate these dictionaries.
|
|
# Keys : C-code-element names (str) found by Doxygen.
|
|
# Values: The <compound> XML-node created by `xml.etree::ElementTree` in `load_xml()` above.
|
|
#
|
|
# - defines,
|
|
# - enums,
|
|
# - variables,
|
|
# - namespaces,
|
|
# - structures,
|
|
# - unions,
|
|
# - typedefs,
|
|
# - functions.
|
|
announce("Building source-code symbol tables...")
|
|
|
|
for compound in index:
|
|
compound.attrib['name'] = compound[0].text.strip()
|
|
if compound.attrib['kind'] in ('example', 'page', 'dir'):
|
|
continue
|
|
|
|
# This below highly-compressed command effectively does this:
|
|
#
|
|
# namespace_dict = globals()
|
|
# compound_elem_kind_upper = compound.attrib['kind'].upper()
|
|
# e.g. 'FUNCTION'
|
|
# class_obj = namespace_dict['FUNCTION']
|
|
# # In each case of `class_obj`, the __init__ args are:
|
|
# # (self, parent, refid, name, **_)
|
|
# # So we get...
|
|
# attrib_keyword_args = **compound.attrib
|
|
# # Passing (**compound.attrib) as an argument creates and
|
|
# # passes a set of keyword arguments produced from the
|
|
# # dictionary `compound.attrib`.
|
|
# new_obj = class_obj(None, node=compound, attrib_keyword_args)
|
|
#
|
|
# Note carefully that `new_obj` gets thrown away, but the new object created
|
|
# doesn't go away because during its execution of __init__(), the new object
|
|
# adds itself to the global dictionary matching its "kind":
|
|
#
|
|
# Class Dictionary New Object Adds Itself To
|
|
# ------------ | ------------------------------------
|
|
# - DEFINE => `defines`
|
|
# - ENUM => `enums`
|
|
# - VARIABLE => `variables`
|
|
# - NAMESPACE => `namespaces`
|
|
# - STRUCT => `structures`
|
|
# - UNION appears to have a different purpose
|
|
# - TYPEDEF => `typedefs`
|
|
# - FUNCTION => `functions`
|
|
# - GROUP => `groups`
|
|
# - FILE => `files`
|
|
# - CLASS => `classes`
|
|
#
|
|
# Populating these dictionaries takes quite a while:
|
|
# ~18-seconds on a medium-speed system.
|
|
globals()[compound.attrib['kind'].upper()](
|
|
None,
|
|
node=compound,
|
|
**compound.attrib
|
|
)
|
|
|
|
# For each directory entry in `doc_paths` array...
|
|
announce("Adding API-page hyperlinks to source docs...")
|
|
|
|
for folder in doc_paths:
|
|
# Fetch a list of '.rst' files excluding 'index.rst'.
|
|
rst_files = list(
|
|
(os.path.splitext(item)[0], os.path.join(folder, item))
|
|
for item in os.listdir(folder)
|
|
if item.endswith('.rst') and 'index.rst' not in item
|
|
)
|
|
|
|
# For each .RST file in that directory...
|
|
for stem, path in rst_files:
|
|
# Start with an empty set.
|
|
html_includes = set()
|
|
|
|
# Build `html_includes` set as a list of tuples containing
|
|
# (name, html_file). Example: "draw.rst" has `stem` == 'draw',
|
|
# and generates a list of tuples from .H files where matching
|
|
# C-code-element names were found. Example:
|
|
# {('lv_draw_line', 'draw\\lv_draw_line.html'),
|
|
# ('lv_draw_sdl', 'draw\\sdl\\lv_draw_sdl.html'),
|
|
# ('lv_draw_sw_blend_to_i1', 'draw\\sw\\blend\\lv_draw_sw_blend_to_i1.html'),
|
|
# etc.}
|
|
for container in (
|
|
defines,
|
|
enums,
|
|
variables,
|
|
namespaces,
|
|
structures,
|
|
unions,
|
|
typedefs,
|
|
functions
|
|
):
|
|
for n, o in container.items():
|
|
get_includes(stem, n, o, html_includes)
|
|
|
|
if html_includes:
|
|
# Convert `html_includes` set to a list of strings containing the
|
|
# Sphinx hyperlink syntax "link references". Example from above:
|
|
# [':ref:`lv_draw_line_h`\n',
|
|
# ':ref:`lv_draw_sdl_h`\n',
|
|
# ':ref:`lv_draw_sw_blend_to_i1_h`\n',
|
|
# etc.]
|
|
html_includes = list(
|
|
':ref:`{0}_h`\n'.format(inc)
|
|
for inc, _ in html_includes
|
|
)
|
|
|
|
# Convert that list to a single string of Sphinx hyperlink
|
|
# references with blank lines between them.
|
|
# :ref:`lv_draw_line_h`
|
|
#
|
|
# :ref:`lv_draw_sdl_h`
|
|
#
|
|
# :ref:`lv_draw_sw_blend_to_i1_h`
|
|
#
|
|
# etc.
|
|
output = ('\n'.join(html_includes)) + '\n'
|
|
|
|
# Append that string to the source .RST file being processed.
|
|
with open(path, 'rb') as f:
|
|
try:
|
|
data = f.read().decode('utf-8')
|
|
except UnicodeDecodeError:
|
|
print(path)
|
|
raise
|
|
|
|
data = data.split('.. Autogenerated', 1)[0]
|
|
|
|
data += '.. Autogenerated\n\n'
|
|
data += output
|
|
|
|
with open(path, 'wb') as f:
|
|
f.write(data.encode('utf-8'))
|