ruff-sync 0.0.1.dev3__tar.gz → 0.0.2__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.1.dev3 → ruff_sync-0.0.2}/.github/dependabot.yml +6 -3
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/.github/workflows/ci.yaml +1 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/PKG-INFO +108 -29
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/README.md +107 -28
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/pyproject.toml +3 -2
- ruff_sync-0.0.2/ruff_sync.py +530 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/scripts/dogfood.sh +1 -1
- ruff_sync-0.0.2/scripts/dogfood_check.sh +48 -0
- ruff_sync-0.0.2/tasks.py +193 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/tests/lifecycle_tomls/no_ruff_cfg_final.toml +4 -2
- ruff_sync-0.0.2/tests/lifecycle_tomls/readme_excludes_final.toml +39 -0
- ruff_sync-0.0.2/tests/lifecycle_tomls/readme_excludes_initial.toml +39 -0
- ruff_sync-0.0.2/tests/lifecycle_tomls/readme_excludes_upstream.toml +37 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/tests/lifecycle_tomls/standard_final.toml +4 -2
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/tests/test_basic.py +130 -16
- ruff_sync-0.0.2/tests/test_check.py +223 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/tests/test_corner_cases.py +1 -3
- ruff_sync-0.0.2/tests/test_e2e.py +227 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/tests/test_project.py +12 -1
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/tests/test_whitespace.py +44 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/uv.lock +3 -1
- ruff_sync-0.0.1.dev3/ruff_sync.py +0 -301
- ruff_sync-0.0.1.dev3/tasks.py +0 -90
- ruff_sync-0.0.1.dev3/tests/test_e2e.py +0 -109
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/.agents/TESTING.md +0 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/.agents/workflows/add-test-case.md +0 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/.github/workflows/complexity.yaml +0 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/.gitignore +0 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/.pre-commit-config.yaml +0 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/AGENTS.md +0 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/LICENSE.md +0 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/codecov.yml +0 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/ruff_sync_banner.png +0 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/tests/__init__.py +0 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/tests/lifecycle_tomls/no_changes_final.toml +0 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/tests/lifecycle_tomls/no_changes_initial.toml +0 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/tests/lifecycle_tomls/no_changes_upstream.toml +0 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/tests/lifecycle_tomls/no_dotted_keys_final.toml +0 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/tests/lifecycle_tomls/no_dotted_keys_initial.toml +0 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/tests/lifecycle_tomls/no_dotted_keys_upstream.toml +0 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/tests/lifecycle_tomls/no_ruff_cfg_initial.toml +0 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/tests/lifecycle_tomls/no_ruff_cfg_upstream.toml +0 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/tests/lifecycle_tomls/standard_initial.toml +0 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/tests/lifecycle_tomls/standard_upstream.toml +0 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/tests/ruff.toml +0 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/tests/test_toml_operations.py +0 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/tests/w_ruff_sync_cfg/pyproject.toml +0 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/tests/wo_ruff_cfg/pyproject.toml +0 -0
- {ruff_sync-0.0.1.dev3 → ruff_sync-0.0.2}/tests/wo_ruff_sync_cfg/pyproject.toml +0 -0
|
@@ -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
|
-
- package-ecosystem: "
|
|
7
|
-
directory: "/"
|
|
7
|
+
- package-ecosystem: "uv"
|
|
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.2
|
|
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, a raw URL) and merges it into your local project, preserving your comments, formatting, and project-specific overrides.
|
|
44
46
|
|
|
45
47
|
---
|
|
46
48
|
|
|
@@ -49,15 +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
55
|
- [Configuration](#configuration)
|
|
56
|
+
- [CI Integration](#ci-integration)
|
|
57
|
+
- [Example Workflow](#example-workflow)
|
|
58
|
+
- [Detailed Check Logic](#detailed-check-logic)
|
|
56
59
|
- [Contributing](#contributing)
|
|
60
|
+
- [Dogfooding](#dogfooding)
|
|
57
61
|
- [License](#license)
|
|
58
62
|
|
|
59
|
-
`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.
|
|
60
|
-
|
|
61
63
|
## The Problem
|
|
62
64
|
|
|
63
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.
|
|
@@ -74,7 +76,7 @@ Ruff's `extend` is perfect inside a monorepo, but if your projects live in **sep
|
|
|
74
76
|
|
|
75
77
|
**That's what `ruff-sync` does.**
|
|
76
78
|
|
|
77
|
-
|
|
79
|
+
### How It Works
|
|
78
80
|
|
|
79
81
|
```
|
|
80
82
|
┌─────────────────────────────┐
|
|
@@ -142,34 +144,32 @@ uv tool install git+https://github.com/Kilo59/ruff-sync
|
|
|
142
144
|
# Sync from a GitHub URL (blob URLs are auto-converted to raw)
|
|
143
145
|
ruff-sync https://github.com/my-org/standards/blob/main/pyproject.toml
|
|
144
146
|
|
|
147
|
+
# Once configured in pyproject.toml (see Configuration), simply run:
|
|
148
|
+
ruff-sync
|
|
149
|
+
|
|
145
150
|
# Sync into a specific project directory
|
|
146
|
-
ruff-sync
|
|
151
|
+
ruff-sync --source ./my-project
|
|
147
152
|
|
|
148
153
|
# Exclude specific sections from being overwritten using dotted paths
|
|
149
|
-
ruff-sync
|
|
150
|
-
```
|
|
154
|
+
ruff-sync --exclude lint.per-file-ignores lint.ignore
|
|
151
155
|
|
|
152
|
-
|
|
156
|
+
# Check if your local config is in sync (useful in CI)
|
|
157
|
+
ruff-sync check https://github.com/my-org/standards/blob/main/pyproject.toml
|
|
153
158
|
|
|
159
|
+
# Semantic check — ignore cosmetic differences like comments and whitespace
|
|
160
|
+
ruff-sync check --semantic
|
|
154
161
|
```
|
|
155
|
-
usage: ruff-sync [-h] [--source SOURCE] [--exclude EXCLUDE [EXCLUDE ...]] upstream
|
|
156
162
|
|
|
157
|
-
|
|
158
|
-
upstream The URL to download the pyproject.toml file from.
|
|
159
|
-
|
|
160
|
-
optional arguments:
|
|
161
|
-
-h, --help show this help message and exit
|
|
162
|
-
--source SOURCE The directory to sync the pyproject.toml file to. Default: .
|
|
163
|
-
--exclude EXCLUDE [EXCLUDE ...]
|
|
164
|
-
Exclude certain ruff configs. Default: lint.per-file-ignores
|
|
165
|
-
```
|
|
163
|
+
Run `ruff-sync --help` for full details on all available options.
|
|
166
164
|
|
|
167
165
|
## Key Features
|
|
168
166
|
|
|
169
167
|
- **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.
|
|
170
168
|
- **GitHub URL support** — Paste a GitHub blob URL and it will automatically convert it to the raw content URL.
|
|
171
|
-
- **Selective exclusions** — Keep project-specific overrides (like `target-version`) from being clobbered by the upstream config.
|
|
169
|
+
- **Selective exclusions** — Keep project-specific overrides (like `per-file-ignores` or `target-version`) from being clobbered by the upstream config.
|
|
172
170
|
- **Works with any host** — GitHub, GitLab, Bitbucket, or any raw URL that serves a `pyproject.toml`.
|
|
171
|
+
- **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))
|
|
172
|
+
- **Semantic mode** — Use `--semantic` to ignore cosmetic differences (comments, whitespace) and only fail on real value changes.
|
|
173
173
|
|
|
174
174
|
## Configuration
|
|
175
175
|
|
|
@@ -177,16 +177,51 @@ You can configure `ruff-sync` itself in your `pyproject.toml`:
|
|
|
177
177
|
|
|
178
178
|
```toml
|
|
179
179
|
[tool.ruff-sync]
|
|
180
|
+
# The source of truth for your Ruff configuration
|
|
181
|
+
upstream = "https://github.com/my-org/standards/blob/main/pyproject.toml"
|
|
182
|
+
|
|
180
183
|
# Use simple names for top-level keys, and dotted paths for nested keys
|
|
181
184
|
exclude = [
|
|
182
|
-
"target-version",
|
|
183
|
-
"lint.per-file-ignores",
|
|
184
|
-
"lint.ignore"
|
|
185
|
+
"target-version", # Top-level [tool.ruff] key — projects target different Python versions
|
|
186
|
+
"lint.per-file-ignores", # Project-specific file overrides
|
|
187
|
+
"lint.ignore", # Project-specific rule suppressions
|
|
188
|
+
"lint.isort.known-first-party", # Every project has different first-party packages
|
|
189
|
+
"lint.flake8-tidy-imports.banned-api", # Entire plugin section — project-specific banned APIs
|
|
190
|
+
"lint.pydocstyle.convention", # Teams may disagree on google vs numpy vs pep257
|
|
185
191
|
]
|
|
186
192
|
```
|
|
187
193
|
|
|
188
|
-
This sets the default exclusions so you don't need to pass
|
|
189
|
-
*Note: Any explicitly provided CLI arguments will override the
|
|
194
|
+
This sets the default upstream and exclusions so you don't need to pass them on the command line every time.
|
|
195
|
+
*Note: Any explicitly provided CLI arguments will override the values in `pyproject.toml`.*
|
|
196
|
+
|
|
197
|
+
## CI Integration
|
|
198
|
+
|
|
199
|
+
The `check` command is designed for use in CI pipelines. Add it as a step to catch config drift before it merges:
|
|
200
|
+
|
|
201
|
+
```yaml
|
|
202
|
+
# .github/workflows/ci.yaml
|
|
203
|
+
- name: Check ruff config is in sync
|
|
204
|
+
run: |
|
|
205
|
+
ruff-sync check --semantic
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
With `--semantic`, minor reformatting of your local file won't cause a false positive — only actual rule or value differences will fail the check.
|
|
209
|
+
|
|
210
|
+
To see exactly what's drifted, omit `--no-diff` (the default) and the output will include a unified diff:
|
|
211
|
+
|
|
212
|
+
```console
|
|
213
|
+
$ ruff-sync check --semantic
|
|
214
|
+
🔍 Checking Ruff sync status...
|
|
215
|
+
❌ Ruff configuration at pyproject.toml is out of sync!
|
|
216
|
+
--- local (semantic)
|
|
217
|
+
+++ upstream (semantic)
|
|
218
|
+
@@ -5,6 +5,7 @@
|
|
219
|
+
"select": [
|
|
220
|
+
+ "PERF",
|
|
221
|
+
"RUF",
|
|
222
|
+
...
|
|
223
|
+
]
|
|
224
|
+
```
|
|
190
225
|
|
|
191
226
|
## Example Workflow
|
|
192
227
|
|
|
@@ -203,6 +238,44 @@ git diff pyproject.toml # review the changes
|
|
|
203
238
|
git commit -am "sync ruff config from upstream"
|
|
204
239
|
```
|
|
205
240
|
|
|
241
|
+
## Detailed Check Logic
|
|
242
|
+
|
|
243
|
+
When you run `ruff-sync check`, it follows this process to determine if your project has drifted from the upstream source:
|
|
244
|
+
|
|
245
|
+
```mermaid
|
|
246
|
+
flowchart TD
|
|
247
|
+
Start([Start]) --> Local[Read Local pyproject.toml]
|
|
248
|
+
Local --> Upstream[Download Upstream pyproject.toml]
|
|
249
|
+
Upstream --> Extract[Extract tool.ruff section]
|
|
250
|
+
Extract --> Exclude[Apply Exclusions]
|
|
251
|
+
Exclude --> Merge[Perform in-memory Merge]
|
|
252
|
+
|
|
253
|
+
subgraph Comparison [Comparison Logic]
|
|
254
|
+
direction TB
|
|
255
|
+
SemanticNode{--semantic?}
|
|
256
|
+
SemanticNode -- Yes --> Unwrap[Unwrap TOML objects to Python Dicts]
|
|
257
|
+
Unwrap --> CompareVal[Compare Key/Value Pairs]
|
|
258
|
+
SemanticNode -- No --> CompareFull[Compare Full File Strings]
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
Merge --> Comparison
|
|
262
|
+
|
|
263
|
+
CompareVal --> ResultNode{Match?}
|
|
264
|
+
CompareFull --> ResultNode
|
|
265
|
+
|
|
266
|
+
ResultNode -- Yes --> Success([Exit 0: In Sync])
|
|
267
|
+
ResultNode -- No --> Diff[Generate Diff]
|
|
268
|
+
Diff --> Fail([Exit 1: Out of Sync])
|
|
269
|
+
|
|
270
|
+
%% Styling
|
|
271
|
+
style Start fill:#4a90e2,color:#fff,stroke:#357abd
|
|
272
|
+
style Success fill:#48c774,color:#fff,stroke:#36975a
|
|
273
|
+
style Fail fill:#f14668,color:#fff,stroke:#b2334b
|
|
274
|
+
style ResultNode fill:#ffdd57,color:#4a4a4a,stroke:#d4b106
|
|
275
|
+
style Comparison fill:none,stroke:#9e9e9e,stroke-dasharray: 5 5,stroke-width:2px
|
|
276
|
+
style SemanticNode fill:#f4f4f4,color:#363636,stroke:#dbdbdb
|
|
277
|
+
```
|
|
278
|
+
|
|
206
279
|
## Contributing
|
|
207
280
|
|
|
208
281
|
This project uses:
|
|
@@ -225,9 +298,15 @@ uv run pytest -vv # test
|
|
|
225
298
|
|
|
226
299
|
## Dogfooding
|
|
227
300
|
|
|
228
|
-
To see `ruff-sync` in action
|
|
301
|
+
To see `ruff-sync` in action, you can "dogfood" it on this project's own config.
|
|
302
|
+
|
|
303
|
+
**Check if this project is in sync with its upstream:**
|
|
304
|
+
|
|
305
|
+
```console
|
|
306
|
+
./scripts/dogfood_check.sh
|
|
307
|
+
```
|
|
229
308
|
|
|
230
|
-
|
|
309
|
+
**Or sync from a large upstream like Pydantic's config:**
|
|
231
310
|
|
|
232
311
|
```console
|
|
233
312
|
./scripts/dogfood.sh
|
|
@@ -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, a raw URL) and merges it into your local project, preserving your comments, formatting, and project-specific overrides.
|
|
14
16
|
|
|
15
17
|
---
|
|
16
18
|
|
|
@@ -19,15 +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
25
|
- [Configuration](#configuration)
|
|
26
|
+
- [CI Integration](#ci-integration)
|
|
27
|
+
- [Example Workflow](#example-workflow)
|
|
28
|
+
- [Detailed Check Logic](#detailed-check-logic)
|
|
26
29
|
- [Contributing](#contributing)
|
|
30
|
+
- [Dogfooding](#dogfooding)
|
|
27
31
|
- [License](#license)
|
|
28
32
|
|
|
29
|
-
`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.
|
|
30
|
-
|
|
31
33
|
## The Problem
|
|
32
34
|
|
|
33
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.
|
|
@@ -44,7 +46,7 @@ Ruff's `extend` is perfect inside a monorepo, but if your projects live in **sep
|
|
|
44
46
|
|
|
45
47
|
**That's what `ruff-sync` does.**
|
|
46
48
|
|
|
47
|
-
|
|
49
|
+
### How It Works
|
|
48
50
|
|
|
49
51
|
```
|
|
50
52
|
┌─────────────────────────────┐
|
|
@@ -112,34 +114,32 @@ uv tool install git+https://github.com/Kilo59/ruff-sync
|
|
|
112
114
|
# Sync from a GitHub URL (blob URLs are auto-converted to raw)
|
|
113
115
|
ruff-sync https://github.com/my-org/standards/blob/main/pyproject.toml
|
|
114
116
|
|
|
117
|
+
# Once configured in pyproject.toml (see Configuration), simply run:
|
|
118
|
+
ruff-sync
|
|
119
|
+
|
|
115
120
|
# Sync into a specific project directory
|
|
116
|
-
ruff-sync
|
|
121
|
+
ruff-sync --source ./my-project
|
|
117
122
|
|
|
118
123
|
# Exclude specific sections from being overwritten using dotted paths
|
|
119
|
-
ruff-sync
|
|
120
|
-
```
|
|
124
|
+
ruff-sync --exclude lint.per-file-ignores lint.ignore
|
|
121
125
|
|
|
122
|
-
|
|
126
|
+
# Check if your local config is in sync (useful in CI)
|
|
127
|
+
ruff-sync check https://github.com/my-org/standards/blob/main/pyproject.toml
|
|
123
128
|
|
|
129
|
+
# Semantic check — ignore cosmetic differences like comments and whitespace
|
|
130
|
+
ruff-sync check --semantic
|
|
124
131
|
```
|
|
125
|
-
usage: ruff-sync [-h] [--source SOURCE] [--exclude EXCLUDE [EXCLUDE ...]] upstream
|
|
126
132
|
|
|
127
|
-
|
|
128
|
-
upstream The URL to download the pyproject.toml file from.
|
|
129
|
-
|
|
130
|
-
optional arguments:
|
|
131
|
-
-h, --help show this help message and exit
|
|
132
|
-
--source SOURCE The directory to sync the pyproject.toml file to. Default: .
|
|
133
|
-
--exclude EXCLUDE [EXCLUDE ...]
|
|
134
|
-
Exclude certain ruff configs. Default: lint.per-file-ignores
|
|
135
|
-
```
|
|
133
|
+
Run `ruff-sync --help` for full details on all available options.
|
|
136
134
|
|
|
137
135
|
## Key Features
|
|
138
136
|
|
|
139
137
|
- **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.
|
|
140
138
|
- **GitHub URL support** — Paste a GitHub blob URL and it will automatically convert it to the raw content URL.
|
|
141
|
-
- **Selective exclusions** — Keep project-specific overrides (like `target-version`) from being clobbered by the upstream config.
|
|
139
|
+
- **Selective exclusions** — Keep project-specific overrides (like `per-file-ignores` or `target-version`) from being clobbered by the upstream config.
|
|
142
140
|
- **Works with any host** — GitHub, GitLab, Bitbucket, or any raw URL that serves a `pyproject.toml`.
|
|
141
|
+
- **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))
|
|
142
|
+
- **Semantic mode** — Use `--semantic` to ignore cosmetic differences (comments, whitespace) and only fail on real value changes.
|
|
143
143
|
|
|
144
144
|
## Configuration
|
|
145
145
|
|
|
@@ -147,16 +147,51 @@ You can configure `ruff-sync` itself in your `pyproject.toml`:
|
|
|
147
147
|
|
|
148
148
|
```toml
|
|
149
149
|
[tool.ruff-sync]
|
|
150
|
+
# The source of truth for your Ruff configuration
|
|
151
|
+
upstream = "https://github.com/my-org/standards/blob/main/pyproject.toml"
|
|
152
|
+
|
|
150
153
|
# Use simple names for top-level keys, and dotted paths for nested keys
|
|
151
154
|
exclude = [
|
|
152
|
-
"target-version",
|
|
153
|
-
"lint.per-file-ignores",
|
|
154
|
-
"lint.ignore"
|
|
155
|
+
"target-version", # Top-level [tool.ruff] key — projects target different Python versions
|
|
156
|
+
"lint.per-file-ignores", # Project-specific file overrides
|
|
157
|
+
"lint.ignore", # Project-specific rule suppressions
|
|
158
|
+
"lint.isort.known-first-party", # Every project has different first-party packages
|
|
159
|
+
"lint.flake8-tidy-imports.banned-api", # Entire plugin section — project-specific banned APIs
|
|
160
|
+
"lint.pydocstyle.convention", # Teams may disagree on google vs numpy vs pep257
|
|
155
161
|
]
|
|
156
162
|
```
|
|
157
163
|
|
|
158
|
-
This sets the default exclusions so you don't need to pass
|
|
159
|
-
*Note: Any explicitly provided CLI arguments will override the
|
|
164
|
+
This sets the default upstream and exclusions so you don't need to pass them on the command line every time.
|
|
165
|
+
*Note: Any explicitly provided CLI arguments will override the values in `pyproject.toml`.*
|
|
166
|
+
|
|
167
|
+
## CI Integration
|
|
168
|
+
|
|
169
|
+
The `check` command is designed for use in CI pipelines. Add it as a step to catch config drift before it merges:
|
|
170
|
+
|
|
171
|
+
```yaml
|
|
172
|
+
# .github/workflows/ci.yaml
|
|
173
|
+
- name: Check ruff config is in sync
|
|
174
|
+
run: |
|
|
175
|
+
ruff-sync check --semantic
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
With `--semantic`, minor reformatting of your local file won't cause a false positive — only actual rule or value differences will fail the check.
|
|
179
|
+
|
|
180
|
+
To see exactly what's drifted, omit `--no-diff` (the default) and the output will include a unified diff:
|
|
181
|
+
|
|
182
|
+
```console
|
|
183
|
+
$ ruff-sync check --semantic
|
|
184
|
+
🔍 Checking Ruff sync status...
|
|
185
|
+
❌ Ruff configuration at pyproject.toml is out of sync!
|
|
186
|
+
--- local (semantic)
|
|
187
|
+
+++ upstream (semantic)
|
|
188
|
+
@@ -5,6 +5,7 @@
|
|
189
|
+
"select": [
|
|
190
|
+
+ "PERF",
|
|
191
|
+
"RUF",
|
|
192
|
+
...
|
|
193
|
+
]
|
|
194
|
+
```
|
|
160
195
|
|
|
161
196
|
## Example Workflow
|
|
162
197
|
|
|
@@ -173,6 +208,44 @@ git diff pyproject.toml # review the changes
|
|
|
173
208
|
git commit -am "sync ruff config from upstream"
|
|
174
209
|
```
|
|
175
210
|
|
|
211
|
+
## Detailed Check Logic
|
|
212
|
+
|
|
213
|
+
When you run `ruff-sync check`, it follows this process to determine if your project has drifted from the upstream source:
|
|
214
|
+
|
|
215
|
+
```mermaid
|
|
216
|
+
flowchart TD
|
|
217
|
+
Start([Start]) --> Local[Read Local pyproject.toml]
|
|
218
|
+
Local --> Upstream[Download Upstream pyproject.toml]
|
|
219
|
+
Upstream --> Extract[Extract tool.ruff section]
|
|
220
|
+
Extract --> Exclude[Apply Exclusions]
|
|
221
|
+
Exclude --> Merge[Perform in-memory Merge]
|
|
222
|
+
|
|
223
|
+
subgraph Comparison [Comparison Logic]
|
|
224
|
+
direction TB
|
|
225
|
+
SemanticNode{--semantic?}
|
|
226
|
+
SemanticNode -- Yes --> Unwrap[Unwrap TOML objects to Python Dicts]
|
|
227
|
+
Unwrap --> CompareVal[Compare Key/Value Pairs]
|
|
228
|
+
SemanticNode -- No --> CompareFull[Compare Full File Strings]
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
Merge --> Comparison
|
|
232
|
+
|
|
233
|
+
CompareVal --> ResultNode{Match?}
|
|
234
|
+
CompareFull --> ResultNode
|
|
235
|
+
|
|
236
|
+
ResultNode -- Yes --> Success([Exit 0: In Sync])
|
|
237
|
+
ResultNode -- No --> Diff[Generate Diff]
|
|
238
|
+
Diff --> Fail([Exit 1: Out of Sync])
|
|
239
|
+
|
|
240
|
+
%% Styling
|
|
241
|
+
style Start fill:#4a90e2,color:#fff,stroke:#357abd
|
|
242
|
+
style Success fill:#48c774,color:#fff,stroke:#36975a
|
|
243
|
+
style Fail fill:#f14668,color:#fff,stroke:#b2334b
|
|
244
|
+
style ResultNode fill:#ffdd57,color:#4a4a4a,stroke:#d4b106
|
|
245
|
+
style Comparison fill:none,stroke:#9e9e9e,stroke-dasharray: 5 5,stroke-width:2px
|
|
246
|
+
style SemanticNode fill:#f4f4f4,color:#363636,stroke:#dbdbdb
|
|
247
|
+
```
|
|
248
|
+
|
|
176
249
|
## Contributing
|
|
177
250
|
|
|
178
251
|
This project uses:
|
|
@@ -195,9 +268,15 @@ uv run pytest -vv # test
|
|
|
195
268
|
|
|
196
269
|
## Dogfooding
|
|
197
270
|
|
|
198
|
-
To see `ruff-sync` in action
|
|
271
|
+
To see `ruff-sync` in action, you can "dogfood" it on this project's own config.
|
|
272
|
+
|
|
273
|
+
**Check if this project is in sync with its upstream:**
|
|
274
|
+
|
|
275
|
+
```console
|
|
276
|
+
./scripts/dogfood_check.sh
|
|
277
|
+
```
|
|
199
278
|
|
|
200
|
-
|
|
279
|
+
**Or sync from a large upstream like Pydantic's config:**
|
|
201
280
|
|
|
202
281
|
```console
|
|
203
282
|
./scripts/dogfood.sh
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ruff-sync"
|
|
3
|
-
version = "0.0.
|
|
3
|
+
version = "0.0.2"
|
|
4
4
|
description = "Synchronize Ruff linter configuration across projects"
|
|
5
5
|
keywords = ["ruff", "linter", "config", "synchronize", "python", "linting", "automation", "tomlkit"]
|
|
6
6
|
authors = [
|
|
@@ -43,6 +43,7 @@ dev = [
|
|
|
43
43
|
"coverage>=7.4.4",
|
|
44
44
|
"invoke>=2.2.0",
|
|
45
45
|
"mypy>=1.10.0",
|
|
46
|
+
"packaging>=26.0",
|
|
46
47
|
"pre-commit>=3.7.0",
|
|
47
48
|
"pyfakefs>=5.4.1",
|
|
48
49
|
"pytest>=8.0.0",
|
|
@@ -80,7 +81,7 @@ exclude = ["target-version", "line-length", "lint.per-file-ignores", "lint.ignor
|
|
|
80
81
|
|
|
81
82
|
[tool.ruff]
|
|
82
83
|
target-version = "py310"
|
|
83
|
-
line-length =
|
|
84
|
+
line-length = 100
|
|
84
85
|
|
|
85
86
|
[tool.ruff.lint]
|
|
86
87
|
select = [
|