-
Notifications
You must be signed in to change notification settings - Fork 62
Expand file tree
/
Copy pathbuild_apidocs.py
More file actions
176 lines (131 loc) · 5.56 KB
/
build_apidocs.py
File metadata and controls
176 lines (131 loc) · 5.56 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
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
"""
Generate the API docs using pydoctor to be integrated into Sphinx build system.
This was designed to generate pydoctor HTML files as part of the
Read The Docs build process.
Inside the Sphinx conf.py file you need to define the following configuration options:
- C{pydoctor_url_path} - defined the URL path to the API documentation
You can use C{{rtd_version}} to have the URL automatically updated
based on Read The Docs build.
- (private usage) a mapping with values URL path definition.
Make sure each definition will produce a unique URL.
- C{pydoctor_args} - Sequence with all the pydoctor command line arguments used to trigger the build.
- (private usage) a mapping with values as sequence of pydoctor command line arguments.
The following format placeholders are resolved for C{pydoctor_args} at runtime:
- C{{outdir}} - the Sphinx output dir
You must call pydoctor with C{--quiet} argument
as otherwise any extra output is converted into Sphinx warnings.
"""
from __future__ import annotations
import os
import pathlib
import shutil
from contextlib import redirect_stdout
from io import StringIO
from typing import Any, Sequence, Mapping
from sphinx.application import Sphinx
from sphinx.errors import ConfigError
from sphinx.util import logging
from pydoctor import __version__
from pydoctor.driver import main
from pydoctor.options import parse_args
logger = logging.getLogger(__name__)
def on_build_finished(app: Sphinx, exception: Exception) -> None:
"""
Called when Sphinx build is done.
"""
if not app.builder or app.builder.name != 'html':
return
runs = app.config.pydoctor_args
placeholders = {
'outdir': str(app.outdir),
}
if not isinstance(runs, Mapping):
# We have a single pydoctor call
runs = {'main': runs}
for key, value in runs.items():
arguments = _get_arguments(value, placeholders)
options = parse_args(arguments)
output_path = pathlib.Path(options.htmloutput)
sphinx_files = output_path.with_suffix('.sphinx_files')
temp_path = output_path.with_suffix('.pydoctor_temp')
shutil.rmtree(sphinx_files, ignore_errors=True)
if output_path.exists():
output_path.rename(sphinx_files)
temp_path.rename(output_path)
def on_builder_inited(app: Sphinx) -> None:
"""
Called to build the API documentation HTML files
and inject our own intersphinx inventory object.
"""
if not app.builder or app.builder.name != 'html':
return
rtd_version = 'latest'
if os.environ.get('READTHEDOCS', '') == 'True':
rtd_version = os.environ.get('READTHEDOCS_VERSION', 'latest')
config = app.config
if not config.pydoctor_args:
raise ConfigError("Missing 'pydoctor_args'.")
placeholders = {
'outdir': str(app.outdir),
}
runs = config.pydoctor_args
if not isinstance(runs, Mapping):
# We have a single pydoctor call
runs = {'main': runs}
pydoctor_url_path = config.pydoctor_url_path
if not isinstance(pydoctor_url_path, Mapping):
pydoctor_url_path = {'main': pydoctor_url_path}
for key, value in runs.items():
arguments = _get_arguments(value, placeholders)
options = parse_args(arguments)
output_path = pathlib.Path(options.htmloutput)
temp_path = output_path.with_suffix('.pydoctor_temp')
# Update intersphinx_mapping.
url_path = pydoctor_url_path.get(key)
if url_path:
intersphinx_mapping = config.intersphinx_mapping
url = url_path.format(**{'rtd_version': rtd_version})
inv = (str(temp_path / 'objects.inv'),)
intersphinx_mapping[f'{key}-api-docs'] = (None, (url, inv))
# Build the API docs in temporary path.
shutil.rmtree(temp_path, ignore_errors=True)
_run_pydoctor(key, arguments)
output_path.rename(temp_path)
def _run_pydoctor(name: str, arguments: Sequence[str]) -> None:
"""
Call pydoctor with arguments.
@param name: A human-readable description of this pydoctor build.
@param arguments: Command line arguments used to call pydoctor.
"""
logger.info(f"Building '{name}' pydoctor API docs as:")
logger.info('\n'.join(arguments))
with StringIO() as stream:
with redirect_stdout(stream):
main(args=arguments)
for line in stream.getvalue().splitlines():
logger.warning(line)
def _get_arguments(arguments: Sequence[str], placeholders: Mapping[str, str]) -> Sequence[str]:
"""
Return the resolved arguments for pydoctor build.
@param arguments: Sequence of proto arguments used to call pydoctor.
@return: Sequence with actual acguments use to call pydoctor.
"""
args = ['--make-html', '--quiet']
for argument in arguments:
args.append(argument.format(**placeholders))
return args
def setup(app: Sphinx) -> Mapping[str, Any]:
"""
Called by Sphinx when the extension is initialized.
@return: The extension version and runtime options.
"""
app.add_config_value("pydoctor_args", None, "env")
app.add_config_value("pydoctor_url_path", None, "env")
# Make sure we have a lower priority than intersphinx extension.
app.connect('builder-inited', on_builder_inited, priority=490)
app.connect('build-finished', on_build_finished)
return {
'version': __version__,
'parallel_read_safe': True,
'parallel_write_safe': True,
}