Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,13 @@ on:
jobs:
pre-commit:
runs-on: ubuntu-latest
name: standardize thyself
name: Standardize thyself
steps:
- uses: actions/checkout@v6
- run: python -m pip install pre-commit
shell: bash
- run: python -m pip freeze --local
shell: bash
- run: pre-commit run --show-diff-on-failure --color=always
shell: bash
- uses: actions/setup-python@v6
with:
python-version: '3.10'
- uses: pre-commit/action@v3.0.1

pytest:
runs-on: ubuntu-latest
Expand All @@ -27,6 +25,19 @@ jobs:
- run: uv sync
- run: uv run pytest

readme-version:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Check README rev matches pyproject.toml version
run: |
VERSION=$(grep '^version' pyproject.toml | sed 's/version = "\(.*\)"/\1/')
grep -q "rev: v$VERSION" README.md || {
echo "README.md rev does not match pyproject.toml version v$VERSION"
echo "Run 'just sync-readme' to fix."
exit 1
}

tag-release:
runs-on: ubuntu-latest
needs: [pre-commit, pytest]
Expand Down
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.10
9 changes: 9 additions & 0 deletions DEVELOPING.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,15 @@ Releases follow semantic versioning:
- **Minor** -- new checks, formatting changes, or new linting checks. May require fixing existing code.
- **Major** -- removed checks or other breaking changes to existing API

To bump the version, use `just bump` -- it updates `pyproject.toml` and syncs the README pin in one step:

```shell
just bump minor # or major / patch
```

CI will fail on PRs where the two are out of sync.
The tag is pushed automatically by CI on merge to main.

## Testing

Add files to `test_files/` to validate linter settings work, if other files of that type are not present in this repo.
16 changes: 16 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,19 @@
verify:
pre-commit clean
pre-commit run --all-files

# Update the rev: pin in README.md to match the current package version.
sync-readme:
#!/usr/bin/env bash
set -euo pipefail
VERSION=$(uv version --short)
sed -i "s/rev: v[0-9]*\.[0-9]*\.[0-9]*/rev: v$VERSION/" README.md
echo "README.md pinned to v$VERSION"

# Bump the package version and sync the README pin in one step.
# Usage: just bump minor (or major / patch)
bump level:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ever heard of bumpver?

Does exactly this, including the readme.

#!/usr/bin/env bash
set -euo pipefail
uv version --bump {{ level }}
just sync-readme
238 changes: 208 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
# Polymath Source Code Standard

Pre-commit hooks that enforce the Polymath Robotics Engineering formatting and linting standard for a variety of languages.

