""" I am so sorry, but i found this extension somewhere in the internet. IT LOOKS REALLY BEAUTIFUL! """ from sphinx.util.docutils import SphinxDirective from sphinx.locale import _ from docutils import nodes from sphinx import addnodes from collections import OrderedDict, namedtuple import importlib import inspect import re class attributetable(nodes.General, nodes.Element): pass class attributetablecolumn(nodes.General, nodes.Element): pass class attributetabletitle(nodes.TextElement): pass class attributetableplaceholder(nodes.General, nodes.Element): pass class attributetablebadge(nodes.TextElement): pass class attributetable_item(nodes.Part, nodes.Element): pass def visit_attributetable_node(self, node): class_ = node["python-class"] self.body.append(f'
') def visit_attributetablecolumn_node(self, node): self.body.append(self.starttag(node, "div", CLASS="py-attribute-table-column")) def visit_attributetabletitle_node(self, node): self.body.append(self.starttag(node, "span")) def visit_attributetablebadge_node(self, node): attributes = { "class": "py-attribute-table-badge", "title": node["badge-type"], } self.body.append(self.starttag(node, "span", **attributes)) def visit_attributetable_item_node(self, node): self.body.append(self.starttag(node, "li", CLASS="py-attribute-table-entry")) def depart_attributetable_node(self, node): self.body.append("
") def depart_attributetablecolumn_node(self, node): self.body.append("") def depart_attributetabletitle_node(self, node): self.body.append("") def depart_attributetablebadge_node(self, node): self.body.append("") def depart_attributetable_item_node(self, node): self.body.append("") _name_parser_regex = re.compile(r"(?P[\w.]+\.)?(?P\w+)") class PyAttributeTable(SphinxDirective): has_content = False required_arguments = 1 optional_arguments = 0 final_argument_whitespace = False option_spec = {} def parse_name(self, content): path, name = _name_parser_regex.match(content).groups() if path: modulename = path.rstrip(".") else: modulename = self.env.temp_data.get("autodoc:module") if not modulename: modulename = self.env.ref_context.get("py:module") if modulename is None: raise RuntimeError( "modulename somehow None for %s in %s." % (content, self.env.docname) ) return modulename, name def run(self): """If you're curious on the HTML this is meant to generate: However, since this requires the tree to be complete and parsed, it'll need to be done at a different stage and then replaced. """ content = self.arguments[0].strip() node = attributetableplaceholder("") modulename, name = self.parse_name(content) node["python-doc"] = self.env.docname node["python-module"] = modulename node["python-class"] = name node["python-full-name"] = f"{modulename}.{name}" return [node] def build_lookup_table(env): # Given an environment, load up a lookup table of # full-class-name: objects result = {} domain = env.domains["py"] ignored = { "data", "exception", "module", "class", } for (fullname, _, objtype, docname, _, _) in domain.get_objects(): if objtype in ignored: continue classname, _, child = fullname.rpartition(".") try: result[classname].append(child) except KeyError: result[classname] = [child] return result TableElement = namedtuple("TableElement", "fullname label badge") def process_attributetable(app, doctree, fromdocname): env = app.builder.env lookup = build_lookup_table(env) for node in doctree.traverse(attributetableplaceholder): modulename, classname, fullname = ( node["python-module"], node["python-class"], node["python-full-name"], ) groups = get_class_results(lookup, modulename, classname, fullname) table = attributetable("") for label, subitems in groups.items(): if not subitems: continue table.append( class_results_to_node(label, sorted(subitems, key=lambda c: c.label)) ) table["python-class"] = fullname node.replace_self([table] if table else []) def get_class_results(lookup, modulename, name, fullname): module = importlib.import_module(modulename) cls = getattr(module, name) groups = OrderedDict( [ (_("Attributes"), []), (_("Methods"), []), ] ) try: members = lookup[fullname] except KeyError: return groups for attr in members: attrlookup = f"{fullname}.{attr}" key = _("Attributes") badge = None label = attr value = None for base in cls.__mro__: value = base.__dict__.get(attr) if value is not None: break if value is not None: doc = value.__doc__ or "" if inspect.iscoroutinefunction(value) or doc.startswith("|coro|"): key = _("Methods") badge = attributetablebadge("async", "async") badge["badge-type"] = _("coroutine") elif isinstance(value, classmethod): key = _("Methods") label = f"{name}.{attr}" badge = attributetablebadge("cls", "cls") badge["badge-type"] = _("classmethod") elif inspect.isfunction(value) or isinstance(value, staticmethod): if doc.startswith(("A decorator", "A shortcut decorator")) or label in ( "event", "loop", ): # finicky but surprisingly consistent badge = attributetablebadge("@", "@") badge["badge-type"] = _("decorator") key = _("Methods") else: key = _("Methods") badge = attributetablebadge("def", "def") badge["badge-type"] = _("method") groups[key].append(TableElement(fullname=attrlookup, label=label, badge=badge)) return groups def class_results_to_node(key, elements): title = attributetabletitle(key, key) ul = nodes.bullet_list("") for element in elements: ref = nodes.reference( "", "", internal=True, refuri="#" + element.fullname, anchorname="", *[nodes.Text(element.label)], ) para = addnodes.compact_paragraph("", "", ref) if element.badge is not None: ul.append(attributetable_item("", element.badge, para)) else: ul.append(attributetable_item("", para)) return attributetablecolumn("", title, ul) def setup(app): app.add_directive("attributetable", PyAttributeTable) app.add_node( attributetable, html=(visit_attributetable_node, depart_attributetable_node) ) app.add_node( attributetablecolumn, html=(visit_attributetablecolumn_node, depart_attributetablecolumn_node), ) app.add_node( attributetabletitle, html=(visit_attributetabletitle_node, depart_attributetabletitle_node), ) app.add_node( attributetablebadge, html=(visit_attributetablebadge_node, depart_attributetablebadge_node), ) app.add_node( attributetable_item, html=(visit_attributetable_item_node, depart_attributetable_item_node), ) app.add_node(attributetableplaceholder) app.connect("doctree-resolved", process_attributetable)