|
| 1 | +import functools |
| 2 | +import os |
| 3 | +from collections import defaultdict |
| 4 | + |
| 5 | + |
| 6 | +def parse_index_xml(): |
| 7 | + root = etree.parse(os.path.join(DOXYGEN_XML_DIR, 'index.xml')) |
| 8 | + compounds = defaultdict(dict) |
| 9 | + for compound in root.iter('compound'): |
| 10 | + kind = compound.attrib['kind'] |
| 11 | + name = compound.find('name').text.strip() |
| 12 | + if name in compounds[kind]: |
| 13 | + print("duplicate compound", kind, name) |
| 14 | + continue |
| 15 | + compound_data = compounds[kind][name] = { |
| 16 | + "kind": kind, |
| 17 | + "name": name, |
| 18 | + "refid": compound.attrib['refid'], |
| 19 | + "members": defaultdict(dict) |
| 20 | + } |
| 21 | + |
| 22 | + for member in compound.iter('member'): |
| 23 | + kind = member.attrib['kind'] |
| 24 | + name = member.find('name').text.strip() |
| 25 | + refid = member.attrib['refid'] |
| 26 | + if refid in compound_data["members"][name]: |
| 27 | + print("duplicate member", kind, name, refid) |
| 28 | + continue |
| 29 | + compound_data["members"][name][refid] = { |
| 30 | + "kind": kind, |
| 31 | + "name": name, |
| 32 | + "refid": refid |
| 33 | + } |
| 34 | + |
| 35 | + return compounds |
| 36 | + |
| 37 | + |
| 38 | +def pythonize_docstrings(klass, name): |
| 39 | + data = DOXYGEN_DATA["class"][klass.__cpp_name__] |
| 40 | + klass.__doc__ += "\n" + DOXYGEN_URL % (data["refid"], "") |
| 41 | + for mem, val in klass.__dict__.items(): |
| 42 | + if mem not in data["members"]: |
| 43 | + print(klass.__cpp_name__, "has no member", mem) |
| 44 | + continue |
| 45 | + try: |
| 46 | + for override in data["members"][mem].values(): |
| 47 | + val.__doc__ += "\n" + DOXYGEN_URL % (data["refid"], override["refid"][len(data["refid"]) + 2:]) |
| 48 | + except: |
| 49 | + import traceback # TODO remove once we can overwrite the __doc__ of CPPOverload etc. |
| 50 | + traceback.print_exc() |
| 51 | + print(val.__doc__) |
| 52 | + pass |
| 53 | + |
| 54 | + |
| 55 | +def find_all_includes(): |
| 56 | + for ctype in ("class", "struct", "namespace"): |
| 57 | + for compound in DOXYGEN_DATA[ctype].values(): |
| 58 | + compound_xml = etree.parse(os.path.join(DOXYGEN_XML_DIR, compound["refid"] + '.xml')) |
| 59 | + for location in compound_xml.findall(".//*[@id]/location"): |
| 60 | + parent = location.getparent() |
| 61 | + if parent.get("id") == compound["refid"]: |
| 62 | + member = compound |
| 63 | + else: |
| 64 | + name = parent.find("name") |
| 65 | + if name.text not in compound["members"]: |
| 66 | + print("got location for unknown object", compound["refid"], parent.get("id"), name.text) |
| 67 | + continue |
| 68 | + else: |
| 69 | + member = compound["members"][name.text][parent.get("id")] |
| 70 | + member["file"] = location.get("declfile", location.get("file")) |
| 71 | + |
| 72 | + |
| 73 | +def find_include(*names): |
| 74 | + if len(names) == 1: |
| 75 | + if isinstance(names[0], str): |
| 76 | + names = names[0].split("::") |
| 77 | + else: |
| 78 | + names = names[0] |
| 79 | + |
| 80 | + name = names[-1] |
| 81 | + qualname = "::".join(names) |
| 82 | + parentname = "::".join(names[:-1]) |
| 83 | + |
| 84 | + data = DOXYGEN_DATA["class"].get(qualname, None) |
| 85 | + filename = None |
| 86 | + if not data: |
| 87 | + data = DOXYGEN_DATA["struct"].get(qualname, None) |
| 88 | + if not data: |
| 89 | + namespace_data = DOXYGEN_DATA["namespace"].get(parentname, None) |
| 90 | + if namespace_data and name in namespace_data["members"]: |
| 91 | + data = next(iter(namespace_data["members"][name].values())) |
| 92 | + filename = namespace_data["refid"] |
| 93 | + if not data: |
| 94 | + return None |
| 95 | + |
| 96 | + if "file" in data: |
| 97 | + return data["file"] |
| 98 | + |
| 99 | + if not filename: |
| 100 | + filename = data["refid"] |
| 101 | + namespace_xml = etree.parse(os.path.join(DOXYGEN_XML_DIR, filename + '.xml')) |
| 102 | + location = namespace_xml.find(".//*[@id='%s']/location" % data["refid"]) |
| 103 | + return location.get("declfile", location.get("file")) |
| 104 | + |
| 105 | + |
| 106 | +def wrap_getattribute(ns): |
| 107 | + getattrib = type(ns).__getattribute__ |
| 108 | + if hasattr(getattrib, "__wrapped__"): return |
| 109 | + |
| 110 | + @functools.wraps(getattrib) |
| 111 | + def helpful_getattribute(ns, name): |
| 112 | + try: |
| 113 | + val = getattrib(ns, name) |
| 114 | + if isinstance(type(val), type(type(ns))): |
| 115 | + wrap_getattribute(val) |
| 116 | + return val |
| 117 | + except AttributeError as e: |
| 118 | + if hasattr(e, "__helpful__"): |
| 119 | + raise e |
| 120 | + msg = e.args[0] |
| 121 | + file = find_include(*ns.__cpp_name__.split("::"), name) |
| 122 | + if file: |
| 123 | + prefix = "include/" |
| 124 | + msg += "\nDid you forget to include file %s? Try running\ncppinclude(\"%s\")" % \ |
| 125 | + (file, file[len(prefix):] if file.startswith(prefix) else file) |
| 126 | + else: |
| 127 | + msg += "\nThe name %s::%s couldn't be found in the docs." % (ns.__cpp_name__, name) |
| 128 | + e.args = (msg, *e.args[1:]) |
| 129 | + e.__helpful__ = True |
| 130 | + raise e |
| 131 | + |
| 132 | + type(ns).__getattribute__ = helpful_getattribute |
| 133 | + |
| 134 | + |
| 135 | +if "OGDF_DOC_DIR" in os.environ: |
| 136 | + from lxml import etree |
| 137 | + |
| 138 | + DOXYGEN_XML_DIR = os.path.join(os.environ["OGDF_DOC_DIR"], "xml") |
| 139 | + DOXYGEN_DATA = parse_index_xml() |
| 140 | + |
| 141 | + if __name__ == "__main__": |
| 142 | + import json, sys |
| 143 | + |
| 144 | + find_all_includes() |
| 145 | + with open("doxygen.json", "wt") as f: |
| 146 | + json.dump(DOXYGEN_DATA, f) |
| 147 | + sys.exit(0) |
| 148 | + |
| 149 | +else: |
| 150 | + import json |
| 151 | + |
| 152 | + DOXYGEN_XML_DIR = None |
| 153 | + |
| 154 | + try: |
| 155 | + with open("doxygen.json", "rt") as f: |
| 156 | + DOXYGEN_DATA = json.load(f) |
| 157 | + except FileNotFoundError: |
| 158 | + import importlib_resources |
| 159 | + |
| 160 | + with importlib_resources.files(__name__).joinpath("doxygen.json").open("rt") as f: |
| 161 | + DOXYGEN_DATA = json.load(f) |
| 162 | + |
| 163 | +if "OGDF_DOC_URL" in os.environ: |
| 164 | + DOXYGEN_URL = os.environ["OGDF_DOC_URL"] |
| 165 | +else: |
| 166 | + DOXYGEN_URL = "https://ogdf.github.io/doc/ogdf/%s.html#%s" |
| 167 | + |
| 168 | +import cppyy |
| 169 | + |
| 170 | +cppyy.py.add_pythonization(pythonize_docstrings, "ogdf") |
| 171 | +wrap_getattribute(cppyy.gbl.ogdf) |
0 commit comments