From 8a5f20121ea1fc5375fab0ba777d36a5605e3763 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0pa=C4=8Dek?= Date: Mon, 4 Jul 2022 11:01:17 +0200 Subject: [PATCH 1/3] Optimize resolve_xref to avoid O(n^2) iteration Formerly resolve_xref() in Sphinx extension called get_objects() from Sphinx API which subsequently iterated the whole list of objects, causing single iteration per single reference, which is essentially. O(n^2). Avoid using get_objects() and access internal dictionary directly intead. The docs build time was still dominated by other factors but speedup is about 10 % on my machine. --- doc/arm/_ext/iscconf.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/doc/arm/_ext/iscconf.py b/doc/arm/_ext/iscconf.py index 4380a8c4c1..53eb2f946a 100644 --- a/doc/arm/_ext/iscconf.py +++ b/doc/arm/_ext/iscconf.py @@ -305,19 +305,18 @@ def domain_factory(domainname, domainlabel, todolist, grammar): Sphinx API: Resolve the pending_xref *node* with the given typ and target. """ - match = [ - (docname, anchor) - for name, sig, typ, docname, anchor, _prio in self.get_objects() - if sig == target - ] - - if len(match) == 0: + try: + obj = self.data["statements"][self.get_statement_name(target)] + except KeyError: return None - todocname = match[0][0] - targ = match[0][1] refnode = make_refnode( - builder, fromdocname, todocname, targ, contnode, targ + builder, + fromdocname, + obj["docname"], + obj["anchor"], + contnode, + obj["anchor"], ) return refnode From b109c8680594f0796ec2726ab9de5ef53b2a9bd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0pa=C4=8Dek?= Date: Mon, 4 Jul 2022 11:30:33 +0200 Subject: [PATCH 2/3] Generate tables of statements in doctree-read phase This change allows us to generate "unresolved" references and let Sphinx deal with dereferencing them in later stages. It is not useful by itself but it serves as preparation for the next commit. --- doc/arm/_ext/iscconf.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/doc/arm/_ext/iscconf.py b/doc/arm/_ext/iscconf.py index 53eb2f946a..ebbfa28fe4 100644 --- a/doc/arm/_ext/iscconf.py +++ b/doc/arm/_ext/iscconf.py @@ -429,7 +429,7 @@ def domain_factory(domainname, domainlabel, todolist, grammar): ) @classmethod - def process_statementlist_nodes(cls, app, doctree, fromdocname): + def process_statementlist_nodes(cls, app, doctree): """ Replace todolist objects (placed into document using .. statementlist::) with automatically generated table @@ -464,7 +464,7 @@ def domain_factory(domainname, domainlabel, todolist, grammar): ) ) ), - iscconf.list_all(fromdocname), + iscconf.list_all(), ), key=lambda x: x["fullname"], ) @@ -481,24 +481,24 @@ def domain_factory(domainname, domainlabel, todolist, grammar): gen_replacement_table(acceptable_blocks, acceptable_tags) ) - def list_all(self, fromdocname): + def list_all(self): for statement in self.data["statements"].values(): + sig = statement["signature"] block_names = set( - path[-1] - for path in self.statement_blocks.get(statement["signature"], []) + path[-1] for path in self.statement_blocks.get(sig, []) ) tags_txt = ", ".join(statement["tags"]) refpara = nodes.inline() - refpara += self.resolve_xref( - self.env, - fromdocname, - self.env.app.builder, - None, - statement["signature"], - None, - nodes.Text(statement["signature"]), + refnode = addnodes.pending_xref( + sig, + reftype="statement", + refdomain=domainname, + reftarget=sig, + refwarn=True, ) + refnode += nodes.Text(sig) + refpara += refnode copy = statement.copy() copy["block_names"] = block_names @@ -574,7 +574,7 @@ def setup(app, domainname, confname, docutilsplaceholder, grammar): Conf = domain_factory(domainname, confname, docutilsplaceholder, grammar) app.add_domain(Conf) - app.connect("doctree-resolved", Conf.process_statementlist_nodes) + app.connect("doctree-read", Conf.process_statementlist_nodes) return { "version": "0.1", From 62f3cf98d37ab629c38333c5fb9313872ac90cf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20=C5=A0pa=C4=8Dek?= Date: Mon, 4 Jul 2022 12:33:04 +0200 Subject: [PATCH 3/3] Parse and render rst syntax in :short: statement descriptions in tables Without this change tables generated by .. namedconf:statementlist:: contained raw text and displayed rst syntax to users. The raw docutil node returned by rst parser can contain unresolved references (pending_xref nodes). We just store those nodes and let Sphinx to resolve them later on. Beware: This would not work if we injected nodes in later stages of processing. All unresolved references must be in place before 'doctree-resolved' event is emitted (i.e. before resolve_references() is called inside Sphinx). --- doc/arm/_ext/iscconf.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/doc/arm/_ext/iscconf.py b/doc/arm/_ext/iscconf.py index ebbfa28fe4..598fb26aa3 100644 --- a/doc/arm/_ext/iscconf.py +++ b/doc/arm/_ext/iscconf.py @@ -122,7 +122,9 @@ def domain_factory(domainname, domainlabel, todolist, grammar): signode["ids"].append(domainname + "-statement-" + sig) iscconf = self.env.get_domain(domainname) - iscconf.add_statement(sig, self.isc_tags, self.isc_short, self.lineno) + iscconf.add_statement( + sig, self.isc_tags, self.isc_short, self.isc_short_node, self.lineno + ) @property def isc_tags(self): @@ -132,6 +134,11 @@ def domain_factory(domainname, domainlabel, todolist, grammar): def isc_short(self): return self.options.get("short", "") + @property + def isc_short_node(self): + """Short description parsed from rst to docutils node""" + return self.parse_nested_str(self.isc_short) + def format_path(self, path): assert path[0] == "_top" if len(path) == 1: @@ -223,8 +230,7 @@ def domain_factory(domainname, domainlabel, todolist, grammar): def transform_content(self, contentnode: addnodes.desc_content) -> None: """autogenerate content from structured data""" if self.isc_short: - short_parsed = self.parse_nested_str(self.isc_short) - contentnode.insert(0, short_parsed) + contentnode.insert(0, self.isc_short_node) if self.isc_tags: tags = nodes.paragraph() tags += nodes.strong(text="Tags: ") @@ -341,7 +347,7 @@ def domain_factory(domainname, domainlabel, todolist, grammar): def get_statement_name(self, signature): return "{}.{}.{}".format(domainname, "statement", signature) - def add_statement(self, signature, tags, short, lineno): + def add_statement(self, signature, tags, short, short_node, lineno): """ Add a new statement to the domain data structures. No visible effect. @@ -352,6 +358,7 @@ def domain_factory(domainname, domainlabel, todolist, grammar): new = { "tags": tags, "short": short, + "short_node": short_node, "filename": self.env.doc2path(self.env.docname), "lineno": lineno, # Sphinx API @@ -439,7 +446,7 @@ def domain_factory(domainname, domainlabel, todolist, grammar): def gen_replacement_table(acceptable_blocks, acceptable_tags): table_header = [ TableColumn("ref", "Statement"), - TableColumn("short", "Description"), + TableColumn("short_node", "Description"), ] tag_header = [] if len(acceptable_tags) != 1: @@ -556,6 +563,8 @@ class DictToDocutilsTableBuilder: value = obj[column.dictkey] if isinstance(value, str): value = nodes.Text(value) + else: + value = value.deepcopy() entry += value row += entry self.tbody.append(row)