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.
Files changed (48) hide show
  1. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/.agents/TESTING.md +3 -3
  2. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/.agents/workflows/add-test-case.md +4 -4
  3. ruff_sync-0.0.3.dev1/.git-blame-ignore-revs +20 -0
  4. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/.github/dependabot.yml +5 -2
  5. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/PKG-INFO +97 -73
  6. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/README.md +96 -72
  7. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/pyproject.toml +2 -2
  8. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/ruff_sync.py +191 -67
  9. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/scripts/dogfood.sh +3 -3
  10. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/scripts/dogfood_check.sh +2 -2
  11. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tasks.py +2 -8
  12. ruff_sync-0.0.3.dev1/tests/lifecycle_tomls/readme_excludes_final.toml +39 -0
  13. ruff_sync-0.0.3.dev1/tests/lifecycle_tomls/readme_excludes_initial.toml +39 -0
  14. ruff_sync-0.0.3.dev1/tests/lifecycle_tomls/readme_excludes_upstream.toml +37 -0
  15. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/test_basic.py +4 -12
  16. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/test_corner_cases.py +1 -3
  17. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/test_e2e.py +78 -21
  18. ruff_sync-0.0.3.dev1/tests/test_url_handling.py +113 -0
  19. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/uv.lock +1 -1
  20. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/.github/workflows/ci.yaml +0 -0
  21. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/.github/workflows/complexity.yaml +0 -0
  22. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/.gitignore +0 -0
  23. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/.pre-commit-config.yaml +0 -0
  24. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/AGENTS.md +0 -0
  25. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/LICENSE.md +0 -0
  26. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/codecov.yml +0 -0
  27. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/ruff_sync_banner.png +0 -0
  28. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/__init__.py +0 -0
  29. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/lifecycle_tomls/no_changes_final.toml +0 -0
  30. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/lifecycle_tomls/no_changes_initial.toml +0 -0
  31. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/lifecycle_tomls/no_changes_upstream.toml +0 -0
  32. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/lifecycle_tomls/no_dotted_keys_final.toml +0 -0
  33. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/lifecycle_tomls/no_dotted_keys_initial.toml +0 -0
  34. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/lifecycle_tomls/no_dotted_keys_upstream.toml +0 -0
  35. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/lifecycle_tomls/no_ruff_cfg_final.toml +0 -0
  36. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/lifecycle_tomls/no_ruff_cfg_initial.toml +0 -0
  37. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/lifecycle_tomls/no_ruff_cfg_upstream.toml +0 -0
  38. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/lifecycle_tomls/standard_final.toml +0 -0
  39. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/lifecycle_tomls/standard_initial.toml +0 -0
  40. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/lifecycle_tomls/standard_upstream.toml +0 -0
  41. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/ruff.toml +0 -0
  42. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/test_check.py +0 -0
  43. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/test_project.py +0 -0
  44. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/test_toml_operations.py +0 -0
  45. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/test_whitespace.py +0 -0
  46. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/w_ruff_sync_cfg/pyproject.toml +0 -0
  47. {ruff_sync-0.0.2.dev0 → ruff_sync-0.0.3.dev1}/tests/wo_ruff_cfg/pyproject.toml +0 -0
  48. {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 `poetry run pytest -vv`.
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
- poetry run invoke new-case --name <case_name> --description "Description of the edge case"
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: `poetry run coverage run -m pytest -vv && poetry run coverage report`
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
- poetry run invoke new-case --name <case_name> --description "Description of the test case"
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
- poetry run pytest -vv tests/test_e2e.py
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
- poetry run invoke lint --check
37
- poetry run invoke types
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: "/" # Location of package manifests
8
+ directory: "/"
8
9
  schedule:
9
- interval: daily
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.2.dev0
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 every repo — automatically.**
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
- ## How It Works
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 URL (blob URLs are auto-converted to raw)
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
- # Once configured in pyproject.toml (see below), simply run:
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/blob/main/pyproject.toml
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
- ### CLI Reference
169
+ Run `ruff-sync --help` for full details on all available options.
163
170
 
164
- #### `ruff-sync`
171
+ ## Key Features
165
172
 
166
- ```
167
- usage: ruff-sync [-h] [--source SOURCE] [--exclude EXCLUDE [EXCLUDE ...]] [-v] [upstream]
168
-
169
- positional arguments:
170
- upstream The URL to download the pyproject.toml file from.
171
- Optional if defined in [tool.ruff-sync]
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
- #### `ruff-sync check`
180
+ ## Configuration
182
181
 
183
- ```
184
- usage: ruff-sync check [-h] [--source SOURCE] [--exclude EXCLUDE [EXCLUDE ...]] [--semantic] [--no-diff] [-v] [upstream]
185
-
186
- positional arguments:
187
- upstream The URL to download the pyproject.toml file from.
188
- Optional if defined in [tool.ruff-sync]
189
-
190
- optional arguments:
191
- -h, --help show this help message and exit
192
- --source SOURCE The directory or file to check. Default: .
193
- --exclude EXCLUDE [EXCLUDE ...]
194
- Exclude certain ruff config keys.
195
- --semantic Ignore cosmetic differences (whitespace, comments);
196
- only fail on actual value changes.
197
- --no-diff Suppress the diff output.
198
- -v, --verbose Increase verbosity. -v for INFO, -vv for DEBUG.
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
- Exits **0** if in sync, **1** if out of sync.
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
- ## Key Features
203
+ ### Advanced Configuration
204
204
 
205
- - **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.
206
- - **GitHub URL support** — Paste a GitHub blob URL and it will automatically convert it to the raw content URL.
207
- - **Selective exclusions** — Keep project-specific overrides (like `target-version`) from being clobbered by the upstream config.
208
- - **Works with any host** — GitHub, GitLab, Bitbucket, or any raw URL that serves a `pyproject.toml`.
209
- - **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.
210
- - **Semantic mode** — Use `--semantic` to ignore cosmetic differences (comments, whitespace) and only fail on real value changes.
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/blob/main/pyproject.toml
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 every repo — automatically.**
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
- ## How It Works
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 URL (blob URLs are auto-converted to raw)
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
- # Once configured in pyproject.toml (see below), simply run:
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/blob/main/pyproject.toml
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
- ### CLI Reference
139
+ Run `ruff-sync --help` for full details on all available options.
133
140
 
134
- #### `ruff-sync`
141
+ ## Key Features
135
142
 
136
- ```
137
- usage: ruff-sync [-h] [--source SOURCE] [--exclude EXCLUDE [EXCLUDE ...]] [-v] [upstream]
138
-
139
- positional arguments:
140
- upstream The URL to download the pyproject.toml file from.
141
- Optional if defined in [tool.ruff-sync]
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
- #### `ruff-sync check`
150
+ ## Configuration
152
151
 
153
- ```
154
- usage: ruff-sync check [-h] [--source SOURCE] [--exclude EXCLUDE [EXCLUDE ...]] [--semantic] [--no-diff] [-v] [upstream]
155
-
156
- positional arguments:
157
- upstream The URL to download the pyproject.toml file from.
158
- Optional if defined in [tool.ruff-sync]
159
-
160
- optional arguments:
161
- -h, --help show this help message and exit
162
- --source SOURCE The directory or file to check. Default: .
163
- --exclude EXCLUDE [EXCLUDE ...]
164
- Exclude certain ruff config keys.
165
- --semantic Ignore cosmetic differences (whitespace, comments);
166
- only fail on actual value changes.
167
- --no-diff Suppress the diff output.
168
- -v, --verbose Increase verbosity. -v for INFO, -vv for DEBUG.
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
- Exits **0** if in sync, **1** if out of sync.
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
- ## Key Features
173
+ ### Advanced Configuration
174
174
 
175
- - **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.
176
- - **GitHub URL support** — Paste a GitHub blob URL and it will automatically convert it to the raw content URL.
177
- - **Selective exclusions** — Keep project-specific overrides (like `target-version`) from being clobbered by the upstream config.
178
- - **Works with any host** — GitHub, GitLab, Bitbucket, or any raw URL that serves a `pyproject.toml`.
179
- - **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.
180
- - **Semantic mode** — Use `--semantic` to ignore cosmetic differences (comments, whitespace) and only fail on real value changes.
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/blob/main/pyproject.toml
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.2.dev0"
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 = 90
84
+ line-length = 100
85
85
 
86
86
  [tool.ruff.lint]
87
87
  select = [