From 97e16521c18921b1acd78f92b10659e62e379a7c Mon Sep 17 00:00:00 2001 From: Paul Mucur Date: Wed, 21 Aug 2024 09:05:50 +0100 Subject: [PATCH 1/6] Remove redundant stripping of whitespace Comparing the resulting AsciiDoc with and without this extra stripping of trailing whitespace on the current Raspberry Pi Pico C/C++ SDK databook results in no change in output. --- nodes.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nodes.py b/nodes.py index 74f3606..7182ccb 100644 --- a/nodes.py +++ b/nodes.py @@ -762,7 +762,7 @@ def to_asciidoc(self, **kwargs): class ComputeroutputNode(Node): def to_asciidoc(self, **kwargs): - return "".join(("`", super().to_asciidoc(**kwargs).rstrip(), "`")) + return "".join(("`", super().to_asciidoc(**kwargs), "`")) class ItemizedlistNode(Node): @@ -858,8 +858,6 @@ def to_asciidoc(self, depth=0, **kwargs): if suffix: definition.append(" ") definition.append(", ".join(suffix)) - # make sure there are no trailing spaces - definition[-1] = definition[-1].rstrip() definition.append("`") output.append("".join(definition)) briefdescription = self.child("briefdescription").to_asciidoc( From 56ee954468864fb781d6f9fa3b686fdcce6bfdad Mon Sep 17 00:00:00 2001 From: Paul Mucur Date: Wed, 21 Aug 2024 10:07:58 +0100 Subject: [PATCH 2/6] Expand usage of argparse to handle CLI options Rather than using optional string arguments for both input and output files, use more of Python's argparse library to handle a single, mandatory, positional file argument (which now returns a handle to read the file). This is a breaking change as the "-f" argument is now a positional one so any downstream usage will need to be updated. Note we don't use `argparse.FileType("w")` for the output file as that would cause the given file to be created or truncated as soon as the program runs when we only want it to produce output if successful. --- README.md | 21 ++++++++++++--------- cli.py | 49 +++++++++++++++++++++++++------------------------ 2 files changed, 37 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 7a30882..aa2b710 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,19 @@ -# doxygentoasciidoc: A Doxygen > Asciidoc Converter +# doxygentoasciidoc: A Doxygen to AsciiDoc Converter -This project converts doxygen XML output to asciidoc. - -Allowed args: - -`-f`: the full path to the file to be converted +``` +usage: doxygentoasciidoc [-h] [-o OUTPUT] [-c] file -`-o`: the full path the the output file (will print to STDOUT if not specified) +Convert Doxygen XML to AsciiDoc -`-c`: process a node other than `doxygenindex` +positional arguments: + file The path of the Doxygen XML file to convert -The following attributes from the XML will be preserved in the generated asciidoc: role, tag, type. +optional arguments: + -h, --help show this help message and exit + -o OUTPUT, --output OUTPUT + Write to file instead of stdout + -c, --child Is NOT the root index file +``` ## Development diff --git a/cli.py b/cli.py index 4ff4a42..48cd21a 100644 --- a/cli.py +++ b/cli.py @@ -1,5 +1,4 @@ import os -import sys import argparse from bs4 import BeautifulSoup @@ -8,40 +7,42 @@ def main(): """Convert the given Doxygen index.xml to AsciiDoc and output the result.""" - parser = argparse.ArgumentParser() + parser = argparse.ArgumentParser( + prog="doxygentoasciidoc", description="Convert Doxygen XML to AsciiDoc" + ) parser.add_argument( - "-f", "--file", help="The path of the file to convert", default=None + "file", + type=argparse.FileType("r", encoding="utf-8"), + help="The path of the Doxygen XML file to convert", ) parser.add_argument( - "-o", "--output", help="The path of the output file", default=None + "-o", + "--output", + help="Write to file instead of stdout", ) parser.add_argument( "-c", "--child", help="Is NOT the root index file", - default=False, action="store_true", ) + args = parser.parse_args() - filename = args.file - output_filename = args.output - is_child = args.child - if filename: - xmldir = os.path.dirname(filename) - with open(filename, encoding="utf-8") as xml: - if is_child: - result = Node( - BeautifulSoup(xml, "xml").doxygen, xmldir=xmldir - ).to_asciidoc(depth=1) - else: - result = DoxygenindexNode( - BeautifulSoup(xml, "xml").doxygenindex, xmldir=xmldir - ).to_asciidoc(depth=2) - if output_filename is not None: - with open(output_filename, "w", encoding="utf-8") as output: + + with args.file as file: + xmldir = os.path.dirname(file.name) + + if args.child: + result = Node( + BeautifulSoup(file, "xml").doxygen, xmldir=xmldir + ).to_asciidoc(depth=1) + else: + result = DoxygenindexNode( + BeautifulSoup(file, "xml").doxygenindex, xmldir=xmldir + ).to_asciidoc(depth=2) + + if args.output: + with open(args.output, "w", encoding="utf-8") as output: output.write(result) else: print(result) - - else: - sys.exit(1) From d354949fea193faa41db9748f465178068e2fd47 Mon Sep 17 00:00:00 2001 From: Paul Mucur Date: Wed, 21 Aug 2024 11:42:15 +0100 Subject: [PATCH 3/6] Refactor attribute handling Rather than passing around an AsciiDoc string of attributes, pass around a dictionary that is only converted to AsciiDoc at the very end when passed to the `title` helper. Note I considered switching to using the role shorthand for multiple roles (e.g. `.h6.contextspecific` rather than `role=h6 contextspecific`) but there is downstream code that relies on matching the existing format. Similarly, the order of the `tag` and `type` attributes is currently relied upon by client code so these have to be special cased for now. This also removes the dedicated Title Node class in favour of handling title rendering in the parent element (in this case, the Sect Node). --- helpers.py | 52 ++++++++++++++++------- nodes.py | 80 ++++++++++++++-------------------- tests/test_helpers.py | 92 ++++++++++++++++++++++++++++++++++++++-- tests/test_node.py | 32 +++----------- tests/test_title_node.py | 20 --------- 5 files changed, 165 insertions(+), 111 deletions(-) delete mode 100644 tests/test_title_node.py diff --git a/helpers.py b/helpers.py index e0ca580..e6f8651 100644 --- a/helpers.py +++ b/helpers.py @@ -19,19 +19,41 @@ def sanitize(identifier): def title(text, level, attributes=None): - """Return text formatted as a title with the given level.""" + """Return text formatted as a title with the given level and attributes.""" + if attributes is None: + attributes = {} + + attrlist = [] + + if "id" in attributes: + attrlist.append(f"#{sanitize(attributes.pop('id'))}") + + roles = [] + if level > 5: - if attributes is not None: - if re.search(r"([,\s]role=)", attributes) is not None: - attributes = re.sub( - r"([,\s]role=)(.*?[,\s]?$)", "\\1h6 \\2", attributes - ) - else: - attributes += ",role=h6" - return f"[{attributes}]\n*{escape_text(text)}*" - return f"[.h6]\n*{escape_text(text)}*" - - marker = "=" * (level + 1) - if attributes is not None: - return f"[{attributes}]\n{marker} {escape_text(text)}" - return f"{marker} {escape_text(text)}" + roles.append("h6") + if "role" in attributes: + roles.append(attributes.pop("role")) + + if roles: + attrlist.append(f"role={' '.join(roles)}") + + if "tag" in attributes: + attrlist.append(f"tag={escape_text(attributes.pop('tag'))}") + if "type" in attributes: + attrlist.append(f"type={escape_text(attributes.pop('type'))}") + for key, value in attributes.items(): + attrlist.append(f'{escape_text(key)}="{escape_text(value)}"') + + output = [] + + if attrlist: + output.append(f"[{','.join(attrlist)}]") + + if level > 5: + output.append(f"*{escape_text(text)}*") + else: + marker = "=" * (level + 1) + output.append(f"{marker} {escape_text(text)}") + + return "\n".join(output) diff --git a/nodes.py b/nodes.py index 7182ccb..8f5c1e3 100644 --- a/nodes.py +++ b/nodes.py @@ -29,7 +29,6 @@ class Node: "sect3", "simplesect", "table", - "title", "verbatim", ) @@ -161,20 +160,13 @@ def children(self, selector=None, **kwargs): for position, child in enumerate(children) ] - def attributes(self, skip=None): - """Return an asciidoc string of any attributes specified, plus the node id.""" - if skip is None: - skip = [] - preserved_attributes = ["role", "tag", "type"] - atts = [] - if self.node.get("id", None) is not None and "id" not in skip: - atts.append(f"#{sanitize(self.node.get('id'))}") - for att in preserved_attributes: - if self.node.get(att) is not None and att not in skip: - atts.append(f"{att}={self.node.get(att)}") - if len(atts) > 0: - return ",".join(atts) - return None + def attributes(self): + """Return the attributes for this node.""" + return { + key: self.node.attrs.get(key) + for key in ("id", "role", "type", "tag") + if key in self.node.attrs + } def descendants(self, selector, **kwargs): """Return a list of descendant Nodes matching the given selector.""" @@ -185,14 +177,6 @@ def descendants(self, selector, **kwargs): ) ] - @staticmethod - def has_page_parent(element): - return any( - parent.get("kind", "") == "page" - for parent in element.parents - if parent.name == "compounddef" - ) - def text(self, selector=None): """Return the stripped text of the given child.""" if not selector: @@ -274,7 +258,6 @@ def nodefor(self, element): "simplesect": SimplesectNode, "sp": SpNode, "table": TableNode, - "title": TitleNode, "type": Node, "ulink": UlinkNode, "verbatim": VerbatimNode, @@ -289,14 +272,16 @@ def to_asciidoc(self, depth=0, **kwargs): output = [] for module in self.rootmodules(): title_ = module.node.text("title") - attributes = [ - f"#{sanitize(module.refid)}", - f'reftext="{escape_text(title_)}"', - ] - if self.attributes(skip=["id"]) is not None: - attributes.append(self.attributes(skip=["id"])) output.append( - title(title_, depth, ",".join(attributes)), + title( + title_, + depth, + attributes={ + **self.attributes(), + "id": module.refid, + "reftext": title_, + }, + ), ) briefdescription = module.node.child("briefdescription").to_asciidoc( **kwargs, depth=depth @@ -433,10 +418,7 @@ def to_asciidoc(self, depth=0, **kwargs): def __output_title(self, depth=0): title_ = self.text("title") - attributes = ["#" + self.id, f'reftext="{escape_text(title_)}"'] - if self.attributes(skip=["id"]) is not None: - attributes.append(self.attributes(skip=["id"])) - return title(title_, depth, ",".join(attributes)) + return title(title_, depth, attributes={**self.attributes(), "reftext": title_}) def __output_briefdescription(self, **kwargs): return self.child("briefdescription").to_asciidoc(**kwargs) @@ -539,9 +521,8 @@ def to_asciidoc(self, depth=0, **kwargs): def __output_title(self, depth=0): title_ = self.text("title") - attributes = ["#" + self.id] if title_: - return title(title_, depth, ",".join(attributes)) + return title(title_, depth, attributes=self.attributes()) return None def __output_detaileddescription(self, **kwargs): @@ -648,13 +629,16 @@ def to_asciidoc(self, **kwargs): class SectNode(Node): def to_asciidoc(self, depth=0, **kwargs): - attributes = f"[{self.attributes()}]" - return "\n".join((attributes, super().to_asciidoc(**kwargs, depth=depth + 1))) + output = [] + title_ = self.text("title") + if title_: + output.append(title(title_, depth + 1, attributes=self.attributes())) -class TitleNode(Node): - def to_asciidoc(self, depth=0, **kwargs): - return title(super().to_asciidoc(**kwargs), depth) + for child in self.children(["para", "sect2", "sect3"]): + output.append(child.to_asciidoc(**kwargs, depth=depth + 1)) + + return "\n\n".join(output) class SimplesectNode(Node): @@ -822,7 +806,7 @@ def to_asciidoc(self, depth=0, **kwargs): title( "Detailed Description", depth, - self.attributes(), + attributes=self.attributes(), ) ) output.append(contents) @@ -832,7 +816,7 @@ def to_asciidoc(self, depth=0, **kwargs): class FunctionMemberdefNode(Node): def to_asciidoc(self, depth=0, **kwargs): - output = [title(self.text("name"), depth, self.attributes())] + output = [title(self.text("name"), depth, attributes=self.attributes())] if self.node["static"] == "yes": definition = ["[.memname]`static "] else: @@ -875,7 +859,7 @@ def to_asciidoc(self, depth=0, **kwargs): class TypedefMemberdefNode(Node): def to_asciidoc(self, depth=0, **kwargs): - output = [title(self.text("name"), depth, self.attributes())] + output = [title(self.text("name"), depth, attributes=self.attributes())] output.append(f"[.memname]`{escape_text(self.text('definition'))}`") briefdescription = self.child("briefdescription").to_asciidoc( **kwargs, depth=depth @@ -893,7 +877,7 @@ def to_asciidoc(self, depth=0, **kwargs): class EnumMemberdefNode(Node): def to_asciidoc(self, depth=0, **kwargs): name = self.text("name") - output = [title(name or "anonymous enum", depth, self.attributes())] + output = [title(name or "anonymous enum", depth, attributes=self.attributes())] if name: output.append(f"[.memname]`enum {escape_text(name)}`") else: @@ -935,7 +919,7 @@ class VariableMemberdefNode(Node): def to_asciidoc(self, depth=0, **kwargs): name = self.text("name") or self.text("qualifiedname") output = [ - title(name, depth, self.attributes()), + title(name, depth, attributes=self.attributes()), ] definition = self.text("definition") if self.text("initializer"): @@ -967,7 +951,7 @@ def to_asciidoc(self, depth=0, **kwargs): class DefineMemberdefNode(Node): def to_asciidoc(self, depth=0, **kwargs): - output = [title(self.text("name"), depth, self.attributes())] + output = [title(self.text("name"), depth, attributes=self.attributes())] name = self.text("name") params = [param.text() for param in self.children("param")] if params: diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 968d002..ab14712 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -58,11 +58,11 @@ def test_title_with_level_5(): def test_title_with_level_6(): - assert title("Level 6 Section Title", 6) == "[.h6]\n*Level 6 Section Title*" + assert title("Level 6 Section Title", 6) == "[role=h6]\n*Level 6 Section Title*" def test_title_with_level_7(): - assert title("Level 7 Section Title", 7) == "[.h6]\n*Level 7 Section Title*" + assert title("Level 7 Section Title", 7) == "[role=h6]\n*Level 7 Section Title*" def test_title_escapes_text(): @@ -70,4 +70,90 @@ def test_title_escapes_text(): def test_title_escapes_text_with_level_6(): - assert title("3 * 2 = 6", 6) == "[.h6]\n*3 ++*++ 2 = 6*" + assert title("3 * 2 = 6", 6) == "[role=h6]\n*3 ++*++ 2 = 6*" + + +def test_title_with_id(): + assert ( + title("Title", 1, attributes={"id": "group__foo"}) == "[#group_foo]\n== Title" + ) + + +def test_title_with_role(): + assert ( + title("Title", 1, attributes={"role": "contextspecific"}) + == "[role=contextspecific]\n== Title" + ) + + +def test_title_with_level_6_and_role(): + assert ( + title("Title", 6, attributes={"role": "contextspecific"}) + == "[role=h6 contextspecific]\n*Title*" + ) + + +def test_title_with_tag_and_type(): + assert ( + title("Title", 1, attributes={"type": "TYPE", "tag": "TAG"}) + == "[tag=TAG,type=TYPE]\n== Title" + ) + + +def test_title_with_all_attributes(): + assert ( + title( + "Title", + 1, + attributes={ + "id": "group__foo", + "role": "contextspecific", + "tag": "TAG", + "type": "TYPE", + }, + ) + == "[#group_foo,role=contextspecific,tag=TAG,type=TYPE]\n== Title" + ) + + +def test_title_with_level_6_and_all_attributes(): + assert ( + title( + "Title", + 6, + attributes={ + "id": "group__foo", + "role": "contextspecific", + "tag": "TAG", + "type": "TYPE", + }, + ) + == "[#group_foo,role=h6 contextspecific,tag=TAG,type=TYPE]\n*Title*" + ) + + +def test_title_with_arbitrary_attributes(): + assert ( + title( + "Title", + 1, + attributes={ + "foo": "bar", + "baz": "quux", + }, + ) + == '[foo="bar",baz="quux"]\n== Title' + ) + + +def test_title_with_attribute_with_space(): + assert ( + title( + "Title", + 1, + attributes={ + "foo": "bar baz", + }, + ) + == '[foo="bar baz"]\n== Title' + ) diff --git a/tests/test_node.py b/tests/test_node.py index 1a01ceb..e56bccb 100644 --- a/tests/test_node.py +++ b/tests/test_node.py @@ -277,7 +277,7 @@ def test_id_returns_sanitized_id(): assert node.id == "group_hardware_base" -def test_attributes_are_preserved(): +def test_attributes_returns_allowed_attributes(): xml = """ void void foo @@ -294,30 +294,12 @@ def test_attributes_are_preserved(): """ node = Node(BeautifulSoup(xml, "xml").memberdef) - assert ( - node.attributes() - == "#group_hardware_gpio_1ga5d7dbadb2233e2e6627e9101411beb27,role=contextspecific,tag=TAG,type=TYPE" - ) - - -def test_attributes_are_preserved_except_skipped(): - xml = """ - void - void foo - () - foo - - A function. - - - - - - - """ - node = Node(BeautifulSoup(xml, "xml").memberdef) - - assert node.attributes(skip=["id"]) == "role=contextspecific,tag=TAG,type=TYPE" + assert node.attributes() == { + "id": "group__hardware__gpio_1ga5d7dbadb2233e2e6627e9101411beb27", + "role": "contextspecific", + "tag": "TAG", + "type": "TYPE", + } def test_nodes_are_subscriptable(): diff --git a/tests/test_title_node.py b/tests/test_title_node.py deleted file mode 100644 index 66ef78e..0000000 --- a/tests/test_title_node.py +++ /dev/null @@ -1,20 +0,0 @@ -from bs4 import BeautifulSoup -from doxygentoasciidoc.nodes import TitleNode - - -def test_title_node(tmp_path): - xml = """Interrupt Numbers""" - - asciidoc = TitleNode(BeautifulSoup(xml, "xml").title, xmldir=tmp_path).to_asciidoc() - - assert asciidoc == "= Interrupt Numbers" - - -def test_title_node_with_depth(tmp_path): - xml = """Interrupt Numbers""" - - asciidoc = TitleNode(BeautifulSoup(xml, "xml").title, xmldir=tmp_path).to_asciidoc( - depth=2 - ) - - assert asciidoc == "=== Interrupt Numbers" From da0c33230e923d5b6aaa1f91692b12d52c8ec7a0 Mon Sep 17 00:00:00 2001 From: Paul Mucur Date: Wed, 21 Aug 2024 12:40:12 +0100 Subject: [PATCH 4/6] Remove redundant second argument to get --- nodes.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/nodes.py b/nodes.py index 8f5c1e3..47e4b1c 100644 --- a/nodes.py +++ b/nodes.py @@ -645,39 +645,39 @@ class SimplesectNode(Node): def to_asciidoc(self, **kwargs): previous_node = self.previous_node() next_node = self.next_node() - if self.node.get("kind", "") == "see": + if self.node.get("kind") == "see": output = [] if not ( previous_node and previous_node.name == "simplesect" - and previous_node.get("kind", "") == "see" + and previous_node.get("kind") == "see" ): output.append("--\n*See also*\n\n") output.append(super().to_asciidoc(**kwargs)) if not ( next_node and next_node.name == "simplesect" - and next_node.get("kind", "") == "see" + and next_node.get("kind") == "see" ): output.append("\n--") return "".join(output) - if self.node.get("kind", "") == "return": + if self.node.get("kind") == "return": return "".join(("--\n*Returns*\n\n", super().to_asciidoc(**kwargs), "\n--")) - if self.node.get("kind", "") == "note": + if self.node.get("kind") == "note": output = [] if not ( previous_node and previous_node.name == "simplesect" - and previous_node.get("kind", "") == "note" + and previous_node.get("kind") == "note" ): output.append("[NOTE]\n====\n") output.append(super().to_asciidoc(**kwargs)) if not ( next_node and next_node.name == "simplesect" - and next_node.get("kind", "") == "note" + and next_node.get("kind") == "note" ): output.append("\n====") From 8412caa33f97d1fee13cb4db3de135f3658b78e5 Mon Sep 17 00:00:00 2001 From: Paul Mucur Date: Wed, 21 Aug 2024 14:11:33 +0100 Subject: [PATCH 5/6] Prefer f-strings where possible Rather than joining tuples on the empty string, use f-strings to build up simple strings with interpolated expressions. --- nodes.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/nodes.py b/nodes.py index 47e4b1c..3ee048e 100644 --- a/nodes.py +++ b/nodes.py @@ -588,7 +588,7 @@ def to_asciidoc(self, **kwargs): class VerbatimNode(Node): def to_asciidoc(self, **kwargs): kwargs["programlisting"] = True - return "".join(("[source,c]\n----\n", super().to_asciidoc(**kwargs), "----")) + return f"[source,c]\n----\n{super().to_asciidoc(**kwargs)}----" class CodelineNode(Node): @@ -619,7 +619,7 @@ def to_asciidoc(self, **kwargs): class UlinkNode(Node): def to_asciidoc(self, **kwargs): - return "".join((f"{self.node['url']}[", super().to_asciidoc(**kwargs), "]")) + return f"{self.node['url']}[{super().to_asciidoc(**kwargs)}]" class NonbreakablespaceNode(Node): @@ -663,7 +663,7 @@ def to_asciidoc(self, **kwargs): return "".join(output) if self.node.get("kind") == "return": - return "".join(("--\n*Returns*\n\n", super().to_asciidoc(**kwargs), "\n--")) + return f"--\n*Returns*\n\n{super().to_asciidoc(**kwargs)}\n--" if self.node.get("kind") == "note": output = [] @@ -731,12 +731,12 @@ def refid(self): class EmphasisNode(Node): def to_asciidoc(self, **kwargs): - return "".join(("_", super().to_asciidoc(**kwargs), "_")) + return f"_{super().to_asciidoc(**kwargs)}_" class BoldNode(Node): def to_asciidoc(self, **kwargs): - return "".join(("*", super().to_asciidoc(**kwargs), "*")) + return f"*{super().to_asciidoc(**kwargs)}*" class CopyrightNode(Node): @@ -746,7 +746,7 @@ def to_asciidoc(self, **kwargs): class ComputeroutputNode(Node): def to_asciidoc(self, **kwargs): - return "".join(("`", super().to_asciidoc(**kwargs), "`")) + return f"`{super().to_asciidoc(**kwargs)}`" class ItemizedlistNode(Node): @@ -781,7 +781,7 @@ def block_separator(self, **kwargs): class TableNode(Node): def to_asciidoc(self, **kwargs): - return "".join(("|===\n", super().to_asciidoc(**kwargs), "\n|===")) + return f"|===\n{super().to_asciidoc(**kwargs)}\n|===" class RowNode(Node): @@ -793,7 +793,7 @@ def block_separator(self, **_kwargs): class EntryNode(Node): def to_asciidoc(self, **kwargs): - return "".join(("|", super().to_asciidoc(**kwargs))) + return f"|{super().to_asciidoc(**kwargs)}" class DetaileddescriptionNode(Node): From 06370ed29ce495af4fb001f61aff36efaa367b66 Mon Sep 17 00:00:00 2001 From: Paul Mucur Date: Wed, 21 Aug 2024 15:10:27 +0100 Subject: [PATCH 6/6] Memoize kind in Simplesect Node Rather than repeatedly loading the kind from the node's attributes, fetch it once and then switch on the resulting value multiple times. --- nodes.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/nodes.py b/nodes.py index 3ee048e..8422962 100644 --- a/nodes.py +++ b/nodes.py @@ -645,7 +645,9 @@ class SimplesectNode(Node): def to_asciidoc(self, **kwargs): previous_node = self.previous_node() next_node = self.next_node() - if self.node.get("kind") == "see": + kind = self.node.get("kind") + + if kind == "see": output = [] if not ( previous_node @@ -662,10 +664,10 @@ def to_asciidoc(self, **kwargs): output.append("\n--") return "".join(output) - if self.node.get("kind") == "return": + if kind == "return": return f"--\n*Returns*\n\n{super().to_asciidoc(**kwargs)}\n--" - if self.node.get("kind") == "note": + if kind == "note": output = [] if not ( previous_node