xproject-python 0.1__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.
- xproject_python-0.1/.gitignore +220 -0
- xproject_python-0.1/LICENSE +1 -0
- xproject_python-0.1/PKG-INFO +19 -0
- xproject_python-0.1/pyproject.toml +46 -0
- xproject_python-0.1/readme.md +1 -0
- xproject_python-0.1/xproject_python/__init__.py +0 -0
- xproject_python-0.1/xproject_python/attribute.py +8 -0
- xproject_python-0.1/xproject_python/configure.py +162 -0
- xproject_python-0.1/xproject_python/projector.py +367 -0
- xproject_python-0.1/xproject_python/templates/module/__init__.py +0 -0
- xproject_python-0.1/xproject_python/templates/module/attribute.py +8 -0
- xproject_python-0.1/xproject_python/templates/package/.coveragerc +12 -0
- xproject_python-0.1/xproject_python/templates/package/.flake8 +9 -0
- xproject_python-0.1/xproject_python/templates/package/.pylintrc +7 -0
- xproject_python-0.1/xproject_python/templates/package/Makefile +59 -0
- xproject_python-0.1/xproject_python/templates/package/hatch_build.py +9 -0
- xproject_python-0.1/xproject_python/templates/package/pyproject.toml +48 -0
- xproject_python-0.1/xproject_python/templates/project/readme.md +3 -0
- xproject_python-0.1/xproject_python/utilities.py +216 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
pip-wheel-metadata/
|
|
24
|
+
share/python-wheels/
|
|
25
|
+
*.egg-info/
|
|
26
|
+
.installed.cfg
|
|
27
|
+
*.egg
|
|
28
|
+
MANIFEST
|
|
29
|
+
|
|
30
|
+
# PyInstaller
|
|
31
|
+
# Usually these files are written by a python script from a template
|
|
32
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
33
|
+
*.manifest
|
|
34
|
+
*.spec
|
|
35
|
+
|
|
36
|
+
# Installer logs
|
|
37
|
+
pip-log.txt
|
|
38
|
+
pip-delete-this-directory.txt
|
|
39
|
+
|
|
40
|
+
# Unit test / coverage reports
|
|
41
|
+
htmlcov/
|
|
42
|
+
.tox/
|
|
43
|
+
.nox/
|
|
44
|
+
.stestr
|
|
45
|
+
.coverage
|
|
46
|
+
.coverage.*
|
|
47
|
+
.cache
|
|
48
|
+
nosetests.xml
|
|
49
|
+
coverage.xml
|
|
50
|
+
*.cover
|
|
51
|
+
*.py.cover
|
|
52
|
+
.hypothesis/
|
|
53
|
+
.pytest_cache/
|
|
54
|
+
cover/
|
|
55
|
+
|
|
56
|
+
# Translations
|
|
57
|
+
*.mo
|
|
58
|
+
*.pot
|
|
59
|
+
|
|
60
|
+
# Django stuff:
|
|
61
|
+
*.log
|
|
62
|
+
local_settings.py
|
|
63
|
+
db.sqlite3
|
|
64
|
+
db.sqlite3-journal
|
|
65
|
+
|
|
66
|
+
# Flask stuff:
|
|
67
|
+
instance/
|
|
68
|
+
.webassets-cache
|
|
69
|
+
|
|
70
|
+
# Scrapy stuff:
|
|
71
|
+
.scrapy
|
|
72
|
+
|
|
73
|
+
# Sphinx documentation
|
|
74
|
+
docs/_build/
|
|
75
|
+
|
|
76
|
+
# PyBuilder
|
|
77
|
+
.pybuilder/
|
|
78
|
+
target/
|
|
79
|
+
|
|
80
|
+
# Jupyter Notebook
|
|
81
|
+
.ipynb_checkpoints
|
|
82
|
+
|
|
83
|
+
# IPython
|
|
84
|
+
profile_default/
|
|
85
|
+
ipython_config.py
|
|
86
|
+
|
|
87
|
+
# pyenv
|
|
88
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
89
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
90
|
+
# .python-version
|
|
91
|
+
|
|
92
|
+
# pipenv
|
|
93
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
94
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
95
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
96
|
+
# install all needed dependencies.
|
|
97
|
+
# Pipfile.lock
|
|
98
|
+
|
|
99
|
+
# UV
|
|
100
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
101
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
102
|
+
# commonly ignored for libraries.
|
|
103
|
+
# uv.lock
|
|
104
|
+
|
|
105
|
+
# poetry
|
|
106
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
107
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
108
|
+
# commonly ignored for libraries.
|
|
109
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
110
|
+
# poetry.lock
|
|
111
|
+
# poetry.toml
|
|
112
|
+
|
|
113
|
+
# pdm
|
|
114
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
115
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
116
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
117
|
+
# pdm.lock
|
|
118
|
+
# pdm.toml
|
|
119
|
+
.pdm-python
|
|
120
|
+
.pdm-build/
|
|
121
|
+
|
|
122
|
+
# pixi
|
|
123
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
124
|
+
# pixi.lock
|
|
125
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
126
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
127
|
+
.pixi
|
|
128
|
+
|
|
129
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
130
|
+
__pypackages__/
|
|
131
|
+
|
|
132
|
+
# Celery stuff
|
|
133
|
+
celerybeat-schedule
|
|
134
|
+
celerybeat.pid
|
|
135
|
+
|
|
136
|
+
# Redis
|
|
137
|
+
*.rdb
|
|
138
|
+
*.aof
|
|
139
|
+
*.pid
|
|
140
|
+
|
|
141
|
+
# RabbitMQ
|
|
142
|
+
mnesia/
|
|
143
|
+
rabbitmq/
|
|
144
|
+
rabbitmq-data/
|
|
145
|
+
|
|
146
|
+
# ActiveMQ
|
|
147
|
+
activemq-data/
|
|
148
|
+
|
|
149
|
+
# SageMath parsed files
|
|
150
|
+
*.sage.py
|
|
151
|
+
|
|
152
|
+
# Environments
|
|
153
|
+
.env
|
|
154
|
+
.envrc
|
|
155
|
+
.venv
|
|
156
|
+
env/
|
|
157
|
+
venv/
|
|
158
|
+
ENV/
|
|
159
|
+
env.bak/
|
|
160
|
+
venv.bak/
|
|
161
|
+
|
|
162
|
+
# Spyder project settings
|
|
163
|
+
.spyderproject
|
|
164
|
+
.spyproject
|
|
165
|
+
|
|
166
|
+
# Rope project settings
|
|
167
|
+
.ropeproject
|
|
168
|
+
|
|
169
|
+
# mkdocs documentation
|
|
170
|
+
/site
|
|
171
|
+
|
|
172
|
+
# mypy
|
|
173
|
+
.mypy_cache/
|
|
174
|
+
.dmypy.json
|
|
175
|
+
dmypy.json
|
|
176
|
+
|
|
177
|
+
# Pyre type checker
|
|
178
|
+
.pyre/
|
|
179
|
+
|
|
180
|
+
# pytype static type analyzer
|
|
181
|
+
.pytype/
|
|
182
|
+
|
|
183
|
+
# Cython debug symbols
|
|
184
|
+
cython_debug/
|
|
185
|
+
|
|
186
|
+
# PyCharm
|
|
187
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
188
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
189
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
190
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
191
|
+
# .idea/
|
|
192
|
+
|
|
193
|
+
# Abstra
|
|
194
|
+
# Abstra is an AI-powered process automation framework.
|
|
195
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
196
|
+
# Learn more at https://abstra.io/docs
|
|
197
|
+
.abstra/
|
|
198
|
+
|
|
199
|
+
# Visual Studio Code
|
|
200
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
201
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
202
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
203
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
204
|
+
# .vscode/
|
|
205
|
+
# Temporary file for partial code execution
|
|
206
|
+
tempCodeRunnerFile.py
|
|
207
|
+
|
|
208
|
+
# Ruff stuff:
|
|
209
|
+
.ruff_cache/
|
|
210
|
+
|
|
211
|
+
# PyPI configuration file
|
|
212
|
+
.pypirc
|
|
213
|
+
|
|
214
|
+
# Marimo
|
|
215
|
+
marimo/_static/
|
|
216
|
+
marimo/_lsp/
|
|
217
|
+
__marimo__/
|
|
218
|
+
|
|
219
|
+
# Streamlit
|
|
220
|
+
.streamlit/secrets.toml
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
../LICENSE
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xproject-python
|
|
3
|
+
Version: 0.1
|
|
4
|
+
Summary: Initialize project files
|
|
5
|
+
Project-URL: Homepage, https://github.com/bondbox/xproject/
|
|
6
|
+
Author-email: Mingzhe Zou <zoumingzhe@outlook.com>
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: project
|
|
9
|
+
Classifier: Programming Language :: Python
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Requires-Dist: xkits-command>=0.6
|
|
13
|
+
Requires-Dist: xkits-config-toml>=0.7
|
|
14
|
+
Requires-Dist: xkits-file>=0.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# xproject
|
|
18
|
+
|
|
19
|
+
> Initialize project files
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
build-backend = "hatchling.build"
|
|
3
|
+
requires = [ "hatch-requirements-txt", "hatchling", "xpip-build>=1.4",]
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
classifiers = [ "Programming Language :: Python", "Programming Language :: Python :: 3",]
|
|
7
|
+
description = "Initialize project files"
|
|
8
|
+
dynamic = [ "dependencies", "version",]
|
|
9
|
+
keywords = [ "project",]
|
|
10
|
+
license-files = [ "LICENSE",]
|
|
11
|
+
name = "xproject-python"
|
|
12
|
+
requires-python = ">=3.8"
|
|
13
|
+
[[project.authors]]
|
|
14
|
+
name = "Mingzhe Zou"
|
|
15
|
+
email = "zoumingzhe@outlook.com"
|
|
16
|
+
|
|
17
|
+
[project.readme]
|
|
18
|
+
content-type = "text/markdown"
|
|
19
|
+
file = "readme.md"
|
|
20
|
+
|
|
21
|
+
[project.scripts]
|
|
22
|
+
xproject-python-generate = "xproject_python.projector:main"
|
|
23
|
+
xproject-python-config = "xproject_python.configure:main"
|
|
24
|
+
|
|
25
|
+
[project.urls]
|
|
26
|
+
Homepage = "https://github.com/bondbox/xproject/"
|
|
27
|
+
|
|
28
|
+
[tool.hatch.version]
|
|
29
|
+
path = "xproject_python/attribute.py"
|
|
30
|
+
|
|
31
|
+
[tool.hatch.build.targets.sdist]
|
|
32
|
+
exclude = [ "xproject_python/unittest",]
|
|
33
|
+
packages = [ "xproject_python",]
|
|
34
|
+
|
|
35
|
+
[tool.hatch.build.targets.wheel]
|
|
36
|
+
exclude = [ "xproject_python/unittest",]
|
|
37
|
+
packages = [ "xproject_python",]
|
|
38
|
+
|
|
39
|
+
[tool.hatch.metadata.hooks.requirements_txt]
|
|
40
|
+
files = [ "requirements.txt",]
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.sdist.force-include]
|
|
43
|
+
templates = "xproject_python/templates"
|
|
44
|
+
|
|
45
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
46
|
+
templates = "xproject_python/templates"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
../readme.md
|
|
File without changes
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# coding:utf-8
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from dataclasses import field
|
|
5
|
+
from errno import ENOENT
|
|
6
|
+
from typing import Dict
|
|
7
|
+
from typing import List
|
|
8
|
+
from typing import Optional
|
|
9
|
+
from typing import Sequence
|
|
10
|
+
|
|
11
|
+
from xkits_command.actuator import Command
|
|
12
|
+
from xkits_command.actuator import CommandArgument
|
|
13
|
+
from xkits_command.actuator import CommandExecutor
|
|
14
|
+
from xkits_command.parser import ArgParser
|
|
15
|
+
from xkits_config import Settings
|
|
16
|
+
from xkits_config_toml import ConfigTOML
|
|
17
|
+
|
|
18
|
+
from xproject_python.attribute import __project_home__ as xproject_home
|
|
19
|
+
from xproject_python.attribute import __project_name__ as xproject_name
|
|
20
|
+
from xproject_python.attribute import __version__ as version
|
|
21
|
+
|
|
22
|
+
DEFAULT_CONFIG_FILE: str = f".{xproject_name}_python"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class AuthorConfig(Settings):
|
|
27
|
+
name: str
|
|
28
|
+
email: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class ModuleConfig(Settings):
|
|
33
|
+
base: Optional[str] = None
|
|
34
|
+
package: Optional[List[str]] = None
|
|
35
|
+
omitted: List[str] = field(default_factory=list)
|
|
36
|
+
exclude: List[str] = field(default_factory=list)
|
|
37
|
+
include: Dict[str, str] = field(default_factory=dict)
|
|
38
|
+
scripts: Dict[str, str] = field(default_factory=dict)
|
|
39
|
+
templates: Dict[str, bool] = field(default_factory=dict)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class PackageConfig(Settings): # pylint: disable=too-many-instance-attributes
|
|
44
|
+
base: Optional[str] = None
|
|
45
|
+
version: Optional[str] = None
|
|
46
|
+
requires_python: Optional[str] = None
|
|
47
|
+
authors: List[str] = field(default_factory=list)
|
|
48
|
+
keywords: List[str] = field(default_factory=list)
|
|
49
|
+
requirements: List[str] = field(default_factory=list)
|
|
50
|
+
modules: Dict[str, ModuleConfig] = field(default_factory=dict)
|
|
51
|
+
max_complexity: int = 10
|
|
52
|
+
max_line_length: int = 127
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@dataclass
|
|
56
|
+
class ProjectConfig(ConfigTOML):
|
|
57
|
+
name: str = xproject_name
|
|
58
|
+
home: str = xproject_home
|
|
59
|
+
description: str = f"Automatically created by [{xproject_name}]({xproject_home})." # noqa:E501
|
|
60
|
+
authors: Dict[str, AuthorConfig] = field(default_factory=dict)
|
|
61
|
+
keywords: List[str] = field(default_factory=list)
|
|
62
|
+
packages: Dict[str, PackageConfig] = field(default_factory=dict)
|
|
63
|
+
version: str = "0.1.alpha.1"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@CommandArgument("update", help="Update Python project configuration")
|
|
67
|
+
def add_cmd_config_update(_arg: ArgParser):
|
|
68
|
+
pass
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@CommandExecutor(add_cmd_config_update)
|
|
72
|
+
def run_cmd_config_update(cmds: Command) -> int:
|
|
73
|
+
try:
|
|
74
|
+
ProjectConfig.loadf(cmds.args.file).dumpf(cmds.args.file)
|
|
75
|
+
cmds.stderr_green(f"Configuration file {cmds.args.file} updated")
|
|
76
|
+
return 0
|
|
77
|
+
except FileNotFoundError:
|
|
78
|
+
cmds.stderr_red(f"Configuration file {cmds.args.file} not found")
|
|
79
|
+
return ENOENT
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@CommandArgument("config", help="Manage Python project configuration")
|
|
83
|
+
def add_cmd_config(_arg: ArgParser):
|
|
84
|
+
_arg.add_argument("--file", dest="file", type=str, nargs=None,
|
|
85
|
+
metavar="FILE", default=DEFAULT_CONFIG_FILE,
|
|
86
|
+
help="Specify configuration file")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@CommandExecutor(add_cmd_config, add_cmd_config_update)
|
|
90
|
+
def run_cmd_config(cmds: Command) -> int: # pylint: disable=unused-argument
|
|
91
|
+
return 0
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
95
|
+
cmds = Command()
|
|
96
|
+
cmds.version = version
|
|
97
|
+
return cmds.run(root=add_cmd_config, argv=argv, epilog=f"For more, please visit {xproject_home}.") # noqa:E501
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
if __name__ == "__main__":
|
|
101
|
+
project_name: str = "xproject"
|
|
102
|
+
project_home: str = "https://github.com/bondbox/xproject/"
|
|
103
|
+
project_desc: str = "Initialize project files"
|
|
104
|
+
|
|
105
|
+
ProjectConfig(
|
|
106
|
+
name=project_name,
|
|
107
|
+
home=project_home,
|
|
108
|
+
description=project_desc,
|
|
109
|
+
authors={
|
|
110
|
+
"zoumingzhe": AuthorConfig(
|
|
111
|
+
name="Mingzhe Zou",
|
|
112
|
+
email="zoumingzhe@outlook.com",
|
|
113
|
+
),
|
|
114
|
+
},
|
|
115
|
+
keywords=[
|
|
116
|
+
"project",
|
|
117
|
+
],
|
|
118
|
+
packages={
|
|
119
|
+
f"{project_name}-python": PackageConfig(
|
|
120
|
+
base=f"{project_name}-python",
|
|
121
|
+
version=None,
|
|
122
|
+
requires_python=">=3.8",
|
|
123
|
+
authors=[
|
|
124
|
+
"zoumingzhe",
|
|
125
|
+
],
|
|
126
|
+
keywords=[
|
|
127
|
+
],
|
|
128
|
+
requirements=[
|
|
129
|
+
"xkits-command",
|
|
130
|
+
"xkits-config-toml>=0.5",
|
|
131
|
+
"xkits-file>=0.9",
|
|
132
|
+
"prompt-toolkit",
|
|
133
|
+
],
|
|
134
|
+
modules={
|
|
135
|
+
f"{project_name}-python": ModuleConfig(
|
|
136
|
+
base=f"{project_name}_python",
|
|
137
|
+
package=None,
|
|
138
|
+
omitted=[
|
|
139
|
+
"attribute.py",
|
|
140
|
+
"unittest/*",
|
|
141
|
+
],
|
|
142
|
+
exclude=[
|
|
143
|
+
"unittest",
|
|
144
|
+
],
|
|
145
|
+
include={
|
|
146
|
+
"templates": "templates",
|
|
147
|
+
},
|
|
148
|
+
scripts={
|
|
149
|
+
f"{project_name}-python": "blueprint:main",
|
|
150
|
+
},
|
|
151
|
+
templates={
|
|
152
|
+
"__init__.py": False,
|
|
153
|
+
"attribute.py": True,
|
|
154
|
+
},
|
|
155
|
+
),
|
|
156
|
+
},
|
|
157
|
+
max_complexity=15,
|
|
158
|
+
max_line_length=127,
|
|
159
|
+
),
|
|
160
|
+
},
|
|
161
|
+
version="0.1.alpha.1",
|
|
162
|
+
).dumpf(DEFAULT_CONFIG_FILE)
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
# coding:utf-8
|
|
2
|
+
|
|
3
|
+
from errno import ENOENT
|
|
4
|
+
from json import dumps
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from sys import version_info
|
|
7
|
+
from typing import Dict
|
|
8
|
+
from typing import Iterator
|
|
9
|
+
from typing import List
|
|
10
|
+
from typing import Optional
|
|
11
|
+
from typing import Sequence
|
|
12
|
+
from typing import Tuple
|
|
13
|
+
from typing import Union
|
|
14
|
+
|
|
15
|
+
from packaging.specifiers import SpecifierSet
|
|
16
|
+
from xkits_command.actuator import Command
|
|
17
|
+
from xkits_command.actuator import CommandArgument
|
|
18
|
+
from xkits_command.actuator import CommandExecutor
|
|
19
|
+
from xkits_command.parser import ArgParser
|
|
20
|
+
from xkits_file.template import TemplateManagerPath
|
|
21
|
+
from xkits_file.template import Variable
|
|
22
|
+
|
|
23
|
+
from xproject_python.attribute import __project_home__ as project_home
|
|
24
|
+
from xproject_python.attribute import __version__ as version
|
|
25
|
+
from xproject_python.configure import AuthorConfig
|
|
26
|
+
from xproject_python.configure import DEFAULT_CONFIG_FILE
|
|
27
|
+
from xproject_python.configure import ModuleConfig
|
|
28
|
+
from xproject_python.configure import PackageConfig
|
|
29
|
+
from xproject_python.configure import ProjectConfig
|
|
30
|
+
from xproject_python.utilities import CoverageRC
|
|
31
|
+
from xproject_python.utilities import Flake8
|
|
32
|
+
from xproject_python.utilities import PylintRC
|
|
33
|
+
from xproject_python.utilities import Pyproject
|
|
34
|
+
from xproject_python.utilities import Requirements
|
|
35
|
+
|
|
36
|
+
TEMPLATES: Path = Path(__file__).parent / "templates"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Module:
|
|
40
|
+
TEMPLATES_MODULE: Path = TEMPLATES / "module"
|
|
41
|
+
ATTRIBUTE: str = "attribute.py"
|
|
42
|
+
DOT: str = "."
|
|
43
|
+
|
|
44
|
+
def __init__(self, name: str, config: PackageConfig, variable: Optional[Variable] = None): # noqa:E501
|
|
45
|
+
option: ModuleConfig = config.modules[name]
|
|
46
|
+
|
|
47
|
+
variables: Variable = variable.duplicate()if isinstance(variable, Variable) else Variable() # noqa:E501
|
|
48
|
+
variables.set_default("module_name", module_name := self.normalize(name)) # noqa:E501
|
|
49
|
+
|
|
50
|
+
self.__name: str = module_name
|
|
51
|
+
self.__option: ModuleConfig = option
|
|
52
|
+
self.__variable: Variable = variables
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def name(self) -> str:
|
|
56
|
+
return self.__name
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def base(self) -> str:
|
|
60
|
+
return self.option.base or self.DOT
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def option(self) -> ModuleConfig:
|
|
64
|
+
return self.__option
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def variable(self) -> Variable:
|
|
68
|
+
return self.__variable
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def include(self) -> Iterator[Tuple[str, str]]:
|
|
72
|
+
for name, path in self.option.include.items():
|
|
73
|
+
yield name, self.path_join(path)
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def exclude(self) -> Iterator[str]:
|
|
77
|
+
for path in self.option.exclude:
|
|
78
|
+
yield self.path_join(path)
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def package(self) -> Iterator[str]:
|
|
82
|
+
if self.option.package is not None:
|
|
83
|
+
for path in self.option.package:
|
|
84
|
+
yield self.path_join(path)
|
|
85
|
+
else:
|
|
86
|
+
yield self.base
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def omitted(self) -> Iterator[str]:
|
|
90
|
+
for path in self.option.omitted:
|
|
91
|
+
yield self.path_join(path)
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def scripts(self) -> Iterator[Tuple[str, str]]:
|
|
95
|
+
for name, entry in self.option.scripts.items():
|
|
96
|
+
point: str = (parts := entry.rsplit(":", maxsplit=1)).pop()
|
|
97
|
+
yield name, ":".join([self.dot_join(*parts), point])
|
|
98
|
+
|
|
99
|
+
def prepend(self, *parts: str) -> Tuple[str, ...]:
|
|
100
|
+
return (base, *parts) if (base := self.base) != self.DOT else parts # noqa:E501
|
|
101
|
+
|
|
102
|
+
def dot_join(self, *parts: str) -> str:
|
|
103
|
+
return self.DOT.join(self.prepend(*parts))
|
|
104
|
+
|
|
105
|
+
def path_join(self, *parts: str) -> str:
|
|
106
|
+
return Path(*self.prepend(*parts)).as_posix()
|
|
107
|
+
|
|
108
|
+
@classmethod
|
|
109
|
+
def normalize(cls, name: str) -> str:
|
|
110
|
+
return Requirements.normalize(requirement=name).name.replace("-", "_")
|
|
111
|
+
|
|
112
|
+
def dump(self, base: Union[str, Path], writable: bool = False) -> None:
|
|
113
|
+
root: Path = base if isinstance(base, Path) else Path(base)
|
|
114
|
+
|
|
115
|
+
files: List[str] = [
|
|
116
|
+
name for name, edit in self.option.templates.items()
|
|
117
|
+
if not (root / name).exists() or edit
|
|
118
|
+
]
|
|
119
|
+
|
|
120
|
+
templates: TemplateManagerPath = TemplateManagerPath(self.variable)
|
|
121
|
+
templates.load(base=self.TEMPLATES_MODULE, include=files)
|
|
122
|
+
templates.dump(base=root, writable=writable)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class Package: # pylint: disable=too-many-instance-attributes
|
|
126
|
+
TEMPLATES_PACKAGE: Path = TEMPLATES / "package"
|
|
127
|
+
FILES: List[str] = ["Makefile"]
|
|
128
|
+
|
|
129
|
+
def __init__(self, name: str, config: ProjectConfig, variable: Optional[Variable] = None): # pylint: disable=too-many-locals # noqa:E501
|
|
130
|
+
option: PackageConfig = config.packages[name]
|
|
131
|
+
|
|
132
|
+
variables: Variable = variable.duplicate() if isinstance(variable, Variable) else Variable() # noqa:E501
|
|
133
|
+
variables.set_default("package_name", package_name := Requirements.normalize(requirement=name).name) # noqa:E501
|
|
134
|
+
variables.set_default("package_version", package_version := option.version or config.version) # noqa:E501
|
|
135
|
+
|
|
136
|
+
authors: List[AuthorConfig] = [config.authors[index] for index in option.authors] # noqa:E501
|
|
137
|
+
variables.set_default("authors", dumps(authors, indent=4, default=lambda i: i.__dict__)) # noqa:E501
|
|
138
|
+
|
|
139
|
+
coverage: CoverageRC = CoverageRC.load(self.TEMPLATES_PACKAGE / CoverageRC.FILENAME) # noqa:E501
|
|
140
|
+
flake8: Flake8 = Flake8.load(self.TEMPLATES_PACKAGE / Flake8.FILENAME)
|
|
141
|
+
pylint: PylintRC = PylintRC.load(self.TEMPLATES_PACKAGE / PylintRC.FILENAME) # noqa:E501
|
|
142
|
+
|
|
143
|
+
python_version: str = option.requires_python or f"{version_info.major}.{version_info.minor}" # noqa:E501
|
|
144
|
+
pyproject: Pyproject = Pyproject.load(self.TEMPLATES_PACKAGE / Pyproject.FILENAME) # noqa:E501
|
|
145
|
+
pyproject.project["name"] = package_name
|
|
146
|
+
pyproject.project["description"] = config.description
|
|
147
|
+
pyproject.project["requires-python"] = python_version
|
|
148
|
+
pyproject.project_authors.extend(author.__dict__ for author in authors)
|
|
149
|
+
pyproject.project_keywords.extend(config.keywords)
|
|
150
|
+
pyproject.project_keywords.extend(option.keywords)
|
|
151
|
+
pyproject.project_urls["Homepage"] = config.home
|
|
152
|
+
|
|
153
|
+
requirements: Requirements = Requirements()
|
|
154
|
+
for requirement in option.requirements:
|
|
155
|
+
requirements.add(requirement)
|
|
156
|
+
|
|
157
|
+
self.__name: str = package_name
|
|
158
|
+
self.__version: str = package_version
|
|
159
|
+
self.__option: PackageConfig = option
|
|
160
|
+
self.__variable: Variable = variables
|
|
161
|
+
self.__coverage: CoverageRC = coverage
|
|
162
|
+
self.__flake8: Flake8 = flake8
|
|
163
|
+
self.__pylint: PylintRC = pylint
|
|
164
|
+
self.__pyproject: Pyproject = pyproject
|
|
165
|
+
self.__requirements: Requirements = requirements
|
|
166
|
+
|
|
167
|
+
def __iter__(self) -> Iterator[Module]:
|
|
168
|
+
for name in self.option.modules:
|
|
169
|
+
yield Module(name=name, config=self.option, variable=self.variable)
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def name(self) -> str:
|
|
173
|
+
return self.__name
|
|
174
|
+
|
|
175
|
+
@property
|
|
176
|
+
def version(self) -> str:
|
|
177
|
+
return self.__version
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def base(self) -> str:
|
|
181
|
+
return self.option.base or "."
|
|
182
|
+
|
|
183
|
+
@property
|
|
184
|
+
def option(self) -> PackageConfig:
|
|
185
|
+
return self.__option
|
|
186
|
+
|
|
187
|
+
@property
|
|
188
|
+
def variable(self) -> Variable:
|
|
189
|
+
return self.__variable
|
|
190
|
+
|
|
191
|
+
@property
|
|
192
|
+
def coverage(self) -> CoverageRC:
|
|
193
|
+
return self.__coverage
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def flake8(self) -> Flake8:
|
|
197
|
+
return self.__flake8
|
|
198
|
+
|
|
199
|
+
@property
|
|
200
|
+
def pylint(self) -> PylintRC:
|
|
201
|
+
return self.__pylint
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def pyproject(self) -> Pyproject:
|
|
205
|
+
return self.__pyproject
|
|
206
|
+
|
|
207
|
+
@property
|
|
208
|
+
def requirements(self) -> Requirements:
|
|
209
|
+
return self.__requirements
|
|
210
|
+
|
|
211
|
+
def dump(self, base: Union[str, Path], writable: bool = False) -> None: # pylint: disable=too-many-locals # noqa:E501
|
|
212
|
+
root: Path = base if isinstance(base, Path) else Path(base)
|
|
213
|
+
modules: List[Module] = list(iter(self))
|
|
214
|
+
|
|
215
|
+
attribute_modules: List[Module] = []
|
|
216
|
+
coverage_omit: str = ""
|
|
217
|
+
coverage_source: str = ""
|
|
218
|
+
flake8_exclude: str = ""
|
|
219
|
+
flake8_modules: List[str] = []
|
|
220
|
+
pylint_files: List[str] = []
|
|
221
|
+
|
|
222
|
+
for module in sorted(modules, key=lambda m: m.base):
|
|
223
|
+
for include_name, include_path in module.include:
|
|
224
|
+
self.pyproject.tool_hatch_build_targets_sdist_force_include[include_name] = include_path # noqa:E501
|
|
225
|
+
self.pyproject.tool_hatch_build_targets_wheel_force_include[include_name] = include_path # noqa:E501
|
|
226
|
+
|
|
227
|
+
for exclude in module.exclude:
|
|
228
|
+
self.pyproject.tool_hatch_build_targets_sdist_exclude.append(exclude) # noqa:E501
|
|
229
|
+
self.pyproject.tool_hatch_build_targets_wheel_exclude.append(exclude) # noqa:E501
|
|
230
|
+
flake8_exclude += f"\n{exclude}"
|
|
231
|
+
|
|
232
|
+
for package in module.package:
|
|
233
|
+
self.pyproject.tool_hatch_build_targets_sdist_packages.append(package) # noqa:E501
|
|
234
|
+
self.pyproject.tool_hatch_build_targets_wheel_packages.append(package) # noqa:E501
|
|
235
|
+
coverage_source += f"\n{package.removesuffix('.py')}"
|
|
236
|
+
flake8_modules.append(package)
|
|
237
|
+
|
|
238
|
+
pylint_files.append(f"{module.base}/*.py")
|
|
239
|
+
|
|
240
|
+
for omitted in module.omitted:
|
|
241
|
+
coverage_omit += f"\n{omitted}"
|
|
242
|
+
|
|
243
|
+
for script_name, script_entry in module.scripts:
|
|
244
|
+
self.pyproject.project_scripts[script_name] = script_entry
|
|
245
|
+
|
|
246
|
+
if Module.ATTRIBUTE in module.option.templates:
|
|
247
|
+
attribute_modules.append(module)
|
|
248
|
+
|
|
249
|
+
if (attribute_module_number := len(attribute_modules)) > 1:
|
|
250
|
+
raise ValueError(f"Package {self.name} has more than one attribute module: {', '.join(m.name for m in attribute_modules)}") # noqa:E501
|
|
251
|
+
if attribute_module_number < 1:
|
|
252
|
+
raise ValueError(f"Package {self.name} has no attribute module")
|
|
253
|
+
|
|
254
|
+
self.coverage.parser["run"]["omit"] = coverage_omit
|
|
255
|
+
self.coverage.parser["run"]["source"] = coverage_source
|
|
256
|
+
self.flake8.parser["flake8"]["exclude"] = flake8_exclude
|
|
257
|
+
self.flake8.parser["flake8"]["max-complexity"] = str(self.option.max_complexity) # noqa:E501
|
|
258
|
+
self.flake8.parser["flake8"]["max-line-length"] = str(self.option.max_line_length) # noqa:E501
|
|
259
|
+
|
|
260
|
+
variables: Variable = self.variable
|
|
261
|
+
variables.set_default("attribute_module", attribute_modules[0].dot_join(Path(Module.ATTRIBUTE).stem)) # noqa:E501
|
|
262
|
+
variables.set_default("flake8_modules", " ".join(flake8_modules))
|
|
263
|
+
variables.set_default("pylint_files", " ".join(pylint_files))
|
|
264
|
+
|
|
265
|
+
templates: TemplateManagerPath = TemplateManagerPath(variables)
|
|
266
|
+
templates.load(base=self.TEMPLATES_PACKAGE, include=self.FILES)
|
|
267
|
+
templates.dump(base=root, writable=writable)
|
|
268
|
+
|
|
269
|
+
self.requirements.dumpf(filepath=root / Requirements.FILENAME, writable=writable) # noqa:E501
|
|
270
|
+
self.pyproject.tool_hatch_metadata_hooks_requirements_txt_files.append(Requirements.FILENAME) # noqa:E501
|
|
271
|
+
self.pyproject.tool_hatch_version["path"] = attribute_modules[0].path_join(Module.ATTRIBUTE) # noqa:E501
|
|
272
|
+
self.pyproject.dump(filepath=root / Pyproject.FILENAME, writable=writable) # noqa:E501
|
|
273
|
+
|
|
274
|
+
self.coverage.dump(filepath=root / CoverageRC.FILENAME, writable=writable) # noqa:E501
|
|
275
|
+
self.flake8.dump(filepath=root / Flake8.FILENAME, writable=writable) # noqa:E501
|
|
276
|
+
self.pylint.dump(filepath=root / PylintRC.FILENAME, writable=writable) # noqa:E501
|
|
277
|
+
|
|
278
|
+
for module in modules:
|
|
279
|
+
module.dump(base=root / module.base, writable=writable)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class Project:
|
|
283
|
+
TEMPLATES_PROJECT: Path = TEMPLATES / "project"
|
|
284
|
+
|
|
285
|
+
def __init__(self, config: ProjectConfig):
|
|
286
|
+
project_name: str = Requirements.normalize(requirement=config.name).name # noqa:E501
|
|
287
|
+
|
|
288
|
+
variables: Variable = Variable(
|
|
289
|
+
project_name=project_name,
|
|
290
|
+
project_home=config.home,
|
|
291
|
+
project_description=config.description,
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
self.__name: str = project_name
|
|
295
|
+
self.__option: ProjectConfig = config
|
|
296
|
+
self.__variable: Variable = variables
|
|
297
|
+
|
|
298
|
+
def __iter__(self) -> Iterator[Package]:
|
|
299
|
+
for name in self.option.packages:
|
|
300
|
+
yield Package(name=name, config=self.option, variable=self.variable) # noqa:E501
|
|
301
|
+
|
|
302
|
+
@property
|
|
303
|
+
def name(self) -> str:
|
|
304
|
+
return self.__name
|
|
305
|
+
|
|
306
|
+
@property
|
|
307
|
+
def option(self) -> ProjectConfig:
|
|
308
|
+
return self.__option
|
|
309
|
+
|
|
310
|
+
@property
|
|
311
|
+
def variable(self) -> Variable:
|
|
312
|
+
return self.__variable
|
|
313
|
+
|
|
314
|
+
def dump(self, base: Union[str, Path], writable: bool = False) -> None:
|
|
315
|
+
root: Path = base if isinstance(base, Path) else Path(base)
|
|
316
|
+
|
|
317
|
+
templates: TemplateManagerPath = TemplateManagerPath(self.variable)
|
|
318
|
+
templates.load(base=self.TEMPLATES_PROJECT, include=None)
|
|
319
|
+
templates.dump(base=root, writable=writable)
|
|
320
|
+
|
|
321
|
+
packages: Dict[str, Package] = {package.name: package for package in iter(self)} # noqa:E501
|
|
322
|
+
|
|
323
|
+
for package in packages.values():
|
|
324
|
+
dest: Path = root / package.base
|
|
325
|
+
|
|
326
|
+
for requirement in package.requirements:
|
|
327
|
+
if dependence := packages.get(requirement.name):
|
|
328
|
+
requirement.specifier = SpecifierSet(f">={dependence.version}") # noqa:E501
|
|
329
|
+
|
|
330
|
+
package.dump(base=dest, writable=writable)
|
|
331
|
+
|
|
332
|
+
if dest != root:
|
|
333
|
+
if not (readme_link := dest / "readme.md").exists():
|
|
334
|
+
readme_link.symlink_to("../readme.md")
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
@CommandArgument("generate", help="Create or update Python project files")
|
|
338
|
+
def add_cmd_generate(_arg: ArgParser):
|
|
339
|
+
_arg.add_opt_on("--change", help="allow changes to existing files")
|
|
340
|
+
_arg.add_opt("--config", dest="config", type=str, nargs=None,
|
|
341
|
+
metavar="FILE", default=DEFAULT_CONFIG_FILE,
|
|
342
|
+
help="Specify configuration file")
|
|
343
|
+
_arg.add_pos("root", type=str, nargs="?", metavar="PATH", default=".",
|
|
344
|
+
help="Project root directory")
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
@CommandExecutor(add_cmd_generate)
|
|
348
|
+
def run_cmd_generate(cmds: Command) -> int:
|
|
349
|
+
try:
|
|
350
|
+
config: ProjectConfig = ProjectConfig.loadf(cmds.args.config)
|
|
351
|
+
except FileNotFoundError:
|
|
352
|
+
cmds.stderr_red(f"Configuration file {cmds.args.config} not found")
|
|
353
|
+
return ENOENT
|
|
354
|
+
|
|
355
|
+
root: Path = Path(cmds.args.root).resolve()
|
|
356
|
+
cmds.stderr_yellow(f"Generate to root directory: {root}")
|
|
357
|
+
|
|
358
|
+
project: Project = Project(config=config)
|
|
359
|
+
project.dump(base=root, writable=cmds.args.change)
|
|
360
|
+
cmds.stderr_green(f"Project {project.name} generated")
|
|
361
|
+
return 0
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def main(argv: Optional[Sequence[str]] = None) -> int:
|
|
365
|
+
cmds = Command()
|
|
366
|
+
cmds.version = version
|
|
367
|
+
return cmds.run(root=add_cmd_generate, argv=argv, epilog=f"For more, please visit {project_home}.") # noqa:E501
|
|
File without changes
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
MAKEFLAGS += --always-make
|
|
2
|
+
|
|
3
|
+
VERSION ?= $(shell python3 -c "from {attribute_module} import __version__; print(__version__)")
|
|
4
|
+
|
|
5
|
+
all: build test
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
release: all
|
|
9
|
+
if [ -n "${{VERSION}}" ]; then \
|
|
10
|
+
git tag -a v${{VERSION}} -m "release v${{VERSION}}"; \
|
|
11
|
+
git push origin --tags; \
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
version:
|
|
15
|
+
@echo ${{VERSION}}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
upload:
|
|
19
|
+
python3 -m pip install --upgrade xpip-upload
|
|
20
|
+
xpip-upload --config-file .pypirc dist/*
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
build-prepare:
|
|
24
|
+
python3 -m pip install --upgrade xpip-build
|
|
25
|
+
build-clean:
|
|
26
|
+
find . -type d -name "__pycache__" -exec rm -rf {{}} +
|
|
27
|
+
rm -rf build dist *.egg-info
|
|
28
|
+
build: build-prepare build-clean
|
|
29
|
+
python3 -m build --sdist --wheel
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
install-requirements:
|
|
33
|
+
python3 -m pip install --upgrade -r requirements.txt
|
|
34
|
+
install: install-requirements
|
|
35
|
+
python3 -m pip install --force-reinstall --no-deps dist/*.whl
|
|
36
|
+
uninstall:
|
|
37
|
+
python3 -m pip uninstall -y {package_name}
|
|
38
|
+
reinstall: uninstall install
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
test-prepare: install-requirements
|
|
42
|
+
python3 -m pip install --upgrade mock flake8 pylint pytest pytest-cov
|
|
43
|
+
flake8:
|
|
44
|
+
flake8 {flake8_modules}
|
|
45
|
+
pylint:
|
|
46
|
+
pylint $(shell git ls-files {pylint_files})
|
|
47
|
+
pytest:
|
|
48
|
+
pytest --cov --cov-config=.coveragerc --cov-report=term-missing --cov-report=xml --cov-report=html
|
|
49
|
+
pytest-clean:
|
|
50
|
+
rm -rf .pytest_cache
|
|
51
|
+
test: test-prepare flake8 pylint pytest
|
|
52
|
+
test-clean: pytest-clean
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
clean-cover:
|
|
56
|
+
rm -rf cover .coverage coverage.xml htmlcov
|
|
57
|
+
clean-tox:
|
|
58
|
+
rm -rf .stestr .tox
|
|
59
|
+
clean: build-clean test-clean clean-cover clean-tox
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
build-backend = "hatchling.build"
|
|
3
|
+
requires = [
|
|
4
|
+
"hatch-requirements-txt",
|
|
5
|
+
"hatchling",
|
|
6
|
+
"xpip-build>=1.4",
|
|
7
|
+
]
|
|
8
|
+
|
|
9
|
+
[project]
|
|
10
|
+
authors = []
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Programming Language :: Python",
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
]
|
|
15
|
+
description = ""
|
|
16
|
+
dynamic = [
|
|
17
|
+
"dependencies",
|
|
18
|
+
"version",
|
|
19
|
+
]
|
|
20
|
+
keywords = []
|
|
21
|
+
license-files = [ "LICENSE",]
|
|
22
|
+
name = ""
|
|
23
|
+
requires-python = ""
|
|
24
|
+
|
|
25
|
+
[project.readme]
|
|
26
|
+
content-type = "text/markdown"
|
|
27
|
+
file = "readme.md"
|
|
28
|
+
|
|
29
|
+
[project.scripts]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
|
|
33
|
+
[tool.hatch.version]
|
|
34
|
+
|
|
35
|
+
[tool.hatch.build.targets.sdist]
|
|
36
|
+
exclude = []
|
|
37
|
+
packages = []
|
|
38
|
+
|
|
39
|
+
[tool.hatch.build.targets.wheel]
|
|
40
|
+
exclude = []
|
|
41
|
+
packages = []
|
|
42
|
+
|
|
43
|
+
[tool.hatch.metadata.hooks.requirements_txt]
|
|
44
|
+
files = []
|
|
45
|
+
|
|
46
|
+
[tool.hatch.build.targets.sdist.force-include]
|
|
47
|
+
|
|
48
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# coding:utf-8
|
|
2
|
+
|
|
3
|
+
from configparser import ConfigParser
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any
|
|
6
|
+
from typing import Dict
|
|
7
|
+
from typing import Iterator
|
|
8
|
+
from typing import List
|
|
9
|
+
from typing import Optional
|
|
10
|
+
from typing import Type
|
|
11
|
+
from typing import TypeVar
|
|
12
|
+
from typing import Union
|
|
13
|
+
|
|
14
|
+
from packaging.requirements import Requirement
|
|
15
|
+
from toml import dump
|
|
16
|
+
from toml import load
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
CFGT = TypeVar("CFGT", bound="Configuration")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Configuration:
|
|
23
|
+
|
|
24
|
+
def __init__(self, parser: Optional[ConfigParser] = None):
|
|
25
|
+
self.__parser: ConfigParser = parser or ConfigParser()
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def parser(self) -> ConfigParser:
|
|
29
|
+
return self.__parser
|
|
30
|
+
|
|
31
|
+
def dump(self, filepath: Union[str, Path], writable: bool = False):
|
|
32
|
+
if isinstance(filepath, str):
|
|
33
|
+
filepath = Path(filepath) # pragma: no cover
|
|
34
|
+
|
|
35
|
+
if not filepath.exists() or writable:
|
|
36
|
+
with filepath.open("w", encoding="utf-8") as whdl:
|
|
37
|
+
self.parser.write(whdl)
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def load(cls: Type[CFGT], filepath: Union[str, Path]) -> CFGT:
|
|
41
|
+
(parser := ConfigParser()).read(filepath)
|
|
42
|
+
return cls(parser=parser)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class CoverageRC(Configuration):
|
|
46
|
+
FILENAME: str = ".coveragerc"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Flake8(Configuration):
|
|
50
|
+
FILENAME: str = ".flake8"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class PylintRC(Configuration):
|
|
54
|
+
FILENAME: str = ".pylintrc"
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class Pyproject: # pylint: disable=too-many-public-methods
|
|
58
|
+
FILENAME: str = "pyproject.toml"
|
|
59
|
+
|
|
60
|
+
def __init__(self, coder: Dict[str, Any]):
|
|
61
|
+
self.__coder: Dict[str, Any] = coder
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def coder(self) -> Dict[str, Any]:
|
|
65
|
+
return self.__coder
|
|
66
|
+
|
|
67
|
+
@property
|
|
68
|
+
def project(self) -> Dict[str, Any]:
|
|
69
|
+
return self.coder["project"]
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def project_authors(self) -> List[Dict[str, str]]:
|
|
73
|
+
return self.project["authors"]
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def project_keywords(self) -> List[str]:
|
|
77
|
+
return self.project["keywords"]
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def project_scripts(self) -> Dict[str, Any]:
|
|
81
|
+
return self.project["scripts"]
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def project_urls(self) -> Dict[str, str]:
|
|
85
|
+
return self.project["urls"]
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def tool(self) -> Dict[str, Any]:
|
|
89
|
+
return self.coder["tool"]
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def tool_hatch(self) -> Dict[str, Any]:
|
|
93
|
+
return self.tool["hatch"]
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def tool_hatch_build(self) -> Dict[str, Any]:
|
|
97
|
+
return self.tool_hatch["build"]
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def tool_hatch_build_targets(self) -> Dict[str, Any]:
|
|
101
|
+
return self.tool_hatch_build["targets"]
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def tool_hatch_build_targets_sdist(self) -> Dict[str, Any]:
|
|
105
|
+
return self.tool_hatch_build_targets["sdist"]
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def tool_hatch_build_targets_sdist_force_include(self) -> Dict[str, Any]:
|
|
109
|
+
return self.tool_hatch_build_targets_sdist["force-include"]
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def tool_hatch_build_targets_sdist_exclude(self) -> List[str]:
|
|
113
|
+
return self.tool_hatch_build_targets_sdist["exclude"]
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def tool_hatch_build_targets_sdist_packages(self) -> List[str]:
|
|
117
|
+
return self.tool_hatch_build_targets_sdist["packages"]
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def tool_hatch_build_targets_wheel(self) -> Dict[str, Any]:
|
|
121
|
+
return self.tool_hatch_build_targets["wheel"]
|
|
122
|
+
|
|
123
|
+
@property
|
|
124
|
+
def tool_hatch_build_targets_wheel_force_include(self) -> Dict[str, Any]:
|
|
125
|
+
return self.tool_hatch_build_targets_wheel["force-include"]
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def tool_hatch_build_targets_wheel_exclude(self) -> List[str]:
|
|
129
|
+
return self.tool_hatch_build_targets_wheel["exclude"]
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def tool_hatch_build_targets_wheel_packages(self) -> List[str]:
|
|
133
|
+
return self.tool_hatch_build_targets_wheel["packages"]
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def tool_hatch_metadata(self) -> Dict[str, Any]:
|
|
137
|
+
return self.tool_hatch["metadata"]
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def tool_hatch_metadata_hooks(self) -> Dict[str, Any]:
|
|
141
|
+
return self.tool_hatch_metadata["hooks"]
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def tool_hatch_metadata_hooks_requirements_txt(self) -> Dict[str, Any]:
|
|
145
|
+
return self.tool_hatch_metadata_hooks["requirements_txt"]
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def tool_hatch_metadata_hooks_requirements_txt_files(self) -> List[str]:
|
|
149
|
+
return self.tool_hatch_metadata_hooks_requirements_txt["files"]
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def tool_hatch_version(self) -> List[str]:
|
|
153
|
+
return self.tool_hatch["version"]
|
|
154
|
+
|
|
155
|
+
def dump(self, filepath: Union[str, Path], writable: bool = False):
|
|
156
|
+
if isinstance(filepath, str):
|
|
157
|
+
filepath = Path(filepath) # pragma: no cover
|
|
158
|
+
|
|
159
|
+
if not filepath.exists() or writable:
|
|
160
|
+
with filepath.open("w", encoding="utf-8") as whdl:
|
|
161
|
+
dump(self.coder, whdl)
|
|
162
|
+
|
|
163
|
+
@classmethod
|
|
164
|
+
def load(cls, filepath: Union[str, Path]) -> "Pyproject":
|
|
165
|
+
if isinstance(filepath, str):
|
|
166
|
+
filepath = Path(filepath) # pragma: no cover
|
|
167
|
+
|
|
168
|
+
with filepath.open("r", encoding="utf-8") as rhdl:
|
|
169
|
+
return cls(coder=load(rhdl))
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class Requirements:
|
|
173
|
+
"""Requirements
|
|
174
|
+
|
|
175
|
+
Reference:
|
|
176
|
+
- [PEP 508](https://peps.python.org/pep-0508/)
|
|
177
|
+
Dependency specification for Python Software Packages
|
|
178
|
+
"""
|
|
179
|
+
FILENAME: str = "requirements.txt"
|
|
180
|
+
|
|
181
|
+
def __init__(self, *requirements: Union[str, Requirement]):
|
|
182
|
+
self.__requirements: List[Requirement] = [self.normalize(requirement) for requirement in requirements] # noqa:E501
|
|
183
|
+
|
|
184
|
+
def __iter__(self) -> Iterator[Requirement]:
|
|
185
|
+
yield from self.__requirements
|
|
186
|
+
|
|
187
|
+
def add(self, requirement: Union[str, Requirement]) -> None:
|
|
188
|
+
self.__requirements.append(self.normalize(requirement))
|
|
189
|
+
|
|
190
|
+
def dumps(self) -> str:
|
|
191
|
+
return "\n".join(str(requirement) for requirement in self.__requirements) # noqa:E501
|
|
192
|
+
|
|
193
|
+
def dumpf(self, filepath: Union[str, Path], writable: bool = False) -> None: # noqa:E501
|
|
194
|
+
if isinstance(filepath, str):
|
|
195
|
+
filepath = Path(filepath) # pragma: no cover
|
|
196
|
+
|
|
197
|
+
if not filepath.exists() or writable:
|
|
198
|
+
with filepath.open("w", encoding="utf-8") as whdl:
|
|
199
|
+
whdl.write(self.dumps())
|
|
200
|
+
whdl.write("\n")
|
|
201
|
+
|
|
202
|
+
@classmethod
|
|
203
|
+
def normalize(cls, requirement: Union[str, Requirement]) -> Requirement: # noqa:E501
|
|
204
|
+
"""Normalized Names with PEP 503
|
|
205
|
+
|
|
206
|
+
Reference:
|
|
207
|
+
- https://peps.python.org/pep-0426/#name
|
|
208
|
+
- https://peps.python.org/pep-0503/#normalized-names
|
|
209
|
+
"""
|
|
210
|
+
from re import sub # pylint: disable=import-outside-toplevel
|
|
211
|
+
|
|
212
|
+
if not isinstance(requirement, Requirement):
|
|
213
|
+
requirement = Requirement(requirement)
|
|
214
|
+
|
|
215
|
+
requirement.name = sub(r"[-_.]+", "-", requirement.name).lower()
|
|
216
|
+
return requirement
|