ruff-sync 0.0.2.dev0__tar.gz → 0.0.3.dev1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/.agents/TESTING.md +3 -3
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/.agents/workflows/add-test-case.md +4 -4
- ruff_sync-0.0.3.dev1/.git-blame-ignore-revs +20 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/.github/dependabot.yml +5 -2
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/PKG-INFO +97 -73
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/README.md +96 -72
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/pyproject.toml +2 -2
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/ruff_sync.py +191 -67
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/scripts/dogfood.sh +3 -3
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/scripts/dogfood_check.sh +2 -2
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tasks.py +2 -8
- ruff_sync-0.0.3.dev1/tests/lifecycle_tomls/readme_excludes_final.toml +39 -0
- ruff_sync-0.0.3.dev1/tests/lifecycle_tomls/readme_excludes_initial.toml +39 -0
- ruff_sync-0.0.3.dev1/tests/lifecycle_tomls/readme_excludes_upstream.toml +37 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/test_basic.py +4 -12
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/test_corner_cases.py +1 -3
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/test_e2e.py +78 -21
- ruff_sync-0.0.3.dev1/tests/test_url_handling.py +113 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/uv.lock +1 -1
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/.github/workflows/ci.yaml +0 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/.github/workflows/complexity.yaml +0 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/.gitignore +0 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/.pre-commit-config.yaml +0 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/AGENTS.md +0 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/LICENSE.md +0 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/codecov.yml +0 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/ruff_sync_banner.png +0 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/__init__.py +0 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/lifecycle_tomls/no_changes_final.toml +0 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/lifecycle_tomls/no_changes_initial.toml +0 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/lifecycle_tomls/no_changes_upstream.toml +0 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/lifecycle_tomls/no_dotted_keys_final.toml +0 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/lifecycle_tomls/no_dotted_keys_initial.toml +0 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/lifecycle_tomls/no_dotted_keys_upstream.toml +0 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/lifecycle_tomls/no_ruff_cfg_final.toml +0 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/lifecycle_tomls/no_ruff_cfg_initial.toml +0 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/lifecycle_tomls/no_ruff_cfg_upstream.toml +0 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/lifecycle_tomls/standard_final.toml +0 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/lifecycle_tomls/standard_initial.toml +0 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/lifecycle_tomls/standard_upstream.toml +0 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/ruff.toml +0 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/test_check.py +0 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/test_project.py +0 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/test_toml_operations.py +0 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/test_whitespace.py +0 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/w_ruff_sync_cfg/pyproject.toml +0 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/wo_ruff_cfg/pyproject.toml +0 -0
- {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/wo_ruff_sync_cfg/pyproject.toml +0 -0
|
@@ -13,7 +13,7 @@ This document defines the mandatory testing standards and patterns for the `ruff
|
|
|
13
13
|
|
|
14
14
|
## 2. Tooling and Environment
|
|
15
15
|
|
|
16
|
-
- **Execution**: Always run tests using `
|
|
16
|
+
- **Execution**: Always run tests using `uv run pytest -vv`.
|
|
17
17
|
- **Async Tests**: We use `pytest-asyncio` in **strict mode**.
|
|
18
18
|
- Always decorate async tests with `@pytest.mark.asyncio`.
|
|
19
19
|
- **HTTP Mocking**: Use [respx](https://github.com/lundberg/respx) for all network interactions.
|
|
@@ -79,7 +79,7 @@ Each test case consists of three files in `tests/lifecycle_tomls/`:
|
|
|
79
79
|
### Scaffolding New Cases
|
|
80
80
|
Use the provided Invoke task to create a new case from a template:
|
|
81
81
|
```bash
|
|
82
|
-
|
|
82
|
+
uv run invoke new-case --name <case_name> --description "Description of the edge case"
|
|
83
83
|
```
|
|
84
84
|
|
|
85
85
|
## 5. Standard Assertions for Merges
|
|
@@ -109,5 +109,5 @@ def test_my_edge_case():
|
|
|
109
109
|
## 6. Code Coverage
|
|
110
110
|
|
|
111
111
|
We target **high coverage** for `ruff_sync.py`.
|
|
112
|
-
- Run coverage locally: `
|
|
112
|
+
- Run coverage locally: `uv run coverage run -m pytest -vv && uv run coverage report`
|
|
113
113
|
- New features MUST include unit tests in `tests/test_basic.py` or specialized files like `tests/test_whitespace.py` if they involve formatting logic.
|
|
@@ -12,7 +12,7 @@ Follow these steps to add a new end-to-end (E2E) test case for `ruff-sync` to te
|
|
|
12
12
|
// turbo
|
|
13
13
|
1. Run the `new-case` task to generate the file triple:
|
|
14
14
|
```bash
|
|
15
|
-
|
|
15
|
+
uv run invoke new-case --name <case_name> --description "Description of the test case"
|
|
16
16
|
```
|
|
17
17
|
*Replace `<case_name>` with a simple name (e.g., `dotted_keys`). This creates files in `tests/lifecycle_tomls/`.*
|
|
18
18
|
|
|
@@ -25,7 +25,7 @@ Follow these steps to add a new end-to-end (E2E) test case for `ruff-sync` to te
|
|
|
25
25
|
// turbo
|
|
26
26
|
1. Execute the tests to ensure the new case is picked up:
|
|
27
27
|
```bash
|
|
28
|
-
|
|
28
|
+
uv run pytest -vv tests/test_e2e.py
|
|
29
29
|
```
|
|
30
30
|
*The E2E suite automatically discovers all file triples in the `lifecycle_tomls/` directory.*
|
|
31
31
|
|
|
@@ -33,6 +33,6 @@ Follow these steps to add a new end-to-end (E2E) test case for `ruff-sync` to te
|
|
|
33
33
|
// turbo
|
|
34
34
|
1. Ensure the new TOML files don't break any project rules:
|
|
35
35
|
```bash
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
uv run invoke lint --check
|
|
37
|
+
uv run invoke types
|
|
38
38
|
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# .git-blame-ignore-revs
|
|
2
|
+
#
|
|
3
|
+
# This file contains a list of Git commit hashes that should be ignored by
|
|
4
|
+
# `git blame`. It is primarily used for large-scale refactorings, purely
|
|
5
|
+
# stylistic changes, or mass-formatting that would otherwise "pollute" the
|
|
6
|
+
# blame history without changing the actual logic.
|
|
7
|
+
#
|
|
8
|
+
# Use this file VERY SELECTIVELY. Only include commits that are truly
|
|
9
|
+
# non-functional and where seeing the original author in the blame history
|
|
10
|
+
# provides no useful context for the code's behavior.
|
|
11
|
+
#
|
|
12
|
+
# Entries should follow this pattern:
|
|
13
|
+
#
|
|
14
|
+
# # <Brief description of the change>
|
|
15
|
+
# # PR: https://github.com/Kilo59/ruff-sync/pull/<PR_NUMBER>
|
|
16
|
+
# <COMMIT_HASH>
|
|
17
|
+
|
|
18
|
+
# Changing linelength to 100, other changes are tests and readme only
|
|
19
|
+
# PR: https://github.com/Kilo59/ruff-sync/pull/76
|
|
20
|
+
75f9e34ced8537a32cdc62f85ae0f8e464756cee
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
# Please see the documentation for all configuration options:
|
|
2
2
|
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
|
3
|
+
# https://docs.astral.sh/uv/guides/integration/dependency-bots/#dependabot
|
|
3
4
|
|
|
4
5
|
version: 2
|
|
5
6
|
updates:
|
|
6
7
|
- package-ecosystem: "uv"
|
|
7
|
-
directory: "/"
|
|
8
|
+
directory: "/"
|
|
8
9
|
schedule:
|
|
9
|
-
interval:
|
|
10
|
+
interval: "weekly"
|
|
11
|
+
cooldown:
|
|
12
|
+
default-days: 7
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ruff-sync
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3.dev1
|
|
4
4
|
Summary: Synchronize Ruff linter configuration across projects
|
|
5
5
|
Project-URL: Homepage, https://github.com/Kilo59/ruff-sync
|
|
6
6
|
Project-URL: Documentation, https://github.com/Kilo59/ruff-sync#readme
|
|
@@ -40,7 +40,9 @@ Description-Content-Type: text/markdown
|
|
|
40
40
|
|
|
41
41
|
# ruff-sync
|
|
42
42
|
|
|
43
|
-
**Keep your Ruff config consistent across
|
|
43
|
+
**Keep your Ruff config consistent across multiple projects.**
|
|
44
|
+
|
|
45
|
+
`ruff-sync` is a CLI tool that pulls a canonical [Ruff](https://docs.astral.sh/ruff/) configuration from an upstream `pyproject.toml` (hosted anywhere — GitHub, GitLab, or any raw URL) and merges it into your local project, preserving your comments, formatting, and project-specific overrides.
|
|
44
46
|
|
|
45
47
|
---
|
|
46
48
|
|
|
@@ -49,16 +51,15 @@ Description-Content-Type: text/markdown
|
|
|
49
51
|
- [The Problem](#the-problem)
|
|
50
52
|
- [How It Works](#how-it-works)
|
|
51
53
|
- [Quick Start](#quick-start)
|
|
52
|
-
- [Install](#install)
|
|
53
|
-
- [Usage](#usage)
|
|
54
54
|
- [Key Features](#key-features)
|
|
55
|
-
- [CI Integration](#ci-integration)
|
|
56
55
|
- [Configuration](#configuration)
|
|
56
|
+
- [CI Integration](#ci-integration)
|
|
57
|
+
- [Example Workflow](#example-workflow)
|
|
58
|
+
- [Detailed Check Logic](#detailed-check-logic)
|
|
57
59
|
- [Contributing](#contributing)
|
|
60
|
+
- [Dogfooding](#dogfooding)
|
|
58
61
|
- [License](#license)
|
|
59
62
|
|
|
60
|
-
`ruff-sync` is a CLI tool that pulls a canonical [Ruff](https://docs.astral.sh/ruff/) configuration from an upstream `pyproject.toml` (hosted anywhere — GitHub, GitLab, a raw URL) and merges it into your local project, preserving your comments, formatting, and project-specific overrides.
|
|
61
|
-
|
|
62
63
|
## The Problem
|
|
63
64
|
|
|
64
65
|
If you maintain more than one Python project, you've probably copy-pasted your `[tool.ruff]` config between repos more than once. When you decide to enable a new rule or bump your target Python version, you get to do it again — in _every_ repo. Configs drift, standards diverge, and your "shared" style guide becomes a polite suggestion.
|
|
@@ -75,7 +76,7 @@ Ruff's `extend` is perfect inside a monorepo, but if your projects live in **sep
|
|
|
75
76
|
|
|
76
77
|
**That's what `ruff-sync` does.**
|
|
77
78
|
|
|
78
|
-
|
|
79
|
+
### How It Works
|
|
79
80
|
|
|
80
81
|
```
|
|
81
82
|
┌─────────────────────────────┐
|
|
@@ -140,10 +141,16 @@ uv tool install git+https://github.com/Kilo59/ruff-sync
|
|
|
140
141
|
### Usage
|
|
141
142
|
|
|
142
143
|
```console
|
|
143
|
-
# Sync from a GitHub
|
|
144
|
+
# Sync from a GitHub repository (defaults to main/pyproject.toml)
|
|
145
|
+
ruff-sync https://github.com/my-org/standards
|
|
146
|
+
|
|
147
|
+
# Or a direct blob/file URL (auto-converts to raw)
|
|
144
148
|
ruff-sync https://github.com/my-org/standards/blob/main/pyproject.toml
|
|
145
149
|
|
|
146
|
-
#
|
|
150
|
+
# GitLab support (including nested projects)
|
|
151
|
+
ruff-sync https://gitlab.com/my-org/my-group/nested/standards
|
|
152
|
+
|
|
153
|
+
# Once configured in pyproject.toml (see Configuration), simply run:
|
|
147
154
|
ruff-sync
|
|
148
155
|
|
|
149
156
|
# Sync into a specific project directory
|
|
@@ -153,61 +160,60 @@ ruff-sync --source ./my-project
|
|
|
153
160
|
ruff-sync --exclude lint.per-file-ignores lint.ignore
|
|
154
161
|
|
|
155
162
|
# Check if your local config is in sync (useful in CI)
|
|
156
|
-
ruff-sync check https://github.com/my-org/standards
|
|
163
|
+
ruff-sync check https://github.com/my-org/standards
|
|
157
164
|
|
|
158
165
|
# Semantic check — ignore cosmetic differences like comments and whitespace
|
|
159
166
|
ruff-sync check --semantic
|
|
160
167
|
```
|
|
161
168
|
|
|
162
|
-
|
|
169
|
+
Run `ruff-sync --help` for full details on all available options.
|
|
163
170
|
|
|
164
|
-
|
|
171
|
+
## Key Features
|
|
165
172
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
optional arguments:
|
|
174
|
-
-h, --help show this help message and exit
|
|
175
|
-
--source SOURCE The directory or file to sync. Default: .
|
|
176
|
-
--exclude EXCLUDE [EXCLUDE ...]
|
|
177
|
-
Exclude certain ruff config keys. Default: lint.per-file-ignores
|
|
178
|
-
-v, --verbose Increase verbosity. -v for INFO, -vv for DEBUG.
|
|
179
|
-
```
|
|
173
|
+
- **Format-preserving merges** — Uses [tomlkit](https://github.com/sdispater/tomlkit) under the hood, so your comments, whitespace, and TOML structure are preserved. No reformatting surprises.
|
|
174
|
+
- **GitHub & GitLab URL support** — Automatically converts GitHub/GitLab repository URLs or blob URLs to raw content URLs.
|
|
175
|
+
- **Selective exclusions** — Keep project-specific overrides (like `per-file-ignores` or `target-version`) from being clobbered by the upstream config.
|
|
176
|
+
- **Works with any host** — GitHub, GitLab, Bitbucket, or any raw URL that serves a `pyproject.toml`.
|
|
177
|
+
- **CI-ready `check` command** — Verify that your local config is in sync without modifying anything. Exits 1 if out of sync, making it perfect for pre-merge gates. ([See detailed logic](#detailed-check-logic))
|
|
178
|
+
- **Semantic mode** — Use `--semantic` to ignore cosmetic differences (comments, whitespace) and only fail on real value changes.
|
|
180
179
|
|
|
181
|
-
|
|
180
|
+
## Configuration
|
|
182
181
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
182
|
+
You can configure `ruff-sync` itself in your `pyproject.toml`:
|
|
183
|
+
|
|
184
|
+
```toml
|
|
185
|
+
[tool.ruff-sync]
|
|
186
|
+
# The source of truth for your Ruff configuration
|
|
187
|
+
upstream = "https://github.com/my-org/standards"
|
|
188
|
+
|
|
189
|
+
# Use simple names for top-level keys, and dotted paths for nested keys
|
|
190
|
+
exclude = [
|
|
191
|
+
"target-version", # Top-level [tool.ruff] key — projects target different Python versions
|
|
192
|
+
"lint.per-file-ignores", # Project-specific file overrides
|
|
193
|
+
"lint.ignore", # Project-specific rule suppressions
|
|
194
|
+
"lint.isort.known-first-party", # Every project has different first-party packages
|
|
195
|
+
"lint.flake8-tidy-imports.banned-api", # Entire plugin section — project-specific banned APIs
|
|
196
|
+
"lint.pydocstyle.convention", # Teams may disagree on google vs numpy vs pep257
|
|
197
|
+
]
|
|
199
198
|
```
|
|
200
199
|
|
|
201
|
-
|
|
200
|
+
This sets the default upstream and exclusions so you don't need to pass them on the command line every time.
|
|
201
|
+
*Note: Any explicitly provided CLI arguments will override the values in `pyproject.toml`.*
|
|
202
202
|
|
|
203
|
-
|
|
203
|
+
### Advanced Configuration
|
|
204
204
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
-
|
|
209
|
-
|
|
210
|
-
|
|
205
|
+
For more complex setups, you can also configure the default branch and parent directory used when resolving repository URLs (e.g. `https://github.com/my-org/standards`):
|
|
206
|
+
|
|
207
|
+
```toml
|
|
208
|
+
[tool.ruff-sync]
|
|
209
|
+
upstream = "https://github.com/my-org/standards"
|
|
210
|
+
|
|
211
|
+
# Use a specific branch or tag (default: "main")
|
|
212
|
+
branch = "develop"
|
|
213
|
+
|
|
214
|
+
# Specify a parent directory if pyproject.toml is not at the repo root
|
|
215
|
+
path = "config/ruff"
|
|
216
|
+
```
|
|
211
217
|
|
|
212
218
|
## CI Integration
|
|
213
219
|
|
|
@@ -238,26 +244,6 @@ $ ruff-sync check --semantic
|
|
|
238
244
|
]
|
|
239
245
|
```
|
|
240
246
|
|
|
241
|
-
## Configuration
|
|
242
|
-
|
|
243
|
-
You can configure `ruff-sync` itself in your `pyproject.toml`:
|
|
244
|
-
|
|
245
|
-
```toml
|
|
246
|
-
[tool.ruff-sync]
|
|
247
|
-
# The source of truth for your ruff configuration
|
|
248
|
-
upstream = "https://github.com/my-org/standards/blob/main/pyproject.toml"
|
|
249
|
-
|
|
250
|
-
# Use simple names for top-level keys, and dotted paths for nested keys
|
|
251
|
-
exclude = [
|
|
252
|
-
"target-version", # A top-level key under [tool.ruff]
|
|
253
|
-
"lint.per-file-ignores", # A nested key under [tool.ruff.lint]
|
|
254
|
-
"lint.ignore"
|
|
255
|
-
]
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
This sets the default exclusions so you don't need to pass `--exclude` on the command line every time.
|
|
259
|
-
*Note: Any explicitly provided CLI arguments will override the list in `pyproject.toml`.*
|
|
260
|
-
|
|
261
247
|
## Example Workflow
|
|
262
248
|
|
|
263
249
|
A typical setup for an organization:
|
|
@@ -268,11 +254,49 @@ A typical setup for an organization:
|
|
|
268
254
|
|
|
269
255
|
```console
|
|
270
256
|
# In each project repo:
|
|
271
|
-
ruff-sync https://github.com/my-org/python-standards
|
|
257
|
+
ruff-sync https://github.com/my-org/python-standards
|
|
272
258
|
git diff pyproject.toml # review the changes
|
|
273
259
|
git commit -am "sync ruff config from upstream"
|
|
274
260
|
```
|
|
275
261
|
|
|
262
|
+
## Detailed Check Logic
|
|
263
|
+
|
|
264
|
+
When you run `ruff-sync check`, it follows this process to determine if your project has drifted from the upstream source:
|
|
265
|
+
|
|
266
|
+
```mermaid
|
|
267
|
+
flowchart TD
|
|
268
|
+
Start([Start]) --> Local[Read Local pyproject.toml]
|
|
269
|
+
Local --> Upstream[Download Upstream pyproject.toml]
|
|
270
|
+
Upstream --> Extract[Extract tool.ruff section]
|
|
271
|
+
Extract --> Exclude[Apply Exclusions]
|
|
272
|
+
Exclude --> Merge[Perform in-memory Merge]
|
|
273
|
+
|
|
274
|
+
subgraph Comparison [Comparison Logic]
|
|
275
|
+
direction TB
|
|
276
|
+
SemanticNode{--semantic?}
|
|
277
|
+
SemanticNode -- Yes --> Unwrap[Unwrap TOML objects to Python Dicts]
|
|
278
|
+
Unwrap --> CompareVal[Compare Key/Value Pairs]
|
|
279
|
+
SemanticNode -- No --> CompareFull[Compare Full File Strings]
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
Merge --> Comparison
|
|
283
|
+
|
|
284
|
+
CompareVal --> ResultNode{Match?}
|
|
285
|
+
CompareFull --> ResultNode
|
|
286
|
+
|
|
287
|
+
ResultNode -- Yes --> Success([Exit 0: In Sync])
|
|
288
|
+
ResultNode -- No --> Diff[Generate Diff]
|
|
289
|
+
Diff --> Fail([Exit 1: Out of Sync])
|
|
290
|
+
|
|
291
|
+
%% Styling
|
|
292
|
+
style Start fill:#4a90e2,color:#fff,stroke:#357abd
|
|
293
|
+
style Success fill:#48c774,color:#fff,stroke:#36975a
|
|
294
|
+
style Fail fill:#f14668,color:#fff,stroke:#b2334b
|
|
295
|
+
style ResultNode fill:#ffdd57,color:#4a4a4a,stroke:#d4b106
|
|
296
|
+
style Comparison fill:none,stroke:#9e9e9e,stroke-dasharray: 5 5,stroke-width:2px
|
|
297
|
+
style SemanticNode fill:#f4f4f4,color:#363636,stroke:#dbdbdb
|
|
298
|
+
```
|
|
299
|
+
|
|
276
300
|
## Contributing
|
|
277
301
|
|
|
278
302
|
This project uses:
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
|
|
11
11
|
# ruff-sync
|
|
12
12
|
|
|
13
|
-
**Keep your Ruff config consistent across
|
|
13
|
+
**Keep your Ruff config consistent across multiple projects.**
|
|
14
|
+
|
|
15
|
+
`ruff-sync` is a CLI tool that pulls a canonical [Ruff](https://docs.astral.sh/ruff/) configuration from an upstream `pyproject.toml` (hosted anywhere — GitHub, GitLab, or any raw URL) and merges it into your local project, preserving your comments, formatting, and project-specific overrides.
|
|
14
16
|
|
|
15
17
|
---
|
|
16
18
|
|
|
@@ -19,16 +21,15 @@
|
|
|
19
21
|
- [The Problem](#the-problem)
|
|
20
22
|
- [How It Works](#how-it-works)
|
|
21
23
|
- [Quick Start](#quick-start)
|
|
22
|
-
- [Install](#install)
|
|
23
|
-
- [Usage](#usage)
|
|
24
24
|
- [Key Features](#key-features)
|
|
25
|
-
- [CI Integration](#ci-integration)
|
|
26
25
|
- [Configuration](#configuration)
|
|
26
|
+
- [CI Integration](#ci-integration)
|
|
27
|
+
- [Example Workflow](#example-workflow)
|
|
28
|
+
- [Detailed Check Logic](#detailed-check-logic)
|
|
27
29
|
- [Contributing](#contributing)
|
|
30
|
+
- [Dogfooding](#dogfooding)
|
|
28
31
|
- [License](#license)
|
|
29
32
|
|
|
30
|
-
`ruff-sync` is a CLI tool that pulls a canonical [Ruff](https://docs.astral.sh/ruff/) configuration from an upstream `pyproject.toml` (hosted anywhere — GitHub, GitLab, a raw URL) and merges it into your local project, preserving your comments, formatting, and project-specific overrides.
|
|
31
|
-
|
|
32
33
|
## The Problem
|
|
33
34
|
|
|
34
35
|
If you maintain more than one Python project, you've probably copy-pasted your `[tool.ruff]` config between repos more than once. When you decide to enable a new rule or bump your target Python version, you get to do it again — in _every_ repo. Configs drift, standards diverge, and your "shared" style guide becomes a polite suggestion.
|
|
@@ -45,7 +46,7 @@ Ruff's `extend` is perfect inside a monorepo, but if your projects live in **sep
|
|
|
45
46
|
|
|
46
47
|
**That's what `ruff-sync` does.**
|
|
47
48
|
|
|
48
|
-
|
|
49
|
+
### How It Works
|
|
49
50
|
|
|
50
51
|
```
|
|
51
52
|
┌─────────────────────────────┐
|
|
@@ -110,10 +111,16 @@ uv tool install git+https://github.com/Kilo59/ruff-sync
|
|
|
110
111
|
### Usage
|
|
111
112
|
|
|
112
113
|
```console
|
|
113
|
-
# Sync from a GitHub
|
|
114
|
+
# Sync from a GitHub repository (defaults to main/pyproject.toml)
|
|
115
|
+
ruff-sync https://github.com/my-org/standards
|
|
116
|
+
|
|
117
|
+
# Or a direct blob/file URL (auto-converts to raw)
|
|
114
118
|
ruff-sync https://github.com/my-org/standards/blob/main/pyproject.toml
|
|
115
119
|
|
|
116
|
-
#
|
|
120
|
+
# GitLab support (including nested projects)
|
|
121
|
+
ruff-sync https://gitlab.com/my-org/my-group/nested/standards
|
|
122
|
+
|
|
123
|
+
# Once configured in pyproject.toml (see Configuration), simply run:
|
|
117
124
|
ruff-sync
|
|
118
125
|
|
|
119
126
|
# Sync into a specific project directory
|
|
@@ -123,61 +130,60 @@ ruff-sync --source ./my-project
|
|
|
123
130
|
ruff-sync --exclude lint.per-file-ignores lint.ignore
|
|
124
131
|
|
|
125
132
|
# Check if your local config is in sync (useful in CI)
|
|
126
|
-
ruff-sync check https://github.com/my-org/standards
|
|
133
|
+
ruff-sync check https://github.com/my-org/standards
|
|
127
134
|
|
|
128
135
|
# Semantic check — ignore cosmetic differences like comments and whitespace
|
|
129
136
|
ruff-sync check --semantic
|
|
130
137
|
```
|
|
131
138
|
|
|
132
|
-
|
|
139
|
+
Run `ruff-sync --help` for full details on all available options.
|
|
133
140
|
|
|
134
|
-
|
|
141
|
+
## Key Features
|
|
135
142
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
optional arguments:
|
|
144
|
-
-h, --help show this help message and exit
|
|
145
|
-
--source SOURCE The directory or file to sync. Default: .
|
|
146
|
-
--exclude EXCLUDE [EXCLUDE ...]
|
|
147
|
-
Exclude certain ruff config keys. Default: lint.per-file-ignores
|
|
148
|
-
-v, --verbose Increase verbosity. -v for INFO, -vv for DEBUG.
|
|
149
|
-
```
|
|
143
|
+
- **Format-preserving merges** — Uses [tomlkit](https://github.com/sdispater/tomlkit) under the hood, so your comments, whitespace, and TOML structure are preserved. No reformatting surprises.
|
|
144
|
+
- **GitHub & GitLab URL support** — Automatically converts GitHub/GitLab repository URLs or blob URLs to raw content URLs.
|
|
145
|
+
- **Selective exclusions** — Keep project-specific overrides (like `per-file-ignores` or `target-version`) from being clobbered by the upstream config.
|
|
146
|
+
- **Works with any host** — GitHub, GitLab, Bitbucket, or any raw URL that serves a `pyproject.toml`.
|
|
147
|
+
- **CI-ready `check` command** — Verify that your local config is in sync without modifying anything. Exits 1 if out of sync, making it perfect for pre-merge gates. ([See detailed logic](#detailed-check-logic))
|
|
148
|
+
- **Semantic mode** — Use `--semantic` to ignore cosmetic differences (comments, whitespace) and only fail on real value changes.
|
|
150
149
|
|
|
151
|
-
|
|
150
|
+
## Configuration
|
|
152
151
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
152
|
+
You can configure `ruff-sync` itself in your `pyproject.toml`:
|
|
153
|
+
|
|
154
|
+
```toml
|
|
155
|
+
[tool.ruff-sync]
|
|
156
|
+
# The source of truth for your Ruff configuration
|
|
157
|
+
upstream = "https://github.com/my-org/standards"
|
|
158
|
+
|
|
159
|
+
# Use simple names for top-level keys, and dotted paths for nested keys
|
|
160
|
+
exclude = [
|
|
161
|
+
"target-version", # Top-level [tool.ruff] key — projects target different Python versions
|
|
162
|
+
"lint.per-file-ignores", # Project-specific file overrides
|
|
163
|
+
"lint.ignore", # Project-specific rule suppressions
|
|
164
|
+
"lint.isort.known-first-party", # Every project has different first-party packages
|
|
165
|
+
"lint.flake8-tidy-imports.banned-api", # Entire plugin section — project-specific banned APIs
|
|
166
|
+
"lint.pydocstyle.convention", # Teams may disagree on google vs numpy vs pep257
|
|
167
|
+
]
|
|
169
168
|
```
|
|
170
169
|
|
|
171
|
-
|
|
170
|
+
This sets the default upstream and exclusions so you don't need to pass them on the command line every time.
|
|
171
|
+
*Note: Any explicitly provided CLI arguments will override the values in `pyproject.toml`.*
|
|
172
172
|
|
|
173
|
-
|
|
173
|
+
### Advanced Configuration
|
|
174
174
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
-
|
|
179
|
-
|
|
180
|
-
|
|
175
|
+
For more complex setups, you can also configure the default branch and parent directory used when resolving repository URLs (e.g. `https://github.com/my-org/standards`):
|
|
176
|
+
|
|
177
|
+
```toml
|
|
178
|
+
[tool.ruff-sync]
|
|
179
|
+
upstream = "https://github.com/my-org/standards"
|
|
180
|
+
|
|
181
|
+
# Use a specific branch or tag (default: "main")
|
|
182
|
+
branch = "develop"
|
|
183
|
+
|
|
184
|
+
# Specify a parent directory if pyproject.toml is not at the repo root
|
|
185
|
+
path = "config/ruff"
|
|
186
|
+
```
|
|
181
187
|
|
|
182
188
|
## CI Integration
|
|
183
189
|
|
|
@@ -208,26 +214,6 @@ $ ruff-sync check --semantic
|
|
|
208
214
|
]
|
|
209
215
|
```
|
|
210
216
|
|
|
211
|
-
## Configuration
|
|
212
|
-
|
|
213
|
-
You can configure `ruff-sync` itself in your `pyproject.toml`:
|
|
214
|
-
|
|
215
|
-
```toml
|
|
216
|
-
[tool.ruff-sync]
|
|
217
|
-
# The source of truth for your ruff configuration
|
|
218
|
-
upstream = "https://github.com/my-org/standards/blob/main/pyproject.toml"
|
|
219
|
-
|
|
220
|
-
# Use simple names for top-level keys, and dotted paths for nested keys
|
|
221
|
-
exclude = [
|
|
222
|
-
"target-version", # A top-level key under [tool.ruff]
|
|
223
|
-
"lint.per-file-ignores", # A nested key under [tool.ruff.lint]
|
|
224
|
-
"lint.ignore"
|
|
225
|
-
]
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
This sets the default exclusions so you don't need to pass `--exclude` on the command line every time.
|
|
229
|
-
*Note: Any explicitly provided CLI arguments will override the list in `pyproject.toml`.*
|
|
230
|
-
|
|
231
217
|
## Example Workflow
|
|
232
218
|
|
|
233
219
|
A typical setup for an organization:
|
|
@@ -238,11 +224,49 @@ A typical setup for an organization:
|
|
|
238
224
|
|
|
239
225
|
```console
|
|
240
226
|
# In each project repo:
|
|
241
|
-
ruff-sync https://github.com/my-org/python-standards
|
|
227
|
+
ruff-sync https://github.com/my-org/python-standards
|
|
242
228
|
git diff pyproject.toml # review the changes
|
|
243
229
|
git commit -am "sync ruff config from upstream"
|
|
244
230
|
```
|
|
245
231
|
|
|
232
|
+
## Detailed Check Logic
|
|
233
|
+
|
|
234
|
+
When you run `ruff-sync check`, it follows this process to determine if your project has drifted from the upstream source:
|
|
235
|
+
|
|
236
|
+
```mermaid
|
|
237
|
+
flowchart TD
|
|
238
|
+
Start([Start]) --> Local[Read Local pyproject.toml]
|
|
239
|
+
Local --> Upstream[Download Upstream pyproject.toml]
|
|
240
|
+
Upstream --> Extract[Extract tool.ruff section]
|
|
241
|
+
Extract --> Exclude[Apply Exclusions]
|
|
242
|
+
Exclude --> Merge[Perform in-memory Merge]
|
|
243
|
+
|
|
244
|
+
subgraph Comparison [Comparison Logic]
|
|
245
|
+
direction TB
|
|
246
|
+
SemanticNode{--semantic?}
|
|
247
|
+
SemanticNode -- Yes --> Unwrap[Unwrap TOML objects to Python Dicts]
|
|
248
|
+
Unwrap --> CompareVal[Compare Key/Value Pairs]
|
|
249
|
+
SemanticNode -- No --> CompareFull[Compare Full File Strings]
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
Merge --> Comparison
|
|
253
|
+
|
|
254
|
+
CompareVal --> ResultNode{Match?}
|
|
255
|
+
CompareFull --> ResultNode
|
|
256
|
+
|
|
257
|
+
ResultNode -- Yes --> Success([Exit 0: In Sync])
|
|
258
|
+
ResultNode -- No --> Diff[Generate Diff]
|
|
259
|
+
Diff --> Fail([Exit 1: Out of Sync])
|
|
260
|
+
|
|
261
|
+
%% Styling
|
|
262
|
+
style Start fill:#4a90e2,color:#fff,stroke:#357abd
|
|
263
|
+
style Success fill:#48c774,color:#fff,stroke:#36975a
|
|
264
|
+
style Fail fill:#f14668,color:#fff,stroke:#b2334b
|
|
265
|
+
style ResultNode fill:#ffdd57,color:#4a4a4a,stroke:#d4b106
|
|
266
|
+
style Comparison fill:none,stroke:#9e9e9e,stroke-dasharray: 5 5,stroke-width:2px
|
|
267
|
+
style SemanticNode fill:#f4f4f4,color:#363636,stroke:#dbdbdb
|
|
268
|
+
```
|
|
269
|
+
|
|
246
270
|
## Contributing
|
|
247
271
|
|
|
248
272
|
This project uses:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ruff-sync"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.3.dev1"
|
|
4
4
|
description = "Synchronize Ruff linter configuration across projects"
|
|
5
5
|
keywords = ["ruff", "linter", "config", "synchronize", "python", "linting", "automation", "tomlkit"]
|
|
6
6
|
authors = [
|
|
@@ -81,7 +81,7 @@ exclude = ["target-version", "line-length", "lint.per-file-ignores", "lint.ignor
|
|
|
81
81
|
|
|
82
82
|
[tool.ruff]
|
|
83
83
|
target-version = "py310"
|
|
84
|
-
line-length =
|
|
84
|
+
line-length = 100
|
|
85
85
|
|
|
86
86
|
[tool.ruff.lint]
|
|
87
87
|
select = [
|