diff options
| -rwxr-xr-x | .githooks/pre-commit | 53 | ||||
| -rw-r--r-- | CONTRIBUTING.md | 14 | ||||
| -rw-r--r-- | idatui/remote_ops.py | 1 | ||||
| -rw-r--r-- | pyproject.toml | 2 | ||||
| -rw-r--r-- | ruff.toml | 33 | ||||
| -rw-r--r-- | uv.lock | 27 |
6 files changed, 128 insertions, 2 deletions
diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..39cd4a5 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,53 @@ +#!/bin/sh +# Format the Python being committed, so what lands in git is already formatted. +# Imports sorted, then laid out -- both by ruff (ruff.toml pins the version). +# +# Install with `git config core.hooksPath .githooks`. Hooks are not versioned +# by git, so a hook in a repo has to be a file somebody opts into -- there is +# no way to ship one that runs on clone, and a repo that could would be a repo +# that runs code on clone. +# +# Skip a commit with `git commit --no-verify` when you mean to. +set -e + +py_files=$(git diff --cached --name-only --diff-filter=ACMR | grep -E '\.py$' || true) +[ -n "$py_files" ] || exit 0 + +# Where ruff is. The repo-local venv first, before $PATH: ruff.toml pins a +# version, and a system ruff of the wrong one would refuse the commit while +# the venv sitting right there has the version it asked for. Off the toplevel, +# so this works from a subdirectory. +root=$(git rev-parse --show-toplevel) +if [ -x "$root/.venv/bin/ruff" ]; then + ruff="$root/.venv/bin/ruff" +else + ruff=$(command -v ruff 2>/dev/null || true) +fi +if [ -z "$ruff" ]; then + echo "pre-commit: ruff is not installed, and this commit touches Python." >&2 + echo " uv sync --extra dev (a venv in .venv, version pinned)" >&2 + echo " ...or commit with --no-verify if you know what you are doing." >&2 + exit 1 +fi + +# A file with unstaged changes is the one case where formatting in place is +# dangerous: the formatter rewrites the *working tree*, and re-staging +# afterwards would commit work that was deliberately left out of the index. +# So say so and stop, rather than quietly widening a commit somebody built +# with `git add -p`. +partial="" +for f in $py_files; do + if ! git diff --quiet -- "$f"; then partial="$partial $f"; fi +done +if [ -n "$partial" ]; then + echo "pre-commit: these files are only partly staged, so formatting them" >&2 + echo " in place would add work you left out of the commit:" >&2 + for f in $partial; do echo " $f" >&2; done + echo " stage them fully, stash the rest, or use --no-verify." >&2 + exit 1 +fi + +cd "$root" +"$ruff" check --select I --fix --quiet $py_files +"$ruff" format --quiet $py_files +git add $py_files diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b66df1c..5cf7765 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -51,6 +51,20 @@ The IDA-backed suites need an interpreter that has `textual`, `idapro` and import instead of doing it at module top. Please don't break that — it's what keeps the fast gate fast and lets people without IDA contribute at all. +## Formatting + +Python is formatted by ruff (`ruff.toml` pins the exact version; the config is +deliberately default: black-style layout plus import sorting, nothing else). +Install the pre-commit hook once and forget about it: + +```bash +uv sync --extra dev # puts the pinned ruff in .venv +git config core.hooksPath .githooks # formats what you stage +``` + +Mechanical reformat commits are listed in `.git-blame-ignore-revs`; +`git config blame.ignoreRevsFile .git-blame-ignore-revs` keeps blame useful. + ## Sending a change - Run at least `python3 tests/run.py --fast` before you push. If your change diff --git a/idatui/remote_ops.py b/idatui/remote_ops.py index cbd1d49..b07bb71 100644 --- a/idatui/remote_ops.py +++ b/idatui/remote_ops.py @@ -1,7 +1,6 @@ """Typed remote operations executed through ida-nexus.""" from __future__ import annotations -# ruff: noqa import threading from collections.abc import Callable diff --git a/pyproject.toml b/pyproject.toml index dc68d12..c16d406 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ dependencies = [ ] [project.optional-dependencies] -dev = ["pytest>=8"] +dev = ["pytest>=8", "ruff==0.16.3"] # ruff.toml pins the same version # The graph view's SESE layout engine (`e` in graph mode) is optional: without # it, layout falls back to the pure-python engine. It is deliberately NOT listed # as a dependency -- PyPI's pytriskel has no wheel for current Pythons, no sdist, diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..6d0f877 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,33 @@ +# How the Python in this tree is formatted, and how its imports are ordered. +# `.githooks/pre-commit` applies both to what you staged (install it with +# `git config core.hooksPath .githooks`); `ruff format --check .` asks. +# +# Adopted when PR #1 arrived black-formatted: ruff's formatter IS black's +# output, it is one static binary instead of a Python dependency chain, and it +# is fast enough that a pre-commit hook is free. Configuration is deliberately +# left at the defaults -- every knob turned here is an argument somebody has to +# have again later. + +# The version this tree was formatted with, enforced by ruff itself: a +# formatter whose output moves between releases turns "formatted" into +# "formatted by whoever committed last", and the diff lands on the next +# person. A mismatch is refused with both versions named rather than quietly +# reformatting everything. Bumping it is one line here, one in the hook's +# install hint, and a reformat commit (added to .git-blame-ignore-revs) -- +# the honest cost of a new version. +required-version = "==0.16.3" + +target-version = "py311" +line-length = 88 + +[lint] +# Import sorting, and nothing else. `ruff check` can enforce a great deal more +# and one day it might, but this hook's job is formatting: one that also +# refused a commit over an unused variable would be a different feature, and +# not one anybody asked for. +# +# Sorting is safe here because ruff sorts import BLOCKS and never hoists across +# a statement: every test file's `sys.path.insert(...)` sits between the +# standard library and the `idatui` imports, and that line is exactly what +# makes them importable. +select = ["I"] @@ -61,6 +61,7 @@ dependencies = [ [package.optional-dependencies] dev = [ { name = "pytest" }, + { name = "ruff" }, ] [package.metadata] @@ -68,6 +69,7 @@ requires-dist = [ { name = "ida-nexus", specifier = ">=0.7.0" }, { name = "pygments", specifier = ">=2" }, { name = "pytest", marker = "extra == 'dev'", specifier = ">=8" }, + { name = "ruff", marker = "extra == 'dev'", specifier = "==0.16.3" }, { name = "textual", specifier = ">=8" }, ] provides-extras = ["dev"] @@ -197,6 +199,31 @@ wheels = [ ] [[package]] +name = "ruff" +version = "0.16.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/61/b3/3213589383f8f1b3938781bd1278713f6d18621a14992b3e81fefb8a5ef9/ruff-0.16.3.tar.gz", hash = "sha256:e76d33a347661a84b5be6d043d0347fdc745dfdcf825a8f4fed64b5e26eebdf2", size = 4891904, upload-time = "2026-08-13T15:17:13.381Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bf/96/493770daebd68c0a67f1549fdf519f53be51fc435186c0585bcc272fd76c/ruff-0.16.3-py3-none-linux_armv6l.whl", hash = "sha256:0c5710e247a58a4521e66e124ba9a74655b414f61ba3a2e9e3811e11098f48f7", size = 10902799, upload-time = "2026-08-13T15:16:27.382Z" }, + { url = "https://files.pythonhosted.org/packages/5e/e6/2becf3942fddc29a29b8df47691d456fb1085391a694f74d84513251418c/ruff-0.16.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:fe155130631a2471fd2e14a7a664a4dfbd7194b8229c3d7b2a40b21178639081", size = 11135539, upload-time = "2026-08-13T15:16:30.87Z" }, + { url = "https://files.pythonhosted.org/packages/3e/1e/4b8b72f0d006dbf19326aa99f9ca0ee2ff374187c4d301cf529a51aa06fe/ruff-0.16.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e2ed719e14aa64d895c2ee922594a90a43c861a93f0575a95ff8c47cdbd13eb9", size = 10475095, upload-time = "2026-08-13T15:16:33.259Z" }, + { url = "https://files.pythonhosted.org/packages/92/32/2201fa49ba1f6c101ee321e83f051ac7a4b8d07b0ef6b4d3f2772b302275/ruff-0.16.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9e0b1da805eb043654645d74d5de1e5ce2edc686e40790d2b86f56d71cc06a84", size = 10668771, upload-time = "2026-08-13T15:16:35.65Z" }, + { url = "https://files.pythonhosted.org/packages/c3/66/4afc5c8363bd04d45effce1b7c8713ca037d7a6740b7451a2403a6e3a972/ruff-0.16.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a37bdea0bbe21780f590bf437d6412c8c4e1b6cd010f91a65c2c40c5e5f5f870", size = 10699568, upload-time = "2026-08-13T15:16:38.195Z" }, + { url = "https://files.pythonhosted.org/packages/53/fd/c67d246bf36bf1698551c56de39e95cd07f70e64433e0098e6267d77061b/ruff-0.16.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:09571e6d1288ed9be475207a3ac04ada404f1cd898104be0f6ab8d7df438575b", size = 11499365, upload-time = "2026-08-13T15:16:40.623Z" }, + { url = "https://files.pythonhosted.org/packages/67/0b/00ecbceb99a263af7b12f6f05ac3c92bc47b905e91adc3f207a836e3bc01/ruff-0.16.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2c18c5a101eb540010638cc1ff3c84944d3adb3df62b8d98ca8f22ba484d3413", size = 12311728, upload-time = "2026-08-13T15:16:43.564Z" }, + { url = "https://files.pythonhosted.org/packages/54/b2/b7b3bb54f4d3f7db504e476ad4ab8de530dceebe2c061384b2757ee419e8/ruff-0.16.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8457c44f15033c85ddbb77b15d451df9e24e4bd03b628396dd3610cedc3b8f82", size = 11699896, upload-time = "2026-08-13T15:16:46.209Z" }, + { url = "https://files.pythonhosted.org/packages/c7/30/4c468429ac195addc5ee1b717b6ab1b66632786737ca3b2ed3443fb0c26a/ruff-0.16.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:294b95c4ae0cda9388525c2047778aa758d6b8d4bb876fd4e9eaa3ebc92343eb", size = 11058736, upload-time = "2026-08-13T15:16:48.823Z" }, + { url = "https://files.pythonhosted.org/packages/43/67/7a113cdaddf24b64d7f75b1242a99d04c82fcef4f6921fdbb832beaffb5f/ruff-0.16.3-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:3d0c7c40c87c2a820509c31ba007968da6e1306468c067b2d82fbfdbcd0e8474", size = 11586911, upload-time = "2026-08-13T15:16:51.913Z" }, + { url = "https://files.pythonhosted.org/packages/f1/c1/2e66f24c0f3ead25a5e660111778685e505e5da353c82802bf49f0cbe7b9/ruff-0.16.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:9f738c0fdfa8eed0b2ce7fb27ee7258208a92a68d7949e62aa15164bc7b389da", size = 10954265, upload-time = "2026-08-13T15:16:54.763Z" }, + { url = "https://files.pythonhosted.org/packages/c2/ba/4cee23bf52cba9a058d3726de623624daf50ef9638868edd86f4126157f6/ruff-0.16.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fb785f0be25abe69d320415cd4f833b59e17ba7613d9ba6a958023b6bceb0a50", size = 10709886, upload-time = "2026-08-13T15:16:57.339Z" }, + { url = "https://files.pythonhosted.org/packages/82/df/7da7194fa5d9dc0a285f7e6fa5a4722e7c63faac0b45b614ded9314363a1/ruff-0.16.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c5536e3acfbf9563085aa2be7b13c629c3077e902afc5b941ac44024dbb9f506", size = 11210392, upload-time = "2026-08-13T15:17:00.171Z" }, + { url = "https://files.pythonhosted.org/packages/35/85/7795f6e817af050e7517bf3e7aa9b061cce70ef33d280aad902c956c1ecf/ruff-0.16.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:a2d85c02f9b8e165d85e6779184d38c4132de12603dab59c51c28e22584f9e4d", size = 11626910, upload-time = "2026-08-13T15:17:03.299Z" }, + { url = "https://files.pythonhosted.org/packages/78/9b/475b927cf27a5cbbda3c7bafb69ed6ff77e1d7923d5d85f17c2749d7ae32/ruff-0.16.3-py3-none-win32.whl", hash = "sha256:388cdf2166642bd9b13d52b5932d3170f34f8abed7e8d9a855f1d84b83645a0a", size = 10931415, upload-time = "2026-08-13T15:17:05.726Z" }, + { url = "https://files.pythonhosted.org/packages/b2/99/e2a2bfc4fbf0a1e8a916bc9ebe6fe6c58cc34c28e0ffc6ce281d572d1c2e/ruff-0.16.3-py3-none-win_amd64.whl", hash = "sha256:e80a7d69ca2a6d1c4d352ec91458cdca6e56c83cdbcabd93e4abe1e53591d948", size = 11445993, upload-time = "2026-08-13T15:17:08.353Z" }, + { url = "https://files.pythonhosted.org/packages/69/3e/4132e539aed78c148854d4997a2685b0ed4dc4e87110b59ce528564e184e/ruff-0.16.3-py3-none-win_arm64.whl", hash = "sha256:b8ca152da82c1acc1fa8d5874b15951935f0eef46f10e6954c83859011b6178a", size = 11399302, upload-time = "2026-08-13T15:17:10.908Z" }, +] + +[[package]] name = "textual" version = "8.2.8" source = { registry = "https://pypi.org/simple" } |
