From 4f97cdf3763a2fb3b5bde99cbf378be34e85b090 Mon Sep 17 00:00:00 2001 From: Paul Mucur Date: Wed, 14 Aug 2024 09:16:15 +0100 Subject: [PATCH] Run formatting, linting and tests in CI To flag any inconsistencies in style or regressions in the test suite, ensure we run Black, Pylint and pytest for every pull request and push to main. --- .github/workflows/test.yml | 22 ++++++++++++++++++++++ .pylintrc | 8 ++++++++ README.md | 26 ++++++++++++++++++++++++++ cli.py | 17 +++++++++++++---- helpers.py | 12 ++++++------ nodes.py | 14 ++++++++++---- tests/test_copyright_node.py | 4 +++- tests/test_heading_node.py | 4 +++- tests/test_none_node.py | 4 +++- tests/test_sect_node.py | 4 +++- 10 files changed, 97 insertions(+), 18 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 .pylintrc diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..80111fe --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,22 @@ +name: Test + +on: + workflow_dispatch: + push: + branches: + - main + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.9' + cache: 'pip' + - run: pip install -r dev.txt + - run: black --check . + - run: pylint doxygentoasciidoc + - run: pytest diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..f6628c8 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,8 @@ +[MESSAGES CONTROL] +disable=missing-module-docstring,missing-function-docstring,missing-class-docstring,too-many-lines + +[FORMAT] +good-names=id + +[MASTER] +ignore=tests diff --git a/README.md b/README.md index 7182187..7a30882 100644 --- a/README.md +++ b/README.md @@ -11,3 +11,29 @@ Allowed args: `-c`: process a node other than `doxygenindex` The following attributes from the XML will be preserved in the generated asciidoc: role, tag, type. + +## Development + +Install the development dependencies: + +```console +$ pip install -r dev.txt +``` + +Run the test suite: + +```console +$ pytest +``` + +Ensure code is formatted consistently: + +```console +$ black --check . +``` + +Ensure code passes linting: + +```console +$ pylint doxygentoasciidoc +``` diff --git a/cli.py b/cli.py index f151d29..67a6a54 100644 --- a/cli.py +++ b/cli.py @@ -1,7 +1,6 @@ import os import sys import argparse -import yaml from bs4 import BeautifulSoup from .nodes import Node, DoxygenindexNode @@ -10,9 +9,19 @@ def main(): """Convert the given Doxygen index.xml to AsciiDoc and output the result.""" parser = argparse.ArgumentParser() - parser.add_argument("-f", "--file", help="The path of the file to convert", default=None) - parser.add_argument("-o", "--output", help="The path of the output file", default=None) - parser.add_argument("-c", "--child", help="Is NOT the root index file", default=False, action='store_true') + parser.add_argument( + "-f", "--file", help="The path of the file to convert", default=None + ) + parser.add_argument( + "-o", "--output", help="The path of the output file", default=None + ) + 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 diff --git a/helpers.py b/helpers.py index 073eddb..e0ca580 100644 --- a/helpers.py +++ b/helpers.py @@ -22,16 +22,16 @@ def title(text, level, attributes=None): """Return text formatted as a title with the given level.""" 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) + 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)}*" - else: - return f"[.h6]\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)}" - else: - return f"{marker} {escape_text(text)}" + return f"{marker} {escape_text(text)}" diff --git a/nodes.py b/nodes.py index 9d1e208..14b8a0e 100644 --- a/nodes.py +++ b/nodes.py @@ -163,8 +163,10 @@ def children(self, selector=None, **kwargs): for position, child in enumerate(children) ] - def attributes(self, skip=[], **kwargs): + 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: @@ -395,6 +397,7 @@ def to_asciidoc_row(self, depth=0): class GroupNode(Node): def to_asciidoc(self, **kwargs): + # pylint: disable=too-many-locals,too-many-branches output = [self.__output_title(**kwargs)] briefdescription = self.__output_briefdescription(**kwargs) if briefdescription: @@ -990,7 +993,8 @@ def to_asciidoc(self, **kwargs): ) else: output.append( - f"[.memname]`#define {escape_text(name)}{escape_text(argsstring)} {escape_text(initializer).rstrip()}`" + f"[.memname]`#define {escape_text(name)}{escape_text(argsstring)} " + f"{escape_text(initializer).rstrip()}`" ) else: output.append( @@ -1063,7 +1067,8 @@ def to_asciidoc(self, **kwargs): for memberdef in self.children("memberdef", kind="typedef"): type_ = memberdef.child("type").to_asciidoc(**kwargs) typedef = [ - f"`typedef {type_} <<{memberdef.id},{escape_text(memberdef.text('name'))}>>{escape_text(memberdef.text('argsstring'))}`::" + f"`typedef {type_} <<{memberdef.id},{escape_text(memberdef.text('name'))}>>" + f"{escape_text(memberdef.text('argsstring'))}`::" ] briefdescription = memberdef.child("briefdescription").to_asciidoc(**kwargs) if briefdescription: @@ -1148,7 +1153,8 @@ def to_asciidoc(self, **kwargs): else: argsstring = "" macro = [ - f"* `#define <<{memberdef.id},{escape_text(memberdef.text('name'))}>>{escape_text(argsstring)}" + f"* `#define <<{memberdef.id},{escape_text(memberdef.text('name'))}>>" + f"{escape_text(argsstring)}" ] if memberdef.text("initializer"): initializer = memberdef.child("initializer").to_asciidoc( diff --git a/tests/test_copyright_node.py b/tests/test_copyright_node.py index ef7571b..1636c45 100644 --- a/tests/test_copyright_node.py +++ b/tests/test_copyright_node.py @@ -5,6 +5,8 @@ def test_copyright_node(tmp_path): xml = """""" - asciidoc = CopyrightNode(BeautifulSoup(xml, "xml").copy, xmldir=tmp_path).to_asciidoc() + asciidoc = CopyrightNode( + BeautifulSoup(xml, "xml").copy, xmldir=tmp_path + ).to_asciidoc() assert asciidoc == "©" diff --git a/tests/test_heading_node.py b/tests/test_heading_node.py index 69082f4..6487b1b 100644 --- a/tests/test_heading_node.py +++ b/tests/test_heading_node.py @@ -5,6 +5,8 @@ def test_heading_node(tmp_path): xml = """Examples Index""" - asciidoc = HeadingNode(BeautifulSoup(xml, "xml").title, xmldir=tmp_path).to_asciidoc() + asciidoc = HeadingNode( + BeautifulSoup(xml, "xml").title, xmldir=tmp_path + ).to_asciidoc() assert asciidoc == "=== Examples Index" diff --git a/tests/test_none_node.py b/tests/test_none_node.py index c4f1d34..a23fafc 100644 --- a/tests/test_none_node.py +++ b/tests/test_none_node.py @@ -5,6 +5,8 @@ def test_none_node(tmp_path): xml = """examples_page""" - asciidoc = NoneNode(BeautifulSoup(xml, "xml").compoundname, xmldir=tmp_path).to_asciidoc() + asciidoc = NoneNode( + BeautifulSoup(xml, "xml").compoundname, xmldir=tmp_path + ).to_asciidoc() assert asciidoc == "" diff --git a/tests/test_sect_node.py b/tests/test_sect_node.py index 8ec40fa..d892330 100644 --- a/tests/test_sect_node.py +++ b/tests/test_sect_node.py @@ -33,7 +33,9 @@ def test_compounddef_kind_page_is_processed_as_sect(tmp_path): """ - asciidoc = Node(BeautifulSoup(xml, "xml").compounddef, xmldir=tmp_path).to_asciidoc() + asciidoc = Node( + BeautifulSoup(xml, "xml").compounddef, xmldir=tmp_path + ).to_asciidoc() assert asciidoc == dedent( """\