meson-python-sources-fixer 1.0__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.
- meson_python_sources_fixer-1.0/LICENSE +19 -0
- meson_python_sources_fixer-1.0/PKG-INFO +130 -0
- meson_python_sources_fixer-1.0/README.md +114 -0
- meson_python_sources_fixer-1.0/meson_python_sources_fixer.egg-info/PKG-INFO +130 -0
- meson_python_sources_fixer-1.0/meson_python_sources_fixer.egg-info/SOURCES.txt +10 -0
- meson_python_sources_fixer-1.0/meson_python_sources_fixer.egg-info/dependency_links.txt +1 -0
- meson_python_sources_fixer-1.0/meson_python_sources_fixer.egg-info/entry_points.txt +2 -0
- meson_python_sources_fixer-1.0/meson_python_sources_fixer.egg-info/requires.txt +4 -0
- meson_python_sources_fixer-1.0/meson_python_sources_fixer.egg-info/top_level.txt +1 -0
- meson_python_sources_fixer-1.0/meson_python_sources_fixer.py +504 -0
- meson_python_sources_fixer-1.0/pyproject.toml +31 -0
- meson_python_sources_fixer-1.0/setup.cfg +4 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: meson-python-sources-fixer
|
|
3
|
+
Version: 1.0
|
|
4
|
+
Summary: Linter to list all Python sources in meson.build
|
|
5
|
+
Author-email: Omar Sandoval <osandov@osandov.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/osandov/meson-python-sources-fixer
|
|
8
|
+
Project-URL: Issues, https://github.com/osandov/meson-python-sources-fixer/issues
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: regex
|
|
13
|
+
Provides-Extra: test
|
|
14
|
+
Requires-Dist: pyfakefs; extra == "test"
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# meson-python-sources-fixer
|
|
18
|
+
|
|
19
|
+
This is a minimal linter that checks that all Python source files in a Python
|
|
20
|
+
package are listed in a `meson.build` file.
|
|
21
|
+
|
|
22
|
+
## Installation and Usage
|
|
23
|
+
|
|
24
|
+
To use it via [pre-commit](https://pre-commit.com/), add the following to your
|
|
25
|
+
`.pre-commit-config.yaml`:
|
|
26
|
+
|
|
27
|
+
```yaml
|
|
28
|
+
repos:
|
|
29
|
+
- repo: https://github.com/osandov/meson-python-sources-fixer
|
|
30
|
+
rev: v1.0
|
|
31
|
+
hooks:
|
|
32
|
+
- id: meson-python-sources-fixer
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
To run it manually:
|
|
36
|
+
|
|
37
|
+
```console
|
|
38
|
+
$ pip install meson-python-sources-fixer
|
|
39
|
+
$ cd <project-directory>
|
|
40
|
+
$ meson-python-sources-fixer
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
By default, `meson-python-sources-fixer` will automatically create or rewrite
|
|
44
|
+
`meson.build` files for Python packages as needed. This behavior can be changed
|
|
45
|
+
with the `--check` and `--diff` options.
|
|
46
|
+
|
|
47
|
+
## Expected Format
|
|
48
|
+
|
|
49
|
+
`meson-python-sources-fixer` expects a specific layout and format.
|
|
50
|
+
|
|
51
|
+
### Root `meson.build` File
|
|
52
|
+
|
|
53
|
+
By default, the root `meson.build` file must have two things:
|
|
54
|
+
|
|
55
|
+
1. A variable initialized to
|
|
56
|
+
[`import('python').find_installation()`](https://mesonbuild.com/Python-module.html#find_installation).
|
|
57
|
+
2. A [`subdir()`](https://mesonbuild.com/Reference-manual_functions.html#subdir)
|
|
58
|
+
call for each top-level Python package in the project.
|
|
59
|
+
|
|
60
|
+
For example:
|
|
61
|
+
|
|
62
|
+
```console
|
|
63
|
+
$ tree
|
|
64
|
+
.
|
|
65
|
+
├── meson.build
|
|
66
|
+
└── pkg
|
|
67
|
+
├── foo.py
|
|
68
|
+
├── __init__.py
|
|
69
|
+
├── meson.build
|
|
70
|
+
└── nested
|
|
71
|
+
├── bar.py
|
|
72
|
+
└── __init__.py
|
|
73
|
+
$ cat meson.build
|
|
74
|
+
...
|
|
75
|
+
py = import('python').find_installation()
|
|
76
|
+
|
|
77
|
+
subdir('pkg')
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
By default, each package is assumed to be installed at the top-level of the
|
|
81
|
+
Python installation. E.g., `subdir('pkg')` and `subdir('src/pkg')` would both
|
|
82
|
+
be installed as `/usr/lib/pythonX.Y/site-packages/pkg`.
|
|
83
|
+
|
|
84
|
+
The `import('python').find_installation()` requirement can be overridden with
|
|
85
|
+
the `--py-installation` option.
|
|
86
|
+
|
|
87
|
+
The list of packages and their installation locations can be overridden with
|
|
88
|
+
the `--package` option.
|
|
89
|
+
|
|
90
|
+
`meson-python-sources-fixer` will not modify the root `meson.build` file.
|
|
91
|
+
|
|
92
|
+
### Package `meson.build` Files
|
|
93
|
+
|
|
94
|
+
Each top-level Python package should have its own `meson.build` file listing
|
|
95
|
+
its sources files with a call to
|
|
96
|
+
[`py.install_sources()`](https://mesonbuild.com/Python-module.html#install_sources)
|
|
97
|
+
(where `py` is the `python_installation` object defined in the root
|
|
98
|
+
`meson.build` file) per subpackage. The sources should be lexicographically
|
|
99
|
+
sorted, as should the subpackages. For example:
|
|
100
|
+
|
|
101
|
+
```console
|
|
102
|
+
$ cat pkg/meson.build
|
|
103
|
+
py.install_sources(
|
|
104
|
+
'__init__.py',
|
|
105
|
+
'foo.py',
|
|
106
|
+
subdir: 'pkg',
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
py.install_sources(
|
|
110
|
+
'nested/__init__.py',
|
|
111
|
+
'nested/bar.py',
|
|
112
|
+
subdir: 'pkg/nested',
|
|
113
|
+
)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Note that subpackages (e.g., `pkg/nested`) should not have their own
|
|
117
|
+
`meson.build` files. They are represented in the top-level package.
|
|
118
|
+
|
|
119
|
+
## Limitations
|
|
120
|
+
|
|
121
|
+
`meson-python-sources-fixer` is relatively simple and has a few limitations:
|
|
122
|
+
|
|
123
|
+
* It requires an `__init__.py` file in each package.
|
|
124
|
+
* It does not require a specific code style, but if a file needs to be updated,
|
|
125
|
+
it will not maintain the existing code style.
|
|
126
|
+
* It allows comments inside of and between calls to `py.install_sources()`, but
|
|
127
|
+
if a file needs to be updated, it cannot preserve comments and will therefore
|
|
128
|
+
refuse to update the file.
|
|
129
|
+
* It does not understand variables, dynamic lists of sources, loops,
|
|
130
|
+
conditionals, etc.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# meson-python-sources-fixer
|
|
2
|
+
|
|
3
|
+
This is a minimal linter that checks that all Python source files in a Python
|
|
4
|
+
package are listed in a `meson.build` file.
|
|
5
|
+
|
|
6
|
+
## Installation and Usage
|
|
7
|
+
|
|
8
|
+
To use it via [pre-commit](https://pre-commit.com/), add the following to your
|
|
9
|
+
`.pre-commit-config.yaml`:
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
repos:
|
|
13
|
+
- repo: https://github.com/osandov/meson-python-sources-fixer
|
|
14
|
+
rev: v1.0
|
|
15
|
+
hooks:
|
|
16
|
+
- id: meson-python-sources-fixer
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
To run it manually:
|
|
20
|
+
|
|
21
|
+
```console
|
|
22
|
+
$ pip install meson-python-sources-fixer
|
|
23
|
+
$ cd <project-directory>
|
|
24
|
+
$ meson-python-sources-fixer
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
By default, `meson-python-sources-fixer` will automatically create or rewrite
|
|
28
|
+
`meson.build` files for Python packages as needed. This behavior can be changed
|
|
29
|
+
with the `--check` and `--diff` options.
|
|
30
|
+
|
|
31
|
+
## Expected Format
|
|
32
|
+
|
|
33
|
+
`meson-python-sources-fixer` expects a specific layout and format.
|
|
34
|
+
|
|
35
|
+
### Root `meson.build` File
|
|
36
|
+
|
|
37
|
+
By default, the root `meson.build` file must have two things:
|
|
38
|
+
|
|
39
|
+
1. A variable initialized to
|
|
40
|
+
[`import('python').find_installation()`](https://mesonbuild.com/Python-module.html#find_installation).
|
|
41
|
+
2. A [`subdir()`](https://mesonbuild.com/Reference-manual_functions.html#subdir)
|
|
42
|
+
call for each top-level Python package in the project.
|
|
43
|
+
|
|
44
|
+
For example:
|
|
45
|
+
|
|
46
|
+
```console
|
|
47
|
+
$ tree
|
|
48
|
+
.
|
|
49
|
+
├── meson.build
|
|
50
|
+
└── pkg
|
|
51
|
+
├── foo.py
|
|
52
|
+
├── __init__.py
|
|
53
|
+
├── meson.build
|
|
54
|
+
└── nested
|
|
55
|
+
├── bar.py
|
|
56
|
+
└── __init__.py
|
|
57
|
+
$ cat meson.build
|
|
58
|
+
...
|
|
59
|
+
py = import('python').find_installation()
|
|
60
|
+
|
|
61
|
+
subdir('pkg')
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
By default, each package is assumed to be installed at the top-level of the
|
|
65
|
+
Python installation. E.g., `subdir('pkg')` and `subdir('src/pkg')` would both
|
|
66
|
+
be installed as `/usr/lib/pythonX.Y/site-packages/pkg`.
|
|
67
|
+
|
|
68
|
+
The `import('python').find_installation()` requirement can be overridden with
|
|
69
|
+
the `--py-installation` option.
|
|
70
|
+
|
|
71
|
+
The list of packages and their installation locations can be overridden with
|
|
72
|
+
the `--package` option.
|
|
73
|
+
|
|
74
|
+
`meson-python-sources-fixer` will not modify the root `meson.build` file.
|
|
75
|
+
|
|
76
|
+
### Package `meson.build` Files
|
|
77
|
+
|
|
78
|
+
Each top-level Python package should have its own `meson.build` file listing
|
|
79
|
+
its sources files with a call to
|
|
80
|
+
[`py.install_sources()`](https://mesonbuild.com/Python-module.html#install_sources)
|
|
81
|
+
(where `py` is the `python_installation` object defined in the root
|
|
82
|
+
`meson.build` file) per subpackage. The sources should be lexicographically
|
|
83
|
+
sorted, as should the subpackages. For example:
|
|
84
|
+
|
|
85
|
+
```console
|
|
86
|
+
$ cat pkg/meson.build
|
|
87
|
+
py.install_sources(
|
|
88
|
+
'__init__.py',
|
|
89
|
+
'foo.py',
|
|
90
|
+
subdir: 'pkg',
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
py.install_sources(
|
|
94
|
+
'nested/__init__.py',
|
|
95
|
+
'nested/bar.py',
|
|
96
|
+
subdir: 'pkg/nested',
|
|
97
|
+
)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Note that subpackages (e.g., `pkg/nested`) should not have their own
|
|
101
|
+
`meson.build` files. They are represented in the top-level package.
|
|
102
|
+
|
|
103
|
+
## Limitations
|
|
104
|
+
|
|
105
|
+
`meson-python-sources-fixer` is relatively simple and has a few limitations:
|
|
106
|
+
|
|
107
|
+
* It requires an `__init__.py` file in each package.
|
|
108
|
+
* It does not require a specific code style, but if a file needs to be updated,
|
|
109
|
+
it will not maintain the existing code style.
|
|
110
|
+
* It allows comments inside of and between calls to `py.install_sources()`, but
|
|
111
|
+
if a file needs to be updated, it cannot preserve comments and will therefore
|
|
112
|
+
refuse to update the file.
|
|
113
|
+
* It does not understand variables, dynamic lists of sources, loops,
|
|
114
|
+
conditionals, etc.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: meson-python-sources-fixer
|
|
3
|
+
Version: 1.0
|
|
4
|
+
Summary: Linter to list all Python sources in meson.build
|
|
5
|
+
Author-email: Omar Sandoval <osandov@osandov.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/osandov/meson-python-sources-fixer
|
|
8
|
+
Project-URL: Issues, https://github.com/osandov/meson-python-sources-fixer/issues
|
|
9
|
+
Requires-Python: >=3.8
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: regex
|
|
13
|
+
Provides-Extra: test
|
|
14
|
+
Requires-Dist: pyfakefs; extra == "test"
|
|
15
|
+
Dynamic: license-file
|
|
16
|
+
|
|
17
|
+
# meson-python-sources-fixer
|
|
18
|
+
|
|
19
|
+
This is a minimal linter that checks that all Python source files in a Python
|
|
20
|
+
package are listed in a `meson.build` file.
|
|
21
|
+
|
|
22
|
+
## Installation and Usage
|
|
23
|
+
|
|
24
|
+
To use it via [pre-commit](https://pre-commit.com/), add the following to your
|
|
25
|
+
`.pre-commit-config.yaml`:
|
|
26
|
+
|
|
27
|
+
```yaml
|
|
28
|
+
repos:
|
|
29
|
+
- repo: https://github.com/osandov/meson-python-sources-fixer
|
|
30
|
+
rev: v1.0
|
|
31
|
+
hooks:
|
|
32
|
+
- id: meson-python-sources-fixer
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
To run it manually:
|
|
36
|
+
|
|
37
|
+
```console
|
|
38
|
+
$ pip install meson-python-sources-fixer
|
|
39
|
+
$ cd <project-directory>
|
|
40
|
+
$ meson-python-sources-fixer
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
By default, `meson-python-sources-fixer` will automatically create or rewrite
|
|
44
|
+
`meson.build` files for Python packages as needed. This behavior can be changed
|
|
45
|
+
with the `--check` and `--diff` options.
|
|
46
|
+
|
|
47
|
+
## Expected Format
|
|
48
|
+
|
|
49
|
+
`meson-python-sources-fixer` expects a specific layout and format.
|
|
50
|
+
|
|
51
|
+
### Root `meson.build` File
|
|
52
|
+
|
|
53
|
+
By default, the root `meson.build` file must have two things:
|
|
54
|
+
|
|
55
|
+
1. A variable initialized to
|
|
56
|
+
[`import('python').find_installation()`](https://mesonbuild.com/Python-module.html#find_installation).
|
|
57
|
+
2. A [`subdir()`](https://mesonbuild.com/Reference-manual_functions.html#subdir)
|
|
58
|
+
call for each top-level Python package in the project.
|
|
59
|
+
|
|
60
|
+
For example:
|
|
61
|
+
|
|
62
|
+
```console
|
|
63
|
+
$ tree
|
|
64
|
+
.
|
|
65
|
+
├── meson.build
|
|
66
|
+
└── pkg
|
|
67
|
+
├── foo.py
|
|
68
|
+
├── __init__.py
|
|
69
|
+
├── meson.build
|
|
70
|
+
└── nested
|
|
71
|
+
├── bar.py
|
|
72
|
+
└── __init__.py
|
|
73
|
+
$ cat meson.build
|
|
74
|
+
...
|
|
75
|
+
py = import('python').find_installation()
|
|
76
|
+
|
|
77
|
+
subdir('pkg')
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
By default, each package is assumed to be installed at the top-level of the
|
|
81
|
+
Python installation. E.g., `subdir('pkg')` and `subdir('src/pkg')` would both
|
|
82
|
+
be installed as `/usr/lib/pythonX.Y/site-packages/pkg`.
|
|
83
|
+
|
|
84
|
+
The `import('python').find_installation()` requirement can be overridden with
|
|
85
|
+
the `--py-installation` option.
|
|
86
|
+
|
|
87
|
+
The list of packages and their installation locations can be overridden with
|
|
88
|
+
the `--package` option.
|
|
89
|
+
|
|
90
|
+
`meson-python-sources-fixer` will not modify the root `meson.build` file.
|
|
91
|
+
|
|
92
|
+
### Package `meson.build` Files
|
|
93
|
+
|
|
94
|
+
Each top-level Python package should have its own `meson.build` file listing
|
|
95
|
+
its sources files with a call to
|
|
96
|
+
[`py.install_sources()`](https://mesonbuild.com/Python-module.html#install_sources)
|
|
97
|
+
(where `py` is the `python_installation` object defined in the root
|
|
98
|
+
`meson.build` file) per subpackage. The sources should be lexicographically
|
|
99
|
+
sorted, as should the subpackages. For example:
|
|
100
|
+
|
|
101
|
+
```console
|
|
102
|
+
$ cat pkg/meson.build
|
|
103
|
+
py.install_sources(
|
|
104
|
+
'__init__.py',
|
|
105
|
+
'foo.py',
|
|
106
|
+
subdir: 'pkg',
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
py.install_sources(
|
|
110
|
+
'nested/__init__.py',
|
|
111
|
+
'nested/bar.py',
|
|
112
|
+
subdir: 'pkg/nested',
|
|
113
|
+
)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Note that subpackages (e.g., `pkg/nested`) should not have their own
|
|
117
|
+
`meson.build` files. They are represented in the top-level package.
|
|
118
|
+
|
|
119
|
+
## Limitations
|
|
120
|
+
|
|
121
|
+
`meson-python-sources-fixer` is relatively simple and has a few limitations:
|
|
122
|
+
|
|
123
|
+
* It requires an `__init__.py` file in each package.
|
|
124
|
+
* It does not require a specific code style, but if a file needs to be updated,
|
|
125
|
+
it will not maintain the existing code style.
|
|
126
|
+
* It allows comments inside of and between calls to `py.install_sources()`, but
|
|
127
|
+
if a file needs to be updated, it cannot preserve comments and will therefore
|
|
128
|
+
refuse to update the file.
|
|
129
|
+
* It does not understand variables, dynamic lists of sources, loops,
|
|
130
|
+
conditionals, etc.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
meson_python_sources_fixer.py
|
|
4
|
+
pyproject.toml
|
|
5
|
+
meson_python_sources_fixer.egg-info/PKG-INFO
|
|
6
|
+
meson_python_sources_fixer.egg-info/SOURCES.txt
|
|
7
|
+
meson_python_sources_fixer.egg-info/dependency_links.txt
|
|
8
|
+
meson_python_sources_fixer.egg-info/entry_points.txt
|
|
9
|
+
meson_python_sources_fixer.egg-info/requires.txt
|
|
10
|
+
meson_python_sources_fixer.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
meson_python_sources_fixer
|
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
3
|
+
# SPDX-License-Identifier: MIT
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import ast
|
|
7
|
+
import difflib
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import sys
|
|
10
|
+
from typing import Dict, Iterable, Literal, Mapping, Optional, Set
|
|
11
|
+
|
|
12
|
+
import regex
|
|
13
|
+
|
|
14
|
+
if sys.version_info >= (3, 10):
|
|
15
|
+
from itertools import pairwise # novermin
|
|
16
|
+
else:
|
|
17
|
+
|
|
18
|
+
def pairwise(iterable):
|
|
19
|
+
iterator = iter(iterable)
|
|
20
|
+
a = next(iterator, None)
|
|
21
|
+
|
|
22
|
+
for b in iterator:
|
|
23
|
+
yield a, b
|
|
24
|
+
a = b
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def print_bold(s: str, *, color: bool) -> None:
|
|
28
|
+
if color:
|
|
29
|
+
sys.stderr.write("\033[1m" + s + "\033[m\n")
|
|
30
|
+
else:
|
|
31
|
+
print(s, file=sys.stderr)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def print_error(s: str, *, color: bool) -> None:
|
|
35
|
+
if color:
|
|
36
|
+
sys.stderr.write("\033[31merror:\033[m " + s + "\n")
|
|
37
|
+
else:
|
|
38
|
+
print(s, file=sys.stderr)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def print_diff(
|
|
42
|
+
old_contents: str, new_contents: str, old_path: str, new_path: str, *, color: bool
|
|
43
|
+
) -> None:
|
|
44
|
+
diff_lines = difflib.unified_diff(
|
|
45
|
+
old_contents.splitlines(keepends=True),
|
|
46
|
+
new_contents.splitlines(keepends=True),
|
|
47
|
+
old_path,
|
|
48
|
+
new_path,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
if not color:
|
|
52
|
+
sys.stdout.writelines(diff_lines)
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
control = True
|
|
56
|
+
for line in diff_lines:
|
|
57
|
+
if line.startswith("@"):
|
|
58
|
+
control = False
|
|
59
|
+
code = "36" # Cyan
|
|
60
|
+
elif control:
|
|
61
|
+
code = "1" # Bold
|
|
62
|
+
elif line.startswith("+"):
|
|
63
|
+
code = "32" # Green
|
|
64
|
+
elif line.startswith("-"):
|
|
65
|
+
code = "31" # Red
|
|
66
|
+
else:
|
|
67
|
+
code = ""
|
|
68
|
+
|
|
69
|
+
if code:
|
|
70
|
+
parts = ["\033[", code, "m"]
|
|
71
|
+
if line.endswith("\n"):
|
|
72
|
+
parts.append(line[:-1])
|
|
73
|
+
parts.append("\033[m\n")
|
|
74
|
+
else:
|
|
75
|
+
parts.append(line)
|
|
76
|
+
parts.append("\033[m")
|
|
77
|
+
sys.stdout.write("".join(parts))
|
|
78
|
+
else:
|
|
79
|
+
sys.stdout.write(line)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
STRING_PATTERN = r"'(?:\\.|[^'])*'"
|
|
83
|
+
COMMENT_PATTERN = r"[#].*"
|
|
84
|
+
NON_NEWLINE_WHITESPACE = r"[^\S\n]*"
|
|
85
|
+
WHITESPACE_OR_COMMENT_CAPTURE = r"(?:\s|(?&comment))*"
|
|
86
|
+
WHITESPACE_OR_COMMENT_EOL_CAPTURE = r"[^\S\n]*(?&comment)?$"
|
|
87
|
+
WHITESPACE_OR_COMMENT = WHITESPACE_OR_COMMENT_CAPTURE.replace(
|
|
88
|
+
"(?&comment)", COMMENT_PATTERN
|
|
89
|
+
)
|
|
90
|
+
WHITESPACE_OR_COMMENT_EOL = WHITESPACE_OR_COMMENT_EOL_CAPTURE.replace(
|
|
91
|
+
"(?&comment)", "(?:" + COMMENT_PATTERN + ")"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
PY_INSTALLATION_PATTERN = rf"""
|
|
96
|
+
^
|
|
97
|
+
{NON_NEWLINE_WHITESPACE}
|
|
98
|
+
(\w+)
|
|
99
|
+
{NON_NEWLINE_WHITESPACE}
|
|
100
|
+
=
|
|
101
|
+
{NON_NEWLINE_WHITESPACE}
|
|
102
|
+
import
|
|
103
|
+
{NON_NEWLINE_WHITESPACE}
|
|
104
|
+
\(
|
|
105
|
+
{WHITESPACE_OR_COMMENT}
|
|
106
|
+
'python'
|
|
107
|
+
{WHITESPACE_OR_COMMENT}
|
|
108
|
+
\)
|
|
109
|
+
{NON_NEWLINE_WHITESPACE}
|
|
110
|
+
\.
|
|
111
|
+
{NON_NEWLINE_WHITESPACE}
|
|
112
|
+
find_installation
|
|
113
|
+
{NON_NEWLINE_WHITESPACE}
|
|
114
|
+
\(
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def find_py_installation(contents: str) -> Optional[str]:
|
|
119
|
+
match = regex.search(
|
|
120
|
+
PY_INSTALLATION_PATTERN, contents, flags=regex.MULTILINE | regex.VERBOSE
|
|
121
|
+
)
|
|
122
|
+
return match.group(1) if match else None
|
|
123
|
+
if not match:
|
|
124
|
+
raise LookupError("import('python').find_installation() not found")
|
|
125
|
+
return match.group(1)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
SUBDIR_PATTERN = rf"""
|
|
129
|
+
^
|
|
130
|
+
{NON_NEWLINE_WHITESPACE}
|
|
131
|
+
subdir
|
|
132
|
+
{NON_NEWLINE_WHITESPACE}
|
|
133
|
+
\(
|
|
134
|
+
{WHITESPACE_OR_COMMENT}
|
|
135
|
+
({STRING_PATTERN})
|
|
136
|
+
{WHITESPACE_OR_COMMENT}
|
|
137
|
+
\)
|
|
138
|
+
{WHITESPACE_OR_COMMENT_EOL}
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def find_subdirs(contents: str) -> Iterable[str]:
|
|
143
|
+
return [
|
|
144
|
+
ast.literal_eval(s)
|
|
145
|
+
for s in regex.findall(
|
|
146
|
+
SUBDIR_PATTERN, contents, flags=regex.MULTILINE | regex.VERBOSE
|
|
147
|
+
)
|
|
148
|
+
]
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def is_python_source(name: str) -> bool:
|
|
152
|
+
return name.endswith(".py") or name.endswith(".pyi") or name == "py.typed"
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def find_package_sources(source_path: Path, install_path: Path) -> Dict[str, Set[str]]:
|
|
156
|
+
sources = {}
|
|
157
|
+
if (source_path / "__init__.py").exists():
|
|
158
|
+
for root, dirs, files in source_path.walk():
|
|
159
|
+
dirs[:] = [dir for dir in dirs if (root / dir / "__init__.py").exists()]
|
|
160
|
+
|
|
161
|
+
relative = root.relative_to(source_path)
|
|
162
|
+
sources[str(install_path / relative)] = {
|
|
163
|
+
str(relative / file) for file in files if is_python_source(file)
|
|
164
|
+
}
|
|
165
|
+
return sources
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
INSTALL_SOURCES_PATTERN = rf"""
|
|
169
|
+
(?(DEFINE)
|
|
170
|
+
(?<posarg>{STRING_PATTERN})
|
|
171
|
+
(?<list>
|
|
172
|
+
\[
|
|
173
|
+
{WHITESPACE_OR_COMMENT_CAPTURE}
|
|
174
|
+
(?:
|
|
175
|
+
(?&posarg)
|
|
176
|
+
{WHITESPACE_OR_COMMENT_CAPTURE}
|
|
177
|
+
,
|
|
178
|
+
{WHITESPACE_OR_COMMENT_CAPTURE}
|
|
179
|
+
)*
|
|
180
|
+
(?:
|
|
181
|
+
(?&posarg)
|
|
182
|
+
{WHITESPACE_OR_COMMENT_CAPTURE}
|
|
183
|
+
)?
|
|
184
|
+
\]
|
|
185
|
+
)
|
|
186
|
+
(?<posarg_or_list>(?&posarg)|(?&list))
|
|
187
|
+
(?<kwarg>
|
|
188
|
+
(?<keyword>\w+)
|
|
189
|
+
{WHITESPACE_OR_COMMENT_CAPTURE}
|
|
190
|
+
:
|
|
191
|
+
{WHITESPACE_OR_COMMENT_CAPTURE}
|
|
192
|
+
(?<value>{STRING_PATTERN}|\w+)
|
|
193
|
+
)
|
|
194
|
+
(?<comment>{COMMENT_PATTERN})
|
|
195
|
+
)
|
|
196
|
+
^
|
|
197
|
+
{NON_NEWLINE_WHITESPACE}
|
|
198
|
+
{{}}
|
|
199
|
+
{NON_NEWLINE_WHITESPACE}
|
|
200
|
+
\.
|
|
201
|
+
{NON_NEWLINE_WHITESPACE}
|
|
202
|
+
install_sources
|
|
203
|
+
{NON_NEWLINE_WHITESPACE}
|
|
204
|
+
\(
|
|
205
|
+
(?:
|
|
206
|
+
{WHITESPACE_OR_COMMENT_CAPTURE}
|
|
207
|
+
(?:
|
|
208
|
+
(?&posarg_or_list)
|
|
209
|
+
{WHITESPACE_OR_COMMENT_CAPTURE}
|
|
210
|
+
,
|
|
211
|
+
{WHITESPACE_OR_COMMENT_CAPTURE}
|
|
212
|
+
)*
|
|
213
|
+
(?:
|
|
214
|
+
(?:
|
|
215
|
+
(?&posarg_or_list)
|
|
216
|
+
{WHITESPACE_OR_COMMENT_CAPTURE}
|
|
217
|
+
)?
|
|
218
|
+
|
|
|
219
|
+
(?:
|
|
220
|
+
(?&kwarg)
|
|
221
|
+
{WHITESPACE_OR_COMMENT_CAPTURE}
|
|
222
|
+
,
|
|
223
|
+
{WHITESPACE_OR_COMMENT_CAPTURE}
|
|
224
|
+
)*
|
|
225
|
+
(?:
|
|
226
|
+
(?&kwarg)
|
|
227
|
+
{WHITESPACE_OR_COMMENT_CAPTURE}
|
|
228
|
+
)?
|
|
229
|
+
)
|
|
230
|
+
\)
|
|
231
|
+
{WHITESPACE_OR_COMMENT_EOL_CAPTURE}
|
|
232
|
+
|
|
|
233
|
+
(?<unrecognized>.|$)
|
|
234
|
+
)
|
|
235
|
+
"""
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class CannotFixError(Exception):
|
|
239
|
+
pass
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def fix_package_meson_build(
|
|
243
|
+
contents: str,
|
|
244
|
+
py_installation: str,
|
|
245
|
+
package_sources: Mapping[str, Set[str]],
|
|
246
|
+
) -> str:
|
|
247
|
+
matches = {}
|
|
248
|
+
found_install_sources = {}
|
|
249
|
+
for match in regex.finditer(
|
|
250
|
+
INSTALL_SOURCES_PATTERN.format(regex.escape(py_installation)),
|
|
251
|
+
contents,
|
|
252
|
+
flags=regex.MULTILINE | regex.VERBOSE,
|
|
253
|
+
):
|
|
254
|
+
if match.group("unrecognized"):
|
|
255
|
+
raise CannotFixError(
|
|
256
|
+
f"unrecognized {py_installation}.install_sources() syntax"
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
kwargs = dict(zip(match.captures("keyword"), match.captures("value")))
|
|
260
|
+
try:
|
|
261
|
+
subdir_arg = kwargs["subdir"]
|
|
262
|
+
except KeyError:
|
|
263
|
+
subdir = "."
|
|
264
|
+
else:
|
|
265
|
+
if not subdir_arg.startswith("'"):
|
|
266
|
+
raise CannotFixError(
|
|
267
|
+
f"unrecognized {py_installation}.install_sources() subdir syntax"
|
|
268
|
+
)
|
|
269
|
+
subdir = ast.literal_eval(subdir_arg)
|
|
270
|
+
|
|
271
|
+
if subdir in matches:
|
|
272
|
+
raise CannotFixError(
|
|
273
|
+
f"duplicate {py_installation}.install_sources(..., subdir: {subdir!r})"
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
matches[subdir] = match
|
|
277
|
+
found_install_sources[subdir] = (
|
|
278
|
+
[ast.literal_eval(arg) for arg in match.captures("posarg")],
|
|
279
|
+
kwargs,
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
expected_install_sources = []
|
|
283
|
+
for subdir, sources in package_sources.items():
|
|
284
|
+
try:
|
|
285
|
+
found_sources, kwargs = found_install_sources[subdir]
|
|
286
|
+
except KeyError:
|
|
287
|
+
kwargs = {"subdir": repr(subdir)}
|
|
288
|
+
else:
|
|
289
|
+
sources = sources | {
|
|
290
|
+
source for source in found_sources if not is_python_source(source)
|
|
291
|
+
}
|
|
292
|
+
expected_install_sources.append((subdir, (sorted(sources), kwargs)))
|
|
293
|
+
expected_install_sources.sort()
|
|
294
|
+
|
|
295
|
+
if list(found_install_sources.items()) == expected_install_sources:
|
|
296
|
+
return contents
|
|
297
|
+
|
|
298
|
+
for match1, match2 in pairwise(matches.values()):
|
|
299
|
+
if regex.search(r"\S", contents, pos=match1.end(), endpos=match2.start()):
|
|
300
|
+
raise CannotFixError(
|
|
301
|
+
f"non-whitespace between {py_installation}.install_sources() calls"
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
if matches:
|
|
305
|
+
matches_start = next(iter(matches.values())).start()
|
|
306
|
+
matches_end = next(reversed(matches.values())).end()
|
|
307
|
+
|
|
308
|
+
if not expected_install_sources:
|
|
309
|
+
if matches_end < len(contents):
|
|
310
|
+
assert contents[matches_end] == "\n"
|
|
311
|
+
matches_end += 1
|
|
312
|
+
|
|
313
|
+
blank_before = matches_start < 2 or contents[matches_start - 2] == "\n"
|
|
314
|
+
blank_after = matches_end == len(contents) or contents[matches_end] == "\n"
|
|
315
|
+
if blank_before and blank_after:
|
|
316
|
+
if matches_end < len(contents):
|
|
317
|
+
matches_end += 1
|
|
318
|
+
elif matches_start > 0:
|
|
319
|
+
matches_start -= 1
|
|
320
|
+
|
|
321
|
+
parts = [contents[:matches_start]]
|
|
322
|
+
else:
|
|
323
|
+
parts = [contents]
|
|
324
|
+
if contents:
|
|
325
|
+
if not contents.endswith("\n"):
|
|
326
|
+
parts.append("\n\n")
|
|
327
|
+
elif not contents.endswith("\n\n"):
|
|
328
|
+
parts.append("\n")
|
|
329
|
+
|
|
330
|
+
for i, (subdir, (expected_sources, kwargs)) in enumerate(expected_install_sources):
|
|
331
|
+
if found_install_sources.get(subdir) == (expected_sources, kwargs):
|
|
332
|
+
parts.append(matches[subdir].group(0))
|
|
333
|
+
else:
|
|
334
|
+
if subdir in matches and matches[subdir].captures("comment"):
|
|
335
|
+
raise CannotFixError(
|
|
336
|
+
f"{py_installation}.install_sources() call contains comments"
|
|
337
|
+
)
|
|
338
|
+
parts.append(py_installation)
|
|
339
|
+
parts.append(".install_sources(\n")
|
|
340
|
+
for source in expected_sources:
|
|
341
|
+
parts.append(f" {source!r},\n")
|
|
342
|
+
for keyword, value in kwargs.items():
|
|
343
|
+
parts.append(f" {keyword}: {value},\n")
|
|
344
|
+
parts.append(")")
|
|
345
|
+
|
|
346
|
+
if i < len(expected_install_sources) - 1:
|
|
347
|
+
parts.append("\n\n")
|
|
348
|
+
|
|
349
|
+
if matches:
|
|
350
|
+
parts.append(contents[matches_end:])
|
|
351
|
+
else:
|
|
352
|
+
parts.append("\n")
|
|
353
|
+
|
|
354
|
+
return "".join(parts)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def update_package_meson_build(
|
|
358
|
+
meson_build_path: Path,
|
|
359
|
+
py_installation: str,
|
|
360
|
+
package_sources: Mapping[str, Set[str]],
|
|
361
|
+
*,
|
|
362
|
+
mode: Literal["update", "check", "diff"],
|
|
363
|
+
color_stdout: bool = False,
|
|
364
|
+
color_stderr: bool = False,
|
|
365
|
+
) -> bool:
|
|
366
|
+
try:
|
|
367
|
+
contents = meson_build_path.read_text()
|
|
368
|
+
exists = True
|
|
369
|
+
except FileNotFoundError:
|
|
370
|
+
contents = ""
|
|
371
|
+
exists = False
|
|
372
|
+
|
|
373
|
+
orig_contents = contents
|
|
374
|
+
|
|
375
|
+
try:
|
|
376
|
+
contents = fix_package_meson_build(contents, py_installation, package_sources)
|
|
377
|
+
except CannotFixError as e:
|
|
378
|
+
print_error(f"cannot fix {meson_build_path}: {e}", color=color_stderr)
|
|
379
|
+
return False
|
|
380
|
+
|
|
381
|
+
if contents != orig_contents:
|
|
382
|
+
if mode == "update":
|
|
383
|
+
meson_build_path.write_text(contents)
|
|
384
|
+
if exists:
|
|
385
|
+
print_bold(f"updated {meson_build_path}", color=color_stderr)
|
|
386
|
+
else:
|
|
387
|
+
print_bold(f"created {meson_build_path}", color=color_stderr)
|
|
388
|
+
else:
|
|
389
|
+
if mode == "diff":
|
|
390
|
+
print_diff(
|
|
391
|
+
orig_contents,
|
|
392
|
+
contents,
|
|
393
|
+
str(meson_build_path) if exists else "/dev/null",
|
|
394
|
+
str(meson_build_path),
|
|
395
|
+
color=color_stdout,
|
|
396
|
+
)
|
|
397
|
+
sys.stdout.flush()
|
|
398
|
+
if exists:
|
|
399
|
+
print_bold(f"{meson_build_path} is out of date", color=color_stderr)
|
|
400
|
+
else:
|
|
401
|
+
print_bold(f"{meson_build_path} does not exist", color=color_stderr)
|
|
402
|
+
return False
|
|
403
|
+
|
|
404
|
+
return True
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def main() -> int:
|
|
408
|
+
parser = argparse.ArgumentParser(
|
|
409
|
+
description="rewrite meson.build files in the current project to include all Python package sources"
|
|
410
|
+
)
|
|
411
|
+
parser.add_argument(
|
|
412
|
+
"--py-installation",
|
|
413
|
+
metavar="NAME",
|
|
414
|
+
help="name of python_installation object (default: find in meson.build)",
|
|
415
|
+
)
|
|
416
|
+
parser.add_argument(
|
|
417
|
+
"--package",
|
|
418
|
+
nargs=2,
|
|
419
|
+
metavar=("SOURCE_PATH", "INSTALL_PATH"),
|
|
420
|
+
type=Path,
|
|
421
|
+
action="append",
|
|
422
|
+
help="paths of packages and where they should be installed (default: find in meson.build)",
|
|
423
|
+
)
|
|
424
|
+
parser.add_argument(
|
|
425
|
+
"--check",
|
|
426
|
+
dest="mode",
|
|
427
|
+
action="store_const",
|
|
428
|
+
const="check",
|
|
429
|
+
default="update",
|
|
430
|
+
help="don't rewrite any files, just exit with status 1 if any files need to be updated",
|
|
431
|
+
)
|
|
432
|
+
parser.add_argument(
|
|
433
|
+
"--diff",
|
|
434
|
+
dest="mode",
|
|
435
|
+
action="store_const",
|
|
436
|
+
const="diff",
|
|
437
|
+
help="don't rewrite any files, just exit with status 1 and print a diff to standard output if any files need to be updated",
|
|
438
|
+
)
|
|
439
|
+
parser.add_argument(
|
|
440
|
+
"--color",
|
|
441
|
+
choices=("auto", "always", "never"),
|
|
442
|
+
default="auto",
|
|
443
|
+
help="colorize output",
|
|
444
|
+
)
|
|
445
|
+
args = parser.parse_args()
|
|
446
|
+
|
|
447
|
+
if args.color == "auto":
|
|
448
|
+
color_stdout = sys.stdout.isatty()
|
|
449
|
+
color_stderr = sys.stderr.isatty()
|
|
450
|
+
else:
|
|
451
|
+
color_stdout = color_stderr = args.color == "always"
|
|
452
|
+
|
|
453
|
+
meson_build_path = Path("meson.build")
|
|
454
|
+
if args.py_installation is None or not args.package:
|
|
455
|
+
meson_build_contents = meson_build_path.read_text()
|
|
456
|
+
|
|
457
|
+
if args.py_installation is None:
|
|
458
|
+
args.py_installation = find_py_installation(meson_build_contents)
|
|
459
|
+
if args.py_installation is None:
|
|
460
|
+
print_error(
|
|
461
|
+
f"{meson_build_path}: import('python').find_installation() not found. Try --py-installation.",
|
|
462
|
+
color=color_stderr,
|
|
463
|
+
)
|
|
464
|
+
return 1
|
|
465
|
+
|
|
466
|
+
if args.package:
|
|
467
|
+
packages = []
|
|
468
|
+
for source_path, install_path in args.package:
|
|
469
|
+
package_sources = find_package_sources(source_path, install_path)
|
|
470
|
+
if not package_sources:
|
|
471
|
+
print_error(f"{source_path}: not a Python package", color=color_stderr)
|
|
472
|
+
return 1
|
|
473
|
+
packages.append((source_path, package_sources))
|
|
474
|
+
else:
|
|
475
|
+
packages = []
|
|
476
|
+
for subdir in find_subdirs(meson_build_contents):
|
|
477
|
+
subdir_path = Path(subdir)
|
|
478
|
+
source_path = subdir_path
|
|
479
|
+
package_sources = find_package_sources(source_path, Path(subdir_path.name))
|
|
480
|
+
if package_sources:
|
|
481
|
+
packages.append((source_path, package_sources))
|
|
482
|
+
if not packages:
|
|
483
|
+
print_error(
|
|
484
|
+
f"{meson_build_path}: no Python package subdir() calls found. Try --package.",
|
|
485
|
+
color=color_stderr,
|
|
486
|
+
)
|
|
487
|
+
return 1
|
|
488
|
+
|
|
489
|
+
exit_status = 0
|
|
490
|
+
for source_path, package_sources in packages:
|
|
491
|
+
if not update_package_meson_build(
|
|
492
|
+
source_path / "meson.build",
|
|
493
|
+
args.py_installation,
|
|
494
|
+
package_sources,
|
|
495
|
+
mode=args.mode,
|
|
496
|
+
color_stdout=color_stdout,
|
|
497
|
+
color_stderr=color_stderr,
|
|
498
|
+
):
|
|
499
|
+
exit_status = 1
|
|
500
|
+
return exit_status
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
if __name__ == "__main__":
|
|
504
|
+
sys.exit(main())
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "meson-python-sources-fixer"
|
|
3
|
+
version = "1.0"
|
|
4
|
+
description = "Linter to list all Python sources in meson.build"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
authors = [
|
|
9
|
+
{name = "Omar Sandoval", email = "osandov@osandov.com"}
|
|
10
|
+
]
|
|
11
|
+
dependencies = ["regex"]
|
|
12
|
+
requires-python = ">= 3.8"
|
|
13
|
+
|
|
14
|
+
[project.urls]
|
|
15
|
+
Homepage = "https://github.com/osandov/meson-python-sources-fixer"
|
|
16
|
+
Issues = "https://github.com/osandov/meson-python-sources-fixer/issues"
|
|
17
|
+
|
|
18
|
+
[project.optional-dependencies]
|
|
19
|
+
test = ["pyfakefs"]
|
|
20
|
+
|
|
21
|
+
[project.scripts]
|
|
22
|
+
meson-python-sources-fixer = "meson_python_sources_fixer:main"
|
|
23
|
+
|
|
24
|
+
[build-system]
|
|
25
|
+
requires = ["setuptools >= 77.0.3"]
|
|
26
|
+
build-backend = "setuptools.build_meta"
|
|
27
|
+
|
|
28
|
+
[tool.isort]
|
|
29
|
+
profile = "black"
|
|
30
|
+
combine_as_imports = true
|
|
31
|
+
force_sort_within_sections = true
|