forked from cctbx/dxtbx
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhatch_build.py
More file actions
98 lines (84 loc) · 3.41 KB
/
hatch_build.py
File metadata and controls
98 lines (84 loc) · 3.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
"""
Dynamically generate the list of console_scripts dxtbx.format entry-points.
"""
from __future__ import annotations
import ast
import re
from pathlib import Path
from hatchling.metadata.plugin.interface import MetadataHookInterface
def get_entry_point(
filename: Path, prefix: str, import_path: str
) -> list[tuple[str, str]]:
"""Returns any entry point strings for a given path.
This looks for LIBTBX_SET_DISPATCHER_NAME, and a root function
named 'run'. It can return multiple results for each file, if more
than one dispatcher name is bound.
Args:
filename:
The python file to parse. Will look for a run() function
and any number of LIBTBX_SET_DISPATCHER_NAME.
prefix: The prefix to output the entry point console script with
import_path: The import path to get to the package the file is in
Returns:
A list of entry_point specifications
"""
contents = filename.read_text()
tree = ast.parse(contents)
# Find root functions named "run"
has_run = any(
x
for x in tree.body
if (isinstance(x, ast.FunctionDef) and x.name == "run")
or (isinstance(x, ast.ImportFrom) and "run" in [a.name for a in x.names])
)
if not has_run:
return []
# Find if we need an alternate name via LIBTBX_SET_DISPATCHER_NAME
alternate_names = re.findall(
r"^#\s*LIBTBX_SET_DISPATCHER_NAME\s+(.*)$", contents, re.M
)
if alternate_names:
return [
(name, f"{import_path}.{filename.stem}:run") for name in alternate_names
]
return [(f"{prefix}.{filename.stem}", f"{import_path}.{filename.stem}:run")]
def enumerate_format_classes(path: Path) -> list[tuple[str, str]]:
"""Find all Format*.py files and contained Format classes in a path"""
format_classes = []
for filename in path.glob("Format*.py"):
content = filename.read_bytes()
try:
parsetree = ast.parse(content)
except SyntaxError:
print(f" *** Could not parse {filename.name}")
continue
for top_level_def in parsetree.body:
if not isinstance(top_level_def, ast.ClassDef):
continue
base_names = [
baseclass.id
for baseclass in top_level_def.bases
if isinstance(baseclass, ast.Name) and baseclass.id.startswith("Format")
]
if base_names:
classname = top_level_def.name
format_classes.append(
(
f"{classname}:{','.join(base_names)}",
f"dxtbx.format.{filename.stem}:{classname}",
)
)
return format_classes
class CustomMetadataHook(MetadataHookInterface):
def update(self, metadata):
scripts = metadata.setdefault("scripts", {})
package_path = Path(self.root) / "src" / "dxtbx"
for file in package_path.joinpath("command_line").glob("*.py"):
for name, symbol in get_entry_point(file, "dxtbx", "dxtbx.command_line"):
if name not in scripts:
scripts[name] = symbol
plugins = metadata.setdefault("entry-points", {})
formats = plugins.setdefault("dxtbx.format", {})
for name, symbol in sorted(enumerate_format_classes(package_path / "format")):
if name not in formats:
formats[name] = symbol