Source code
Revision control
Copy as Markdown
Other Tools
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
"""
Sphinx extension that adds a ``js:autotypedef`` directive, which shows the
properties of one or more JSDoc typedefs as a single table.
"""
import re
from os.path import join, relpath
from docutils import nodes
from docutils.parsers.rst import Directive
from sphinx_js.parsers import PathVisitor, path_and_formal_params
from sphinx_js.suffix_tree import SuffixAmbiguous, SuffixNotFound
HEADERS = ("Property", "Type", "Required", "Description")
COLUMN_WIDTHS = (2, 2, 1, 5)
def lookup_typedef(analyzer, name):
"""Returns the jsdoc doclet for the typedef with the given name."""
path = PathVisitor().visit(path_and_formal_params["path"].parse(name))
doclet = analyzer._doclets_by_path.get(path)
if doclet.get("kind") != "typedef":
raise SuffixNotFound(path)
return doclet
def format_type(prop):
"""Returns the property's type as text, such as ``string[]``."""
names = prop.get("type", {}).get("names", [])
return " | ".join(
re.sub(r"^Array\.<(.+)>$", r"\1[]", n).replace(".<", "<") for n in names
)
def make_row(cells):
"""Returns a table row with one entry per list of inline nodes."""
row = nodes.row()
for cell in cells:
row += nodes.entry("", nodes.paragraph("", "", *cell))
return row
class AutoTypedefDirective(Directive):
required_arguments = 1
optional_arguments = 10
def run(self):
env = self.state.document.settings.env
analyzer = env.app._sphinxjs_analyzer
props = []
for name in self.arguments:
try:
doclet = lookup_typedef(analyzer, name)
except (SuffixNotFound, SuffixAmbiguous):
raise self.error(f"No unique JSDoc typedef named {name!r} was found.")
meta = doclet["meta"]
env.note_dependency(
relpath(join(meta["path"], meta["filename"]), env.app.srcdir)
)
props.extend(doclet.get("properties", []))
tgroup = nodes.tgroup(cols=len(HEADERS))
for width in COLUMN_WIDTHS:
tgroup += nodes.colspec(colwidth=width)
tgroup += nodes.thead("", make_row([[nodes.Text(h)] for h in HEADERS]))
tbody = nodes.tbody()
messages = []
for prop in props:
type_text = format_type(prop)
description = " ".join(prop.get("description", "").split())
description_nodes, description_messages = self.state.inline_text(
description, self.lineno
)
messages.extend(description_messages)
tbody += make_row([
[nodes.literal(prop["name"], prop["name"])],
[nodes.literal(type_text, type_text)],
[nodes.Text("no" if prop.get("optional") else "yes")],
description_nodes,
])
tgroup += tbody
return [nodes.table("", tgroup), *messages]
def setup(app):
app.add_directive_to_domain("js", "autotypedef", AutoTypedefDirective)
return {"parallel_read_safe": True}