sphinx-java-autodoc 0.2.0__py3-none-any.whl
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.
- sphinx_java_autodoc/__init__.py +239 -0
- sphinx_java_autodoc/__main__.py +67 -0
- sphinx_java_autodoc/domain.py +387 -0
- sphinx_java_autodoc/extractor.py +254 -0
- sphinx_java_autodoc/java/SphinxDoclet.java +1036 -0
- sphinx_java_autodoc/javadoc.py +45 -0
- sphinx_java_autodoc/model.py +173 -0
- sphinx_java_autodoc/render.py +469 -0
- sphinx_java_autodoc/static/java-autodoc.css +28 -0
- sphinx_java_autodoc-0.2.0.dist-info/METADATA +109 -0
- sphinx_java_autodoc-0.2.0.dist-info/RECORD +14 -0
- sphinx_java_autodoc-0.2.0.dist-info/WHEEL +4 -0
- sphinx_java_autodoc-0.2.0.dist-info/entry_points.txt +2 -0
- sphinx_java_autodoc-0.2.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""Java source documentation in Sphinx, without importing application code."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import re
|
|
9
|
+
|
|
10
|
+
from sphinx.errors import ExtensionError
|
|
11
|
+
from sphinx.util import logging
|
|
12
|
+
|
|
13
|
+
from .domain import JavaDomain
|
|
14
|
+
from .extractor import VERSION, atomic_write, extract
|
|
15
|
+
from .javadoc import check_mapping
|
|
16
|
+
from .model import (
|
|
17
|
+
MEMBER_GROUPS,
|
|
18
|
+
TYPE_GROUPS,
|
|
19
|
+
grouped,
|
|
20
|
+
local_name,
|
|
21
|
+
read_model,
|
|
22
|
+
use_model,
|
|
23
|
+
visible,
|
|
24
|
+
)
|
|
25
|
+
from .render import quiet_reference
|
|
26
|
+
|
|
27
|
+
__version__ = VERSION
|
|
28
|
+
LOGGER = logging.getLogger(__name__)
|
|
29
|
+
MARKER = ".. Generated by sphinx-java-autodoc; do not edit.\n\n"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def heading(title: str, underline: str) -> str:
|
|
33
|
+
return f"{title}\n{underline * len(title)}\n\n"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def summary_table(symbols) -> str:
|
|
37
|
+
return ".. java:autosummary::\n\n" + "".join(f" {s}\n" for s in symbols) + "\n"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def generate_pages(root, directory: Path, visibility: str) -> None:
|
|
41
|
+
pages = {}
|
|
42
|
+
entries = []
|
|
43
|
+
for package in root.findall("package"):
|
|
44
|
+
name = package.get("name", "")
|
|
45
|
+
if not re.fullmatch(r"[\w.$]*", name):
|
|
46
|
+
raise ExtensionError(f"Java autodoc: invalid package name {name!r}")
|
|
47
|
+
types = [
|
|
48
|
+
t
|
|
49
|
+
for t in root.findall("type")
|
|
50
|
+
if t.get("package") == name and visible(t, visibility)
|
|
51
|
+
]
|
|
52
|
+
if not types:
|
|
53
|
+
continue
|
|
54
|
+
filename = "package-" + (name or "default")
|
|
55
|
+
entries.append((name, filename))
|
|
56
|
+
title = name or "Default package"
|
|
57
|
+
body = heading(title, "=")
|
|
58
|
+
if name:
|
|
59
|
+
body += f".. java:autopackage:: {name}\n\n"
|
|
60
|
+
for kind, group in TYPE_GROUPS.items():
|
|
61
|
+
symbols = [t.get("id") for t in types if t.get("kind") == kind]
|
|
62
|
+
if symbols:
|
|
63
|
+
body += heading(group, "-") + summary_table(symbols)
|
|
64
|
+
body += ".. toctree::\n :hidden:\n\n"
|
|
65
|
+
for t in types:
|
|
66
|
+
symbol = t.get("id")
|
|
67
|
+
if not re.fullmatch(r"[\w.$]+", symbol):
|
|
68
|
+
raise ExtensionError(f"Java autodoc: invalid type name {symbol!r}")
|
|
69
|
+
typefile = "type-" + symbol
|
|
70
|
+
body += f" {typefile}\n"
|
|
71
|
+
# Titles drop the package, which the sidebar and the signature already show.
|
|
72
|
+
page = heading(local_name(t), "=") + f".. java:autoclass:: {symbol}\n\n"
|
|
73
|
+
members = [m for m in t.findall("member") if visible(m, visibility)]
|
|
74
|
+
for group, found in grouped(members).items():
|
|
75
|
+
symbols = [m.get("id") for m in found]
|
|
76
|
+
directive = "autofield" if group == "field" else "automethod"
|
|
77
|
+
page += heading(MEMBER_GROUPS[group], "-") + summary_table(symbols)
|
|
78
|
+
page += "".join(f".. java:{directive}:: {s}\n\n" for s in symbols)
|
|
79
|
+
pages[typefile + ".rst"] = MARKER + page
|
|
80
|
+
pages[filename + ".rst"] = MARKER + body
|
|
81
|
+
named = [name for name, _ in entries if name]
|
|
82
|
+
pages["index.rst"] = (
|
|
83
|
+
MARKER
|
|
84
|
+
+ heading("Java API", "=")
|
|
85
|
+
+ (summary_table(named) if named else "")
|
|
86
|
+
# The summary cannot list the unnamed package, so then the toctree shows.
|
|
87
|
+
+ (
|
|
88
|
+
".. toctree::\n :hidden:\n\n"
|
|
89
|
+
if len(named) == len(entries)
|
|
90
|
+
else ".. toctree::\n\n"
|
|
91
|
+
)
|
|
92
|
+
+ "".join(f" {filename}\n" for _, filename in entries)
|
|
93
|
+
)
|
|
94
|
+
manifest_path = directory / ".java-autodoc-manifest.json"
|
|
95
|
+
try:
|
|
96
|
+
old = json.loads(manifest_path.read_text()) if manifest_path.exists() else {}
|
|
97
|
+
if not isinstance(old, dict):
|
|
98
|
+
raise ValueError("expected a filename-to-hashes mapping")
|
|
99
|
+
for filename, hashes in old.items():
|
|
100
|
+
if isinstance(hashes, str):
|
|
101
|
+
hashes = old[filename] = [hashes]
|
|
102
|
+
if (
|
|
103
|
+
Path(filename).name != filename
|
|
104
|
+
or not filename.endswith(".rst")
|
|
105
|
+
or not isinstance(hashes, list)
|
|
106
|
+
or not hashes
|
|
107
|
+
or any(
|
|
108
|
+
not isinstance(h, str) or not re.fullmatch(r"[0-9a-f]{64}", h)
|
|
109
|
+
for h in hashes
|
|
110
|
+
)
|
|
111
|
+
):
|
|
112
|
+
raise ValueError("invalid filename or content hash")
|
|
113
|
+
except (OSError, ValueError) as exc:
|
|
114
|
+
raise ExtensionError(
|
|
115
|
+
f"Java autodoc: invalid generated-page manifest: {exc}"
|
|
116
|
+
) from exc
|
|
117
|
+
# Check ownership before recording any new files as ours.
|
|
118
|
+
for filename in old.keys() | pages.keys():
|
|
119
|
+
path = directory / filename
|
|
120
|
+
if path.exists() and hashlib.sha256(
|
|
121
|
+
path.read_bytes()
|
|
122
|
+
).hexdigest() not in old.get(filename, []):
|
|
123
|
+
raise ExtensionError(
|
|
124
|
+
f"Java autodoc: refusing to overwrite edited or unowned page {path}; move your edits into a guide."
|
|
125
|
+
)
|
|
126
|
+
hashes = {
|
|
127
|
+
name: hashlib.sha256(content.encode()).hexdigest()
|
|
128
|
+
for name, content in pages.items()
|
|
129
|
+
}
|
|
130
|
+
pending = {name: set(values) for name, values in old.items()}
|
|
131
|
+
for name, digest in hashes.items():
|
|
132
|
+
pending.setdefault(name, set()).add(digest)
|
|
133
|
+
# Journal both versions before any page changes, so an interrupted build can retry.
|
|
134
|
+
atomic_write(
|
|
135
|
+
manifest_path,
|
|
136
|
+
json.dumps(
|
|
137
|
+
{name: sorted(values) for name, values in sorted(pending.items())}
|
|
138
|
+
).encode(),
|
|
139
|
+
)
|
|
140
|
+
for filename, content in pages.items():
|
|
141
|
+
atomic_write(directory / filename, content.encode())
|
|
142
|
+
for filename in old.keys() - pages.keys():
|
|
143
|
+
(directory / filename).unlink(missing_ok=True)
|
|
144
|
+
atomic_write(manifest_path, json.dumps(hashes, indent=2, sort_keys=True).encode())
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def prepare(app):
|
|
148
|
+
config = app.config
|
|
149
|
+
if config.java_visibility not in {"public", "protected"}:
|
|
150
|
+
raise ExtensionError(
|
|
151
|
+
"java_visibility must be 'public' or 'protected' (public plus protected)."
|
|
152
|
+
)
|
|
153
|
+
check_mapping(config.java_external_javadoc)
|
|
154
|
+
base = Path(app.confdir)
|
|
155
|
+
if config.java_model and config.java_sources:
|
|
156
|
+
raise ExtensionError("Set either java_sources or java_model, not both.")
|
|
157
|
+
if config.java_model:
|
|
158
|
+
path = (base / config.java_model).resolve()
|
|
159
|
+
root = read_model(path)
|
|
160
|
+
fingerprint = hashlib.sha256(path.read_bytes()).hexdigest()
|
|
161
|
+
elif config.java_sources:
|
|
162
|
+
cache = Path(app.doctreedir) / "java-cache"
|
|
163
|
+
path, fingerprint = extract(
|
|
164
|
+
[base / s for s in config.java_sources],
|
|
165
|
+
[base / s for s in config.java_classpath],
|
|
166
|
+
cache,
|
|
167
|
+
jdk=config.java_jdk,
|
|
168
|
+
release=str(config.java_release),
|
|
169
|
+
sourcepath=[base / s for s in config.java_sourcepath],
|
|
170
|
+
)
|
|
171
|
+
root = read_model(path)
|
|
172
|
+
# This cache belongs to one doctree directory, so only the current model is kept.
|
|
173
|
+
for old in cache.glob("*.xml"):
|
|
174
|
+
if old != path:
|
|
175
|
+
old.unlink(missing_ok=True)
|
|
176
|
+
else:
|
|
177
|
+
# A consumer using only Java roles and intersphinx needs no local sources.
|
|
178
|
+
import xml.etree.ElementTree as ET
|
|
179
|
+
|
|
180
|
+
root = ET.Element("api", schema="1")
|
|
181
|
+
path = base / "conf.py"
|
|
182
|
+
fingerprint = "empty"
|
|
183
|
+
app._java_changed = getattr(app.env, "java_fingerprint", None) != fingerprint
|
|
184
|
+
app.env.java_fingerprint = fingerprint
|
|
185
|
+
app.env.java_model_path = str(path)
|
|
186
|
+
use_model(root)
|
|
187
|
+
for diagnostic in root.findall("diagnostic"):
|
|
188
|
+
LOGGER.warning(diagnostic.text, type="java", subtype="extract")
|
|
189
|
+
if config.java_generate:
|
|
190
|
+
directory = (Path(app.srcdir) / config.java_generate).resolve()
|
|
191
|
+
if (
|
|
192
|
+
not directory.is_relative_to(Path(app.srcdir).resolve())
|
|
193
|
+
or directory == Path(app.srcdir).resolve()
|
|
194
|
+
):
|
|
195
|
+
raise ExtensionError(
|
|
196
|
+
"java_generate must be a subdirectory of the Sphinx source directory."
|
|
197
|
+
)
|
|
198
|
+
if not root.findall("type"):
|
|
199
|
+
raise ExtensionError(
|
|
200
|
+
"Java autodoc: cannot generate API pages without extracted types."
|
|
201
|
+
)
|
|
202
|
+
generate_pages(root, directory, config.java_visibility)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def outdated(app, env, added, changed, removed):
|
|
206
|
+
# Roles and missing-type directives also depend on the complete Java model.
|
|
207
|
+
return sorted(env.found_docs - removed) if app._java_changed else []
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def add_static_path(app):
|
|
211
|
+
app.config.html_static_path.append(str(Path(__file__).parent / "static"))
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def setup(app):
|
|
215
|
+
for name, default in {
|
|
216
|
+
"java_sources": [],
|
|
217
|
+
"java_classpath": [],
|
|
218
|
+
"java_sourcepath": [],
|
|
219
|
+
"java_jdk": "",
|
|
220
|
+
"java_release": "",
|
|
221
|
+
"java_model": "",
|
|
222
|
+
"java_generate": "",
|
|
223
|
+
"java_visibility": "protected",
|
|
224
|
+
"java_external_javadoc": {},
|
|
225
|
+
"java_maximum_signature_line_length": None,
|
|
226
|
+
}.items():
|
|
227
|
+
app.add_config_value(name, default, "env")
|
|
228
|
+
app.add_domain(JavaDomain)
|
|
229
|
+
app.add_css_file("java-autodoc.css")
|
|
230
|
+
app.connect("builder-inited", add_static_path)
|
|
231
|
+
app.connect("builder-inited", prepare)
|
|
232
|
+
app.connect("env-get-outdated", outdated)
|
|
233
|
+
app.connect("missing-reference", quiet_reference)
|
|
234
|
+
return {
|
|
235
|
+
"version": VERSION,
|
|
236
|
+
"env_version": 4,
|
|
237
|
+
"parallel_read_safe": True,
|
|
238
|
+
"parallel_write_safe": True,
|
|
239
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Explicit extraction for build systems and JDK-free Sphinx rendering."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from sphinx.errors import ExtensionError
|
|
9
|
+
|
|
10
|
+
from .extractor import atomic_write, extract
|
|
11
|
+
from .model import read_model
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main():
|
|
15
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
16
|
+
parser.add_argument(
|
|
17
|
+
"sources", nargs="+", type=Path, help="Java files or source roots"
|
|
18
|
+
)
|
|
19
|
+
parser.add_argument("-o", "--output", required=True, type=Path)
|
|
20
|
+
parser.add_argument(
|
|
21
|
+
"--classpath", default="", help=f"Compile classpath separated by {os.pathsep!r}"
|
|
22
|
+
)
|
|
23
|
+
parser.add_argument(
|
|
24
|
+
"--classpath-file",
|
|
25
|
+
type=Path,
|
|
26
|
+
help="UTF-8 file containing a platform-separated compile classpath",
|
|
27
|
+
)
|
|
28
|
+
parser.add_argument(
|
|
29
|
+
"--sourcepath",
|
|
30
|
+
action="append",
|
|
31
|
+
default=[],
|
|
32
|
+
type=Path,
|
|
33
|
+
help="Source lookup directory for ancestor docs; repeat for multiple roots",
|
|
34
|
+
)
|
|
35
|
+
parser.add_argument("--jdk", default="", help="JDK 25 home (default: java on PATH)")
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
"--release", default="", help="Source/API release accepted by the installed JDK"
|
|
38
|
+
)
|
|
39
|
+
parser.add_argument("--cache", type=Path, default=Path(".java-autodoc-cache"))
|
|
40
|
+
parser.add_argument("--warnings-as-errors", action="store_true")
|
|
41
|
+
args = parser.parse_args()
|
|
42
|
+
try:
|
|
43
|
+
cp = args.classpath
|
|
44
|
+
if args.classpath_file:
|
|
45
|
+
cp += os.pathsep + args.classpath_file.read_text(encoding="utf-8").strip()
|
|
46
|
+
path, _ = extract(
|
|
47
|
+
args.sources,
|
|
48
|
+
[Path(p) for p in cp.split(os.pathsep) if p],
|
|
49
|
+
args.cache,
|
|
50
|
+
jdk=args.jdk,
|
|
51
|
+
release=args.release,
|
|
52
|
+
sourcepath=args.sourcepath,
|
|
53
|
+
)
|
|
54
|
+
diagnostics = read_model(path).findall("diagnostic")
|
|
55
|
+
for diagnostic in diagnostics:
|
|
56
|
+
print("warning: " + (diagnostic.text or ""), file=sys.stderr)
|
|
57
|
+
if diagnostics and args.warnings_as_errors:
|
|
58
|
+
return 1
|
|
59
|
+
atomic_write(args.output, path.read_bytes())
|
|
60
|
+
except (ExtensionError, OSError) as exc:
|
|
61
|
+
print(str(exc), file=sys.stderr)
|
|
62
|
+
return 1
|
|
63
|
+
return 0
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
sys.exit(main())
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
from docutils import nodes
|
|
6
|
+
from docutils.parsers.rst import directives
|
|
7
|
+
from sphinx import addnodes
|
|
8
|
+
from sphinx.domains import Domain, ObjType
|
|
9
|
+
from sphinx.roles import XRefRole
|
|
10
|
+
from sphinx.util import logging
|
|
11
|
+
from sphinx.util.docutils import SphinxDirective
|
|
12
|
+
from sphinx.util.nodes import make_refnode
|
|
13
|
+
|
|
14
|
+
from .javadoc import external_url
|
|
15
|
+
from .model import (
|
|
16
|
+
MEMBER_GROUPS,
|
|
17
|
+
PACKAGES,
|
|
18
|
+
REFERENCES,
|
|
19
|
+
SYMBOLS,
|
|
20
|
+
grouped,
|
|
21
|
+
local_name,
|
|
22
|
+
member_label,
|
|
23
|
+
visible,
|
|
24
|
+
)
|
|
25
|
+
from .render import deprecated, documentation, summary
|
|
26
|
+
|
|
27
|
+
LOGGER = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def anchor(symbol: str) -> str:
|
|
31
|
+
return "java-" + symbol.encode("utf-8").hex()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def role_for(element):
|
|
35
|
+
return (
|
|
36
|
+
"type"
|
|
37
|
+
if element.tag == "type"
|
|
38
|
+
else "method"
|
|
39
|
+
if element.get("kind") in {"method", "constructor"}
|
|
40
|
+
else "field"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Signature tokens from the model mapped to Sphinx's standard signature nodes.
|
|
45
|
+
SIGNATURE_NODES = {
|
|
46
|
+
"k": addnodes.desc_sig_keyword,
|
|
47
|
+
"kt": addnodes.desc_sig_keyword_type,
|
|
48
|
+
"q": addnodes.desc_addname,
|
|
49
|
+
"n": addnodes.desc_name,
|
|
50
|
+
"tv": addnodes.desc_sig_name,
|
|
51
|
+
"pname": addnodes.desc_sig_name,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def signature_nodes(tokens) -> list[nodes.Node]:
|
|
56
|
+
result: list[nodes.Node] = []
|
|
57
|
+
for token in tokens:
|
|
58
|
+
text = token.text or ""
|
|
59
|
+
if token.tag == "params":
|
|
60
|
+
parameters = addnodes.desc_parameterlist()
|
|
61
|
+
for parameter in token.findall("param"):
|
|
62
|
+
parameters += addnodes.desc_parameter(
|
|
63
|
+
"", "", *signature_nodes(parameter)
|
|
64
|
+
)
|
|
65
|
+
result.append(parameters)
|
|
66
|
+
elif token.tag == "t":
|
|
67
|
+
for part in re.split(r"( +)", text):
|
|
68
|
+
if part:
|
|
69
|
+
result.append(
|
|
70
|
+
addnodes.desc_sig_space()
|
|
71
|
+
if part.isspace()
|
|
72
|
+
else addnodes.desc_sig_punctuation("", part)
|
|
73
|
+
)
|
|
74
|
+
elif token.tag == "v":
|
|
75
|
+
literal = (
|
|
76
|
+
addnodes.desc_sig_literal_string
|
|
77
|
+
if text[:1] in {'"', "'"}
|
|
78
|
+
else addnodes.desc_sig_literal_number
|
|
79
|
+
)
|
|
80
|
+
result.append(literal("", text))
|
|
81
|
+
elif token.tag == "r":
|
|
82
|
+
# Like type annotations in Sphinx's Python domain, unresolved types stay
|
|
83
|
+
# plain text and only warn in nitpicky mode.
|
|
84
|
+
reference = addnodes.pending_xref(
|
|
85
|
+
"",
|
|
86
|
+
nodes.Text(text),
|
|
87
|
+
refdomain="java",
|
|
88
|
+
reftype="type",
|
|
89
|
+
reftarget=token.get("target", ""),
|
|
90
|
+
refexplicit=True,
|
|
91
|
+
refwarn=False,
|
|
92
|
+
)
|
|
93
|
+
reference["java:external"] = dict(token.attrib)
|
|
94
|
+
result.append(reference)
|
|
95
|
+
elif token.tag in SIGNATURE_NODES:
|
|
96
|
+
result.append(SIGNATURE_NODES[token.tag]("", text))
|
|
97
|
+
return result
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def display_name(symbol: str) -> str:
|
|
101
|
+
owner, _, member = symbol.partition("#")
|
|
102
|
+
return f"{owner}.{member_label(symbol)}" if member else symbol
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class JavaDirective(SphinxDirective):
|
|
106
|
+
def visible(self, element) -> bool:
|
|
107
|
+
return visible(
|
|
108
|
+
element, self.options.get("visibility", self.config.java_visibility)
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
def lookup(self, symbol, tags=("type", "member")):
|
|
112
|
+
self.env.note_dependency(self.env.java_model_path)
|
|
113
|
+
element = SYMBOLS.get(symbol)
|
|
114
|
+
return element if element is not None and element.tag in tags else None
|
|
115
|
+
|
|
116
|
+
def missing(self, what, symbol, subtype):
|
|
117
|
+
LOGGER.warning(
|
|
118
|
+
"Java %s %s was not extracted; check java_sources and visibility.",
|
|
119
|
+
what,
|
|
120
|
+
symbol,
|
|
121
|
+
location=self.get_location(),
|
|
122
|
+
type="java",
|
|
123
|
+
subtype=subtype,
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
def describe(self, element):
|
|
127
|
+
symbol = element.get("id")
|
|
128
|
+
role = role_for(element)
|
|
129
|
+
desc = addnodes.desc(
|
|
130
|
+
"", domain="java", objtype=role, desctype=role, classes=["java", role]
|
|
131
|
+
)
|
|
132
|
+
sig = addnodes.desc_signature("", "", fullname=symbol)
|
|
133
|
+
sig.extend(signature_nodes(element.find("sig")))
|
|
134
|
+
limit = (
|
|
135
|
+
self.config.java_maximum_signature_line_length
|
|
136
|
+
or self.config.maximum_signature_line_length
|
|
137
|
+
)
|
|
138
|
+
if limit and len(sig.astext()) > limit:
|
|
139
|
+
for parameters in sig.findall(addnodes.desc_parameterlist):
|
|
140
|
+
parameters["multi_line_parameter_list"] = True
|
|
141
|
+
owner, _, member = symbol.partition("#")
|
|
142
|
+
# Sphinx lists objects in the page's table of contents from these attributes.
|
|
143
|
+
sig["_toc_parts"] = (owner, member) if member else (owner,)
|
|
144
|
+
sig["_toc_name"] = member_label(symbol) if member else owner.rpartition(".")[2]
|
|
145
|
+
result = [desc]
|
|
146
|
+
if "no-index" not in self.options:
|
|
147
|
+
target = anchor(symbol)
|
|
148
|
+
sig["ids"].append(target)
|
|
149
|
+
self.state.document.note_explicit_target(sig)
|
|
150
|
+
domain = self.env.get_domain("java")
|
|
151
|
+
if domain.data["objects"].get(symbol):
|
|
152
|
+
LOGGER.warning(
|
|
153
|
+
"Duplicate Java documentation for %s; use :no-index: on repeated excerpts.",
|
|
154
|
+
symbol,
|
|
155
|
+
location=self.get_location(),
|
|
156
|
+
type="java",
|
|
157
|
+
subtype="duplicate",
|
|
158
|
+
)
|
|
159
|
+
else:
|
|
160
|
+
domain.data["objects"][symbol] = (self.env.docname, target, role)
|
|
161
|
+
where = f" in {owner}" if member else ""
|
|
162
|
+
entry = f"{sig['_toc_name']} (Java {element.get('kind').replace('_', ' ')}{where})"
|
|
163
|
+
result.insert(
|
|
164
|
+
0, addnodes.index(entries=[("single", entry, target, "", None)])
|
|
165
|
+
)
|
|
166
|
+
desc += sig
|
|
167
|
+
desc += addnodes.desc_content("", *documentation(element, self.get_location()))
|
|
168
|
+
return result
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class AutoClass(JavaDirective):
|
|
172
|
+
required_arguments = 1
|
|
173
|
+
option_spec = {
|
|
174
|
+
"members": directives.unchanged,
|
|
175
|
+
"exclude-members": directives.unchanged,
|
|
176
|
+
"visibility": lambda v: directives.choice(v, ("public", "protected")),
|
|
177
|
+
"no-index": directives.flag,
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
def run(self):
|
|
181
|
+
symbol = self.arguments[0].strip()
|
|
182
|
+
element = self.lookup(symbol, ("type",))
|
|
183
|
+
if element is None:
|
|
184
|
+
self.missing("type", symbol, "type")
|
|
185
|
+
return []
|
|
186
|
+
if not self.visible(element):
|
|
187
|
+
return []
|
|
188
|
+
result = self.describe(element)
|
|
189
|
+
if "members" in self.options:
|
|
190
|
+
# Semicolons allow exact signatures containing commas.
|
|
191
|
+
selected = {
|
|
192
|
+
s.strip() for s in self.options["members"].split(";") if s.strip()
|
|
193
|
+
}
|
|
194
|
+
excluded = {
|
|
195
|
+
s.strip()
|
|
196
|
+
for s in self.options.get("exclude-members", "").split(";")
|
|
197
|
+
if s.strip()
|
|
198
|
+
}
|
|
199
|
+
matched = set()
|
|
200
|
+
members = []
|
|
201
|
+
for member in element.findall("member"):
|
|
202
|
+
names = {member.get("name"), member.get("id").split("#", 1)[1]}
|
|
203
|
+
if selected and not names & selected:
|
|
204
|
+
continue
|
|
205
|
+
matched.update(names & selected)
|
|
206
|
+
if names & excluded or not self.visible(member):
|
|
207
|
+
continue
|
|
208
|
+
members.append(member)
|
|
209
|
+
for missing in sorted(selected - matched):
|
|
210
|
+
LOGGER.warning(
|
|
211
|
+
"Java member %s#%s was not found.",
|
|
212
|
+
symbol,
|
|
213
|
+
missing,
|
|
214
|
+
location=self.get_location(),
|
|
215
|
+
type="java",
|
|
216
|
+
subtype="member",
|
|
217
|
+
)
|
|
218
|
+
content = result[-1][-1]
|
|
219
|
+
for group, found in grouped(members).items():
|
|
220
|
+
content += nodes.rubric("", MEMBER_GROUPS[group])
|
|
221
|
+
for member in found:
|
|
222
|
+
content.extend(self.describe(member))
|
|
223
|
+
return result
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class AutoMember(JavaDirective):
|
|
227
|
+
"""One method, constructor or field, like autodoc's automethod and autoattribute."""
|
|
228
|
+
|
|
229
|
+
required_arguments = 1
|
|
230
|
+
option_spec = {"no-index": directives.flag}
|
|
231
|
+
|
|
232
|
+
def run(self):
|
|
233
|
+
symbol = re.sub(r"\s+", "", self.arguments[0])
|
|
234
|
+
element = self.lookup(symbol.replace("...", "[]"), ("member",))
|
|
235
|
+
if element is None:
|
|
236
|
+
self.missing("member", symbol, "member")
|
|
237
|
+
return []
|
|
238
|
+
return self.describe(element)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class AutoPackage(JavaDirective):
|
|
242
|
+
required_arguments = 1
|
|
243
|
+
|
|
244
|
+
def run(self):
|
|
245
|
+
package = self.arguments[0].strip()
|
|
246
|
+
self.env.note_dependency(self.env.java_model_path)
|
|
247
|
+
element = PACKAGES.get(package)
|
|
248
|
+
if element is None:
|
|
249
|
+
self.missing("package", package, "package")
|
|
250
|
+
return []
|
|
251
|
+
domain = self.env.get_domain("java")
|
|
252
|
+
target = nodes.target("", "", ids=[anchor(package)])
|
|
253
|
+
self.state.document.note_explicit_target(target)
|
|
254
|
+
domain.data["objects"][package] = (self.env.docname, anchor(package), "package")
|
|
255
|
+
index = addnodes.index(
|
|
256
|
+
entries=[("single", f"{package} (Java package)", anchor(package), "", None)]
|
|
257
|
+
)
|
|
258
|
+
return [index, target, *documentation(element, self.get_location())]
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class AutoSummary(JavaDirective):
|
|
262
|
+
"""A table of linked names and first sentences, like sphinx.ext.autosummary."""
|
|
263
|
+
|
|
264
|
+
has_content = True
|
|
265
|
+
|
|
266
|
+
def run(self):
|
|
267
|
+
self.env.note_dependency(self.env.java_model_path)
|
|
268
|
+
table = nodes.table("", classes=["autosummary", "longtable", "java"])
|
|
269
|
+
group = nodes.tgroup("", cols=2)
|
|
270
|
+
group += nodes.colspec("", colwidth=10)
|
|
271
|
+
group += nodes.colspec("", colwidth=90)
|
|
272
|
+
body = nodes.tbody()
|
|
273
|
+
for line in self.content:
|
|
274
|
+
symbol = re.sub(r"\s+", "", line)
|
|
275
|
+
if not symbol:
|
|
276
|
+
continue
|
|
277
|
+
element = self.lookup(symbol)
|
|
278
|
+
if element is not None:
|
|
279
|
+
label = local_name(element)
|
|
280
|
+
elif symbol in PACKAGES:
|
|
281
|
+
element, label = PACKAGES[symbol], symbol
|
|
282
|
+
else:
|
|
283
|
+
self.missing("object", symbol, "summary")
|
|
284
|
+
continue
|
|
285
|
+
link = addnodes.pending_xref(
|
|
286
|
+
"",
|
|
287
|
+
nodes.literal("", label, classes=["xref", "java"]),
|
|
288
|
+
refdomain="java",
|
|
289
|
+
reftype="obj",
|
|
290
|
+
reftarget=symbol,
|
|
291
|
+
refexplicit=True,
|
|
292
|
+
refwarn=True,
|
|
293
|
+
)
|
|
294
|
+
text = nodes.paragraph("")
|
|
295
|
+
if deprecated(element):
|
|
296
|
+
text += [nodes.strong("", "Deprecated."), nodes.Text(" ")]
|
|
297
|
+
text.extend(summary(element, self.get_location()))
|
|
298
|
+
body += nodes.row(
|
|
299
|
+
"",
|
|
300
|
+
nodes.entry("", nodes.paragraph("", "", link)),
|
|
301
|
+
nodes.entry("", text),
|
|
302
|
+
)
|
|
303
|
+
if not body.children:
|
|
304
|
+
return []
|
|
305
|
+
group += body
|
|
306
|
+
table += group
|
|
307
|
+
return [table]
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
class JavaDomain(Domain):
|
|
311
|
+
name = "java"
|
|
312
|
+
label = "Java"
|
|
313
|
+
object_types = {
|
|
314
|
+
name: ObjType(name, name, "obj")
|
|
315
|
+
for name in ("type", "method", "field", "package")
|
|
316
|
+
}
|
|
317
|
+
roles = {
|
|
318
|
+
name: XRefRole(warn_dangling=True)
|
|
319
|
+
for name in ("type", "method", "field", "package", "obj")
|
|
320
|
+
}
|
|
321
|
+
directives = {
|
|
322
|
+
"autoclass": AutoClass,
|
|
323
|
+
"automethod": AutoMember,
|
|
324
|
+
"autofield": AutoMember,
|
|
325
|
+
"autopackage": AutoPackage,
|
|
326
|
+
"autosummary": AutoSummary,
|
|
327
|
+
}
|
|
328
|
+
initial_data = {"objects": {}}
|
|
329
|
+
|
|
330
|
+
def clear_doc(self, docname):
|
|
331
|
+
self.data["objects"] = {
|
|
332
|
+
k: v for k, v in self.data["objects"].items() if v[0] != docname
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
def merge_domaindata(self, docnames, otherdata):
|
|
336
|
+
for symbol, value in otherdata["objects"].items():
|
|
337
|
+
if value[0] in docnames:
|
|
338
|
+
if (
|
|
339
|
+
symbol in self.data["objects"]
|
|
340
|
+
and self.data["objects"][symbol] != value
|
|
341
|
+
):
|
|
342
|
+
LOGGER.warning(
|
|
343
|
+
"Duplicate Java documentation for %s.",
|
|
344
|
+
symbol,
|
|
345
|
+
type="java",
|
|
346
|
+
subtype="duplicate",
|
|
347
|
+
)
|
|
348
|
+
self.data["objects"][symbol] = value
|
|
349
|
+
|
|
350
|
+
def get_objects(self):
|
|
351
|
+
for symbol, (docname, target, kind) in sorted(self.data["objects"].items()):
|
|
352
|
+
# Sphinx's search treats the text after the last dot as the object's name,
|
|
353
|
+
# so members display as Type.member(SimpleTypes); IDs stay canonical.
|
|
354
|
+
yield symbol, display_name(symbol), kind, docname, target, 0
|
|
355
|
+
|
|
356
|
+
def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode):
|
|
357
|
+
spelling = re.sub(r"\s+", "", target)
|
|
358
|
+
target = spelling.replace("...", "[]")
|
|
359
|
+
objects = self.data["objects"]
|
|
360
|
+
# Only member names can omit their parameter list.
|
|
361
|
+
if target not in objects and "#" in target and "(" not in target:
|
|
362
|
+
matches = [key for key in objects if key.startswith(target + "(")]
|
|
363
|
+
if len(matches) == 1:
|
|
364
|
+
target = matches[0]
|
|
365
|
+
elif len(matches) > 1:
|
|
366
|
+
LOGGER.warning(
|
|
367
|
+
"Ambiguous Java reference %s; include fully qualified parameter types.",
|
|
368
|
+
target,
|
|
369
|
+
location=node,
|
|
370
|
+
type="java",
|
|
371
|
+
subtype="ambiguous",
|
|
372
|
+
)
|
|
373
|
+
return None
|
|
374
|
+
if target in objects:
|
|
375
|
+
docname, ident, kind = objects[target]
|
|
376
|
+
if typ not in {"obj", kind}:
|
|
377
|
+
return None
|
|
378
|
+
return make_refnode(builder, fromdocname, docname, ident, contnode, target)
|
|
379
|
+
metadata = node.get("java:external") or REFERENCES.get(target, {})
|
|
380
|
+
url = external_url(target, spelling, metadata, env.config.java_external_javadoc)
|
|
381
|
+
return nodes.reference("", "", contnode, refuri=url) if url else None
|
|
382
|
+
|
|
383
|
+
def resolve_any_xref(self, env, fromdocname, builder, target, node, contnode):
|
|
384
|
+
resolved = self.resolve_xref(
|
|
385
|
+
env, fromdocname, builder, "obj", target, node, contnode
|
|
386
|
+
)
|
|
387
|
+
return [("java:obj", resolved)] if resolved else []
|