This is a low-configuration, highly opinionated set of hooks that take the guesswork out of formatting.
[![CI](https://github.com/polymathrobotics/polymath_code_standard/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/polymathrobotics/polymath_code_standard/actions/workflows/test.yml)

One check is provided per file type, with all necessary settings bundled within the hook.
Pre-commit hooks that enforce the Polymath Robotics Engineering formatting and linting standard for a variety of languages.
This is a low-configuration, opinionated set of hooks that take the guesswork out of formatting.
One hook is provided per file type, with all necessary settings bundled.
Consuming repositories reference this repo directly via `.pre-commit-config.yaml` -- no config files need to be copied or kept in sync.

See [DEVELOPING.md](./DEVELOPING.md) for the development workflows to make updates to this tool.

# Usage
See [DEVELOPING.md](./DEVELOPING.md) for development workflows.

## Prerequisites

> [!NOTE]
> You may want to add the following text from this prerequisites section to your own repository's `README.md`!
> Consider adding this prerequisites section to your own repository's `README.md`.

Install [pre-commit](https://pre-commit.com).
While there are several ways to do this, our favorit is with [uv](https://github.com/astral-sh/uv) - it's "scary fast".
Our recommended approach is with [uv](https://github.com/astral-sh/uv).

```shell
uv tool install --with pre-commit-uv pre-commit
Expand All @@ -29,24 +27,23 @@ Set up pre-commit hooks in the repository:
pre-commit install
```

### Hooks
## Configuration

In your repository's `.pre-commit-config.yaml`, use these hooks.
See the following for a list of all available hooks.
Feel free to use only the ones that apply to your usage.
Add the following to your repository's `.pre-commit-config.yaml`.
Use only the hooks that apply to your project.

```yaml
---
repos:
- repo: https://github.com/polymathrobotics/polymath_code_standard
rev: v2.2.0
rev: v2.3.1
hooks:
# Basic checks and fixes that apply to any text file and the git repository itself
# File hygiene for all staged files
- id: polymath-general
# Enforce and insert copyright headers in source code for the project's license
# Copyright headers and LICENSE file management
- id: polymath-copyright
args: [--license, <SPDX_ID or 'proprietary'>, --copyright-org, <organization name>]
# Specific languages
args: [--license, Apache-2.0, --copyright-org, "Your Org Name"]
# Language-specific checks
- id: polymath-python
- id: polymath-cpp
- id: polymath-ros
Expand All @@ -58,35 +55,216 @@ repos:
- id: polymath-yaml
- id: polymath-toml
- id: polymath-json
- id: polymath-ansible
```

See the [Hook Reference](#hook-reference) for details on each hook and its available arguments.

## First-time use

Apply your newly configured hooks to all sources with the following.
You should also do this whenever you update to a newer version.
Apply your newly configured hooks to all existing files:

```shell
pre-commit run --all-files
```

You may now want to stage the new changes, then run again to check for any failures that require manual correction.
Stage the reformatted files, then run again to surface any failures that require manual correction.

> [!NOTE]
> These formatters are likely not compatible with other formatting standards, for example in ROS you will now want to remove `ament_lint` in favor of these hooks.
> These formatters are not compatible with other formatting standards.
> In ROS projects, remove `ament_lint` in favor of these hooks.

> [!NOTE]
> After a large reformatting pass, add the commit hash to `.git-blame-ignore-revs` so that `git blame` points back to the original authors rather than the reformatting commit.
> After a large reformatting pass, add the commit hash to `.git-blame-ignore-revs` so `git blame` points back to the original authors rather than the reformatting commit.

## CI

See [.github/workflows/test.yml](./.github/workflows/test.yml) for a simple GitHub Actions configuration that runs pre-commit hooks.
Add the following GitHub Actions workflow to run pre-commit on every push and pull request:

```yaml
---
name: Lint

on:
push:
branches: [main]
pull_request:

jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.10'
- uses: pre-commit/action@v3.0.1
```

## Hook Reference

### `polymath-general`

Applies file hygiene checks to all staged files.

- Prevents committing large files
- Detects filename case conflicts
- Detects merge conflict markers
- Ensures shebanged scripts are executable
- Validates symlinks
- Blocks git submodules
- Adds a trailing newline to files
- Normalizes line endings
- Removes trailing whitespace

No arguments.

---

### `polymath-copyright`

Inserts and validates copyright headers for Python, CMake, Shell, C, and C++ files.
Also creates or updates the `LICENSE` file (skipped for proprietary licenses).
Python, CMake, and Shell files use `#` comment style.
C and C++ files use `//` comment style.

**Required:**

- `--license SPDX_ID` -- SPDX license ID (e.g. `Apache-2.0`, `MIT`) or `proprietary`
- `--copyright-org ORG` -- Name of the copyright-holding organization (mutually exclusive with `--wildcard-copyright-org`)
- `--wildcard-copyright-org` -- Accept any copyright holder on the copyright line, for multi-contributor repos (mutually exclusive with `--copyright-org`)

**Optional:**

- `--copyright-year YEAR` -- Copyright start year (default: current year)
- `--reuse-style` -- Force REUSE-style 2-line copyright headers
- `--relicense` -- Strip any existing leading comment block before inserting the new header

Example:

```yaml
- id: polymath-copyright
args: [--license, Apache-2.0, --copyright-org, "Polymath Robotics, Inc."]
```

---

### `polymath-python`

Runs `ruff format`, `ruff check --fix`, and Python AST validation.

> [!NOTE]
> This hook writes `/.ruff.toml` to the consuming repo root.
> Add `/.ruff.toml` to `.gitignore`.
> See [`.ruff.toml` note](#rufftoml-is-written-to-the-consuming-repo) for details.

No arguments.

---

### `polymath-cpp`

Runs `clang-format` and `cpplint` on C and C++ files using Polymath's bundled configuration.

No arguments.

---

### `polymath-ros`

Enforces ROS-specific C++ conventions.
Requires that multi-threaded executors (`MultiThreadedExecutor`, `EventsCBGExecutor`) specify an explicit thread count.
Suppress a check on a specific line with a trailing `// NOLINT` comment.

No arguments.

---

### `polymath-shell`

Runs `shellcheck` on shell scripts.
Detects scripts by shebang line, not just file extension.
Excludes `.envrc` files.

No arguments.

---

### `polymath-cmake`

Runs `cmakelint` on CMake files with a maximum line length of 140.

No arguments.

---

### `polymath-docker`

Runs `hadolint` on Dockerfiles.

No arguments.

---

### `polymath-markdown`

Runs `pymarkdown` with the line-length rule disabled and auto-fixes issues where possible.

No arguments.

---

### `polymath-xml`

Validates XML well-formedness and schema compliance.
Supports `xml-model` processing instructions and `xsi:noNamespaceSchemaLocation`.
Includes a bundled `package_format3.xsd` schema for ROS `package.xml` validation.

## NOTE: `.ruff.toml` is written to the consuming repo
No arguments.

---

### `polymath-yaml`

Formats and validates YAML files using `yamlfix`.
By default, adds a `---` explicit document start marker.

**Optional:**

- `--no-explicit-start` -- Omit the `---` document start marker

---

### `polymath-toml`

Validates TOML syntax.

No arguments.

---

### `polymath-json`

Validates JSON and JSON5 syntax.
Excludes `.geojson` files.

No arguments.

---

### `polymath-ansible`

Installs Ansible collections and roles from `ansible/requirements.yml` and runs `ansible-lint` with Polymath's configuration.
Dependencies are cached in `.polymath-ansible/` (automatically gitignored) and only reinstalled when `requirements.yml` changes.

No arguments.

---

While `ruff` can take a `--config` argument to an absolute file, we are currently allowing subdirectories of a repository to override Ruff configuration.
## `.ruff.toml` is written to the consuming repo

To enable this, we have to omit `--config` and let Ruff walk up the directory tree.
This means we need to install our baseline `.ruff.toml` configuration in the root of the repo for Ruff to find.
Because `pre-commit` can run the same hook in parallel on batches of files, there is a race condition if we try to clean up that file after running.
While `ruff` can take a `--config` argument to an absolute file, subdirectory overrides require Ruff to walk up the directory tree.
To support this, the baseline `.ruff.toml` is installed in the repo root for Ruff to find.
Because pre-commit can run the same hook in parallel on batches of files, cleaning up that file after running would introduce a race condition.

TL;DR Add `/.ruff.toml` to `.gitignore` for the repository to ignore that it's been put there.
Add `/.ruff.toml` to `.gitignore` in the consuming repository.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "polymath-code-standard"
version = "2.3.0"
version = "2.3.1"
description = "Polymath Code Standard pre-commit hooks"
requires-python = ">=3.10"
dependencies = [
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading