linktools-setup 0.0.2__tar.gz → 0.0.3__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.
- {linktools_setup-0.0.2/src/linktools_setup.egg-info → linktools_setup-0.0.3}/PKG-INFO +3 -2
- {linktools_setup-0.0.2 → linktools_setup-0.0.3}/pyproject.toml +10 -2
- {linktools_setup-0.0.2 → linktools_setup-0.0.3}/src/linktools_setup/build_meta.py +9 -3
- linktools_setup-0.0.3/src/linktools_setup/setup.py +585 -0
- {linktools_setup-0.0.2 → linktools_setup-0.0.3/src/linktools_setup.egg-info}/PKG-INFO +3 -2
- {linktools_setup-0.0.2 → linktools_setup-0.0.3}/src/linktools_setup.egg-info/SOURCES.txt +0 -1
- {linktools_setup-0.0.2 → linktools_setup-0.0.3}/src/linktools_setup.egg-info/entry_points.txt +3 -0
- {linktools_setup-0.0.2 → linktools_setup-0.0.3}/src/linktools_setup.egg-info/requires.txt +3 -0
- linktools_setup-0.0.2/MANIFEST.in +0 -2
- linktools_setup-0.0.2/src/linktools_setup/setup.py +0 -298
- {linktools_setup-0.0.2 → linktools_setup-0.0.3}/LICENSE +0 -0
- {linktools_setup-0.0.2 → linktools_setup-0.0.3}/setup.cfg +0 -0
- {linktools_setup-0.0.2 → linktools_setup-0.0.3}/setup.py +0 -0
- {linktools_setup-0.0.2 → linktools_setup-0.0.3}/src/linktools_setup/__init__.py +0 -0
- {linktools_setup-0.0.2 → linktools_setup-0.0.3}/src/linktools_setup.egg-info/dependency_links.txt +0 -0
- {linktools_setup-0.0.2 → linktools_setup-0.0.3}/src/linktools_setup.egg-info/top_level.txt +0 -0
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: linktools-setup
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: linktools toolkit
|
|
5
5
|
Author-email: Hu Ji <669898595@qq.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
7
7
|
Project-URL: Homepage, https://github.com/linktools-toolkit/linktools-setup
|
|
8
8
|
Project-URL: Repository, https://github.com/linktools-toolkit/linktools-setup.git
|
|
9
|
-
Requires-Python: >=3.
|
|
9
|
+
Requires-Python: >=3.7
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Requires-Dist: pyyaml
|
|
12
12
|
Requires-Dist: jinja2
|
|
13
13
|
Requires-Dist: setuptools<77.0.0,>=64.0.0
|
|
14
|
+
Requires-Dist: tomli>=1.2.3; python_version < "3.11"
|
|
14
15
|
Dynamic: license-file
|
|
@@ -5,10 +5,15 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "linktools-setup"
|
|
7
7
|
description = "linktools toolkit"
|
|
8
|
-
requires-python = ">=3.
|
|
8
|
+
requires-python = ">=3.7"
|
|
9
9
|
authors = [{name = "Hu Ji", email = "669898595@qq.com"}]
|
|
10
10
|
license = "Apache-2.0"
|
|
11
|
-
dependencies = [
|
|
11
|
+
dependencies = [
|
|
12
|
+
"pyyaml",
|
|
13
|
+
"jinja2",
|
|
14
|
+
"setuptools>=64.0.0,<77.0.0",
|
|
15
|
+
"tomli>=1.2.3; python_version < '3.11'",
|
|
16
|
+
]
|
|
12
17
|
dynamic = ["version"]
|
|
13
18
|
|
|
14
19
|
[project.urls]
|
|
@@ -18,6 +23,9 @@ Repository = "https://github.com/linktools-toolkit/linktools-setup.git"
|
|
|
18
23
|
[project.entry-points."setuptools.finalize_distribution_options"]
|
|
19
24
|
linktools_setup = "linktools_setup.setup:finalize_distribution_options"
|
|
20
25
|
|
|
26
|
+
[project.entry-points."setuptools.file_finders"]
|
|
27
|
+
linktools = "linktools_setup.setup:find_linktools_files"
|
|
28
|
+
|
|
21
29
|
[tool.setuptools.packages.find]
|
|
22
30
|
where = ["src"]
|
|
23
31
|
include = ["linktools_setup*"]
|
|
@@ -1,21 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
import os
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
_CONFIG_FILE = "linktools.yml"
|
|
7
|
+
|
|
8
|
+
if not os.path.isfile(_CONFIG_FILE):
|
|
9
|
+
raise RuntimeError("missing Linktools config: %s" % os.path.abspath(_CONFIG_FILE))
|
|
4
10
|
|
|
5
11
|
from setuptools import build_meta as _build_meta
|
|
6
12
|
from setuptools.build_meta import * # noqa
|
|
7
13
|
|
|
8
14
|
|
|
9
|
-
def get_requires_for_build_editable(*args, **kwargs):
|
|
15
|
+
def get_requires_for_build_editable(*args: Any, **kwargs: Any) -> Any:
|
|
10
16
|
os.environ["SETUP_EDITABLE_MODE"] = "true"
|
|
11
17
|
return _build_meta.get_requires_for_build_editable(*args, **kwargs)
|
|
12
18
|
|
|
13
19
|
|
|
14
|
-
def prepare_metadata_for_build_editable(*args, **kwargs):
|
|
20
|
+
def prepare_metadata_for_build_editable(*args: Any, **kwargs: Any) -> Any:
|
|
15
21
|
os.environ["SETUP_EDITABLE_MODE"] = "true"
|
|
16
22
|
return _build_meta.prepare_metadata_for_build_editable(*args, **kwargs)
|
|
17
23
|
|
|
18
24
|
|
|
19
|
-
def build_editable(*args, **kwargs):
|
|
25
|
+
def build_editable(*args: Any, **kwargs: Any) -> Any:
|
|
20
26
|
os.environ["SETUP_EDITABLE_MODE"] = "true"
|
|
21
27
|
return _build_meta.build_editable(*args, **kwargs)
|
|
@@ -0,0 +1,585 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
import abc
|
|
4
|
+
import hashlib
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import os
|
|
8
|
+
import pkgutil
|
|
9
|
+
from importlib.util import module_from_spec
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Dict, Iterable, Iterator, List, Optional, Sequence
|
|
12
|
+
|
|
13
|
+
import setuptools
|
|
14
|
+
import yaml
|
|
15
|
+
from jinja2 import Template
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
import tomllib
|
|
19
|
+
except ImportError:
|
|
20
|
+
import tomli as tomllib
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger("linktools_setup")
|
|
23
|
+
|
|
24
|
+
_CONFIG_FILE = "linktools.yml"
|
|
25
|
+
_PROJECT_FILE = "pyproject.toml"
|
|
26
|
+
_MODULE_COMMAND_KEY = "__command__"
|
|
27
|
+
_CAPABILITY_GROUP = "linktools_capability"
|
|
28
|
+
_SCRIPTS_GROUP = "linktools_scripts"
|
|
29
|
+
_DEFAULT_SCRIPT_OBJECT = "command"
|
|
30
|
+
_DEFAULT_SCRIPT_ATTR = "main"
|
|
31
|
+
_SCRIPT_FIELDS = {"capability", "console", "gui", "commands"}
|
|
32
|
+
_SCRIPT_ITEM_FIELDS = {"name", "path", "module", "object", "attr"}
|
|
33
|
+
_CONVERT_FIELDS = {"type", "source", "dest"}
|
|
34
|
+
_CONVERT_TYPES = {"jinja2", "yml2json"}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class _UniqueKeyLoader(yaml.SafeLoader):
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _construct_unique_mapping(
|
|
42
|
+
loader: _UniqueKeyLoader,
|
|
43
|
+
node: yaml.nodes.MappingNode,
|
|
44
|
+
deep: bool = False,
|
|
45
|
+
) -> Dict[Any, Any]:
|
|
46
|
+
mapping: Dict[Any, Any] = {}
|
|
47
|
+
for key_node, value_node in node.value:
|
|
48
|
+
key = loader.construct_object(key_node, deep=deep)
|
|
49
|
+
try:
|
|
50
|
+
duplicated = key in mapping
|
|
51
|
+
except TypeError as exc:
|
|
52
|
+
raise ValueError("mapping keys must be scalar values") from exc
|
|
53
|
+
if duplicated:
|
|
54
|
+
raise ValueError("duplicate YAML key: %s" % key)
|
|
55
|
+
mapping[key] = loader.construct_object(value_node, deep=deep)
|
|
56
|
+
return mapping
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
_UniqueKeyLoader.add_constructor(
|
|
60
|
+
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
|
|
61
|
+
_construct_unique_mapping,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class EntryPoint(abc.ABC):
|
|
66
|
+
|
|
67
|
+
@abc.abstractmethod
|
|
68
|
+
def as_script(self) -> str:
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ScriptEntryPoint(EntryPoint):
|
|
73
|
+
|
|
74
|
+
def __init__(
|
|
75
|
+
self,
|
|
76
|
+
name: str,
|
|
77
|
+
module: str,
|
|
78
|
+
object: Optional[str],
|
|
79
|
+
attr: Optional[str],
|
|
80
|
+
):
|
|
81
|
+
self.name = name
|
|
82
|
+
self.module = module
|
|
83
|
+
self.object = object
|
|
84
|
+
self.attr = attr
|
|
85
|
+
|
|
86
|
+
def as_script(self) -> str:
|
|
87
|
+
name = self.name.replace("_", "-")
|
|
88
|
+
value = self.module
|
|
89
|
+
if self.object:
|
|
90
|
+
value = "%s:%s" % (value, self.object)
|
|
91
|
+
if self.attr:
|
|
92
|
+
value = "%s.%s" % (value, self.attr)
|
|
93
|
+
return "%s = %s" % (name, value)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class SubScriptEntryPoint(ScriptEntryPoint):
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class ModuleEntryPoint(EntryPoint):
|
|
101
|
+
|
|
102
|
+
def __init__(self, name: str, module: str):
|
|
103
|
+
self.name = name
|
|
104
|
+
self.module = module
|
|
105
|
+
|
|
106
|
+
def as_script(self) -> str:
|
|
107
|
+
name = self.name.replace("_", "-")
|
|
108
|
+
return "%s = %s" % (name, self.module)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class SetupConfig:
|
|
112
|
+
|
|
113
|
+
def __init__(self, root_path: Optional[Path] = None):
|
|
114
|
+
self.root_path = (root_path or Path.cwd()).resolve()
|
|
115
|
+
self.path = self.root_path / _CONFIG_FILE
|
|
116
|
+
self._config = self._load()
|
|
117
|
+
|
|
118
|
+
def _load(self) -> Dict[str, Any]:
|
|
119
|
+
try:
|
|
120
|
+
with self.path.open("r", encoding="utf-8") as file:
|
|
121
|
+
data = yaml.load(file, Loader=_UniqueKeyLoader)
|
|
122
|
+
except FileNotFoundError as exc:
|
|
123
|
+
raise ValueError("missing Linktools config: %s" % self.path) from exc
|
|
124
|
+
except (OSError, ValueError, yaml.YAMLError) as exc:
|
|
125
|
+
raise ValueError("invalid Linktools config %s: %s" % (self.path, exc)) from exc
|
|
126
|
+
|
|
127
|
+
config = self._require_mapping(data, "config")
|
|
128
|
+
|
|
129
|
+
version = config.get("version")
|
|
130
|
+
if not isinstance(version, str) or not version.strip():
|
|
131
|
+
raise ValueError("config.version must be a non-empty string")
|
|
132
|
+
version = version.strip()
|
|
133
|
+
if version.startswith("v"):
|
|
134
|
+
raise ValueError("config.version must not start with 'v'")
|
|
135
|
+
config["version"] = version
|
|
136
|
+
|
|
137
|
+
name = config.get("name")
|
|
138
|
+
if name is not None and (not isinstance(name, str) or not name.strip()):
|
|
139
|
+
raise ValueError("config.name must be a non-empty string")
|
|
140
|
+
if isinstance(name, str):
|
|
141
|
+
config["name"] = name.strip()
|
|
142
|
+
|
|
143
|
+
for key in ("dependencies", "dev-dependencies", "release-dependencies"):
|
|
144
|
+
config[key] = self._require_string_list(config.get(key, []), "config.%s" % key)
|
|
145
|
+
|
|
146
|
+
optional = self._require_mapping(
|
|
147
|
+
config.get("optional-dependencies", {}),
|
|
148
|
+
"config.optional-dependencies",
|
|
149
|
+
)
|
|
150
|
+
normalized_optional: Dict[str, List[str]] = {}
|
|
151
|
+
for extra, requirements in optional.items():
|
|
152
|
+
if not isinstance(extra, str) or not extra:
|
|
153
|
+
raise ValueError("config.optional-dependencies keys must be non-empty strings")
|
|
154
|
+
normalized_optional[extra] = self._require_string_list(
|
|
155
|
+
requirements,
|
|
156
|
+
"config.optional-dependencies.%s" % extra,
|
|
157
|
+
)
|
|
158
|
+
config["optional-dependencies"] = normalized_optional
|
|
159
|
+
|
|
160
|
+
config["scripts"] = self._validate_scripts(config.get("scripts", {}))
|
|
161
|
+
config["convert"] = self._validate_convert(config.get("convert", []))
|
|
162
|
+
return config
|
|
163
|
+
|
|
164
|
+
def __getitem__(self, key: str) -> Any:
|
|
165
|
+
return self._config[key]
|
|
166
|
+
|
|
167
|
+
@staticmethod
|
|
168
|
+
def _require_mapping(value: Any, label: str) -> Dict[str, Any]:
|
|
169
|
+
if not isinstance(value, dict):
|
|
170
|
+
raise ValueError("%s must be a mapping" % label)
|
|
171
|
+
if not all(isinstance(key, str) for key in value):
|
|
172
|
+
raise ValueError("%s field names must be strings" % label)
|
|
173
|
+
return dict(value)
|
|
174
|
+
|
|
175
|
+
@staticmethod
|
|
176
|
+
def _reject_unknown(value: Dict[str, Any], allowed: set, label: str) -> None:
|
|
177
|
+
unknown = sorted(set(value) - allowed)
|
|
178
|
+
if unknown:
|
|
179
|
+
raise ValueError("%s has unknown field(s): %s" % (label, ", ".join(unknown)))
|
|
180
|
+
|
|
181
|
+
@staticmethod
|
|
182
|
+
def _require_string_list(value: Any, label: str) -> List[str]:
|
|
183
|
+
if not isinstance(value, list):
|
|
184
|
+
raise ValueError("%s must be a list of strings" % label)
|
|
185
|
+
result: List[str] = []
|
|
186
|
+
for item in value:
|
|
187
|
+
if not isinstance(item, str) or not item:
|
|
188
|
+
raise ValueError("%s must be a list of non-empty strings" % label)
|
|
189
|
+
result.append(item)
|
|
190
|
+
return result
|
|
191
|
+
|
|
192
|
+
@staticmethod
|
|
193
|
+
def _require_optional_string(value: Any, label: str) -> Optional[str]:
|
|
194
|
+
if value is None:
|
|
195
|
+
return None
|
|
196
|
+
if not isinstance(value, str) or not value:
|
|
197
|
+
raise ValueError("%s must be a non-empty string or null" % label)
|
|
198
|
+
return value
|
|
199
|
+
|
|
200
|
+
def _resolve_path(self, value: Any, label: str) -> Path:
|
|
201
|
+
if not isinstance(value, str) or not value:
|
|
202
|
+
raise ValueError("%s must be a non-empty relative path" % label)
|
|
203
|
+
if os.path.isabs(value):
|
|
204
|
+
raise ValueError("%s must be relative: %s" % (label, value))
|
|
205
|
+
path = (self.root_path / value).resolve()
|
|
206
|
+
self._require_inside_root(path, label)
|
|
207
|
+
return path
|
|
208
|
+
|
|
209
|
+
def _resolve_source(self, value: Any, label: str, directory: bool = False) -> str:
|
|
210
|
+
path = self._resolve_path(value, label)
|
|
211
|
+
if directory:
|
|
212
|
+
if path.exists() and not path.is_dir():
|
|
213
|
+
raise ValueError("%s must be a directory: %s" % (label, value))
|
|
214
|
+
elif not path.is_file():
|
|
215
|
+
raise ValueError("%s file does not exist: %s" % (label, value))
|
|
216
|
+
return value
|
|
217
|
+
|
|
218
|
+
def _resolve_dest(self, value: Any, label: str) -> str:
|
|
219
|
+
self._resolve_path(value, label)
|
|
220
|
+
return value
|
|
221
|
+
|
|
222
|
+
def _require_inside_root(self, path: Path, label: str) -> None:
|
|
223
|
+
try:
|
|
224
|
+
inside = os.path.commonpath((str(self.root_path), str(path))) == str(self.root_path)
|
|
225
|
+
except ValueError:
|
|
226
|
+
inside = False
|
|
227
|
+
if not inside:
|
|
228
|
+
raise ValueError("%s escapes project root: %s" % (label, path))
|
|
229
|
+
|
|
230
|
+
def _validate_scripts(self, value: Any) -> Dict[str, Any]:
|
|
231
|
+
scripts = self._require_mapping(value, "config.scripts")
|
|
232
|
+
self._reject_unknown(scripts, _SCRIPT_FIELDS, "config.scripts")
|
|
233
|
+
|
|
234
|
+
capability = scripts.get("capability")
|
|
235
|
+
if capability is not None:
|
|
236
|
+
if not isinstance(capability, str) or not capability:
|
|
237
|
+
raise ValueError("config.scripts.capability must be a non-empty string")
|
|
238
|
+
|
|
239
|
+
for group in ("console", "gui", "commands"):
|
|
240
|
+
if group not in scripts:
|
|
241
|
+
continue
|
|
242
|
+
raw_items = scripts[group]
|
|
243
|
+
items = raw_items if isinstance(raw_items, list) else [raw_items]
|
|
244
|
+
if not items:
|
|
245
|
+
raise ValueError("config.scripts.%s must not be empty" % group)
|
|
246
|
+
normalized = []
|
|
247
|
+
for index, raw_item in enumerate(items):
|
|
248
|
+
label = "config.scripts.%s[%d]" % (group, index)
|
|
249
|
+
item = self._require_mapping(raw_item, label)
|
|
250
|
+
self._reject_unknown(item, _SCRIPT_ITEM_FIELDS, label)
|
|
251
|
+
has_name = "name" in item
|
|
252
|
+
has_path = "path" in item
|
|
253
|
+
if has_name == has_path:
|
|
254
|
+
raise ValueError("%s must define exactly one of name or path" % label)
|
|
255
|
+
|
|
256
|
+
module = item.get("module")
|
|
257
|
+
if not isinstance(module, str) or not module:
|
|
258
|
+
raise ValueError("%s.module must be a non-empty string" % label)
|
|
259
|
+
|
|
260
|
+
if has_name:
|
|
261
|
+
name = item.get("name")
|
|
262
|
+
if not isinstance(name, str) or not name:
|
|
263
|
+
raise ValueError("%s.name must be a non-empty string" % label)
|
|
264
|
+
else:
|
|
265
|
+
item["path"] = self._resolve_source(
|
|
266
|
+
item.get("path"),
|
|
267
|
+
"%s.path" % label,
|
|
268
|
+
directory=True,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
if "object" in item:
|
|
272
|
+
item["object"] = self._require_optional_string(
|
|
273
|
+
item.get("object"),
|
|
274
|
+
"%s.object" % label,
|
|
275
|
+
)
|
|
276
|
+
if "attr" in item:
|
|
277
|
+
item["attr"] = self._require_optional_string(
|
|
278
|
+
item.get("attr"),
|
|
279
|
+
"%s.attr" % label,
|
|
280
|
+
)
|
|
281
|
+
normalized.append(item)
|
|
282
|
+
scripts[group] = normalized if isinstance(raw_items, list) else normalized[0]
|
|
283
|
+
return scripts
|
|
284
|
+
|
|
285
|
+
def _validate_convert(self, value: Any) -> List[Dict[str, str]]:
|
|
286
|
+
if not isinstance(value, list):
|
|
287
|
+
raise ValueError("config.convert must be a list")
|
|
288
|
+
result: List[Dict[str, str]] = []
|
|
289
|
+
for index, raw_item in enumerate(value):
|
|
290
|
+
label = "config.convert[%d]" % index
|
|
291
|
+
item = self._require_mapping(raw_item, label)
|
|
292
|
+
self._reject_unknown(item, _CONVERT_FIELDS, label)
|
|
293
|
+
convert_type = item.get("type")
|
|
294
|
+
if convert_type not in _CONVERT_TYPES:
|
|
295
|
+
raise ValueError("%s.type must be one of: %s" % (
|
|
296
|
+
label,
|
|
297
|
+
", ".join(sorted(_CONVERT_TYPES)),
|
|
298
|
+
))
|
|
299
|
+
source = self._resolve_source(item.get("source"), "%s.source" % label)
|
|
300
|
+
dest = self._resolve_dest(item.get("dest"), "%s.dest" % label)
|
|
301
|
+
result.append({
|
|
302
|
+
"type": convert_type,
|
|
303
|
+
"source": source,
|
|
304
|
+
"dest": dest,
|
|
305
|
+
})
|
|
306
|
+
return result
|
|
307
|
+
|
|
308
|
+
def get(self, *keys: str, default: Any = None) -> Any:
|
|
309
|
+
value: Any = self._config
|
|
310
|
+
for key in keys:
|
|
311
|
+
if not isinstance(value, dict) or key not in value:
|
|
312
|
+
return default
|
|
313
|
+
value = value[key]
|
|
314
|
+
return value
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
class SetupContext:
|
|
318
|
+
|
|
319
|
+
def __init__(self, dist: setuptools.Distribution):
|
|
320
|
+
self._dist = dist
|
|
321
|
+
self.config = SetupConfig()
|
|
322
|
+
self.project = self._load_project()
|
|
323
|
+
self.groups = {
|
|
324
|
+
"capability": _CAPABILITY_GROUP,
|
|
325
|
+
"scripts": _SCRIPTS_GROUP,
|
|
326
|
+
}
|
|
327
|
+
self.release = os.environ.get("RELEASE", "false").lower() in ("true", "1", "yes")
|
|
328
|
+
self.develop = os.environ.get("SETUP_EDITABLE_MODE", "false").lower() in ("true", "1", "yes")
|
|
329
|
+
self.version = self._fill_version()
|
|
330
|
+
self._fill_dependencies()
|
|
331
|
+
self._fill_entry_points()
|
|
332
|
+
|
|
333
|
+
def _load_project(self) -> Dict[str, Any]:
|
|
334
|
+
path = self.config.root_path / _PROJECT_FILE
|
|
335
|
+
try:
|
|
336
|
+
with path.open("rb") as file:
|
|
337
|
+
data = tomllib.load(file)
|
|
338
|
+
except (OSError, tomllib.TOMLDecodeError) as exc:
|
|
339
|
+
raise ValueError("invalid project config %s: %s" % (path, exc)) from exc
|
|
340
|
+
project = data.get("project")
|
|
341
|
+
if not isinstance(project, dict):
|
|
342
|
+
raise ValueError("pyproject.toml must define [project]")
|
|
343
|
+
return dict(project)
|
|
344
|
+
|
|
345
|
+
def get_template_context(self) -> Dict[str, Any]:
|
|
346
|
+
return {
|
|
347
|
+
key: value
|
|
348
|
+
for key, value in vars(self).items()
|
|
349
|
+
if not key.startswith("_")
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
def _fill_version(self) -> str:
|
|
353
|
+
version = self._dist.metadata.version
|
|
354
|
+
if not version:
|
|
355
|
+
version = self.config.get("version")
|
|
356
|
+
self._dist.metadata.version = version
|
|
357
|
+
return version
|
|
358
|
+
|
|
359
|
+
def _fill_dependencies(self) -> None:
|
|
360
|
+
dist_install_requires = self._dist.install_requires = self._dist.metadata.install_requires = \
|
|
361
|
+
getattr(self._dist.metadata, "install_requires", None) or []
|
|
362
|
+
dist_extras_require = self._dist.extras_require = self._dist.metadata.extras_require = \
|
|
363
|
+
getattr(self._dist.metadata, "extras_require", None) or {}
|
|
364
|
+
|
|
365
|
+
install_requires: List[str] = []
|
|
366
|
+
install_requires.extend(self.config.get("dependencies", default=[]))
|
|
367
|
+
if self.develop:
|
|
368
|
+
install_requires.extend(self.config.get("dev-dependencies", default=[]))
|
|
369
|
+
if self.release:
|
|
370
|
+
install_requires.extend(self.config.get("release-dependencies", default=[]))
|
|
371
|
+
|
|
372
|
+
extras_require = {
|
|
373
|
+
name: list(requirements)
|
|
374
|
+
for name, requirements in self.config.get(
|
|
375
|
+
"optional-dependencies",
|
|
376
|
+
default={},
|
|
377
|
+
).items()
|
|
378
|
+
}
|
|
379
|
+
if extras_require:
|
|
380
|
+
all_requires = extras_require.setdefault("all", [])
|
|
381
|
+
for name, requirements in extras_require.items():
|
|
382
|
+
if name != "all":
|
|
383
|
+
all_requires.extend(requirements)
|
|
384
|
+
|
|
385
|
+
dist_install_requires.extend(install_requires)
|
|
386
|
+
dist_extras_require.update(extras_require)
|
|
387
|
+
|
|
388
|
+
def _entry_points(self) -> Dict[str, List[str]]:
|
|
389
|
+
entry_points = getattr(self._dist.metadata, "entry_points", None) or {}
|
|
390
|
+
self._dist.entry_points = self._dist.metadata.entry_points = entry_points
|
|
391
|
+
return entry_points
|
|
392
|
+
|
|
393
|
+
def _fill_entry_points(self) -> None:
|
|
394
|
+
scripts = self.config.get("scripts", "console")
|
|
395
|
+
if scripts:
|
|
396
|
+
console_scripts = self._entry_points().setdefault("console_scripts", [])
|
|
397
|
+
for entry_point in self._parse_scripts(scripts):
|
|
398
|
+
if isinstance(entry_point, ScriptEntryPoint):
|
|
399
|
+
console_scripts.append(entry_point.as_script())
|
|
400
|
+
|
|
401
|
+
scripts = self.config.get("scripts", "gui")
|
|
402
|
+
if scripts:
|
|
403
|
+
gui_scripts = self._entry_points().setdefault("gui_scripts", [])
|
|
404
|
+
for entry_point in self._parse_scripts(scripts):
|
|
405
|
+
if isinstance(entry_point, ScriptEntryPoint):
|
|
406
|
+
gui_scripts.append(entry_point.as_script())
|
|
407
|
+
|
|
408
|
+
scripts = self.config.get("scripts", "commands")
|
|
409
|
+
if scripts:
|
|
410
|
+
entry_points = self._entry_points()
|
|
411
|
+
console_scripts = entry_points.setdefault("console_scripts", [])
|
|
412
|
+
linktools_scripts = entry_points.setdefault(_SCRIPTS_GROUP, [])
|
|
413
|
+
for entry_point in self._parse_scripts(scripts):
|
|
414
|
+
if isinstance(entry_point, ScriptEntryPoint):
|
|
415
|
+
console_scripts.append(entry_point.as_script())
|
|
416
|
+
if not isinstance(entry_point, SubScriptEntryPoint):
|
|
417
|
+
linktools_scripts.append(entry_point.as_script())
|
|
418
|
+
|
|
419
|
+
capability = self.config.get("scripts", "capability")
|
|
420
|
+
if capability:
|
|
421
|
+
linktools_module = self._entry_points().setdefault(
|
|
422
|
+
_CAPABILITY_GROUP,
|
|
423
|
+
[],
|
|
424
|
+
)
|
|
425
|
+
linktools_module.append(ModuleEntryPoint(
|
|
426
|
+
name="module-%s" % self.get_md5(capability),
|
|
427
|
+
module=capability,
|
|
428
|
+
).as_script())
|
|
429
|
+
|
|
430
|
+
def _parse_scripts(self, scripts: Any) -> Iterator[EntryPoint]:
|
|
431
|
+
items = scripts if isinstance(scripts, list) else [scripts]
|
|
432
|
+
for script in items:
|
|
433
|
+
yield from self._parse_script(script)
|
|
434
|
+
|
|
435
|
+
def _parse_script(self, script: Dict[str, Any]) -> Iterator[EntryPoint]:
|
|
436
|
+
if "name" in script:
|
|
437
|
+
yield ScriptEntryPoint(
|
|
438
|
+
name=script["name"],
|
|
439
|
+
module=script["module"],
|
|
440
|
+
object=script.get("object", _DEFAULT_SCRIPT_OBJECT),
|
|
441
|
+
attr=script.get("attr", _DEFAULT_SCRIPT_ATTR),
|
|
442
|
+
)
|
|
443
|
+
return
|
|
444
|
+
|
|
445
|
+
module = script["module"].rstrip(".")
|
|
446
|
+
path = str((self.config.root_path / script["path"]).resolve())
|
|
447
|
+
yield ModuleEntryPoint(
|
|
448
|
+
name="module-%s" % self.get_md5(script["path"]),
|
|
449
|
+
module=module,
|
|
450
|
+
)
|
|
451
|
+
yield from self._iter_module_scripts(
|
|
452
|
+
path=path,
|
|
453
|
+
prefix="%s." % module,
|
|
454
|
+
object=script.get("object", _DEFAULT_SCRIPT_OBJECT),
|
|
455
|
+
attr=script.get("attr", _DEFAULT_SCRIPT_ATTR),
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
def _iter_module_scripts(
|
|
459
|
+
self,
|
|
460
|
+
path: str,
|
|
461
|
+
prefix: str,
|
|
462
|
+
object: Optional[str],
|
|
463
|
+
attr: Optional[str],
|
|
464
|
+
parents: Optional[Sequence[str]] = None,
|
|
465
|
+
) -> Iterator[EntryPoint]:
|
|
466
|
+
for module_info in pkgutil.iter_modules([path]):
|
|
467
|
+
if module_info.ispkg:
|
|
468
|
+
spec = module_info.module_finder.find_spec(module_info.name)
|
|
469
|
+
if spec is None or spec.loader is None:
|
|
470
|
+
raise ValueError("cannot load command package: %s" % module_info.name)
|
|
471
|
+
module = module_from_spec(spec)
|
|
472
|
+
spec.loader.exec_module(module)
|
|
473
|
+
items = list(parents or [])
|
|
474
|
+
items.append(getattr(module, _MODULE_COMMAND_KEY, module_info.name))
|
|
475
|
+
yield from self._iter_module_scripts(
|
|
476
|
+
path=os.path.join(path, module_info.name),
|
|
477
|
+
prefix="%s%s." % (prefix, module_info.name),
|
|
478
|
+
object=object,
|
|
479
|
+
attr=attr,
|
|
480
|
+
parents=items,
|
|
481
|
+
)
|
|
482
|
+
elif not module_info.name.startswith("_"):
|
|
483
|
+
yield SubScriptEntryPoint(
|
|
484
|
+
name="%s-%s" % ("-".join(parents), module_info.name)
|
|
485
|
+
if parents else module_info.name,
|
|
486
|
+
module="%s%s" % (prefix, module_info.name),
|
|
487
|
+
object=object,
|
|
488
|
+
attr=attr,
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
def convert_files(self) -> None:
|
|
492
|
+
context = self.get_template_context()
|
|
493
|
+
for item in self.config.get("convert", default=[]):
|
|
494
|
+
source = self.config.root_path / item["source"]
|
|
495
|
+
dest = self.config.root_path / item["dest"]
|
|
496
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
497
|
+
if item["type"] == "jinja2":
|
|
498
|
+
with source.open("r", encoding="utf-8") as fd_in:
|
|
499
|
+
rendered = Template(fd_in.read()).render(**context)
|
|
500
|
+
dest.write_text(rendered, encoding="utf-8")
|
|
501
|
+
elif item["type"] == "yml2json":
|
|
502
|
+
with source.open("r", encoding="utf-8") as fd_in:
|
|
503
|
+
data = yaml.safe_load(fd_in)
|
|
504
|
+
if not isinstance(data, dict):
|
|
505
|
+
raise ValueError("yml2json source must contain a mapping: %s" % item["source"])
|
|
506
|
+
result = {}
|
|
507
|
+
for key, value in data.items():
|
|
508
|
+
if not isinstance(key, str):
|
|
509
|
+
raise ValueError("yml2json source keys must be strings: %s" % item["source"])
|
|
510
|
+
if not key.startswith("$"):
|
|
511
|
+
result[key] = value
|
|
512
|
+
with dest.open("w", encoding="utf-8") as fd_out:
|
|
513
|
+
json.dump(result, fd_out)
|
|
514
|
+
|
|
515
|
+
@staticmethod
|
|
516
|
+
def get_md5(data: Any) -> str:
|
|
517
|
+
if isinstance(data, str):
|
|
518
|
+
data = data.encode()
|
|
519
|
+
if not isinstance(data, bytes):
|
|
520
|
+
raise TypeError("data must be str or bytes")
|
|
521
|
+
digest = hashlib.md5()
|
|
522
|
+
digest.update(data)
|
|
523
|
+
return digest.hexdigest()
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def _finder_path(dirname: str, relative: str) -> str:
|
|
527
|
+
normalized = os.path.normpath(relative)
|
|
528
|
+
if dirname:
|
|
529
|
+
return os.path.join(dirname, normalized)
|
|
530
|
+
return normalized
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
def find_linktools_files(dirname: str) -> Iterable[str]:
|
|
534
|
+
root = Path(dirname or ".")
|
|
535
|
+
config_path = root / _CONFIG_FILE
|
|
536
|
+
if not config_path.is_file():
|
|
537
|
+
return []
|
|
538
|
+
|
|
539
|
+
result = [_finder_path(dirname, _CONFIG_FILE)]
|
|
540
|
+
try:
|
|
541
|
+
with config_path.open("r", encoding="utf-8") as file:
|
|
542
|
+
data = yaml.safe_load(file)
|
|
543
|
+
if not isinstance(data, dict):
|
|
544
|
+
return result
|
|
545
|
+
convert = data.get("convert", [])
|
|
546
|
+
if not isinstance(convert, list):
|
|
547
|
+
return result
|
|
548
|
+
|
|
549
|
+
root_resolved = root.resolve()
|
|
550
|
+
for item in convert:
|
|
551
|
+
if not isinstance(item, dict):
|
|
552
|
+
continue
|
|
553
|
+
source = item.get("source")
|
|
554
|
+
if not isinstance(source, str) or not source or os.path.isabs(source):
|
|
555
|
+
continue
|
|
556
|
+
normalized = os.path.normpath(source)
|
|
557
|
+
if normalized == os.pardir or normalized.startswith(os.pardir + os.sep):
|
|
558
|
+
continue
|
|
559
|
+
candidate = (root_resolved / normalized).resolve()
|
|
560
|
+
try:
|
|
561
|
+
inside = os.path.commonpath((str(root_resolved), str(candidate))) == str(root_resolved)
|
|
562
|
+
except ValueError:
|
|
563
|
+
inside = False
|
|
564
|
+
if inside and candidate.is_file():
|
|
565
|
+
result.append(_finder_path(dirname, normalized))
|
|
566
|
+
except (OSError, ValueError, TypeError, yaml.YAMLError) as exc:
|
|
567
|
+
logger.warning("cannot inspect %s for source files: %s", config_path, exc)
|
|
568
|
+
return result
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
def finalize_distribution_options(dist: setuptools.Distribution) -> None:
|
|
572
|
+
if not (Path.cwd() / _CONFIG_FILE).is_file():
|
|
573
|
+
return
|
|
574
|
+
context = SetupContext(dist)
|
|
575
|
+
context.convert_files()
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
if __name__ == "__main__":
|
|
579
|
+
context = SetupContext(setuptools.Distribution())
|
|
580
|
+
scripts = {"name": "ct-cntr", "module": "linktools_cntr.__main__"}
|
|
581
|
+
print([
|
|
582
|
+
entry_point.as_script()
|
|
583
|
+
for entry_point in context._parse_scripts(scripts)
|
|
584
|
+
if not isinstance(entry_point, SubScriptEntryPoint)
|
|
585
|
+
])
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: linktools-setup
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: linktools toolkit
|
|
5
5
|
Author-email: Hu Ji <669898595@qq.com>
|
|
6
6
|
License-Expression: Apache-2.0
|
|
7
7
|
Project-URL: Homepage, https://github.com/linktools-toolkit/linktools-setup
|
|
8
8
|
Project-URL: Repository, https://github.com/linktools-toolkit/linktools-setup.git
|
|
9
|
-
Requires-Python: >=3.
|
|
9
|
+
Requires-Python: >=3.7
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Requires-Dist: pyyaml
|
|
12
12
|
Requires-Dist: jinja2
|
|
13
13
|
Requires-Dist: setuptools<77.0.0,>=64.0.0
|
|
14
|
+
Requires-Dist: tomli>=1.2.3; python_version < "3.11"
|
|
14
15
|
Dynamic: license-file
|
|
@@ -1,298 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# -*- coding: utf-8 -*-
|
|
3
|
-
import abc
|
|
4
|
-
import hashlib
|
|
5
|
-
import json
|
|
6
|
-
import logging
|
|
7
|
-
import os
|
|
8
|
-
import pkgutil
|
|
9
|
-
import re
|
|
10
|
-
from importlib.util import module_from_spec
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
from typing import Any, Optional
|
|
13
|
-
|
|
14
|
-
import setuptools
|
|
15
|
-
import yaml
|
|
16
|
-
from jinja2 import Template
|
|
17
|
-
from setuptools.config.pyprojecttoml import load_file
|
|
18
|
-
|
|
19
|
-
logger = logging.getLogger("linktools_setup")
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class EntryPoint(abc.ABC):
|
|
23
|
-
|
|
24
|
-
@abc.abstractmethod
|
|
25
|
-
def as_script(self) -> str:
|
|
26
|
-
pass
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
class ScriptEntryPoint(EntryPoint):
|
|
30
|
-
|
|
31
|
-
def __init__(self, name: str, module: str, object: Optional[str], attr: Optional[str]):
|
|
32
|
-
self.name = name
|
|
33
|
-
self.module = module
|
|
34
|
-
self.object = object
|
|
35
|
-
self.attr = attr
|
|
36
|
-
|
|
37
|
-
def as_script(self) -> str:
|
|
38
|
-
name = self.name.replace('_', '-')
|
|
39
|
-
value = self.module
|
|
40
|
-
if self.object:
|
|
41
|
-
value = f"{value}:{self.object}"
|
|
42
|
-
if self.attr:
|
|
43
|
-
value = f"{value}.{self.attr}"
|
|
44
|
-
return f"{name} = {value}"
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
class SubScriptEntryPoint(ScriptEntryPoint):
|
|
48
|
-
pass
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
class ModuleEntryPoint(EntryPoint):
|
|
52
|
-
|
|
53
|
-
def __init__(self, name: str, module: str):
|
|
54
|
-
self.name = name
|
|
55
|
-
self.module = module
|
|
56
|
-
|
|
57
|
-
def as_script(self) -> str:
|
|
58
|
-
name = self.name.replace('_', '-')
|
|
59
|
-
value = self.module
|
|
60
|
-
return f"{name} = {value}"
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
class SetupConst:
|
|
64
|
-
|
|
65
|
-
def __init__(self):
|
|
66
|
-
self.module_command_key = "__command__"
|
|
67
|
-
self.capability_entrypoint = "linktools_capability"
|
|
68
|
-
self.scripts_entrypoint = "linktools_scripts"
|
|
69
|
-
self.default_script_object = "command"
|
|
70
|
-
self.default_script_attr = "main"
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
class SetupConfig:
|
|
74
|
-
|
|
75
|
-
def __init__(self):
|
|
76
|
-
self._config = load_file("pyproject.toml")
|
|
77
|
-
|
|
78
|
-
def get(self, *keys: str, default=None) -> Any:
|
|
79
|
-
missing = object()
|
|
80
|
-
value = self._config
|
|
81
|
-
try:
|
|
82
|
-
for key in keys:
|
|
83
|
-
value = value.get(key, missing)
|
|
84
|
-
if value is missing:
|
|
85
|
-
return default
|
|
86
|
-
except Exception as e:
|
|
87
|
-
logger.warning(f"get {keys} failed: {e}")
|
|
88
|
-
return default
|
|
89
|
-
return value
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
class SetupContext:
|
|
93
|
-
|
|
94
|
-
def __init__(self, dist: setuptools.Distribution):
|
|
95
|
-
self.dist = dist
|
|
96
|
-
self.const = SetupConst()
|
|
97
|
-
self.config = SetupConfig()
|
|
98
|
-
self.release = os.environ.get("RELEASE", "false").lower() in ("true", "1", "yes")
|
|
99
|
-
self.develop = os.environ.get("SETUP_EDITABLE_MODE", "false").lower() in ("true", "1", "yes")
|
|
100
|
-
self.version = self._fill_version()
|
|
101
|
-
self._fill_dependencies()
|
|
102
|
-
self._fill_entry_points()
|
|
103
|
-
|
|
104
|
-
def _fill_version(self):
|
|
105
|
-
version = self.dist.metadata.version
|
|
106
|
-
if not version:
|
|
107
|
-
version = os.environ.get("VERSION", None)
|
|
108
|
-
if not version:
|
|
109
|
-
file = self.config.get("tool", "linktools", "version", "file")
|
|
110
|
-
if file:
|
|
111
|
-
path = os.path.abspath(file)
|
|
112
|
-
if os.path.isfile(path):
|
|
113
|
-
with open(path, encoding="utf-8") as fd:
|
|
114
|
-
version = fd.read().strip()
|
|
115
|
-
if not version:
|
|
116
|
-
version = "0.0.1"
|
|
117
|
-
if version.startswith("v"):
|
|
118
|
-
version = version[len("v"):]
|
|
119
|
-
if not self.release:
|
|
120
|
-
items = []
|
|
121
|
-
for item in version.split("."):
|
|
122
|
-
find = re.findall(r"^\d+", item)
|
|
123
|
-
if find:
|
|
124
|
-
items.append(int(find[0]))
|
|
125
|
-
version = ".".join(map(str, items))
|
|
126
|
-
version = f"{version}.post100.dev0"
|
|
127
|
-
self.dist.metadata.version = version
|
|
128
|
-
return version
|
|
129
|
-
|
|
130
|
-
def _fill_dependencies(self):
|
|
131
|
-
file = self.config.get("tool", "linktools", "dependencies", "file")
|
|
132
|
-
if file:
|
|
133
|
-
dist_install_requires = self.dist.install_requires = self.dist.metadata.install_requires = \
|
|
134
|
-
getattr(self.dist.metadata, "install_requires", None) or []
|
|
135
|
-
dist_extras_require = self.dist.extras_require = self.dist.metadata.extras_require = \
|
|
136
|
-
getattr(self.dist.metadata, "extras_require", None) or {}
|
|
137
|
-
|
|
138
|
-
install_requires, extras_require = [], {}
|
|
139
|
-
with open(file, "rt", encoding="utf-8") as fd:
|
|
140
|
-
data = yaml.safe_load(fd)
|
|
141
|
-
# install_requires = dependencies + dev-dependencies
|
|
142
|
-
install_requires.extend(data.get("dependencies", []))
|
|
143
|
-
if self.develop:
|
|
144
|
-
install_requires.extend(data.get("dev-dependencies", []))
|
|
145
|
-
if self.release:
|
|
146
|
-
install_requires.extend(data.get("release-dependencies", []))
|
|
147
|
-
# extras_require = optional-dependencies
|
|
148
|
-
extras_require.update(data.get("optional-dependencies", {}))
|
|
149
|
-
if extras_require:
|
|
150
|
-
all_requires = extras_require.setdefault("all", [])
|
|
151
|
-
for requires in extras_require.values():
|
|
152
|
-
all_requires.extend(requires)
|
|
153
|
-
|
|
154
|
-
dist_install_requires.extend(install_requires)
|
|
155
|
-
dist_extras_require.update(extras_require)
|
|
156
|
-
|
|
157
|
-
def _fill_entry_points(self):
|
|
158
|
-
scripts = self.config.get("tool", "linktools", "scripts", "console")
|
|
159
|
-
if scripts:
|
|
160
|
-
dist_entry_points = self.dist.entry_points = self.dist.metadata.entry_points = \
|
|
161
|
-
getattr(self.dist.metadata, "entry_points", None) or {}
|
|
162
|
-
console_scripts = dist_entry_points.setdefault("console_scripts", [])
|
|
163
|
-
for entry_point in self._parse_scripts(scripts):
|
|
164
|
-
if isinstance(entry_point, ScriptEntryPoint):
|
|
165
|
-
console_scripts.append(entry_point.as_script())
|
|
166
|
-
|
|
167
|
-
scripts = self.config.get("tool", "linktools", "scripts", "gui")
|
|
168
|
-
if scripts:
|
|
169
|
-
dist_entry_points = self.dist.entry_points = self.dist.metadata.entry_points = \
|
|
170
|
-
getattr(self.dist.metadata, "entry_points", None) or {}
|
|
171
|
-
console_scripts = dist_entry_points.setdefault("gui_scripts", [])
|
|
172
|
-
for entry_point in self._parse_scripts(scripts):
|
|
173
|
-
if isinstance(entry_point, ScriptEntryPoint):
|
|
174
|
-
console_scripts.append(entry_point.as_script())
|
|
175
|
-
|
|
176
|
-
scripts = self.config.get("tool", "linktools", "scripts", "commands")
|
|
177
|
-
if scripts:
|
|
178
|
-
dist_entry_points = self.dist.entry_points = self.dist.metadata.entry_points = \
|
|
179
|
-
getattr(self.dist.metadata, "entry_points", None) or {}
|
|
180
|
-
console_scripts = dist_entry_points.setdefault("console_scripts", [])
|
|
181
|
-
linktools_scripts = dist_entry_points.setdefault(self.const.scripts_entrypoint, [])
|
|
182
|
-
for entry_point in self._parse_scripts(scripts):
|
|
183
|
-
if isinstance(entry_point, ScriptEntryPoint):
|
|
184
|
-
console_scripts.append(entry_point.as_script())
|
|
185
|
-
if not isinstance(entry_point, SubScriptEntryPoint):
|
|
186
|
-
linktools_scripts.append(entry_point.as_script())
|
|
187
|
-
|
|
188
|
-
capability = self.config.get("tool", "linktools", "scripts", "capability")
|
|
189
|
-
if capability:
|
|
190
|
-
dist_entry_points = self.dist.entry_points = self.dist.metadata.entry_points = \
|
|
191
|
-
getattr(self.dist.metadata, "entry_points", None) or {}
|
|
192
|
-
linktools_module = dist_entry_points.setdefault(self.const.capability_entrypoint, [])
|
|
193
|
-
linktools_module.append(ModuleEntryPoint(
|
|
194
|
-
name=f"module-{self.get_md5(capability)}",
|
|
195
|
-
module=capability,
|
|
196
|
-
).as_script())
|
|
197
|
-
|
|
198
|
-
def _parse_scripts(self, scripts):
|
|
199
|
-
if not isinstance(scripts, (list, tuple, set)):
|
|
200
|
-
scripts = [scripts]
|
|
201
|
-
for script in scripts:
|
|
202
|
-
yield from self._parse_script(script)
|
|
203
|
-
|
|
204
|
-
def _parse_script(self, script):
|
|
205
|
-
if "name" in script:
|
|
206
|
-
yield ScriptEntryPoint(
|
|
207
|
-
name=script.get("name"),
|
|
208
|
-
module=script.get("module"),
|
|
209
|
-
object=script.get("object", self.const.default_script_object),
|
|
210
|
-
attr=script.get("object", self.const.default_script_attr)
|
|
211
|
-
)
|
|
212
|
-
elif "path" in script:
|
|
213
|
-
module = script.get("module").rstrip(".")
|
|
214
|
-
path = script.get("path")
|
|
215
|
-
if not os.path.isfile(path):
|
|
216
|
-
yield ModuleEntryPoint(
|
|
217
|
-
name=f"module-{self.get_md5(path)}",
|
|
218
|
-
module=module,
|
|
219
|
-
)
|
|
220
|
-
yield from self._iter_module_scripts(
|
|
221
|
-
path=path,
|
|
222
|
-
prefix=f"{module}.",
|
|
223
|
-
object=script.get("object", self.const.default_script_object),
|
|
224
|
-
attr=script.get("object", self.const.default_script_attr),
|
|
225
|
-
)
|
|
226
|
-
|
|
227
|
-
def _iter_module_scripts(self, path, prefix, object, attr, parents=None):
|
|
228
|
-
for module_info in pkgutil.iter_modules([path]):
|
|
229
|
-
if module_info.ispkg:
|
|
230
|
-
spec = module_info.module_finder.find_spec(module_info.name)
|
|
231
|
-
module = module_from_spec(spec)
|
|
232
|
-
spec.loader.exec_module(module)
|
|
233
|
-
items = list(parents or [])
|
|
234
|
-
items.append(getattr(module, self.const.module_command_key, module_info.name))
|
|
235
|
-
yield from self._iter_module_scripts(
|
|
236
|
-
path=os.path.join(path, module_info.name),
|
|
237
|
-
prefix=f"{prefix}{module_info.name}.",
|
|
238
|
-
object=object,
|
|
239
|
-
attr=attr,
|
|
240
|
-
parents=items,
|
|
241
|
-
)
|
|
242
|
-
elif not module_info.name.startswith("_"):
|
|
243
|
-
yield SubScriptEntryPoint(
|
|
244
|
-
name=f"{'-'.join(parents)}-{module_info.name}" if parents else module_info.name,
|
|
245
|
-
module=f"{prefix}{module_info.name}",
|
|
246
|
-
object=object,
|
|
247
|
-
attr=attr,
|
|
248
|
-
)
|
|
249
|
-
|
|
250
|
-
def convert_files(self):
|
|
251
|
-
convert = self.config.get("tool", "linktools", "convert")
|
|
252
|
-
if convert:
|
|
253
|
-
for item in convert:
|
|
254
|
-
type = item.get("type")
|
|
255
|
-
source = item.get("source")
|
|
256
|
-
dest = item.get("dest")
|
|
257
|
-
if type == "jinja2":
|
|
258
|
-
with open(source, "rt", encoding="utf-8") as fd_in:
|
|
259
|
-
Path(dest).parent.mkdir(parents=True, exist_ok=True)
|
|
260
|
-
with open(dest, "wt", encoding="utf-8") as fd_out:
|
|
261
|
-
fd_out.write(Template(fd_in.read()).render(
|
|
262
|
-
metadata=self.dist.metadata,
|
|
263
|
-
**{k: v for k, v in vars(self).items() if k[0] not in "_"},
|
|
264
|
-
))
|
|
265
|
-
elif type == "yml2json":
|
|
266
|
-
with open(source, "rb") as fd_in:
|
|
267
|
-
Path(dest).parent.mkdir(parents=True, exist_ok=True)
|
|
268
|
-
with open(dest, "wt") as fd_out:
|
|
269
|
-
json.dump({
|
|
270
|
-
key: value
|
|
271
|
-
for key, value in yaml.safe_load(fd_in).items()
|
|
272
|
-
if key[0] not in ("$",)
|
|
273
|
-
}, fd_out)
|
|
274
|
-
|
|
275
|
-
def get_md5(self, data):
|
|
276
|
-
if isinstance(data, str):
|
|
277
|
-
data = data.encode()
|
|
278
|
-
m = hashlib.md5()
|
|
279
|
-
m.update(data)
|
|
280
|
-
return m.hexdigest()
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
def finalize_distribution_options(dist: setuptools.Distribution) -> None:
|
|
284
|
-
context = SetupContext(dist)
|
|
285
|
-
context.convert_files()
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
if __name__ == '__main__':
|
|
289
|
-
context = SetupContext(setuptools.Distribution())
|
|
290
|
-
|
|
291
|
-
# scripts = [{
|
|
292
|
-
# "path": os.path.expanduser("~/Projects/linktools/src/linktools/cli/commands"),
|
|
293
|
-
# "module": "linktools.cli.commands",
|
|
294
|
-
# }]
|
|
295
|
-
scripts = {"name": "ct-cntr", "module": "linktools_cntr.__main__"}
|
|
296
|
-
# print([ep.as_script() for ep in context._parse_scripts(scripts)])
|
|
297
|
-
# print([ep.as_script() for ep in context._parse_scripts(scripts) if isinstance(ep, ScriptEntryPoint)])
|
|
298
|
-
print([ep.as_script() for ep in context._parse_scripts(scripts) if not isinstance(ep, SubScriptEntryPoint)])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{linktools_setup-0.0.2 → linktools_setup-0.0.3}/src/linktools_setup.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|