From cb77d1fe956d6011a5739bce5eddf6f6daf80661 Mon Sep 17 00:00:00 2001 From: "Claudius \"keldu\" Holeksa" Date: Thu, 11 Apr 2024 18:25:22 +0200 Subject: docs: Introduce old base scripts for handling doc generation --- docs/scripts/gasp.py | 516 +++++++++++++++++++++++++++++++++++++++++++++++ docs/scripts/make_rst.py | 73 +++++++ 2 files changed, 589 insertions(+) create mode 100644 docs/scripts/gasp.py create mode 100644 docs/scripts/make_rst.py (limited to 'docs') diff --git a/docs/scripts/gasp.py b/docs/scripts/gasp.py new file mode 100644 index 0000000..5160bdc --- /dev/null +++ b/docs/scripts/gasp.py @@ -0,0 +1,516 @@ +#!/usr/bin/env python3 + +import argparse +import os +import platform +import re +import sys +import xml.etree.ElementTree as ET +import json +from pathlib import Path + + +class GaspFileDescription: + def __init__(self, f_id, f_name, attribs, funcs): + self._id = f_id; + self._name = f_name; + self._attributes = attribs; + self._functions = funcs; + pass + + def gasp_to_json(self): + return { + "id" : self._id, + "name" : self._name, + "functions" : self._functions, + "attributes" : self._attributes + }; + +class GaspNamespaceDescription: + def __init__(self, ns_id, ns_name, attribs, funcs): + self._id = ns_id; + self._name = ns_name; + self._attributes = attribs; + self._functions = funcs; + pass + + def gasp_to_json(self): + return { + "id" : self._id, + "name" : self._name, + "functions" : self._functions, + "attributes" : self._attributes + }; + +class GaspTypeRefDescription: + def __init__(self, type_name, type_id): + self._name = type_name; + self._id = type_id; + pass + + def gasp_to_json(self): + return { + "name" : self._name, + "id" : self._id + }; + +class GaspAttributeDescription: + def __init__(self, member_id, name): + self._type_name = []; + self._id = member_id; + self._name = name; + self._brief_description = ""; + self._detailed_description = []; + self._const = False; + self._static = False; + self._initializer = ""; + pass + + def gasp_to_json(self): + return { + "type" : self._type_name, + "id" : self._id, + "name" : self._name, + "brief_description" : self._brief_description, + "detailed_description" : self._detailed_description, + "static" : self._static, + "const" : self._const, + "initializer" : self._initializer + }; + +class GaspFunctionDescription: + def __init__(self, member_id, name): + self._type_name = []; + self._id = member_id; + self._name = name; + self._brief_description = ""; + self._detailed_description = []; + self._params = []; + self._const = False; + self._static = False; + self._virtual = False; + pass + + def gasp_to_json(self): + return { + "type" : self._type_name, + "id" : self._id, + "name" : self._name, + "brief_description" : self._brief_description, + "detailed_description" : self._detailed_description, + "parameters" : self._params, + "static" : self._static, + "const" : self._const, + "virtual" : self._virtual + }; + +class GaspClassDescription: + def __init__(self, class_id, name, + attributes, + functions + ): + self._id = class_id; + self._name = name; + self._brief_description = ""; + self._detailed_description = []; + + self._attributes = attributes; + self._functions = functions; + + self._specializations = []; + self._is_special = False; + + self._templates = []; + pass + + def append_specialization(self, name, cls_id): + self._specializations.append({"name" : name, "id" : cls_id}); + pass + + + def gasp_to_json(self): + return { + "id" : self._id, + "name" : self._name, + "brief_description" : self._brief_description, + "detailed_description" : self._detailed_description, + "attributes" : self._attributes, + "functions" : self._functions, + "specializations" : self._specializations, + "is_special" : self._is_special, + "templates" : self._templates + }; + +def strip_class_name_specialization(name): + return ''.join(name.partition('<')[0:1]).rstrip(); + # Sleeper function + # We need a reductionist approach from back to front + # The following part only reduces one level of depth in inner class specialization cases + split_name = name.split('::'); + counts = [0,0]; + # Technically we need to check if we are in a string literal or a char + counts[0] = split_name[-1].count('<'); + counts[1] = split_name[-1].count('>'); + if counts[0] == counts[1]: + split_name[-1] = ''.join(split_name[-1].partition('<')[0:1]).rstrip(); + return '::'.join(split_name); + return ''.join(name.partition('<')[0:1]).rstrip(); + +def is_class_name_specialization(name): + return strip_class_name_specialization != name; + +class GaspEncoder(json.JSONEncoder): + def default(self, o): + if "gasp_to_json" in dir(o): + return o.gasp_to_json(); + return json.JSONEncoder.default(self,o); + +def convert_doxy_xml_type_to_type_tuple(type_tag): + type_tuple = []; + for ele in type_tag.iter(): + if ele.tag == "type": + if ele.text: + type_tuple.append(ele.text.strip()); + elif ele.tag == "ref": + refid = ele.attrib["refid"]; + refname = ele.text; + type_tuple.append(GaspTypeRefDescription( + refname, + refid + )); + if ele.tail: + type_tuple.append(ele.tail.strip()); + return type_tuple; + +def convert_doxy_xml_section_to_attribs(member_section, members_attrib): + for memberdef in member_section: + type_name = convert_doxy_xml_type_to_type_tuple(memberdef.find('type')); + member_id = memberdef.attrib["id"]; + name = memberdef.find('name').text; + mem_brief_desc = memberdef.find('briefdescription').text; + mem_detail_desc = []; + for para in memberdef.find('detaileddescription').findall('para'): + mem_detail_desc.append(para.text); + #endfor + + members_attrib[member_id]._type_name = type_name; + members_attrib[member_id]._brief_description = mem_brief_desc; + members_attrib[member_id]._detailed_description = mem_detail_desc; + member_static = False; + if memberdef.attrib['static'] == "yes": + member_static = True; + #endif + members_attrib[member_id]._static = member_static; + member_init = memberdef.find('initializer'); + if member_init is not None: + init_text = member_init.text.strip(); + if init_text.startswith('='): + init_text = init_text[1:].lstrip(); + #endif + members_attrib[member_id]._initializer = init_text; + #endif + #endfor + + pass +def convert_doxy_xml_section_to_functions(member_section, members_func): + for memberdef in member_section: + type_name = convert_doxy_xml_type_to_type_tuple(memberdef.find('type')); + member_id = memberdef.attrib["id"]; + + mem_brief_desc = memberdef.find('briefdescription').text; + mem_detail_desc = []; + for para in memberdef.find('detaileddescription').findall('para'): + mem_detail_desc.append(para.text); + #endfor + params = []; + for par in memberdef.findall('param'): + declname = par.find('declname'); + declname_txt = ""; + if declname is not None: + declname_txt = declname.text; + params.append({ + "type" : convert_doxy_xml_type_to_type_tuple(par.find('type')), + "name" : declname_txt + }); + #endfor + + members_func[member_id]._type_name = type_name; + members_func[member_id]._brief_description = mem_brief_desc; + members_func[member_id]._detailed_description = mem_detail_desc; + members_func[member_id]._params = params; + member_static = False; + if memberdef.attrib['static'] == "yes": + member_static = True; + #endif + members_func[member_id]._static = member_static; + #endfor + pass + +def convert_doxy_xml_to_class(class_tree, xml_text, namespace): + doxy_root = ET.fromstring(xml_text); + + compound_class = doxy_root.find("compounddef"); + + # Find the class id ( used for files/html/etc ) + compound_id = compound_class.attrib['id']; + + # Find the class descriptions + class_brief_desc = compound_class.find("briefdescription"); + class_tree._brief_description = class_brief_desc.text; + for para in compound_class.find('detaileddescription').findall('para'): + class_tree._detailed_description.append(para.text); + + for section in compound_class.findall('sectiondef'): + section_kind = section.attrib['kind']; + if section_kind == 'private-attrib' or section_kind == 'public-static-attrib' or section_kind == 'public-attrib' or section_kind == 'private-static-attrib': + convert_doxy_xml_section_to_attribs(section.findall('memberdef'), class_tree._attributes); + elif section.attrib['kind'] == 'private-func': + convert_doxy_xml_section_to_functions(section.findall('memberdef'), class_tree._functions); + elif section.attrib['kind'] == 'public-func': + convert_doxy_xml_section_to_functions(section.findall('memberdef'), class_tree._functions); + + pass + +def convert_doxy_file_to_file(doxy_tree, xml_text, namespace): + doxy_file_root = ET.fromstring(xml_text); + + compound_file = doxy_file_root.find("compounddef"); + + compound_id = compound_file.attrib['id']; + + for section in compound_file.findall('sectiondef'): + section_kind = section.attrib['kind']; + if section_kind == "var": + convert_doxy_xml_section_to_attribs(section.findall('memberdef'), doxy_tree["attributes"]); + elif section_kind == "func": + convert_doxy_xml_section_to_functions(section.findall('memberdef'), doxy_tree["functions"]); + #endif + pass + +def convert_doxy_namespace_to_namespace(doxy_tree, xml_text, namespace): + doxy_file_root = ET.fromstring(xml_text); + + compound_file = doxy_file_root.find("compounddef"); + + compound_id = compound_file.attrib['id']; + + for section in compound_file.findall('sectiondef'): + section_kind = section.attrib['kind']; + if section_kind == "var": + convert_doxy_xml_section_to_attribs(section.findall('memberdef'), doxy_tree["attributes"]); + elif section_kind == "func": + convert_doxy_xml_section_to_functions(section.findall('memberdef'), doxy_tree["functions"]); + #endif + pass + +def convert_doxy_index_xml_to_index(xml_text, doxy_tree, namespace): + doxy_index_root = ET.fromstring(xml_text); + for compound in doxy_index_root.findall('compound'): + compound_kind = compound.attrib['kind']; + compound_id = compound.attrib['refid']; + compound_name = compound.find('name').text; + if compound_kind == 'class' or compound_kind == 'struct': + attribs = {}; + funcs = {}; + + for member in compound.findall('member'): + member_kind = member.attrib['kind']; + member_id = member.attrib['refid']; + member_name = member.find('name').text; + if member_kind == 'function': + gasp_func = GaspFunctionDescription( + member_id, + member_name + ); + funcs[member_id] = gasp_func; + elif member_kind == 'variable': + gasp_attrib = GaspAttributeDescription( + member_id, + member_name + ); + attribs[member_id] = gasp_attrib; + #endif + #endfor + # Strip the namespaced class name with the provided prefix + class_name = compound_name; + if class_name.startswith(namespace): + class_name = class_name[len(namespace):]; + + gasp_class = GaspClassDescription( + compound_id, + class_name, + attribs, + funcs + ); + doxy_tree['classes'][compound_id] = gasp_class; + #endif + elif compound_kind == 'namespace' and compound_name.startswith(doxy_tree['namespace_filter']): + attribs = []; + funcs = []; + + for member in compound.findall('member'): + member_kind = member.attrib['kind']; + member_id = member.attrib['refid']; + member_name = member.find('name').text; + + if member_kind == 'function': + gasp_func = GaspFunctionDescription( + member_id, + member_name + ); + funcs.append(member_id); + doxy_tree['functions'][member_id] = gasp_func; + elif member_kind == 'variable': + gasp_attrib = GaspAttributeDescription( + member_id, + member_name + ); + attribs.append(member_id); + doxy_tree['attributes'][member_id] = gasp_attrib; + #endif + #endfor + gasp_namespace = GaspNamespaceDescription( + compound_id, + compound_name, + attribs, + funcs + ); + doxy_tree['namespaces'][compound_id] = gasp_namespace; + elif compound_kind == 'file' and len(doxy_tree['namespace_filter']) == 0: + attribs = []; + funcs = []; + + for member in compound.findall('member'): + member_kind = member.attrib['kind']; + member_id = member.attrib['refid']; + member_name = member.find('name').text; + + if member_kind == 'function': + gasp_func = GaspFunctionDescription( + member_id, + member_name + ); + funcs.append(member_id); + doxy_tree['functions'][member_id] = gasp_func; + elif member_kind == 'variable': + gasp_attrib = GaspAttributeDescription( + member_id, + member_name + ); + attribs.append(member_id); + doxy_tree['attributes'][member_id] = gasp_attrib; + #endif + #endfor + gasp_file = GaspFileDescription( + compound_id, + compound_name, + attribs, + funcs + ); + doxy_tree['files'][compound_id] = gasp_file; + #endif + #endfor + pass + +def main(): + parser = argparse.ArgumentParser( + prog='gasp', + description='Converts Doxygen XML to a JSON usable by jinja2 templates' + ); + + parser.add_argument('xml_dir'); + parser.add_argument( + '-n','--namespace', required=False, + help='Strips the namespace from the class names', + default="" + ); + + args = parser.parse_args(); + + namespace = args.namespace + "::"; + if len(args.namespace) == 0: + namespace = ""; + + xml_dir = Path(args.xml_dir); + if xml_dir.is_file(): + print("XML dir path is not a dir"); + exit(-1); + #endif + + doxy_tree = { + "namespace_filter" : args.namespace, + "classes" : {}, + "functions" : {}, + "attributes" : {}, + "namespaces" : {}, + "files" : {} + }; + + index_path = xml_dir/"index.xml"; + if index_path.is_file(): + index_xml_file = open(index_path, "r"); + xml_index_text = index_xml_file.read(); + convert_doxy_index_xml_to_index(xml_index_text, doxy_tree, namespace); + else: + print("XML Index file doesn't exist"); + exit(-1); + #endif + + for cls_key,cls in doxy_tree['classes'].items(): + cls_file_name = cls._id + ".xml"; + p = xml_dir/cls_file_name; + if p.is_file(): + cls_xml_file = open(p, "r"); + cls_xml_text = cls_xml_file.read(); + convert_doxy_xml_to_class(cls,cls_xml_text,namespace); + else: + print("Class file is missing"); + exit(-1); + #endif + #endfor + + for key,cls in doxy_tree["classes"].items(): + stripped_cls_name = strip_class_name_specialization(cls._name); + if stripped_cls_name != cls._name: + cls._is_special = True; + stripped_ids = []; + for k,v in doxy_tree["classes"].items(): + if v._name == stripped_cls_name: + stripped_ids.append(k); + #endif + #endfor + if len(stripped_ids) != 1: + print("Panic. This is supposed to be a unique name which exists exactly once. Name: " + stripped_cls_name + "\nMatching IDs: " + json.dumps(stripped_ids, indent=2)); + exit(-1); + #endif + doxy_tree["classes"][stripped_ids[0]].append_specialization(cls._name, key); + #endif + #endfor + + for key, ns in doxy_tree["namespaces"].items(): + ns_file_name = ns._id + ".xml"; + p = xml_dir/ns_file_name; + if p.is_file(): + ns_xml_file = open(p, "r"); + ns_xml_text = ns_xml_file.read(); + convert_doxy_namespace_to_namespace(doxy_tree, ns_xml_text, namespace); + else: + print("Namespace file is missing"); + exit(-1); + #endfor + + for key,file in doxy_tree["files"].items(): + f_file_name = file._id + ".xml"; + p = xml_dir/f_file_name; + if p.is_file(): + f_xml_file = open(p, "r"); + f_xml_text = f_xml_file.read(); + convert_doxy_file_to_file(doxy_tree, f_xml_text, namespace); + #endfor + + print(json.dumps(doxy_tree,indent=2,cls=GaspEncoder)); + pass + +if __name__ == "__main__": + main(); +#endif diff --git a/docs/scripts/make_rst.py b/docs/scripts/make_rst.py new file mode 100644 index 0000000..78126b4 --- /dev/null +++ b/docs/scripts/make_rst.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 +import argparse +import json +import jinja2 +from pathlib import Path + + +def parse_args(): + parser = argparse.ArgumentParser( + description="Generates source files based on a jinja2 template " + "and a json variable map") + parser.add_argument( + '-t', '--template', required=True, + help='path to the jinja2 template dir') + parser.add_argument( + '-m', '--map', required=True, + help='path to the json variable map file') + parser.add_argument( + '-o', '--output', required=True, + help='path to the output dir') + parser.add_argument( + '--title', default="C++ API Reference", + help='The title of the index for the API' + ); + + return parser.parse_args() + + +def read_template(path): + with open(path, "r") as f: + return jinja2.Template(f.read(), keep_trailing_newline=True) + + +def read_var_map(path): + with open(path, "r") as f: + return json.loads(f.read()); + #endwith + + +def main(): + args = parse_args() + + template_dir = Path(args.template); + template = read_template(template_dir/"class.rst.tmpl"); + var_map = read_var_map(args.map); + + var_map['title'] = args.title; + + out_dir = Path(args.output); + for k,v in var_map["classes"].items(): + out_name = k+".rst"; + out_file = out_dir / out_name; + v["specializations"] = sorted(v["specializations"], key=lambda k: k["name"]); + with open(out_file, "w") as f: + f.write(template.render(v)); + #endwith + #endfor + + out_globs = out_dir / "globals.rst"; + template_globs = read_template(template_dir/"globals.rst.tmpl"); + with open(out_globs, "w") as f: + f.write(template_globs.render(var_map)); + #endwith + + out_index = out_dir / "index.rst"; + template_index = read_template(template_dir/"index.rst.tmpl"); + var_map["classes"] = dict(sorted(var_map["classes"].items(), key=lambda k: k[1]["name"])); + with open(out_index, "w") as f: + f.write(template_index.render(var_map)); + pass + +if __name__ == "__main__": + main(); -- cgit v1.2.3