From 7d7cf96d571a804306495584190ad6f68eaa0eb4 Mon Sep 17 00:00:00 2001 From: Jay Bazuzi Date: Sat, 26 Apr 2025 12:22:05 -0700 Subject: [PATCH] ! F regex for precommit hooks - embed the regex in the README - includes tests for the regex - adds CI to run those tests - keep formatting and mdsnippets up to date in an Action, Llewellyn-style --- .github/workflows/build_and_test.yml | 13 ++++++ .../format_and_update_mdsnippets.yml | 19 ++++++++ .gitignore | 1 + README.md | 10 +++++ build_and_test | 4 ++ format_and_update_mdsnippets | 8 ++++ mdsnippets.json | 5 +++ tools/__init__.py | 0 tools/compliance_regex.txt | 1 + tools/test__compliance_regex.py | 43 +++++++++++++++++++ 10 files changed, 104 insertions(+) create mode 100644 .github/workflows/build_and_test.yml create mode 100644 .github/workflows/format_and_update_mdsnippets.yml create mode 100644 .gitignore create mode 100755 build_and_test create mode 100755 format_and_update_mdsnippets create mode 100644 mdsnippets.json create mode 100644 tools/__init__.py create mode 100644 tools/compliance_regex.txt create mode 100644 tools/test__compliance_regex.py diff --git a/.github/workflows/build_and_test.yml b/.github/workflows/build_and_test.yml new file mode 100644 index 0000000..e05e17a --- /dev/null +++ b/.github/workflows/build_and_test.yml @@ -0,0 +1,13 @@ +name: Build and Test + +on: + push: + pull_request: + workflow_dispatch: + +jobs: + build-and-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: ./build_and_test diff --git a/.github/workflows/format_and_update_mdsnippets.yml b/.github/workflows/format_and_update_mdsnippets.yml new file mode 100644 index 0000000..0aa73d5 --- /dev/null +++ b/.github/workflows/format_and_update_mdsnippets.yml @@ -0,0 +1,19 @@ +name: Format and Update MDSnippets +on: + push: +jobs: + format_and_snippets: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Run format_and_update_mdsnippets + run: ./format_and_update_mdsnippets + - name: Git Commit and Push + uses: github-actions-x/commit@v2.9 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + commit-message: ". d format and update mdsnippets" + rebase: 'true' + push-branch: 'main' + name: GitHub Actions [bot] + email: actions@github.com diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/README.md b/README.md index 750dacc..8b6196d 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,16 @@ Changes that don't impact the code, but do change documentation around the code. The basic intention annotations are comprehensive to describe any kind of change, but it may be useful to extend the notation to your project to provide additional detail that is useful in your context. Read more about [Extension Intensions](Extension%20Intentions.md). +# Compliance validation + +You can validate that a commit message is valid, for example in a Git pre-commit hook, a GitHub Action, or GitLab's [Validate Commit Messages](https://docs.gitlab.com/user/project/repository/push_rules/#validate-commit-messages) by using the following regex: + + +```txt +^[.!^@] [a-zA-Z@]( .*)?$ +``` + + # Provable Refactorings [2]:#provable-refactorings diff --git a/build_and_test b/build_and_test new file mode 100755 index 0000000..3c83ec8 --- /dev/null +++ b/build_and_test @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail + +python3 -m unittest -v diff --git a/format_and_update_mdsnippets b/format_and_update_mdsnippets new file mode 100755 index 0000000..dd84436 --- /dev/null +++ b/format_and_update_mdsnippets @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -euo pipefail + +dotnet tool install --global MarkdownSnippets.Tool +mdsnippets . + +python3 -m pip install black +black . diff --git a/mdsnippets.json b/mdsnippets.json new file mode 100644 index 0000000..1434008 --- /dev/null +++ b/mdsnippets.json @@ -0,0 +1,5 @@ +{ + "Convention": "InPlaceOverwrite", + "WriteHeader": false, + "OmitSnippetLinks": true +} diff --git a/tools/__init__.py b/tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tools/compliance_regex.txt b/tools/compliance_regex.txt new file mode 100644 index 0000000..7d7fbf9 --- /dev/null +++ b/tools/compliance_regex.txt @@ -0,0 +1 @@ +^[.!^@] [a-zA-Z@]( .*)?$ diff --git a/tools/test__compliance_regex.py b/tools/test__compliance_regex.py new file mode 100644 index 0000000..e0a6459 --- /dev/null +++ b/tools/test__compliance_regex.py @@ -0,0 +1,43 @@ +import unittest + +import re +from pathlib import Path + +SCRIPT_DIR = Path(__file__).parent + +COMPLIANCE_REGEX = re.compile((SCRIPT_DIR / "compliance_regex.txt").read_text().strip()) + +MATCHING = [ + ". r rename variable", + "^ r rename variable", + "! r rename variable", + "@ r clean up a bunch of stuff", + "! F Add about page", + "! B Fix crash on startup", + ". a Format with prettier", + ". n No-op commit", + ". t Add missing tests", + "@ @ Checkpoint: work in progress", + ". e capture artifacts in CI", +] + +NONMATCHING_EXAMPLES = [ + "r rename variable", + ". rename variable", + ".r rename variable", + "! B Fix crash on startup", + ". rt rename variable in test", + "- r rename variable", + "Merge branch 'startup-crash'", + "WIP: diagnosing crash on startup", +] + + +class Tess(unittest.TestCase): + def test__matching(self): + for example in MATCHING: + self.assertTrue(COMPLIANCE_REGEX.match(example)) + + def test__nonmatching(self): + for example in NONMATCHING_EXAMPLES: + self.assertFalse(COMPLIANCE_REGEX.match(example))