diff options
author | Kevin Schlosser <kdschlosser@users.noreply.github.com> | 2023-04-27 06:42:02 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-04-27 14:42:02 +0200 |
commit | e485dd8bb400ef29469311591656936ae9beffb8 (patch) | |
tree | d3cfbcdcaedafc3c202c184d1029824833ad1f3d /docs/doc_builder.py | |
parent | e7f88efa5853128bf871dde335c0ca8da9eb7731 (diff) | |
download | lvgl-e485dd8bb400ef29469311591656936ae9beffb8.tar.gz lvgl-e485dd8bb400ef29469311591656936ae9beffb8.zip |
feat(docs): migrate from .md to .rst (#4129)
Diffstat (limited to 'docs/doc_builder.py')
-rw-r--r-- | docs/doc_builder.py | 713 |
1 files changed, 713 insertions, 0 deletions
diff --git a/docs/doc_builder.py b/docs/doc_builder.py new file mode 100644 index 000000000..ec9cd46b7 --- /dev/null +++ b/docs/doc_builder.py @@ -0,0 +1,713 @@ +import os +from xml.etree import ElementTree as ET + +base_path = '' +xml_path = '' + + +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 = {} + + +class STRUCT(object): + 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__) + return + + structures[name] = self + self.parent = parent + self.refid = refid + self.name = name + self.types = set() + self._deps = None + self.header_file = '' + + 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] + + if child.tag != 'sectiondef': + continue + + for memberdef in child: + t = get_type(memberdef) + + if t is None: + continue + + self.types.add(t) + + @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): + 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__) + return + + variables[name] = self + self.parent = parent + self.refid = refid + self.name = name + + 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__) + return + + namespaces[name] = self + self.parent = parent + self.refid = refid + self.name = name + + 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__) + return + + functions[name] = self + self.parent = parent + self.refid = refid + self.name = name + self.types = set() + self.restype = None + self._deps = 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'] != 'func': + continue + + for memberdef in child: + 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 == 'param': + t = get_type(child) + if t is not None: + self.types.add(t) + + 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: + 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) + + +class ENUM(object): + template = '''\ +.. doxygenenum:: {name} + :project: lvgl +''' + + def __init__(self, parent, refid, name, **_): + if name in enums: + self.__dict__.update(enums[name].__dict__) + return + + enums[name] = self + + self.parent = parent + self.refid = refid + self.name = name + self.members = [] + + def is_member(self, member): + return ( + member.attrib['kind'] == 'enumvalue' and + member.attrib['refid'].startswith(self.refid) + ) + + def add_member(self, member): + self.members.append( + ENUMVALUE( + self, + member.attrib['refid'], + member[0].text.strip() + ) + ) + + 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 = {} + + +class DEFINE(object): + template = '''\ +.. doxygendefine:: {name} + :project: lvgl +''' + + def __init__(self, parent, refid, name, **_): + if name in defines: + self.__dict__.update(defines[name].__dict__) + return + + defines[name] = self + + self.parent = parent + self.refid = refid + self.name = name + + 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 + + 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__) + return + + typedefs[name] = self + + self.parent = parent + self.refid = refid + self.name = name + self.type = None + self._deps = 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 memberdef.attrib['id'] == refid: + break + else: + continue + + break + else: + continue + + break + else: + return + + 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}:'.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): + if nme.startswith('_lv_'): + nme = nme[4:] + elif nme.startswith('lv_'): + nme = nme[3:] + + if nme.endswith('_t'): + nme = nme[:-2] + + return nme + + +def is_name_match(item_name, obj_name): + u_num = item_name.count('_') + 1 + + obj_name = obj_name.split('_') + 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: + 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])) + + +def run(project_path, temp_directory, *doc_paths): + 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') + + if not os.path.exists(api_path): + os.makedirs(api_path) + + iter_src('API', '') + index = load_xml('index') + + for compound in 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 + ) + + for folder in doc_paths: + items = list( + (os.path.splitext(item)[0], os.path.join(folder, item)) + for item in os.listdir(folder) + if item.endswith('rst') and 'index' not in item + ) + + for name, path in items: + html_includes = set() + + for container in ( + defines, + enums, + variables, + namespaces, + structures, + unions, + typedefs, + functions + ): + for n, o in container.items(): + get_includes(name, n, o, html_includes) + + if html_includes: + html_includes = list( + ':ref:`{0}`\n'.format(inc) + for inc, _ in html_includes + ) + + output = ('\n'.join(html_includes)) + '\n' + + 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')) |