-
Notifications
You must be signed in to change notification settings - Fork 1
/
package.py
140 lines (129 loc) · 5.92 KB
/
package.py
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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
import subprocess
import platform
import os
import sys
from typing import Optional, Dict
import click
import packaging.version
@click.group()
def main() -> None:
if not os.getcwd().endswith("Fagus"):
raise EnvironmentError(
f"{sys.argv[0]} must be run from the project directory (Fagus, where this script is placed)"
)
@main.command(help="version number, documentation, package")
@click.option(
"-v",
"--version",
default=None,
help="Bumps version number to the next patch, minor or major release. See poetry version command",
)
@click.option("-b", "--build", is_flag=True, help="Builds the package wheel into dist")
@click.option("-d", "--documentation", is_flag=True, help="Updates the sphinx documentation")
@click.option("-l", "--latex-pdf", is_flag=True, help="Builds documentation pdf using latex.")
@click.option(
"-p",
"--pre-commit",
is_flag=True,
help="Runs pre-commit to make sure everything is formatted correctly",
)
def update(version: str, build: bool, documentation: bool, latex_pdf: bool, pre_commit: bool) -> None:
if version:
new_version = subprocess.run(f"poetry version {version}", shell=True, capture_output=True, text=True)
if new_version.returncode:
print(new_version.stderr, file=sys.stderr)
exit(new_version.returncode)
print(new_version.stdout.strip())
with open("fagus/__init__.py") as init_py:
lines = init_py.read().splitlines() + [""]
for i, l in filter(lambda e: e[1].startswith("__version__"), enumerate(lines)):
lines[i] = f'__version__ = "{new_version.stdout.split()[-1]}"'
break
with open("fagus/__init__.py", "w") as init_py:
init_py.write("\n".join(lines))
if build:
subprocess.run("poetry build", shell=True)
if documentation or latex_pdf:
subprocess.run(
f"sphinx-apidoc -f --module-first --separate -o docs . tests {sys.argv[0]}",
shell=True,
)
original_files = sphinx_hacks("general")
subprocess.run( # type: ignore
"make clean", shell=True, **({} if os.getcwd().endswith("docs") else {"cwd": "docs"})
)
if documentation:
if packaging.version.parse(platform.python_version()) < packaging.version.parse("3.7"):
raise EnvironmentError(
"Sphinx-documentation can't be built on Python < 3.7 (required by the RTD theme)"
)
subprocess.run( # type: ignore
"make html", shell=True, **({} if os.getcwd().endswith("docs") else {"cwd": "docs"})
)
if latex_pdf:
original_files.update(sphinx_hacks("pdf", original_files))
subprocess.run( # type: ignore
"make latexpdf", shell=True, **({} if os.getcwd().endswith("docs") else {"cwd": "docs"})
)
sphinx_hacks(restore=original_files)
if pre_commit:
subprocess.run("git add -A; pre-commit run; git add -A", shell=True)
def sphinx_hacks(hack: str = "", restore: Optional[Dict[str, str]] = None) -> Dict[str, str]:
"""Change some files before building, or restore them if restore has been specified
Args:
hack: general always has to be applied, pdf has to be applied when pdf's are generated
restore: dict with filepath as key and the original content of the file before the hack as value
Returns:
None dict with filepath as key and the original content of the file before the hack as value
"""
files = {}
basepath = ".." if os.getcwd().endswith("docs") else "."
try:
if hack == "general":
for file in (pyfile for pyfile in os.listdir(f"{basepath}/fagus") if pyfile.endswith(".py")):
filepath = f"{basepath}/fagus/{file}"
with open(filepath, "r+") as f:
files[filepath] = f.read()
f.seek(0)
new_c = files[filepath].replace('"""\n', '"""\n\nfrom __future__ import annotations\n', 1)
if file == "fagus.py": # __options must be renamed to options to get the doc right (at runtime, the
new_c = new_c.replace("def __options(", "def options(", 1) # same rename is done in __init__)
f.write(new_c)
f.seek(0)
filepath = f"{basepath}/LICENSE.md"
with open(filepath, "r+") as f:
files[filepath] = f.read()
f.seek(0)
f.write("# " + files[filepath]) # make ISC-License a heading
f.seek(0)
filepath = f"{'.' if os.getcwd().endswith('docs') else 'docs'}/modules.rst"
if os.path.exists(filepath):
os.remove(filepath)
elif hack == "pdf":
filepath = f"{basepath}/README.md"
with open(filepath, "r+") as f:
files[filepath] = f.read()
f.seek(0)
lines = files[filepath].splitlines()
f.write("\n".join(["# README" + (len(lines[0]) - len("README") - 2) * " "] + lines[1:]))
f.seek(0)
filepath = f"{'.' if os.getcwd().endswith('docs') else 'docs'}/index.rst"
with open(filepath, "r+") as f:
files[filepath] = f.read()
pos = files[filepath].index("Indices and tables")
f.seek(pos)
f.write(" " * (len(files[filepath]) - pos))
f.seek(0)
elif restore:
for file, content in restore.items():
with open(file, "w") as f:
f.write(content)
except (FileNotFoundError, ValueError, KeyError) as e:
for file, content in {**files, **(restore if restore else {})}.items():
with open(file, "w") as f:
f.write(content)
print("Restored all changed files before aborting")
raise e
return files
if __name__ == "__main__":
main()