From 65907b56ee2b35c3ba4fe2cb345fd427e1a94ee0 Mon Sep 17 00:00:00 2001 From: Hugh Sorby Date: Thu, 25 Sep 2025 14:19:07 +1200 Subject: [PATCH] Perform local execution of Codespell without involving DockerHub. --- .codespell-dict | 1 + .codespellexclude => .codespell-exclude | 0 .github/scripts/codespell_annotations.py | 75 ++++++++++++++++++++++++ .github/workflows/spell-check.yml | 32 ++++++---- 4 files changed, 95 insertions(+), 13 deletions(-) create mode 100644 .codespell-dict rename .codespellexclude => .codespell-exclude (100%) create mode 100644 .github/scripts/codespell_annotations.py diff --git a/.codespell-dict b/.codespell-dict new file mode 100644 index 0000000000..12a79ddcf7 --- /dev/null +++ b/.codespell-dict @@ -0,0 +1 @@ +sectoin->section diff --git a/.codespellexclude b/.codespell-exclude similarity index 100% rename from .codespellexclude rename to .codespell-exclude diff --git a/.github/scripts/codespell_annotations.py b/.github/scripts/codespell_annotations.py new file mode 100644 index 0000000000..acb4d8882e --- /dev/null +++ b/.github/scripts/codespell_annotations.py @@ -0,0 +1,75 @@ +import argparse +import subprocess +import re +import sys + + +def _parse_args(): + parser = argparse.ArgumentParser(description="Runs codespell and creates GitHub annotations for any errors found.") + parser.add_argument( + "--exclude-file", + type=str, + help="Path to a file containing exclude patterns for codespell." + ) + + parser.add_argument( + "--dictionary", + action='append', + type=str, + help="Path to a file containing custom spelling corrections for codespell or '-' for default dictionary." + ) + parser.add_argument( + "--skip", + type=str, + help="Comma-separated list of files/folders to skip for codespell." + ) + + return parser.parse_args() + + +def run_codespell_and_create_annotations(args): + """ + Runs codespell with provided arguments and formats its output as GitHub annotations. + """ + command = ["codespell", ".", "--quiet-level", "3"] + if args.exclude_file: + command.extend(["--exclude-file", args.exclude_file]) + if args.skip: + command.extend(["--skip", args.skip]) + if args.dictionary: + for dict_path in args.dictionary: + command.extend(["--dictionary", dict_path]) + + print(f"Running command: {' '.join(command)}") + + # Execute the command + result = subprocess.run(command, capture_output=True, text=True) + + # If codespell printed anything, it means there are errors + if result.stdout: + errors_output = result.stdout.strip() + + error_pattern = re.compile(r"^(.*):(\d+): (\w+) ==> (.*)$") + + print("Spelling errors found. Creating annotations...") + error_count = 0 + for line in errors_output.split('\n'): + match = error_pattern.match(line) + if match: + file_path, line_num, wrong_word, suggestion = match.groups() + # This is the magic line that creates the annotation in GitHub + title="Spelling error" + message = f"{wrong_word} -> {suggestion}" + if file_path.startswith("./"): + file_path = file_path[2:] + print(f"::error file={file_path},line={line_num},title={title}::{message}") + error_count += 1 + + if error_count > 0: + sys.exit(1) + else: + print("✅ No spelling errors found.") + + +if __name__ == "__main__": + run_codespell_and_create_annotations(_parse_args()) diff --git a/.github/workflows/spell-check.yml b/.github/workflows/spell-check.yml index 458afd9971..85b42f4ae5 100644 --- a/.github/workflows/spell-check.yml +++ b/.github/workflows/spell-check.yml @@ -1,21 +1,27 @@ -# GitHub Action to automate the identification of common misspellings in text files. -# https://github.com/codespell-project/actions-codespell -# https://github.com/codespell-project/codespell -name: codespell -on: [pull_request] +name: Check Spelling -permissions: - contents: read +on: [pull_request] jobs: codespell: if: github.repository == 'cellml/libcellml' - name: Check for spelling errors runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: codespell-project/actions-codespell@v2 + - name: Check out code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 with: - check_filenames: true - skip: ./.git,./tests/gtest/include/gtest/gtest.h,./tests/gtest/src/gtest-all.cc,./src/dtds/mathml2/* - exclude_file: .codespellexclude + python-version: '3.x' + + - name: Install codespell + run: pip install codespell + + - name: Create spelling annotations + run: | + python .github/scripts/codespell_annotations.py \ + --exclude-file .codespell-exclude \ + --skip ./.git,.codespell-dict,./tests/gtest/include/gtest/gtest.h,./tests/gtest/src/gtest-all.cc,./src/dtds/mathml2/* \ + --dictionary .codespell-dict \ + --dictionary -