ruff-sync 0.0.5.dev1__tar.gz → 0.1.0.dev0__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 (67) hide show
  1. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/.github/workflows/ci.yaml +21 -4
  2. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/.github/workflows/complexity.yaml +11 -16
  3. ruff_sync-0.1.0.dev0/.github/workflows/docs.yaml +32 -0
  4. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/AGENTS.md +15 -12
  5. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/PKG-INFO +51 -19
  6. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/README.md +49 -18
  7. ruff_sync-0.1.0.dev0/configs/fastapi/ruff.toml +150 -0
  8. ruff_sync-0.1.0.dev0/docs/ci-integration.md +59 -0
  9. ruff_sync-0.1.0.dev0/docs/configuration.md +64 -0
  10. ruff_sync-0.1.0.dev0/docs/gen_ref_pages.py +34 -0
  11. ruff_sync-0.1.0.dev0/docs/index.md +40 -0
  12. ruff_sync-0.1.0.dev0/docs/installation.md +47 -0
  13. ruff_sync-0.1.0.dev0/docs/usage.md +75 -0
  14. ruff_sync-0.1.0.dev0/mkdocs.yml +100 -0
  15. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/pyproject.toml +61 -6
  16. ruff_sync-0.1.0.dev0/src/ruff_sync/__init__.py +44 -0
  17. ruff_sync-0.1.0.dev0/src/ruff_sync/__main__.py +10 -0
  18. ruff_sync-0.0.5.dev1/ruff_sync.py → ruff_sync-0.1.0.dev0/src/ruff_sync/cli.py +275 -127
  19. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tasks.py +16 -6
  20. ruff_sync-0.1.0.dev0/tests/__init__.py +1 -0
  21. ruff_sync-0.1.0.dev0/tests/conftest.py +12 -0
  22. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/test_basic.py +68 -52
  23. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/test_check.py +6 -6
  24. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/test_config_validation.py +2 -1
  25. ruff_sync-0.1.0.dev0/tests/test_deprecation.py +215 -0
  26. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/test_e2e.py +9 -8
  27. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/test_git_fetch.py +7 -4
  28. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/test_project.py +2 -1
  29. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/test_scaffold.py +6 -6
  30. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/test_url_handling.py +38 -3
  31. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/uv.lock +510 -1
  32. ruff_sync-0.0.5.dev1/tests/__init__.py +0 -0
  33. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/.agents/TESTING.md +0 -0
  34. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/.agents/workflows/add-test-case.md +0 -0
  35. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/.git-blame-ignore-revs +0 -0
  36. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/.github/dependabot.yml +0 -0
  37. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/.gitignore +0 -0
  38. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/.pre-commit-config.yaml +0 -0
  39. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/LICENSE.md +0 -0
  40. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/codecov.yml +0 -0
  41. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/configs/kitchen-sink/ruff.toml +0 -0
  42. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/ruff_sync_banner.png +0 -0
  43. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/scripts/check_dogfood.sh +0 -0
  44. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/scripts/gitclone_dogfood.sh +0 -0
  45. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/scripts/pull_dogfood.sh +0 -0
  46. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/no_changes_final.toml +0 -0
  47. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/no_changes_initial.toml +0 -0
  48. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/no_changes_upstream.toml +0 -0
  49. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/no_dotted_keys_final.toml +0 -0
  50. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/no_dotted_keys_initial.toml +0 -0
  51. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/no_dotted_keys_upstream.toml +0 -0
  52. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/no_ruff_cfg_final.toml +0 -0
  53. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/no_ruff_cfg_initial.toml +0 -0
  54. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/no_ruff_cfg_upstream.toml +0 -0
  55. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/readme_excludes_final.toml +0 -0
  56. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/readme_excludes_initial.toml +0 -0
  57. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/readme_excludes_upstream.toml +0 -0
  58. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/standard_final.toml +0 -0
  59. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/standard_initial.toml +0 -0
  60. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/lifecycle_tomls/standard_upstream.toml +0 -0
  61. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/ruff.toml +0 -0
  62. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/test_corner_cases.py +0 -0
  63. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/test_toml_operations.py +0 -0
  64. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/test_whitespace.py +0 -0
  65. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/w_ruff_sync_cfg/pyproject.toml +0 -0
  66. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/wo_ruff_cfg/pyproject.toml +0 -0
  67. {ruff_sync-0.0.5.dev1 → ruff_sync-0.1.0.dev0}/tests/wo_ruff_sync_cfg/pyproject.toml +0 -0
@@ -66,8 +66,10 @@ jobs:
66
66
 
67
67
  pre-publish:
68
68
  name: Test package installation
69
- if: github.event_name == 'push' && github.ref == 'refs/heads/main'
70
69
  needs: [static-analysis, tests]
70
+ if: >-
71
+ (github.event_name == 'push' && github.ref == 'refs/heads/main') ||
72
+ (github.event_name == 'pull_request' && github.event.pull_request.draft == false)
71
73
  runs-on: ubuntu-latest
72
74
  steps:
