ruff-sync 0.0.1.dev2__tar.gz → 0.0.1.dev4__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.dev2 → ruff_sync-0.0.1.dev4}/.github/dependabot.yml +1 -1
- ruff_sync-0.0.1.dev2/README.md → ruff_sync-0.0.1.dev4/PKG-INFO +75 -16
- ruff_sync-0.0.1.dev2/PKG-INFO → ruff_sync-0.0.1.dev4/README.md +45 -28
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/pyproject.toml +24 -2
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/ruff_sync.py +83 -13
- ruff_sync-0.0.1.dev4/ruff_sync_banner.png +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/scripts/dogfood.sh +1 -1
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/test_basic.py +122 -5
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/test_e2e.py +1 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/test_project.py +12 -1
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/test_whitespace.py +22 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/uv.lock +1 -1
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/.agents/TESTING.md +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/.agents/workflows/add-test-case.md +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/.github/workflows/ci.yaml +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/.github/workflows/complexity.yaml +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/.gitignore +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/.pre-commit-config.yaml +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/AGENTS.md +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/LICENSE.md +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/codecov.yml +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tasks.py +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/__init__.py +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/lifecycle_tomls/no_changes_final.toml +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/lifecycle_tomls/no_changes_initial.toml +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/lifecycle_tomls/no_changes_upstream.toml +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/lifecycle_tomls/no_dotted_keys_final.toml +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/lifecycle_tomls/no_dotted_keys_initial.toml +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/lifecycle_tomls/no_dotted_keys_upstream.toml +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/lifecycle_tomls/no_ruff_cfg_final.toml +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/lifecycle_tomls/no_ruff_cfg_initial.toml +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/lifecycle_tomls/no_ruff_cfg_upstream.toml +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/lifecycle_tomls/standard_final.toml +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/lifecycle_tomls/standard_initial.toml +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/lifecycle_tomls/standard_upstream.toml +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/ruff.toml +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/test_corner_cases.py +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/test_toml_operations.py +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/w_ruff_sync_cfg/pyproject.toml +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/wo_ruff_cfg/pyproject.toml +0 -0
- {ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/wo_ruff_sync_cfg/pyproject.toml +0 -0
|
@@ -1,12 +1,61 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ruff-sync
|
|
3
|
+
Version: 0.0.1.dev4
|
|
4
|
+
Summary: Synchronize Ruff linter configuration across projects
|
|
5
|
+
Project-URL: Homepage, https://github.com/Kilo59/ruff-sync
|
|
6
|
+
Project-URL: Documentation, https://github.com/Kilo59/ruff-sync#readme
|
|
7
|
+
Project-URL: Repository, https://github.com/Kilo59/ruff-sync
|
|
8
|
+
Project-URL: Issues, https://github.com/Kilo59/ruff-sync/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/Kilo59/ruff-sync/releases
|
|
10
|
+
Author-email: Gabriel Gore <gabriel59kg@gmail.com>
|
|
11
|
+
License: MIT
|
|
12
|
+
License-File: LICENSE.md
|
|
13
|
+
Keywords: automation,config,linter,linting,python,ruff,synchronize,tomlkit
|
|
14
|
+
Classifier: Development Status :: 3 - Alpha
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
25
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Requires-Dist: httpx<1.0.0,>=0.27.0
|
|
28
|
+
Requires-Dist: tomlkit<2.0.0,>=0.12.3
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
<p align="center">
|
|
32
|
+
<img src="https://raw.githubusercontent.com/Kilo59/ruff-sync/main/ruff_sync_banner.png" alt="ruff-sync banner" style="max-width: 600px; width: 100%; height: auto; margin-bottom: 1rem;">
|
|
33
|
+
<br>
|
|
34
|
+
<a href="https://pypi.org/project/ruff-sync/"><img src="https://img.shields.io/pypi/v/ruff-sync" alt="PyPI version"></a>
|
|
35
|
+
<a href="https://codecov.io/gh/Kilo59/ruff-sync"><img src="https://codecov.io/gh/Kilo59/ruff-sync/graph/badge.svg?token=kMZw0XtoFW" alt="codecov"></a>
|
|
36
|
+
<a href="https://results.pre-commit.ci/latest/github/Kilo59/ruff-sync/main"><img src="https://results.pre-commit.ci/badge/github/Kilo59/ruff-sync/main.svg" alt="pre-commit.ci status"></a>
|
|
37
|
+
<a href="https://github.com/astral-sh/ruff"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff"></a>
|
|
38
|
+
<a href="https://wily.readthedocs.io/"><img src="https://img.shields.io/badge/%F0%9F%A6%8A%20wily-passing-brightgreen.svg" alt="Wily"></a>
|
|
39
|
+
</p>
|
|
5
40
|
|
|
6
41
|
# ruff-sync
|
|
7
42
|
|
|
8
43
|
**Keep your Ruff config consistent across every repo — automatically.**
|
|
9
44
|
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
### Table of Contents
|
|
48
|
+
|
|
49
|
+
- [The Problem](#the-problem)
|
|
50
|
+
- [How It Works](#how-it-works)
|
|
51
|
+
- [Quick Start](#quick-start)
|
|
52
|
+
- [Install](#install)
|
|
53
|
+
- [Usage](#usage)
|
|
54
|
+
- [Key Features](#key-features)
|
|
55
|
+
- [Configuration](#configuration)
|
|
56
|
+
- [Contributing](#contributing)
|
|
57
|
+
- [License](#license)
|
|
58
|
+
|
|
10
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.
|
|
11
60
|
|
|
12
61
|
## The Problem
|
|
@@ -61,28 +110,30 @@ No package registry. No publishing step. Just a URL.
|
|
|
61
110
|
|
|
62
111
|
### Install
|
|
63
112
|
|
|
64
|
-
|
|
113
|
+
With [uv](https://docs.astral.sh/uv/) (recommended):
|
|
65
114
|
|
|
66
115
|
```console
|
|
67
|
-
|
|
68
|
-
```
|
|
116
|
+
uv tool install ruff-sync
|
|
117
|
+
```
|
|
69
118
|
|
|
70
|
-
|
|
119
|
+
With [pipx](https://pipx.pypa.io/stable/):
|
|
71
120
|
|
|
72
121
|
```console
|
|
73
|
-
|
|
122
|
+
pipx install ruff-sync
|
|
74
123
|
```
|
|
75
124
|
|
|
76
|
-
|
|
125
|
+
With [pip](https://pip.pypa.io/en/stable/):
|
|
77
126
|
|
|
78
127
|
```console
|
|
79
|
-
|
|
128
|
+
pip install ruff-sync
|
|
80
129
|
```
|
|
81
130
|
|
|
82
|
-
|
|
131
|
+
#### From Source (Bleeding Edge)
|
|
132
|
+
|
|
133
|
+
If you want the latest development version:
|
|
83
134
|
|
|
84
135
|
```console
|
|
85
|
-
|
|
136
|
+
uv tool install git+https://github.com/Kilo59/ruff-sync
|
|
86
137
|
```
|
|
87
138
|
|
|
88
139
|
### Usage
|
|
@@ -91,26 +142,31 @@ pip install git+https://github.com/Kilo59/ruff-sync
|
|
|
91
142
|
# Sync from a GitHub URL (blob URLs are auto-converted to raw)
|
|
92
143
|
ruff-sync https://github.com/my-org/standards/blob/main/pyproject.toml
|
|
93
144
|
|
|
145
|
+
# Once configured in pyproject.toml (see below), simply run:
|
|
146
|
+
ruff-sync
|
|
147
|
+
|
|
94
148
|
# Sync into a specific project directory
|
|
95
|
-
ruff-sync
|
|
149
|
+
ruff-sync --source ./my-project
|
|
96
150
|
|
|
97
151
|
# Exclude specific sections from being overwritten using dotted paths
|
|
98
|
-
ruff-sync
|
|
152
|
+
ruff-sync --exclude lint.per-file-ignores lint.ignore
|
|
99
153
|
```
|
|
100
154
|
|
|
101
155
|
### CLI Reference
|
|
102
156
|
|
|
103
157
|
```
|
|
104
|
-
usage: ruff-sync [-h] [--source SOURCE] [--exclude EXCLUDE [EXCLUDE ...]] upstream
|
|
158
|
+
usage: ruff-sync [-h] [--source SOURCE] [--exclude EXCLUDE [EXCLUDE ...]] [-v] [upstream]
|
|
105
159
|
|
|
106
160
|
positional arguments:
|
|
107
161
|
upstream The URL to download the pyproject.toml file from.
|
|
162
|
+
Optional if defined in [tool.ruff-sync]
|
|
108
163
|
|
|
109
164
|
optional arguments:
|
|
110
165
|
-h, --help show this help message and exit
|
|
111
166
|
--source SOURCE The directory to sync the pyproject.toml file to. Default: .
|
|
112
167
|
--exclude EXCLUDE [EXCLUDE ...]
|
|
113
168
|
Exclude certain ruff configs. Default: lint.per-file-ignores
|
|
169
|
+
-v, --verbose Increase verbosity. -v for INFO, -vv for DEBUG.
|
|
114
170
|
```
|
|
115
171
|
|
|
116
172
|
## Key Features
|
|
@@ -126,6 +182,9 @@ You can configure `ruff-sync` itself in your `pyproject.toml`:
|
|
|
126
182
|
|
|
127
183
|
```toml
|
|
128
184
|
[tool.ruff-sync]
|
|
185
|
+
# The source of truth for your ruff configuration
|
|
186
|
+
upstream = "https://github.com/my-org/standards/blob/main/pyproject.toml"
|
|
187
|
+
|
|
129
188
|
# Use simple names for top-level keys, and dotted paths for nested keys
|
|
130
189
|
exclude = [
|
|
131
190
|
"target-version", # A top-level key under [tool.ruff]
|
|
@@ -1,24 +1,31 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
Requires-Dist: tomlkit<2.0.0,>=0.12.3
|
|
11
|
-
Description-Content-Type: text/markdown
|
|
12
|
-
|
|
13
|
-
[](https://codecov.io/gh/Kilo59/ruff-sync)
|
|
14
|
-
[](https://results.pre-commit.ci/latest/github/Kilo59/ruff-sync/main)
|
|
15
|
-
[](https://wily.readthedocs.io/)
|
|
16
|
-
[](https://github.com/astral-sh/ruff)
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/Kilo59/ruff-sync/main/ruff_sync_banner.png" alt="ruff-sync banner" style="max-width: 600px; width: 100%; height: auto; margin-bottom: 1rem;">
|
|
3
|
+
<br>
|
|
4
|
+
<a href="https://pypi.org/project/ruff-sync/"><img src="https://img.shields.io/pypi/v/ruff-sync" alt="PyPI version"></a>
|
|
5
|
+
<a href="https://codecov.io/gh/Kilo59/ruff-sync"><img src="https://codecov.io/gh/Kilo59/ruff-sync/graph/badge.svg?token=kMZw0XtoFW" alt="codecov"></a>
|
|
6
|
+
<a href="https://results.pre-commit.ci/latest/github/Kilo59/ruff-sync/main"><img src="https://results.pre-commit.ci/badge/github/Kilo59/ruff-sync/main.svg" alt="pre-commit.ci status"></a>
|
|
7
|
+
<a href="https://github.com/astral-sh/ruff"><img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff"></a>
|
|
8
|
+
<a href="https://wily.readthedocs.io/"><img src="https://img.shields.io/badge/%F0%9F%A6%8A%20wily-passing-brightgreen.svg" alt="Wily"></a>
|
|
9
|
+
</p>
|
|
17
10
|
|
|
18
11
|
# ruff-sync
|
|
19
12
|
|
|
20
13
|
**Keep your Ruff config consistent across every repo — automatically.**
|
|
21
14
|
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
### Table of Contents
|
|
18
|
+
|
|
19
|
+
- [The Problem](#the-problem)
|
|
20
|
+
- [How It Works](#how-it-works)
|
|
21
|
+
- [Quick Start](#quick-start)
|
|
22
|
+
- [Install](#install)
|
|
23
|
+
- [Usage](#usage)
|
|
24
|
+
- [Key Features](#key-features)
|
|
25
|
+
- [Configuration](#configuration)
|
|
26
|
+
- [Contributing](#contributing)
|
|
27
|
+
- [License](#license)
|
|
28
|
+
|
|
22
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.
|
|
23
30
|
|
|
24
31
|
## The Problem
|
|
@@ -73,28 +80,30 @@ No package registry. No publishing step. Just a URL.
|
|
|
73
80
|
|
|
74
81
|
### Install
|
|
75
82
|
|
|
76
|
-
|
|
83
|
+
With [uv](https://docs.astral.sh/uv/) (recommended):
|
|
77
84
|
|
|
78
85
|
```console
|
|
79
|
-
|
|
80
|
-
```
|
|
86
|
+
uv tool install ruff-sync
|
|
87
|
+
```
|
|
81
88
|
|
|
82
|
-
|
|
89
|
+
With [pipx](https://pipx.pypa.io/stable/):
|
|
83
90
|
|
|
84
91
|
```console
|
|
85
|
-
|
|
92
|
+
pipx install ruff-sync
|
|
86
93
|
```
|
|
87
94
|
|
|
88
|
-
|
|
95
|
+
With [pip](https://pip.pypa.io/en/stable/):
|
|
89
96
|
|
|
90
97
|
```console
|
|
91
|
-
|
|
98
|
+
pip install ruff-sync
|
|
92
99
|
```
|
|
93
100
|
|
|
94
|
-
|
|
101
|
+
#### From Source (Bleeding Edge)
|
|
102
|
+
|
|
103
|
+
If you want the latest development version:
|
|
95
104
|
|
|
96
105
|
```console
|
|
97
|
-
|
|
106
|
+
uv tool install git+https://github.com/Kilo59/ruff-sync
|
|
98
107
|
```
|
|
99
108
|
|
|
100
109
|
### Usage
|
|
@@ -103,26 +112,31 @@ pip install git+https://github.com/Kilo59/ruff-sync
|
|
|
103
112
|
# Sync from a GitHub URL (blob URLs are auto-converted to raw)
|
|
104
113
|
ruff-sync https://github.com/my-org/standards/blob/main/pyproject.toml
|
|
105
114
|
|
|
115
|
+
# Once configured in pyproject.toml (see below), simply run:
|
|
116
|
+
ruff-sync
|
|
117
|
+
|
|
106
118
|
# Sync into a specific project directory
|
|
107
|
-
ruff-sync
|
|
119
|
+
ruff-sync --source ./my-project
|
|
108
120
|
|
|
109
121
|
# Exclude specific sections from being overwritten using dotted paths
|
|
110
|
-
ruff-sync
|
|
122
|
+
ruff-sync --exclude lint.per-file-ignores lint.ignore
|
|
111
123
|
```
|
|
112
124
|
|
|
113
125
|
### CLI Reference
|
|
114
126
|
|
|
115
127
|
```
|
|
116
|
-
usage: ruff-sync [-h] [--source SOURCE] [--exclude EXCLUDE [EXCLUDE ...]] upstream
|
|
128
|
+
usage: ruff-sync [-h] [--source SOURCE] [--exclude EXCLUDE [EXCLUDE ...]] [-v] [upstream]
|
|
117
129
|
|
|
118
130
|
positional arguments:
|
|
119
131
|
upstream The URL to download the pyproject.toml file from.
|
|
132
|
+
Optional if defined in [tool.ruff-sync]
|
|
120
133
|
|
|
121
134
|
optional arguments:
|
|
122
135
|
-h, --help show this help message and exit
|
|
123
136
|
--source SOURCE The directory to sync the pyproject.toml file to. Default: .
|
|
124
137
|
--exclude EXCLUDE [EXCLUDE ...]
|
|
125
138
|
Exclude certain ruff configs. Default: lint.per-file-ignores
|
|
139
|
+
-v, --verbose Increase verbosity. -v for INFO, -vv for DEBUG.
|
|
126
140
|
```
|
|
127
141
|
|
|
128
142
|
## Key Features
|
|
@@ -138,6 +152,9 @@ You can configure `ruff-sync` itself in your `pyproject.toml`:
|
|
|
138
152
|
|
|
139
153
|
```toml
|
|
140
154
|
[tool.ruff-sync]
|
|
155
|
+
# The source of truth for your ruff configuration
|
|
156
|
+
upstream = "https://github.com/my-org/standards/blob/main/pyproject.toml"
|
|
157
|
+
|
|
141
158
|
# Use simple names for top-level keys, and dotted paths for nested keys
|
|
142
159
|
exclude = [
|
|
143
160
|
"target-version", # A top-level key under [tool.ruff]
|
|
@@ -1,18 +1,40 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "ruff-sync"
|
|
3
|
-
version = "0.0.1.
|
|
4
|
-
description = "
|
|
3
|
+
version = "0.0.1.dev4"
|
|
4
|
+
description = "Synchronize Ruff linter configuration across projects"
|
|
5
|
+
keywords = ["ruff", "linter", "config", "synchronize", "python", "linting", "automation", "tomlkit"]
|
|
5
6
|
authors = [
|
|
6
7
|
{ name = "Gabriel Gore", email = "gabriel59kg@gmail.com" }
|
|
7
8
|
]
|
|
8
9
|
license = { text = "MIT" }
|
|
9
10
|
readme = "README.md"
|
|
10
11
|
requires-python = ">=3.10"
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 3 - Alpha",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
"Programming Language :: Python",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Programming Language :: Python :: 3.14",
|
|
24
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
25
|
+
]
|
|
11
26
|
dependencies = [
|
|
12
27
|
"httpx>=0.27.0,<1.0.0",
|
|
13
28
|
"tomlkit>=0.12.3,<2.0.0",
|
|
14
29
|
]
|
|
15
30
|
|
|
31
|
+
[project.urls]
|
|
32
|
+
Homepage = "https://github.com/Kilo59/ruff-sync"
|
|
33
|
+
Documentation = "https://github.com/Kilo59/ruff-sync#readme"
|
|
34
|
+
Repository = "https://github.com/Kilo59/ruff-sync"
|
|
35
|
+
Issues = "https://github.com/Kilo59/ruff-sync/issues"
|
|
36
|
+
Changelog = "https://github.com/Kilo59/ruff-sync/releases"
|
|
37
|
+
|
|
16
38
|
[project.scripts]
|
|
17
39
|
ruff-sync = "ruff_sync:main"
|
|
18
40
|
|
|
@@ -3,12 +3,12 @@ from __future__ import annotations
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import logging
|
|
5
5
|
import pathlib
|
|
6
|
-
import
|
|
6
|
+
import sys
|
|
7
7
|
from argparse import ArgumentParser
|
|
8
8
|
from collections.abc import Iterable, Mapping
|
|
9
9
|
from functools import lru_cache
|
|
10
10
|
from io import StringIO
|
|
11
|
-
from typing import Any, Final, Literal, NamedTuple, overload
|
|
11
|
+
from typing import Any, ClassVar, Final, Literal, NamedTuple, overload
|
|
12
12
|
|
|
13
13
|
import httpx
|
|
14
14
|
import tomlkit
|
|
@@ -17,17 +17,40 @@ from tomlkit import TOMLDocument, table
|
|
|
17
17
|
from tomlkit.items import Table
|
|
18
18
|
from tomlkit.toml_file import TOMLFile
|
|
19
19
|
|
|
20
|
-
__version__ = "0.0.1.
|
|
20
|
+
__version__ = "0.0.1.dev4"
|
|
21
21
|
|
|
22
22
|
_DEFAULT_EXCLUDE: Final[set[str]] = {"lint.per-file-ignores"}
|
|
23
23
|
|
|
24
24
|
LOGGER = logging.getLogger(__name__)
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
class ColoredFormatter(logging.Formatter):
|
|
28
|
+
"""Logging Formatter to add colors"""
|
|
29
|
+
|
|
30
|
+
RESET: ClassVar[str] = "\x1b[0m"
|
|
31
|
+
COLORS: ClassVar[Mapping[int, str]] = {
|
|
32
|
+
logging.DEBUG: "\x1b[36m", # Cyan
|
|
33
|
+
logging.INFO: "\x1b[32m", # Green
|
|
34
|
+
logging.WARNING: "\x1b[33m", # Yellow
|
|
35
|
+
logging.ERROR: "\x1b[31m", # Red
|
|
36
|
+
logging.CRITICAL: "\x1b[1;31m", # Bold Red
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
def __init__(self, fmt: str = "%(message)s") -> None:
|
|
40
|
+
super().__init__(fmt)
|
|
41
|
+
|
|
42
|
+
def format(self, record: logging.LogRecord) -> str: # type: ignore[explicit-override]
|
|
43
|
+
if sys.stderr.isatty():
|
|
44
|
+
color = self.COLORS.get(record.levelno, self.RESET)
|
|
45
|
+
return f"{color}{super().format(record)}{self.RESET}"
|
|
46
|
+
return super().format(record)
|
|
47
|
+
|
|
48
|
+
|
|
27
49
|
class Arguments(NamedTuple):
|
|
28
50
|
upstream: URL
|
|
29
51
|
source: pathlib.Path
|
|
30
52
|
exclude: Iterable[str]
|
|
53
|
+
verbose: int
|
|
31
54
|
|
|
32
55
|
@classmethod
|
|
33
56
|
@lru_cache(maxsize=1)
|
|
@@ -50,7 +73,7 @@ def get_config(
|
|
|
50
73
|
if arg in Arguments.fields():
|
|
51
74
|
cfg_result[arg] = value
|
|
52
75
|
else:
|
|
53
|
-
|
|
76
|
+
LOGGER.warning(f"Unknown ruff-sync configuration: {arg}")
|
|
54
77
|
return cfg_result
|
|
55
78
|
|
|
56
79
|
|
|
@@ -62,13 +85,14 @@ def _resolve_source(source: str | pathlib.Path) -> pathlib.Path:
|
|
|
62
85
|
|
|
63
86
|
|
|
64
87
|
def _get_cli_parser() -> ArgumentParser:
|
|
65
|
-
# TODO: determine if args was provided by user or not
|
|
66
88
|
# https://docs.python.org/3/library/argparse.html#nargs
|
|
67
89
|
parser = ArgumentParser()
|
|
68
90
|
parser.add_argument(
|
|
69
91
|
"upstream",
|
|
70
92
|
type=URL,
|
|
71
|
-
|
|
93
|
+
nargs="?",
|
|
94
|
+
help="The URL to download the pyproject.toml file from."
|
|
95
|
+
" Optional if defined in [tool.ruff-sync].",
|
|
72
96
|
)
|
|
73
97
|
parser.add_argument(
|
|
74
98
|
"--source",
|
|
@@ -83,6 +107,13 @@ def _get_cli_parser() -> ArgumentParser:
|
|
|
83
107
|
help=f"Exclude certain ruff configs. Default: {' '.join(_DEFAULT_EXCLUDE)}",
|
|
84
108
|
default=None,
|
|
85
109
|
)
|
|
110
|
+
parser.add_argument(
|
|
111
|
+
"-v",
|
|
112
|
+
"--verbose",
|
|
113
|
+
action="count",
|
|
114
|
+
default=0,
|
|
115
|
+
help="Increase verbosity. -v for INFO, -vv for DEBUG.",
|
|
116
|
+
)
|
|
86
117
|
return parser
|
|
87
118
|
|
|
88
119
|
|
|
@@ -100,8 +131,10 @@ def github_url_to_raw_url(url: URL) -> URL:
|
|
|
100
131
|
raw_url_str = url_str.replace("github.com", "raw.githubusercontent.com").replace(
|
|
101
132
|
"/blob/", "/"
|
|
102
133
|
)
|
|
134
|
+
LOGGER.debug(f"Converting GitHub URL to raw content URL: {raw_url_str}")
|
|
103
135
|
return httpx.URL(raw_url_str)
|
|
104
136
|
else:
|
|
137
|
+
LOGGER.debug("URL is not a GitHub URL, returning as is.")
|
|
105
138
|
return url
|
|
106
139
|
|
|
107
140
|
|
|
@@ -147,7 +180,7 @@ def get_ruff_tool_table(
|
|
|
147
180
|
except KeyError:
|
|
148
181
|
if not create_if_missing:
|
|
149
182
|
return None
|
|
150
|
-
LOGGER.info("No `tool.ruff` section found, creating it.")
|
|
183
|
+
LOGGER.info("✨ No `tool.ruff` section found, creating it.")
|
|
151
184
|
tool = table(True)
|
|
152
185
|
ruff = table()
|
|
153
186
|
tool.append("ruff", ruff)
|
|
@@ -183,7 +216,7 @@ def toml_ruff_parse(toml_s: str, exclude: Iterable[str]) -> TOMLDocument:
|
|
|
183
216
|
"""Parse a TOML string for the tool.ruff section excluding certain ruff configs."""
|
|
184
217
|
ruff_toml: TOMLDocument = tomlkit.parse(toml_s)["tool"]["ruff"] # type: ignore[index,assignment]
|
|
185
218
|
for section in exclude:
|
|
186
|
-
LOGGER.info(f"
|
|
219
|
+
LOGGER.info(f"Excluding section `lint.{section}` from ruff config.")
|
|
187
220
|
ruff_toml["lint"].pop(section, None) # type: ignore[union-attr]
|
|
188
221
|
return ruff_toml
|
|
189
222
|
|
|
@@ -234,6 +267,11 @@ def merge_ruff_toml(
|
|
|
234
267
|
|
|
235
268
|
_recursive_update(source_tool_ruff, upstream_ruff_doc)
|
|
236
269
|
|
|
270
|
+
# Ensure a newline at the end of the section for better readability.
|
|
271
|
+
# We only add it if it's missing to avoid triple newlines between sections.
|
|
272
|
+
if not source_tool_ruff.as_string().endswith("\n\n"):
|
|
273
|
+
source_tool_ruff.add(tomlkit.nl())
|
|
274
|
+
|
|
237
275
|
return source
|
|
238
276
|
|
|
239
277
|
|
|
@@ -241,7 +279,7 @@ async def sync(
|
|
|
241
279
|
args: Arguments,
|
|
242
280
|
) -> None:
|
|
243
281
|
"""Sync the upstream pyproject.toml file to the source directory."""
|
|
244
|
-
print("Syncing Ruff...")
|
|
282
|
+
print("🔄 Syncing Ruff...")
|
|
245
283
|
if args.source.is_file():
|
|
246
284
|
_source_toml_path = args.source
|
|
247
285
|
else:
|
|
@@ -261,7 +299,7 @@ async def sync(
|
|
|
261
299
|
upstream_ruff_toml,
|
|
262
300
|
)
|
|
263
301
|
source_toml_file.write(merged_toml)
|
|
264
|
-
print(f"Updated {_source_toml_path.resolve().relative_to(pathlib.Path.cwd())}")
|
|
302
|
+
print(f"✅ Updated {_source_toml_path.resolve().relative_to(pathlib.Path.cwd())}")
|
|
265
303
|
|
|
266
304
|
|
|
267
305
|
PARSER: Final[ArgumentParser] = _get_cli_parser()
|
|
@@ -271,6 +309,37 @@ def main() -> None:
|
|
|
271
309
|
args = PARSER.parse_args()
|
|
272
310
|
config = get_config(args.source)
|
|
273
311
|
|
|
312
|
+
# Configure logging
|
|
313
|
+
log_level = {
|
|
314
|
+
0: logging.WARNING,
|
|
315
|
+
1: logging.INFO,
|
|
316
|
+
}.get(args.verbose, logging.DEBUG)
|
|
317
|
+
|
|
318
|
+
LOGGER.setLevel(log_level)
|
|
319
|
+
handler = logging.StreamHandler()
|
|
320
|
+
handler.setFormatter(ColoredFormatter())
|
|
321
|
+
LOGGER.addHandler(handler)
|
|
322
|
+
LOGGER.propagate = False # Avoid double logging if root is also configured
|
|
323
|
+
|
|
324
|
+
# Resolve upstream: use CLI value if explicitly provided, else file config
|
|
325
|
+
upstream: URL
|
|
326
|
+
if args.upstream:
|
|
327
|
+
upstream = args.upstream
|
|
328
|
+
elif "upstream" in config:
|
|
329
|
+
config_upstream = config["upstream"]
|
|
330
|
+
if not isinstance(config_upstream, str):
|
|
331
|
+
PARSER.error(
|
|
332
|
+
"❌ upstream in [tool.ruff-sync] must be a string, "
|
|
333
|
+
f"got {type(config_upstream).__name__}"
|
|
334
|
+
)
|
|
335
|
+
upstream = URL(config_upstream)
|
|
336
|
+
LOGGER.info(f"📂 Using upstream from [tool.ruff-sync]: {upstream}")
|
|
337
|
+
else:
|
|
338
|
+
PARSER.error(
|
|
339
|
+
"❌ the following arguments are required: upstream "
|
|
340
|
+
"(or define it in [tool.ruff-sync] in pyproject.toml) 💥"
|
|
341
|
+
)
|
|
342
|
+
|
|
274
343
|
# Merge exclude: use CLI value if explicitly provided, else file config,
|
|
275
344
|
# else the built-in default.
|
|
276
345
|
exclude: Iterable[str]
|
|
@@ -279,19 +348,20 @@ def main() -> None:
|
|
|
279
348
|
exclude = args.exclude
|
|
280
349
|
elif "exclude" in config:
|
|
281
350
|
exclude = config["exclude"]
|
|
282
|
-
LOGGER.info(f"Using exclude from [tool.ruff-sync]: {list(exclude)}")
|
|
351
|
+
LOGGER.info(f"🚫 Using exclude from [tool.ruff-sync]: {list(exclude)}")
|
|
283
352
|
else:
|
|
284
353
|
exclude = _DEFAULT_EXCLUDE
|
|
285
354
|
|
|
286
355
|
# Convert non-raw github upstream url to the raw equivalent
|
|
287
|
-
|
|
356
|
+
upstream = github_url_to_raw_url(upstream)
|
|
288
357
|
|
|
289
358
|
asyncio.run(
|
|
290
359
|
sync(
|
|
291
360
|
Arguments(
|
|
292
|
-
upstream=
|
|
361
|
+
upstream=upstream,
|
|
293
362
|
source=args.source,
|
|
294
363
|
exclude=exclude,
|
|
364
|
+
verbose=args.verbose,
|
|
295
365
|
)
|
|
296
366
|
)
|
|
297
367
|
)
|
|
Binary file
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import contextlib
|
|
5
|
+
import logging
|
|
5
6
|
import os
|
|
6
7
|
import pathlib
|
|
7
8
|
import shutil
|
|
@@ -65,10 +66,6 @@ def pyproject_toml_s() -> str:
|
|
|
65
66
|
return s
|
|
66
67
|
|
|
67
68
|
|
|
68
|
-
def test_ruff_sync():
|
|
69
|
-
assert ruff_sync.__version__ == "0.0.1.dev0"
|
|
70
|
-
|
|
71
|
-
|
|
72
69
|
@pytest.fixture
|
|
73
70
|
def toml_s() -> str:
|
|
74
71
|
"""A sample pyproject.toml file with ruff config."""
|
|
@@ -273,7 +270,9 @@ async def test_sync_updates_ruff_config(
|
|
|
273
270
|
upstream = URL("https://example.com/pyproject.toml")
|
|
274
271
|
upstream_toml = httpx.get(upstream).text # blocking but doesn't matter
|
|
275
272
|
await ruff_sync.sync(
|
|
276
|
-
ruff_sync.Arguments(
|
|
273
|
+
ruff_sync.Arguments(
|
|
274
|
+
upstream=upstream, source=fake_fs_source, exclude=(), verbose=0
|
|
275
|
+
)
|
|
277
276
|
)
|
|
278
277
|
updated_toml = fake_fs_source.read_text()
|
|
279
278
|
updated_ruff_config: Table = tomlkit.parse(updated_toml)["tool"]["ruff"] # type: ignore[index,assignment]
|
|
@@ -406,6 +405,123 @@ def test_exclude_resolution_default(monkeypatch: pytest.MonkeyPatch):
|
|
|
406
405
|
assert captured_args[0].exclude == ruff_sync._DEFAULT_EXCLUDE
|
|
407
406
|
|
|
408
407
|
|
|
408
|
+
def test_upstream_resolution_cli_precedence(monkeypatch: pytest.MonkeyPatch):
|
|
409
|
+
"""CLI upstream should override config."""
|
|
410
|
+
captured_args: list[ruff_sync.Arguments] = []
|
|
411
|
+
|
|
412
|
+
def mock_sync(args: ruff_sync.Arguments) -> Any:
|
|
413
|
+
captured_args.append(args)
|
|
414
|
+
return asyncio.sleep(0)
|
|
415
|
+
|
|
416
|
+
monkeypatch.setattr(sys, "argv", ["ruff-sync", "http://cli.com"])
|
|
417
|
+
monkeypatch.setattr(
|
|
418
|
+
ruff_sync, "get_config", lambda _: {"upstream": "http://config.com"}
|
|
419
|
+
)
|
|
420
|
+
monkeypatch.setattr(ruff_sync, "sync", mock_sync)
|
|
421
|
+
monkeypatch.setattr(asyncio, "run", lambda _coro: None)
|
|
422
|
+
|
|
423
|
+
ruff_sync.main()
|
|
424
|
+
|
|
425
|
+
assert len(captured_args) == 1
|
|
426
|
+
assert str(captured_args[0].upstream) == "http://cli.com"
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def test_upstream_resolution_missing(
|
|
430
|
+
monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str]
|
|
431
|
+
) -> None:
|
|
432
|
+
"""Error when no upstream is provided via CLI or config."""
|
|
433
|
+
captured_args: list[ruff_sync.Arguments] = []
|
|
434
|
+
|
|
435
|
+
def mock_sync(args: ruff_sync.Arguments) -> Any:
|
|
436
|
+
captured_args.append(args)
|
|
437
|
+
return asyncio.sleep(0)
|
|
438
|
+
|
|
439
|
+
# No CLI upstream argument
|
|
440
|
+
monkeypatch.setattr(sys, "argv", ["ruff-sync"])
|
|
441
|
+
# No upstream in config
|
|
442
|
+
monkeypatch.setattr(ruff_sync, "get_config", lambda _: {})
|
|
443
|
+
# Ensure sync is never called if upstream is missing
|
|
444
|
+
monkeypatch.setattr(ruff_sync, "sync", mock_sync)
|
|
445
|
+
monkeypatch.setattr(asyncio, "run", lambda _coro: None)
|
|
446
|
+
|
|
447
|
+
with pytest.raises(SystemExit) as excinfo:
|
|
448
|
+
ruff_sync.main()
|
|
449
|
+
|
|
450
|
+
# Non-zero exit code on failure
|
|
451
|
+
assert excinfo.value.code != 0
|
|
452
|
+
|
|
453
|
+
captured = capsys.readouterr()
|
|
454
|
+
# Error message should indicate that an upstream is required
|
|
455
|
+
assert "upstream" in captured.err
|
|
456
|
+
assert "[tool.ruff-sync]" in captured.err
|
|
457
|
+
|
|
458
|
+
# When erroring early, sync must not be invoked
|
|
459
|
+
assert captured_args == []
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def test_upstream_resolution_config_precedence(monkeypatch: pytest.MonkeyPatch):
|
|
463
|
+
"""[tool.ruff-sync] upstream should be used if CLI one is missing."""
|
|
464
|
+
captured_args: list[ruff_sync.Arguments] = []
|
|
465
|
+
|
|
466
|
+
def mock_sync(args: ruff_sync.Arguments) -> Any:
|
|
467
|
+
captured_args.append(args)
|
|
468
|
+
return asyncio.sleep(0)
|
|
469
|
+
|
|
470
|
+
monkeypatch.setattr(sys, "argv", ["ruff-sync"])
|
|
471
|
+
monkeypatch.setattr(
|
|
472
|
+
ruff_sync, "get_config", lambda _: {"upstream": "http://config.com"}
|
|
473
|
+
)
|
|
474
|
+
monkeypatch.setattr(ruff_sync, "sync", mock_sync)
|
|
475
|
+
monkeypatch.setattr(asyncio, "run", lambda _coro: None)
|
|
476
|
+
|
|
477
|
+
ruff_sync.main()
|
|
478
|
+
|
|
479
|
+
assert len(captured_args) == 1
|
|
480
|
+
assert str(captured_args[0].upstream) == "http://config.com"
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
@pytest.mark.parametrize(
|
|
484
|
+
["verbose_count", "expected_level"],
|
|
485
|
+
[
|
|
486
|
+
(0, logging.WARNING),
|
|
487
|
+
(1, logging.INFO),
|
|
488
|
+
(2, logging.DEBUG),
|
|
489
|
+
(3, logging.DEBUG),
|
|
490
|
+
],
|
|
491
|
+
)
|
|
492
|
+
def test_verbosity_log_level(
|
|
493
|
+
monkeypatch: pytest.MonkeyPatch, verbose_count: int, expected_level: int
|
|
494
|
+
):
|
|
495
|
+
"""Test that the log level is correctly set based on the verbose count."""
|
|
496
|
+
captured_args: list[ruff_sync.Arguments] = []
|
|
497
|
+
|
|
498
|
+
def mock_sync(args: ruff_sync.Arguments) -> Any:
|
|
499
|
+
captured_args.append(args)
|
|
500
|
+
return asyncio.sleep(0)
|
|
501
|
+
|
|
502
|
+
argv = ["ruff-sync", "http://example.com"]
|
|
503
|
+
if verbose_count > 0:
|
|
504
|
+
argv.append(f"-{'v' * verbose_count}")
|
|
505
|
+
|
|
506
|
+
monkeypatch.setattr(sys, "argv", argv)
|
|
507
|
+
monkeypatch.setattr(ruff_sync, "get_config", lambda _: {})
|
|
508
|
+
monkeypatch.setattr(ruff_sync, "sync", mock_sync)
|
|
509
|
+
monkeypatch.setattr(asyncio, "run", lambda _coro: None)
|
|
510
|
+
|
|
511
|
+
# Reset LOGGER state before test
|
|
512
|
+
monkeypatch.setattr(ruff_sync.LOGGER, "level", logging.NOTSET)
|
|
513
|
+
monkeypatch.setattr(ruff_sync.LOGGER, "handlers", [])
|
|
514
|
+
|
|
515
|
+
ruff_sync.main()
|
|
516
|
+
|
|
517
|
+
# Verify that the computed log level matches what we expect for this verbosity
|
|
518
|
+
assert ruff_sync.LOGGER.level == expected_level
|
|
519
|
+
|
|
520
|
+
# Verify that the verbose flag value propagates into Arguments.verbose
|
|
521
|
+
assert len(captured_args) == 1
|
|
522
|
+
assert captured_args[0].verbose == verbose_count
|
|
523
|
+
|
|
524
|
+
|
|
409
525
|
@pytest.mark.asyncio
|
|
410
526
|
async def test_sync_default_exclude(fs: FakeFilesystem):
|
|
411
527
|
"""Integration style test for default exclude functionality."""
|
|
@@ -431,6 +547,7 @@ target-version = "py311"
|
|
|
431
547
|
upstream=URL("https://example.com/pyproject.toml"),
|
|
432
548
|
source=ff_path,
|
|
433
549
|
exclude=ruff_sync._DEFAULT_EXCLUDE,
|
|
550
|
+
verbose=0,
|
|
434
551
|
)
|
|
435
552
|
)
|
|
436
553
|
|
|
@@ -4,13 +4,15 @@ import logging
|
|
|
4
4
|
import pathlib
|
|
5
5
|
import sys
|
|
6
6
|
from pprint import pformat as pf
|
|
7
|
-
from typing import TYPE_CHECKING, Final
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Final, cast
|
|
8
8
|
|
|
9
9
|
import pytest
|
|
10
10
|
import tomlkit
|
|
11
11
|
from packaging.version import Version
|
|
12
12
|
from ruamel.yaml import YAML
|
|
13
13
|
|
|
14
|
+
import ruff_sync
|
|
15
|
+
|
|
14
16
|
if TYPE_CHECKING:
|
|
15
17
|
from collections.abc import Mapping
|
|
16
18
|
|
|
@@ -62,5 +64,14 @@ def test_pre_commit_versions_are_in_sync(
|
|
|
62
64
|
)
|
|
63
65
|
|
|
64
66
|
|
|
67
|
+
def test_ruff_sync_version_is_in_sync_with_pyproject():
|
|
68
|
+
"""
|
|
69
|
+
Ensure the version in ruff_sync.py matches the version in pyproject.toml
|
|
70
|
+
"""
|
|
71
|
+
toml_doc = tomlkit.loads(PYPROJECT_TOML.read_text())
|
|
72
|
+
pyproject_version = cast("Any", toml_doc)["project"]["version"]
|
|
73
|
+
assert ruff_sync.__version__ == pyproject_version
|
|
74
|
+
|
|
75
|
+
|
|
65
76
|
if __name__ == "__main__":
|
|
66
77
|
pytest.main([__file__, "-vv", "-rEf"])
|
|
@@ -101,5 +101,27 @@ lint.per-file-ignores = {"__init__.py" = ["F401", "F403"]}
|
|
|
101
101
|
assert list(per_file_ignores["__init__.py"]) == ["F401", "F403"]
|
|
102
102
|
|
|
103
103
|
|
|
104
|
+
def test_merge_adds_newline_at_end():
|
|
105
|
+
"""
|
|
106
|
+
Test that merging adds a newline at the end of the ruff section if missing.
|
|
107
|
+
"""
|
|
108
|
+
source_toml_s = """[tool.ruff]
|
|
109
|
+
target-version = "py310"
|
|
110
|
+
"""
|
|
111
|
+
upstream_ruff_s = """[tool.ruff]
|
|
112
|
+
line-length = 100
|
|
113
|
+
"""
|
|
114
|
+
source_doc = tomlkit.parse(source_toml_s)
|
|
115
|
+
upstream_ruff = cast("Any", tomlkit.parse(upstream_ruff_s))["tool"]["ruff"]
|
|
116
|
+
|
|
117
|
+
merged_doc = ruff_sync.merge_ruff_toml(source_doc, upstream_ruff)
|
|
118
|
+
merged_s = merged_doc.as_string()
|
|
119
|
+
|
|
120
|
+
print(f"Merged Result:\n{merged_s!r}")
|
|
121
|
+
# In TOML, sections usually end with a newline.
|
|
122
|
+
# We want to ensure it ends with \n\n if it's the last section.
|
|
123
|
+
assert merged_s.endswith("\n\n") or "\n\n[" in merged_s
|
|
124
|
+
|
|
125
|
+
|
|
104
126
|
if __name__ == "__main__":
|
|
105
127
|
pytest.main([__file__, "-vv"])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/lifecycle_tomls/no_changes_upstream.toml
RENAMED
|
File without changes
|
{ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/lifecycle_tomls/no_dotted_keys_final.toml
RENAMED
|
File without changes
|
{ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/lifecycle_tomls/no_dotted_keys_initial.toml
RENAMED
|
File without changes
|
{ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/lifecycle_tomls/no_dotted_keys_upstream.toml
RENAMED
|
File without changes
|
|
File without changes
|
{ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/lifecycle_tomls/no_ruff_cfg_initial.toml
RENAMED
|
File without changes
|
{ruff_sync-0.0.1.dev2 → ruff_sync-0.0.1.dev4}/tests/lifecycle_tomls/no_ruff_cfg_upstream.toml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|