From 2509d216eb644ea086611daa2d4623901cb2675d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=B6nke?= Date: Wed, 10 Jun 2026 11:58:59 +0000 Subject: [PATCH 1/7] feat: add a composite action to set up zpretty Installs zpretty from a configurable spec; dogfooded by a workflow. --- .github/actions/zpretty/action.yml | 33 ++++++++++++++++++++++++++++ .github/workflows/zpretty-action.yml | 22 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 .github/actions/zpretty/action.yml create mode 100644 .github/workflows/zpretty-action.yml diff --git a/.github/actions/zpretty/action.yml b/.github/actions/zpretty/action.yml new file mode 100644 index 0000000..1e73b8a --- /dev/null +++ b/.github/actions/zpretty/action.yml @@ -0,0 +1,33 @@ +name: Set up zpretty +description: > + Install zpretty so a workflow can run it (for example `zpretty --check`). + +inputs: + spec: + description: > + pip requirement specifier for zpretty: a release such as + "zpretty==4.0.2", a VCS URL, or "." to install the checked-out source. + required: false + default: zpretty + python-version: + description: Python version to set up. + required: false + default: "3.x" + +runs: + using: composite + steps: + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: ${{ inputs.python-version }} + + - name: Install zpretty + shell: bash + run: | + python -m pip install --upgrade pip + python -m pip install "${{ inputs.spec }}" + + - name: Report zpretty version + shell: bash + run: zpretty --version diff --git a/.github/workflows/zpretty-action.yml b/.github/workflows/zpretty-action.yml new file mode 100644 index 0000000..9cbbd3b --- /dev/null +++ b/.github/workflows/zpretty-action.yml @@ -0,0 +1,22 @@ +name: zpretty action + +on: [push, pull_request] + +permissions: + contents: read + +jobs: + self-test: + name: Set up zpretty from source and run it + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Set up zpretty from this checkout + uses: ./.github/actions/zpretty + with: + spec: . + + - name: Smoke-test the installed zpretty + shell: bash + run: zpretty --check zpretty/tests/original/sample_xml.xml From 49ad700d8aa4cea5b667c19c9d70fae94135aa76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=B6nke?= Date: Wed, 10 Jun 2026 12:27:31 +0000 Subject: [PATCH 2/7] feat: add a GitLab CI template for zpretty --- MANIFEST.in | 1 + gitlab/zpretty.gitlab-ci.yml | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 gitlab/zpretty.gitlab-ci.yml diff --git a/MANIFEST.in b/MANIFEST.in index bf6471f..3327a49 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,5 @@ global-exclude *.pyc __pycache__ pyvenv.cfg graft zpretty graft zpretty/tests/include-excludes/.git +recursive-include gitlab *.yml include *.cfg *.txt *.md *.in LICENSE Makefile .pre-commit-config.yaml .pre-commit-hooks.yaml tox.ini pyproject.toml diff --git a/gitlab/zpretty.gitlab-ci.yml b/gitlab/zpretty.gitlab-ci.yml new file mode 100644 index 0000000..09e0296 --- /dev/null +++ b/gitlab/zpretty.gitlab-ci.yml @@ -0,0 +1,15 @@ +# Reusable zpretty setup for GitLab CI. Usage: +# include: +# - remote: "https://raw.githubusercontent.com/collective/zpretty//gitlab/zpretty.gitlab-ci.yml" +# zpretty: +# extends: .zpretty +# variables: { ZPRETTY_SPEC: "zpretty==4.0.2" } # optional +# script: [zpretty --check path/to/file.xml] +.zpretty: + image: "python:$ZPRETTY_PYTHON_VERSION" + variables: + ZPRETTY_PYTHON_VERSION: "3" + ZPRETTY_SPEC: zpretty + before_script: + - pip install "$ZPRETTY_SPEC" + - zpretty --version From 39b2fe0f950afee49c8f0bfdf81c475fa826e8c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=B6nke?= Date: Thu, 11 Jun 2026 09:58:09 +0000 Subject: [PATCH 3/7] docs: document the GitHub Action and GitLab CI/CD component Add a Continuous integration section to the README with usage examples for the composite action and the GitLab include, and replace the empty 4.0.3 changelog entry in HISTORY.md. --- HISTORY.md | 4 ++++ README.md | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/HISTORY.md b/HISTORY.md index c7b9120..a2ef23d 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -7,6 +7,10 @@ @ale-rt - Fix possible issue with whitespaces lost around comments in XML documents. @ale-rt +- Add a reusable GitHub Action and GitLab CI/CD component to set up + `zpretty` in CI pipelines. + (#232) + @gronke ## 4.0.2 (2026-06-09) diff --git a/README.md b/README.md index 49d0ec5..c49fa6d 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,43 @@ To do so, add the following to your `.pre-commit-config.yaml`: - id: zpretty ``` +# Continuous integration + +## GitHub Actions + +This repository ships a composite action that installs `zpretty` so a +workflow can run it: + +```yaml +- uses: actions/checkout@v6 +- uses: collective/zpretty/.github/actions/zpretty@master +- run: zpretty --check path/to/file.xml +``` + +The action takes two optional inputs: `spec`, a pip requirement specifier +(a release such as `zpretty==4.0.2`, a VCS URL, or `.` to install the +checked-out source; defaults to `zpretty`), and `python-version` +(defaults to `3.x`). + +## GitLab CI/CD + +For GitLab there is a reusable CI/CD configuration. Include it and extend +the `.zpretty` job it defines: + +```yaml +include: + - remote: "https://raw.githubusercontent.com/collective/zpretty/master/gitlab/zpretty.gitlab-ci.yml" + +zpretty: + extends: .zpretty + script: + - zpretty --check path/to/file.xml +``` + +Set the `ZPRETTY_SPEC` variable (default `zpretty`) to change the pip +specifier and `ZPRETTY_PYTHON_VERSION` (default `3`) to pick the Python +image tag. + # VSCode extension There is a VSCode extension that uses `zpretty`: From 913d6d2bd640932408fbb2e4d666e8d6402d1a5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=B6nke?= Date: Fri, 12 Jun 2026 10:38:07 +0000 Subject: [PATCH 4/7] ci: install zpretty with a single python -m pip call in both runners Drop the pip self-upgrade from the composite action: if pip cannot install zpretty it will hardly succeed in upgrading itself first. Use `python -m pip` in the GitLab template too, so both runners install with the identical one-liner. --- .github/actions/zpretty/action.yml | 4 +--- gitlab/zpretty.gitlab-ci.yml | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/actions/zpretty/action.yml b/.github/actions/zpretty/action.yml index 1e73b8a..681e32d 100644 --- a/.github/actions/zpretty/action.yml +++ b/.github/actions/zpretty/action.yml @@ -24,9 +24,7 @@ runs: - name: Install zpretty shell: bash - run: | - python -m pip install --upgrade pip - python -m pip install "${{ inputs.spec }}" + run: python -m pip install "${{ inputs.spec }}" - name: Report zpretty version shell: bash diff --git a/gitlab/zpretty.gitlab-ci.yml b/gitlab/zpretty.gitlab-ci.yml index 09e0296..c39fc62 100644 --- a/gitlab/zpretty.gitlab-ci.yml +++ b/gitlab/zpretty.gitlab-ci.yml @@ -11,5 +11,5 @@ ZPRETTY_PYTHON_VERSION: "3" ZPRETTY_SPEC: zpretty before_script: - - pip install "$ZPRETTY_SPEC" + - python -m pip install "$ZPRETTY_SPEC" - zpretty --version From aeda4f652fa526c5c49a669a01f136e0f50cac3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=B6nke?= Date: Fri, 12 Jun 2026 10:38:47 +0000 Subject: [PATCH 5/7] docs: pin the CI examples to the upcoming release Replace the hardcoded `master` in the GitHub Action and GitLab include examples with the `4.0.3` release tag, the first one that will ship the action and the template. Copy-pasted pipelines are then pinned, so the formatting behavior does not change unexpectedly between runs; a note explains that any other git reference (a newer tag, a commit SHA, or `master`) works as well. Also clarify that the checkout step in the Action example checks out the consumer repository, not zpretty itself. --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c49fa6d..bb31320 100644 --- a/README.md +++ b/README.md @@ -138,14 +138,20 @@ To do so, add the following to your `.pre-commit-config.yaml`: # Continuous integration +The examples below pin `4.0.3`, the first release that ships the action +and the template. Pinning a release keeps the formatting behavior stable +across repeated CI runs; any other git reference (a newer tag, a commit +SHA, or `master` to follow the development version) works as well. + ## GitHub Actions This repository ships a composite action that installs `zpretty` so a workflow can run it: ```yaml +# Check out the repository whose files zpretty should check - uses: actions/checkout@v6 -- uses: collective/zpretty/.github/actions/zpretty@master +- uses: collective/zpretty/.github/actions/zpretty@4.0.3 - run: zpretty --check path/to/file.xml ``` @@ -161,7 +167,7 @@ the `.zpretty` job it defines: ```yaml include: - - remote: "https://raw.githubusercontent.com/collective/zpretty/master/gitlab/zpretty.gitlab-ci.yml" + - remote: "https://raw.githubusercontent.com/collective/zpretty/4.0.3/gitlab/zpretty.gitlab-ci.yml" zpretty: extends: .zpretty From 4479a9762f392db7e25b00ae2e6d188d1e23d0d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Gr=C3=B6nke?= Date: Fri, 12 Jun 2026 11:12:49 +0000 Subject: [PATCH 6/7] test: guard the README CI examples against version drift Assert that both CI examples pin the same version and that it is either the latest released version or the upcoming one from HISTORY.md. The test stays quiet during a normal development cycle, where the README keeps pointing at the latest release, and fails as soon as the pin falls a full release behind. --- zpretty/tests/test_readme.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/zpretty/tests/test_readme.py b/zpretty/tests/test_readme.py index a7429f0..d3b5ca6 100644 --- a/zpretty/tests/test_readme.py +++ b/zpretty/tests/test_readme.py @@ -3,6 +3,7 @@ from zpretty.tests.mock import MockCLIRunner import argparse +import re class TestReadme(TestCase): @@ -36,3 +37,34 @@ def test_readme(self): observed = self.extract_usage_from_readme() expected = self.extract_usage_from_parser() self.assertListEqual(observed, expected) + + def extract_pinned_versions_from_readme(self): + """Extract the versions pinned in the CI examples""" + readme_path = files("zpretty").parent / "README.md" + readme = readme_path.read_text() + github = re.search( + r"collective/zpretty/\.github/actions/zpretty@(\S+)", readme + ) + gitlab = re.search( + r"collective/zpretty/([^/\s]+)/gitlab/zpretty\.gitlab-ci\.yml", readme + ) + return github.group(1), gitlab.group(1) + + def extract_versions_from_history(self): + """Return the topmost and the latest released version from HISTORY.md""" + history_path = files("zpretty").parent / "HISTORY.md" + entries = re.findall( + r"^## (\d\S*) \(([^)]+)\)", history_path.read_text(), re.MULTILINE + ) + released = [version for version, date in entries if date != "unreleased"] + return entries[0][0], released[0] + + def test_ci_examples_pin_a_current_release(self): + """Both CI examples must pin the same version, and it must be the + latest released or the upcoming one, so the README cannot silently + fall a release behind. + """ + github, gitlab = self.extract_pinned_versions_from_readme() + self.assertEqual(github, gitlab) + topmost, latest_released = self.extract_versions_from_history() + self.assertIn(github, (topmost, latest_released)) From eedf02e11d03faacf05bd5ac9511b339c429e68f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 12 Jun 2026 12:28:54 +0000 Subject: [PATCH 7/7] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- zpretty/tests/test_readme.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/zpretty/tests/test_readme.py b/zpretty/tests/test_readme.py index d3b5ca6..7f378b8 100644 --- a/zpretty/tests/test_readme.py +++ b/zpretty/tests/test_readme.py @@ -42,9 +42,7 @@ def extract_pinned_versions_from_readme(self): """Extract the versions pinned in the CI examples""" readme_path = files("zpretty").parent / "README.md" readme = readme_path.read_text() - github = re.search( - r"collective/zpretty/\.github/actions/zpretty@(\S+)", readme - ) + github = re.search(r"collective/zpretty/\.github/actions/zpretty@(\S+)", readme) gitlab = re.search( r"collective/zpretty/([^/\s]+)/gitlab/zpretty\.gitlab-ci\.yml", readme )