73
75
  - name: Checkout
@@ -80,14 +82,29 @@ jobs:
80
82
  run: uv python install 3.10
81
83
 
82
84
  - name: Build package
83
- run: uv build
85
+ run: |
86
+ echo "Building ruff-sync package..."
87
+ uv build
84
88
 
85
- - name: Install and test packaged program
89
+ - name: Install packaged program
86
90
  run: |
91
+ echo "Installing built wheel..."
87
92
  uv tool install $(ls dist/*.whl)
88
- ruff-sync --version
93
+ echo "Version: $(ruff-sync --version)"
94
+
95
+ - name: Test package installation (Pull)
96
+ run: |
97
+ echo "Testing ruff-sync pull against Kilo59/ruff-sync..."
89
98
  ruff-sync https://github.com/Kilo59/ruff-sync
99
+
100
+ - name: Test package installation (Check)
101
+ run: |
102
+ echo "Testing ruff-sync check against Kilo59/ruff-sync..."
90
103
  ruff-sync check https://github.com/Kilo59/ruff-sync
104
+
105
+ - name: Verify semantic check failure
106
+ run: |
107
+ echo "Verifying that --semantic check fails for kitchen-sink config (expected failure)..."
91
108
  ! ruff-sync check --semantic https://github.com/Kilo59/ruff-sync --path configs/kitchen-sink
92
109
 
93
110
  publish:
@@ -12,14 +12,13 @@ jobs:
12
12
 
13
13
  steps:
14
14
  - name: Checkout repository
15
- uses: actions/checkout@v2
15
+ uses: actions/checkout@v4
16
16
  with:
17
17
  fetch-depth: 0
18
- ref: ${{ github.event.pull_request.head.ref }}
19
18
  - name: Set up Python
20
- uses: actions/setup-python@v2
19
+ uses: actions/setup-python@v5
21
20
  with:
22
- python-version: 3.9
21
+ python-version: "3.10"
23
22
  - name: Install Wily
24
23
  run: pip install 'wily>=1.25.0'
25
24
  - name: Build cache and diff
@@ -29,30 +28,26 @@ jobs:
29
28
  DIFF=$(wily diff ruff_sync.py tasks.py tests/ --no-detail -r origin/${{ github.event.pull_request.base.ref }})
30
29
  echo "$DIFF"
31
30
 
32
- # Build multine output
33
- DIFF="${DIFF//'%'/'%25'}"
34
- DIFF="${DIFF//$'\n'/'%0A'}"
35
- DIFF="${DIFF//$'\r'/'%0D'}"
36
- echo "::set-output name=diff::$DIFF"
37
- - name: Find current PR
38
- uses: jwalton/gh-find-current-pr@v1
39
- id: findPr
31
+ # Build multiline output
32
+ echo "diff<<DIFF_EOF" >> "$GITHUB_OUTPUT"
33
+ echo "$DIFF" >> "$GITHUB_OUTPUT"
34
+ echo "DIFF_EOF" >> "$GITHUB_OUTPUT"
40
35
  - name: Add Wily PR Comment
41
36
  uses: marocchino/sticky-pull-request-comment@v2
42
- if: steps.findPr.outputs.number && steps.wily.outputs.diff != ''
37
+ if: github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.number && steps.wily.outputs.diff != ''
43
38
  with:
44
39
  recreate: true
45
- number: ${{ steps.findPr.outputs.number }}
40
+ number: ${{ github.event.pull_request.number }}
46
41
  message: |
47
42
  ```
48
43
  ${{ steps.wily.outputs.diff }}
49
44
  ```
50
45
  - name: Add Wily PR Comment
51
46
  uses: marocchino/sticky-pull-request-comment@v2
52
- if: steps.findPr.outputs.number && steps.wily.outputs.diff == ''
47
+ if: github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.number && steps.wily.outputs.diff == ''
53
48
  with:
54
49
  recreate: true
55
- number: ${{ steps.findPr.outputs.number }}
50
+ number: ${{ github.event.pull_request.number }}
56
51
  message: |
57
52
  ```
58
53
  Wily: No changes in complexity detected.
@@ -0,0 +1,32 @@
1
+ name: Docs
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: write
11
+
12
+ jobs:
13
+ deploy:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ with:
18
+ fetch-depth: 0
19
+
20
+ - name: Install uv
21
+ uses: astral-sh/setup-uv@v5
22
+ with:
23
+ enable-cache: true
24
+
25
+ - name: Set up Python
26
+ run: uv python install
27
+
28
+ - name: Install dependencies
29
+ run: uv sync --all-extras --dev --group docs
30
+
31
+ - name: Build and deploy documentation
32
+ run: uv run mkdocs gh-deploy --force
@@ -5,7 +5,7 @@
5
5
  **ruff-sync** is a CLI tool that synchronizes [Ruff](https://docs.astral.sh/ruff/) linter configuration across multiple Python projects. It downloads an upstream `pyproject.toml`, extracts the `[tool.ruff]` section, and merges it into a local project's `pyproject.toml` while preserving formatting, comments, and whitespace.
6
6
 
7
7
  - **GitHub Repository**: [`Kilo59/ruff-sync`](https://github.com/Kilo59/ruff-sync)
8
- - The entire application lives in a single module: `ruff_sync.py`.
8
+ - The application uses a `src` layout in `src/ruff_sync/`.
9
9
  - Dev tasks are defined in `tasks.py` using [Invoke](https://www.pyinvoke.org/).
10
10
 
11
11
  ## GitHub Context
@@ -54,11 +54,14 @@ gh label list # See available labels
54
54
 
55
55
  ## Project Structure
56
56
 
57
- ```
57
+ ```text
58
58
  .agents/ # Agent-specific instructions (Deep Standards)
59
59
  TESTING.md # Mandatory testing patterns and rules
60
60
  workflows/ # Step-by-step guides for common tasks
61
- ruff_sync.py # The entire application — CLI, merging logic, HTTP
61
+ src/ruff_sync/ # The application source
62
+ __init__.py # Public API
63
+ cli.py # CLI, merging logic, HTTP
64
+ __main__.py # -m support
62
65
  tasks.py # Invoke tasks: lint, fmt, type-check, deps, new-case
63
66
  pyproject.toml # Project config, dependencies, ruff/mypy settings
64
67
  tests/
@@ -114,7 +117,7 @@ uv run mypy .
114
117
  ```
115
118
 
116
119
  - mypy is configured in strict mode with `python_version = "3.10"`.
117
- - It checks `ruff_sync.py`, `tests/`, and `tasks.py`.
120
+ - It checks `src/`, `tests/`, and `tasks.py`.
118
121
  - Tests have relaxed rules: `type-arg` and `no-untyped-def` are disabled for `tests.*`.
119
122
  - `tomlkit` returns complex union types — use `cast(Any, ...)` in tests when indexing parsed TOML documents to satisfy mypy without verbose type narrowing.
120
123
 
@@ -130,7 +133,7 @@ Or with coverage:
130
133
  uv run coverage run -m pytest -vv
131
134
  ```
132
135
 
133
- - Coverage is tracked for `ruff_sync.py` only.
136
+ - Coverage is tracked for `src/ruff_sync/` only.
134
137
  - Tests use `respx` to mock HTTP calls and `pyfakefs` for filesystem mocking.
135
138
  - `pytest-asyncio` is in **strict** mode — async tests need the `@pytest.mark.asyncio` decorator.
136
139
 
@@ -165,13 +168,13 @@ uv run coverage run -m pytest -vv
165
168
 
166
169
  Defined in `tasks.py`. **ALWAYS** run these through uv: `uv run invoke <task>`
167
170
 
168
- | Task | Alias | Description |
169
- | ------------ | ------------ | ----------------------------------- |
170
- | `lint` | | Lint with ruff (auto-fixes by default) |
171
- | `fmt` | | Format with ruff format |
172
- | `type-check` | `types` | Type-check with mypy |
173
- | `deps` | `sync` | Sync dependencies with uv |
174
- | `new-case` | `new-lifecycle-tomls` | Scaffold lifecycle TOML fixtures (See [Workflow](.agents/workflows/add-test-case.md)) |
171
+ | Task | Alias | Description |
172
+ | ------------ | --------------------- | -------------------------------------- |
173
+ | `lint` | | Lint with ruff (auto-fixes by default) |
174
+ | `fmt` | | Format with ruff format |
175
+ | `type-check` | `types` | Type-check with mypy |
176
+ | `deps` | `sync` | Sync dependencies with uv |
177
+ | `new-case` | `new-lifecycle-tomls` | Scaffold lifecycle TOML fixtures |
175
178
 
176
179
  ## CI
177
180
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ruff-sync
3
- Version: 0.0.5.dev1
3
+ Version: 0.1.0.dev0
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
@@ -26,6 +26,7 @@ Classifier: Topic :: Software Development :: Quality Assurance
26
26
  Requires-Python: >=3.10
27
27
  Requires-Dist: httpx<1.0.0,>=0.27.0
28
28
  Requires-Dist: tomlkit<2.0.0,>=0.12.3
29
+ Requires-Dist: typing-extensions>=4.5.0
29
30
  Description-Content-Type: text/markdown
30
31
 
31
32
  <p align="center">
@@ -46,7 +47,7 @@ Description-Content-Type: text/markdown
46
47
 
47
48
  ---
48
49
 
49
- ### Table of Contents
50
+ ## Table of Contents
50
51
 
51
52
  - [The Problem](#the-problem)
52
53
  - [How It Works](#how-it-works)
@@ -56,7 +57,6 @@ Description-Content-Type: text/markdown
56
57
  - [CI Integration](#ci-integration)
57
58
  - [Example Workflow](#example-workflow)
58
59
  - [Detailed Check Logic](#detailed-check-logic)
59
- - [Contributing](#contributing)
60
60
  - [Dogfooding](#dogfooding)
61
61
  - [License](#license)
62
62
 
@@ -102,9 +102,9 @@ Ruff's `extend` is perfect inside a monorepo, but if your projects live in **sep
102
102
  └─────────────────────────────┘
103
103
  ```
104
104
 
105
- 1. You point `ruff-sync` at the URL of your canonical `pyproject.toml`.
106
- 2. It downloads the file, extracts the `[tool.ruff]` section.
107
- 3. It **merges** the upstream config into your local `pyproject.toml` — updating values that changed, adding new rules, but preserving your local comments, whitespace, and any sections you've chosen to exclude (like `per-file-ignores`).
105
+ 1. You point `ruff-sync` at the URL of your canonical configuration (repository, directory, or direct file).
106
+ 2. It downloads the file and extracts the configuration (from `[tool.ruff]` in `pyproject.toml` or the top-level in `ruff.toml`).
107
+ 3. It **merges** the upstream config into your local project — updating values that changed, adding new rules, but preserving your local comments, whitespace, and any sections you've chosen to exclude (like `per-file-ignores`).
108
108
 
109
109
  No package registry. No publishing step. Just a URL.
110
110
 
@@ -133,8 +133,9 @@ pip install ruff-sync
133
133
  ### Usage
134
134
 
135
135
  ```console
136
- # Sync from a GitHub/GitLab repository (defaults to main/pyproject.toml)
136
+ # Sync from a GitHub/GitLab repository (root or specific directory)
137
137
  ruff-sync https://github.com/my-org/standards
138
+ ruff-sync https://github.com/my-org/standards/tree/main/configs/shared
138
139
 
139
140
  # Or a direct blob/file URL (auto-converts to raw)
140
141
  ruff-sync https://github.com/my-org/standards/blob/main/pyproject.toml
@@ -162,7 +163,8 @@ Run `ruff-sync --help` for full details on all available options.
162
163
  ## Key Features
163
164
 
164
165
  - 🏗️ **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.
165
- - 🌐 **GitHub & GitLab URL support** — Automatically converts GitHub/GitLab repository URLs or blob URLs to raw content URLs.
166
+ - 🌐 **GitHub & GitLab URL support** — Automatically converts GitHub/GitLab repository URLs, tree (directory) URLs, or blob (file) URLs to raw content URLs.
167
+ - 🔍 **Smart configuration discovery** — Point at a directory and `ruff-sync` will automatically find your config. It checks `pyproject.toml`, `ruff.toml`, and `.ruff.toml` (in that order).
166
168
  - 📥 **Git clone support** — If the URL starts with `git@` or uses the `ssh://`, `git://`, or `git+ssh://` schemes, `ruff-sync` will perform an efficient shallow clone (using `--filter=blob:none` and `--no-checkout`) to safely extract the configuration with minimal network traffic.
167
169
  - 🛡️ **Selective exclusions** — Keep project-specific overrides (like `per-file-ignores` or `target-version`) from being clobbered by the upstream config.
168
170
  - 🌍 **Works with any host** — GitHub, GitLab, Bitbucket, private SSH servers, or any raw URL that serves a `pyproject.toml` or `ruff.toml`.
@@ -190,7 +192,7 @@ exclude = [
190
192
  ```
191
193
 
192
194
  This sets the default upstream and exclusions so you don't need to pass them on the command line every time.
193
- *Note: Any explicitly provided CLI arguments will override the values in `pyproject.toml`.*
195
+ _Note: Any explicitly provided CLI arguments will override the values in `pyproject.toml`._
194
196
 
195
197
  ### Advanced Configuration
196
198
 
@@ -212,8 +214,11 @@ exclude = [
212
214
  branch = "develop"
213
215
 
214
216
  # A directory prefix to use when looking for a configuration file in a repository. (Default: "")
215
- # Useful if the upstream pyproject.toml is not at the repository root.
217
+ # Useful if the upstream config is not at the repository root.
216
218
  path = "config/ruff"
219
+
220
+ # The local target directory or file to sync into. (Default: ".")
221
+ to = "."
217
222
  ```
218
223
 
219
224
  ## CI Integration
@@ -262,20 +267,48 @@ git commit -am "sync ruff config from upstream"
262
267
 
263
268
  ### Curated Examples
264
269
 
265
- While `ruff-sync` is designed to sync from *any* repository or URL of your choosing, this repository also provides a few curated configurations in the [`configs/`](./configs/) directory that you can use directly.
270
+ While `ruff-sync` is designed to sync from _any_ repository or URL of your choosing, this repository also provides a few curated configurations in the [`configs/`](./configs/) directory that you can use directly.
271
+
272
+ `ruff-sync` is flexible with URLs. You can point it at a repository root, a specific directory (tree), a direct file (blob), or even a raw content URL.
266
273
 
267
- For example, to sync an exhaustive "kitchen-sink" configuration that explicitly enables all rules and documents them:
274
+ #### Kitchen Sink
275
+
276
+ An exhaustive configuration that explicitly enables and documents almost all available Ruff rules. Great for establishing a strict baseline.
268
277
 
269
278
  ```console
279
+ # Directory URL (recommended)
280
+ ruff-sync https://github.com/Kilo59/ruff-sync/tree/main/configs/kitchen-sink
281
+
282
+ # Direct file URL (blob)
270
283
  ruff-sync https://github.com/Kilo59/ruff-sync/blob/main/configs/kitchen-sink/ruff.toml
284
+
285
+ # Raw content URL
286
+ ruff-sync https://raw.githubusercontent.com/Kilo59/ruff-sync/main/configs/kitchen-sink/ruff.toml
287
+
288
+ # Git SSH URL (clones the repo)
289
+ ruff-sync git@github.com:Kilo59/ruff-sync.git --path configs/kitchen-sink
290
+ ```
291
+
292
+ #### FastAPI & Async
293
+
294
+ Tailored for modern web applications. Includes rules for `asyncio`, security (`flake8-bandit`), and Pydantic-friendly naming conventions.
295
+
296
+ ```console
297
+ # Repository Root (if the config is at the root)
298
+ ruff-sync https://github.com/my-org/fastapi-standards
299
+
300
+ # Directory URL
301
+ ruff-sync https://github.com/Kilo59/ruff-sync/tree/main/configs/fastapi
271
302
  ```
272
303
 
273
- Or configure it using `pyproject.toml` so it's always the default for your local project:
304
+ #### Default Syncing
305
+
306
+ Set your preferred standard as the default in your `pyproject.toml`:
274
307
 
275
308
  ```toml
276
309
  [tool.ruff-sync]
277
310
  upstream = "https://github.com/Kilo59/ruff-sync"
278
- path = "configs/kitchen-sink"
311
+ path = "configs/fastapi"
279
312
  ```
280
313
 
281
314
  ## Bootstrapping a New Project
@@ -287,8 +320,7 @@ By default, `ruff-sync` requires an existing configuration file (`pyproject.toml
287
320
  ruff-sync pull https://github.com/my-org/standards --init
288
321
  ```
289
322
 
290
- `ruff-sync` seamlessly supports both `pyproject.toml` and standalone `ruff.toml` (or `.ruff.toml`) files. If your upstream source or your local target is a `ruff.toml`, it will automatically adapt and sync the root configuration rather than looking for a `[tool.ruff]` section.
291
-
323
+ `ruff-sync` seamlessly supports both `pyproject.toml` and standalone `ruff.toml` (or `.ruff.toml`) files. If your local target is a directory, it will look for configuration files in the following order: `ruff.toml` -> `.ruff.toml` -> `pyproject.toml`. If your upstream source or your local target is a `ruff.toml`, it will automatically adapt and sync the root configuration rather than looking for a `[tool.ruff]` section.
292
324
 
293
325
  ## Detailed Check Logic
294
326
 
@@ -296,9 +328,9 @@ When you run `ruff-sync check`, it follows this process to determine if your pro
296
328
 
297
329
  ```mermaid
298
330
  flowchart TD
299
- Start([Start]) --> Local[Read Local pyproject.toml]
300
- Local --> Upstream[Download Upstream pyproject.toml]
301
- Upstream --> Extract[Extract tool.ruff section]
331
+ Start([Start]) --> Local[Read Local Configuration]
332
+ Local --> Upstream[Download Upstream Configuration]
333
+ Upstream --> Extract[Extract tool.ruff section if needed]
302
334
  Extract --> Exclude[Apply Exclusions]
303
335
  Exclude --> Merge[Perform in-memory Merge]
304
336
 
@@ -16,7 +16,7 @@
16
16
 
17
17
  ---
18
18
 
19
- ### Table of Contents
19
+ ## Table of Contents
20
20
 
21
21
  - [The Problem](#the-problem)
22
22
  - [How It Works](#how-it-works)
@@ -26,7 +26,6 @@
26
26
  - [CI Integration](#ci-integration)
27
27
  - [Example Workflow](#example-workflow)
28
28
  - [Detailed Check Logic](#detailed-check-logic)
29
- - [Contributing](#contributing)
30
29
  - [Dogfooding](#dogfooding)
31
30
  - [License](#license)
32
31
 
@@ -72,9 +71,9 @@ Ruff's `extend` is perfect inside a monorepo, but if your projects live in **sep
72
71
  └─────────────────────────────┘
73
72
  ```
74
73
 
75
- 1. You point `ruff-sync` at the URL of your canonical `pyproject.toml`.
76
- 2. It downloads the file, extracts the `[tool.ruff]` section.
77
- 3. It **merges** the upstream config into your local `pyproject.toml` — updating values that changed, adding new rules, but preserving your local comments, whitespace, and any sections you've chosen to exclude (like `per-file-ignores`).
74
+ 1. You point `ruff-sync` at the URL of your canonical configuration (repository, directory, or direct file).
75
+ 2. It downloads the file and extracts the configuration (from `[tool.ruff]` in `pyproject.toml` or the top-level in `ruff.toml`).
76
+ 3. It **merges** the upstream config into your local project — updating values that changed, adding new rules, but preserving your local comments, whitespace, and any sections you've chosen to exclude (like `per-file-ignores`).
78
77
 
79
78
  No package registry. No publishing step. Just a URL.
80
79
 
@@ -103,8 +102,9 @@ pip install ruff-sync
103
102
  ### Usage
104
103
 
105
104
  ```console
106
- # Sync from a GitHub/GitLab repository (defaults to main/pyproject.toml)
105
+ # Sync from a GitHub/GitLab repository (root or specific directory)
107
106
  ruff-sync https://github.com/my-org/standards
107
+ ruff-sync https://github.com/my-org/standards/tree/main/configs/shared
108
108
 
109
109
  # Or a direct blob/file URL (auto-converts to raw)
110
110
  ruff-sync https://github.com/my-org/standards/blob/main/pyproject.toml
@@ -132,7 +132,8 @@ Run `ruff-sync --help` for full details on all available options.
132
132
  ## Key Features
133
133
 
134
134
  - 🏗️ **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.
135
- - 🌐 **GitHub & GitLab URL support** — Automatically converts GitHub/GitLab repository URLs or blob URLs to raw content URLs.
135
+ - 🌐 **GitHub & GitLab URL support** — Automatically converts GitHub/GitLab repository URLs, tree (directory) URLs, or blob (file) URLs to raw content URLs.
136
+ - 🔍 **Smart configuration discovery** — Point at a directory and `ruff-sync` will automatically find your config. It checks `pyproject.toml`, `ruff.toml`, and `.ruff.toml` (in that order).
136
137
  - 📥 **Git clone support** — If the URL starts with `git@` or uses the `ssh://`, `git://`, or `git+ssh://` schemes, `ruff-sync` will perform an efficient shallow clone (using `--filter=blob:none` and `--no-checkout`) to safely extract the configuration with minimal network traffic.
137
138
  - 🛡️ **Selective exclusions** — Keep project-specific overrides (like `per-file-ignores` or `target-version`) from being clobbered by the upstream config.
138
139
  - 🌍 **Works with any host** — GitHub, GitLab, Bitbucket, private SSH servers, or any raw URL that serves a `pyproject.toml` or `ruff.toml`.
@@ -160,7 +161,7 @@ exclude = [
160
161
  ```
161
162
 
162
163
  This sets the default upstream and exclusions so you don't need to pass them on the command line every time.
163
- *Note: Any explicitly provided CLI arguments will override the values in `pyproject.toml`.*
164
+ _Note: Any explicitly provided CLI arguments will override the values in `pyproject.toml`._
164
165
 
165
166
  ### Advanced Configuration
166
167
 
@@ -182,8 +183,11 @@ exclude = [
182
183
  branch = "develop"
183
184
 
184
185
  # A directory prefix to use when looking for a configuration file in a repository. (Default: "")
185
- # Useful if the upstream pyproject.toml is not at the repository root.
186
+ # Useful if the upstream config is not at the repository root.
186
187
  path = "config/ruff"
188
+
189
+ # The local target directory or file to sync into. (Default: ".")
190
+ to = "."
187
191
  ```
188
192
 
189
193
  ## CI Integration
@@ -232,20 +236,48 @@ git commit -am "sync ruff config from upstream"
232
236
 
233
237
  ### Curated Examples
234
238
 
235
- While `ruff-sync` is designed to sync from *any* repository or URL of your choosing, this repository also provides a few curated configurations in the [`configs/`](./configs/) directory that you can use directly.
239
+ While `ruff-sync` is designed to sync from _any_ repository or URL of your choosing, this repository also provides a few curated configurations in the [`configs/`](./configs/) directory that you can use directly.
240
+
241
+ `ruff-sync` is flexible with URLs. You can point it at a repository root, a specific directory (tree), a direct file (blob), or even a raw content URL.
236
242
 
237
- For example, to sync an exhaustive "kitchen-sink" configuration that explicitly enables all rules and documents them:
243
+ #### Kitchen Sink
244
+
245
+ An exhaustive configuration that explicitly enables and documents almost all available Ruff rules. Great for establishing a strict baseline.
238
246
 
239
247
  ```console
248
+ # Directory URL (recommended)
249
+ ruff-sync https://github.com/Kilo59/ruff-sync/tree/main/configs/kitchen-sink
250
+
251
+ # Direct file URL (blob)
240
252
  ruff-sync https://github.com/Kilo59/ruff-sync/blob/main/configs/kitchen-sink/ruff.toml
253
+
254
+ # Raw content URL
255
+ ruff-sync https://raw.githubusercontent.com/Kilo59/ruff-sync/main/configs/kitchen-sink/ruff.toml
256
+
257
+ # Git SSH URL (clones the repo)
258
+ ruff-sync git@github.com:Kilo59/ruff-sync.git --path configs/kitchen-sink
259
+ ```
260
+
261
+ #### FastAPI & Async
262
+
263
+ Tailored for modern web applications. Includes rules for `asyncio`, security (`flake8-bandit`), and Pydantic-friendly naming conventions.
264
+
265
+ ```console
266
+ # Repository Root (if the config is at the root)
267
+ ruff-sync https://github.com/my-org/fastapi-standards
268
+
269
+ # Directory URL
270
+ ruff-sync https://github.com/Kilo59/ruff-sync/tree/main/configs/fastapi
241
271
  ```
242
272
 
243
- Or configure it using `pyproject.toml` so it's always the default for your local project:
273
+ #### Default Syncing
274
+
275
+ Set your preferred standard as the default in your `pyproject.toml`:
244
276
 
245
277
  ```toml
246
278
  [tool.ruff-sync]
247
279
  upstream = "https://github.com/Kilo59/ruff-sync"
248
- path = "configs/kitchen-sink"
280
+ path = "configs/fastapi"
249
281
  ```
250
282
 
251
283
  ## Bootstrapping a New Project
@@ -257,8 +289,7 @@ By default, `ruff-sync` requires an existing configuration file (`pyproject.toml
257
289
  ruff-sync pull https://github.com/my-org/standards --init
258
290
  ```
259
291
 
260
- `ruff-sync` seamlessly supports both `pyproject.toml` and standalone `ruff.toml` (or `.ruff.toml`) files. If your upstream source or your local target is a `ruff.toml`, it will automatically adapt and sync the root configuration rather than looking for a `[tool.ruff]` section.
261
-
292
+ `ruff-sync` seamlessly supports both `pyproject.toml` and standalone `ruff.toml` (or `.ruff.toml`) files. If your local target is a directory, it will look for configuration files in the following order: `ruff.toml` -> `.ruff.toml` -> `pyproject.toml`. If your upstream source or your local target is a `ruff.toml`, it will automatically adapt and sync the root configuration rather than looking for a `[tool.ruff]` section.
262
293
 
263
294
  ## Detailed Check Logic
264
295
 
@@ -266,9 +297,9 @@ When you run `ruff-sync check`, it follows this process to determine if your pro
266
297
 
267
298
  ```mermaid
268
299
  flowchart TD
269
- Start([Start]) --> Local[Read Local pyproject.toml]
270
- Local --> Upstream[Download Upstream pyproject.toml]
271
- Upstream --> Extract[Extract tool.ruff section]
300
+ Start([Start]) --> Local[Read Local Configuration]
301
+ Local --> Upstream[Download Upstream Configuration]
302
+ Upstream --> Extract[Extract tool.ruff section if needed]
272
303
  Extract --> Exclude[Apply Exclusions]
273
304
  Exclude --> Merge[Perform in-memory Merge]
274
305
 
@@ -0,0 +1,150 @@
1
+ # ruff-sync FastAPI Configuration
2
+ # https://github.com/Kilo59/ruff-sync
3
+ #
4
+ # This is a Ruff configuration tailored for FastAPI / Web App development.
5
+ # It enables rules directly relevant to web development and async Python.
6
+ #
7
+ # Usage (direct Ruff config, assuming this file is vendored into your repo):
8
+ # extend = "configs/fastapi/ruff.toml"
9
+ #
10
+ # Usage (with ruff-sync in pyproject.toml):
11
+ # [tool.ruff-sync]
12
+ # path = "configs/fastapi/ruff.toml"
13
+
14
+ # Same as Black.
15
+ line-length = 88
16
+ indent-width = 4
17
+
18
+ # Assume Python 3.10. Consumers should override this to match their project's Python version.
19
+ target-version = "py310"
20
+
21
+ [lint]
22
+ # Enable rules relevant to web development and async Python.
23
+ select = [
24
+ # https://docs.astral.sh/ruff/rules/#pyflakes-f
25
+ "F", # Pyflakes: Essential checks for Python bugs
26
+ # https://docs.astral.sh/ruff/rules/#error-e
27
+ "E", # pycodestyle errors: PEP8 styling
28
+ # https://docs.astral.sh/ruff/rules/#warning-w
29
+ "W", # pycodestyle warnings: PEP8 styling
30
+ # https://docs.astral.sh/ruff/rules/#mccabe-c90
31
+ "C90", # mccabe: Code complexity (cyclomatic complexity)
32
+ # https://docs.astral.sh/ruff/rules/#isort-i
33
+ "I", # isort: Import sorting
34
+ # https://docs.astral.sh/ruff/rules/#pep8-naming-n
35
+ "N", # pep8-naming: Naming conventions
36
+ # https://docs.astral.sh/ruff/rules/#pydocstyle-d
37
+ "D", # pydocstyle: Docstring conventions
38
+ # https://docs.astral.sh/ruff/rules/#pyupgrade-up
39
+ "UP", # pyupgrade: Upgrade syntax for newer Python versions
40
+ # https://docs.astral.sh/ruff/rules/#flake8-annotations-ann
41
+ "ANN", # flake8-annotations: Type annotation checks
42
+ # https://docs.astral.sh/ruff/rules/#flake8-async-async
43
+ "ASYNC", # flake8-async: Asynchronous code checks
44
+ # https://docs.astral.sh/ruff/rules/#flake8-bandit-s
45
+ "S", # flake8-bandit: Security testing
46
+ # https://docs.astral.sh/ruff/rules/#flake8-bugbear-b
47
+ "B", # flake8-bugbear: Finding likely bugs and design problems
48
+ # https://docs.astral.sh/ruff/rules/#flake8-pie-pie
49
+ "PIE", # flake8-pie: Misc. lints
50
+ # https://docs.astral.sh/ruff/rules/#flake8-print-t20
51
+ "T20", # flake8-print: Check for Print statements
52
+ # https://docs.astral.sh/ruff/rules/#flake8-return-ret
53
+ "RET", # flake8-return: Check return values
54
+ # https://docs.astral.sh/ruff/rules/#flake8-simplify-sim
55
+ "SIM", # flake8-simplify: Code simplification
56
+ # https://docs.astral.sh/ruff/rules/#flake8-logging-format-g
57
+ "G", # flake8-logging-format: Validate logging format strings
58
+ # https://docs.astral.sh/ruff/rules/#flake8-quotes-q
59
+ "Q", # flake8-quotes: Lint for quotes
60
+ # https://docs.astral.sh/ruff/rules/#flake8-pytest-style-pt
61
+ "PT", # flake8-pytest-style: Pytest style checks
62
+ # https://docs.astral.sh/ruff/rules/#pylint-pl
63
+ "PL", # Pylint: Pylint rules
64
+ ]
65
+
66
+ # Ignore rules that conflict with the Ruff formatter.
67
+ # See: https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
68
+ ignore = [
69
+ "W191", # tab-indentation
70
+ "E111", # indentation-with-invalid-multiple
71
+ "E114", # indentation-with-invalid-multiple-comment
72
+ "E117", # over-indented
73
+ "D206", # docstring-tab-indentation
74
+ "D300", # triple-single-quotes
75
+ "Q000", # bad-quotes-inline-string
76
+ "Q001", # bad-quotes-multiline-string
77
+ "Q002", # bad-quotes-docstring
78
+ "Q003", # avoidable-escaped-quote
79
+ "Q004", # unnecessary-escaped-quote
80
+ "COM812", # missing-trailing-comma
81
+ "COM819", # prohibited-trailing-comma
82
+ "ISC001", # single-line-implicit-string-concatenation
83
+ "ISC002", # multi-line-implicit-string-concatenation
84
+ ]
85
+
86
+ # Allow autofix for all enabled rules.
87
+ fixable = ["ALL"]
88
+
89
+ # Allow unused variables when underscore-prefixed.
90
+ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
91
+
92
+ [lint.mccabe]
93
+ # Flag errors (C901) whenever the complexity level exceeds 10.
94
+ # Default: 10
95
+ max-complexity = 10
96
+
97
+ [lint.pydocstyle]
98
+ # Use Google-style docstrings (common in FastAPI projects).
99
+ # Default: "pep257"
100
+ convention = "google"
101
+
102
+ [lint.flake8-quotes]
103
+ # Use double quotes for inline strings (same as Black).
104
+ # Default: "double"
105
+ inline-quotes = "double"
106
+ # Use double quotes for multiline strings.
107
+ # Default: "double"
108
+ multiline-quotes = "double"
109
+ # Use double quotes for docstrings.
110
+ # Default: "double"
111
+ docstring-quotes = "double"
112
+ # Avoid escaping quotes if the other quote type would save an escape.
113
+ # Default: true
114
+ avoid-escape = true
115
+
116
+ [lint.flake8-pytest-style]
117
+ # Whether to require parentheses for pytest fixtures.
118
+ # Default: true
119
+ fixture-parentheses = true
120
+ # The type of pytest.mark.parametrize names to use: "tuple", "list", or "csv"
121
+ # Default: "tuple"
122
+ parametrize-names-type = "tuple"
123
+
124
+ [lint.pylint]
125
+ # The maximum number of arguments allowed for a function.
126
+ # Default: 5
127
+ max-args = 5
128
+ # The maximum number of branches allowed for a function.
129
+ # Default: 12
130
+ max-branches = 12
131
+
132
+ [lint.pep8-naming]
133
+ # Additional decorators that should be treated as classmethod.
134
+ # Pydantic v1/v2 decorators.
135
+ classmethod-decorators = [
136
+ "pydantic.validator",
137
+ "pydantic.root_validator",
138
+ "pydantic.field_validator",
139
+ "pydantic.model_validator",
140
+ ]
141
+
142
+ [format]
143
+ # Like Black, use double quotes for strings.
144
+ quote-style = "double"
145
+ # Like Black, indent with spaces, rather than tabs.
146
+ indent-style = "space"
147
+ # Like Black, respect magic trailing commas.
148
+ skip-magic-trailing-comma = false
149
+ # Like Black, automatically detect the appropriate line ending.
150
+ line-ending = "auto"