PyDoc: replace in-lined enum references with links where possible

Avoid in-lining large enums such as icons and event types, linking
to them instead.

This mitigates T76453, where long enums took a lot of space in the docs,
this was a problem with `UILayout` where each icon argument would list
all icons. [0] worked around the issue using CSS to scroll the list.
However this has the draw-back where some items are clipped in a way
that's not obvious, see: T87008.

The reason this isn't a complete solution is that Python defined enums
aren't written into their own pages which can be linked to, although
currently there are no large Python enums included in the API docs.
All in-lined enums are now under 20 items.

[0]: 1e8f266591
This commit is contained in:
Campbell Barton 2022-05-31 14:07:14 +10:00
parent 94444aaadf
commit 1c6b66c9cf
Notes: blender-bot 2023-02-14 01:52:41 +01:00
Referenced by commit f60ac5068a, PyDoc: remove CSS override for scrolling long enum lists
2 changed files with 97 additions and 11 deletions

View File

@ -89,6 +89,16 @@ if USE_SHARED_RNA_ENUM_ITEMS_STATIC:
del rna_enum_dict[key]
del key, rna_enum_items_static
# Build enum `{pointer: identifier}` map, so any enum property pointer can
# lookup an identifier using `InfoPropertyRNA.enum_pointer` as the key.
rna_enum_pointer_to_id_map = {
enum_prop.as_pointer(): key
for key, enum_items in rna_enum_dict.items()
# It's possible the first item is a heading (which has no identifier).
# skip these as the `EnumProperty.enum_items` does not expose them.
if (enum_prop := next(iter(enum_prop for enum_prop in enum_items if enum_prop.identifier), None))
}
def handle_args():
"""
@ -1231,15 +1241,23 @@ def pycontext2sphinx(basepath):
# No need to check if there are duplicates yet as it's known there wont be.
unique.add(prop.identifier)
enum_descr_override = None
if USE_SHARED_RNA_ENUM_ITEMS_STATIC:
enum_descr_override = pyrna_enum2sphinx_shared_link(prop)
type_descr = prop.get_type_description(
class_fmt=":class:`bpy.types.%s`", collection_id=_BPY_PROP_COLLECTION_ID)
class_fmt=":class:`bpy.types.%s`",
collection_id=_BPY_PROP_COLLECTION_ID,
enum_descr_override=enum_descr_override,
)
fw(".. data:: %s\n\n" % prop.identifier)
if prop.description:
fw(" %s\n\n" % prop.description)
# Special exception, can't use generic code here for enums.
if prop.type == "enum":
enum_text = pyrna_enum2sphinx(prop)
# If the link has been written, no need to inline the enum items.
enum_text = "" if enum_descr_override else pyrna_enum2sphinx(prop)
if enum_text:
write_indented_lines(" ", fw, enum_text)
fw("\n")
@ -1301,6 +1319,11 @@ def pyrna_enum2sphinx(prop, use_empty_descriptions=False):
Write a bullet point list of enum + descriptions.
"""
# Write a link to the enum if this is part of `rna_enum_pointer_map`.
if USE_SHARED_RNA_ENUM_ITEMS_STATIC:
if (result := pyrna_enum2sphinx_shared_link(prop)) is not None:
return result
if use_empty_descriptions:
ok = True
else:
@ -1379,10 +1402,15 @@ def pyrna2sphinx(basepath):
kwargs["collection_id"] = _BPY_PROP_COLLECTION_ID
enum_descr_override = None
if USE_SHARED_RNA_ENUM_ITEMS_STATIC:
enum_descr_override = pyrna_enum2sphinx_shared_link(prop)
kwargs["enum_descr_override"] = enum_descr_override
type_descr = prop.get_type_description(**kwargs)
enum_text = pyrna_enum2sphinx(prop)
# If the link has been written, no need to inline the enum items.
enum_text = "" if enum_descr_override else pyrna_enum2sphinx(prop)
if prop.name or prop.description or enum_text:
fw(ident + ":%s%s:\n\n" % (id_name, identifier))
@ -1483,7 +1511,15 @@ def pyrna2sphinx(basepath):
if identifier in struct_blacklist:
continue
type_descr = prop.get_type_description(class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID)
enum_descr_override = None
if USE_SHARED_RNA_ENUM_ITEMS_STATIC:
enum_descr_override = pyrna_enum2sphinx_shared_link(prop)
type_descr = prop.get_type_description(
class_fmt=":class:`%s`",
collection_id=_BPY_PROP_COLLECTION_ID,
enum_descr_override=enum_descr_override,
)
# Read-only properties use "data" directive, variables properties use "attribute" directive.
if "readonly" in type_descr:
fw(" .. data:: %s\n" % identifier)
@ -1500,7 +1536,8 @@ def pyrna2sphinx(basepath):
# Special exception, can't use generic code here for enums.
if prop.type == "enum":
enum_text = pyrna_enum2sphinx(prop)
# If the link has been written, no need to inline the enum items.
enum_text = "" if enum_descr_override else pyrna_enum2sphinx(prop)
if enum_text:
write_indented_lines(" ", fw, enum_text)
fw("\n")
@ -1539,8 +1576,16 @@ def pyrna2sphinx(basepath):
for prop in func.return_values:
# TODO: pyrna_enum2sphinx for multiple return values... actually don't
# think we even use this but still!
enum_descr_override = None
if USE_SHARED_RNA_ENUM_ITEMS_STATIC:
enum_descr_override = pyrna_enum2sphinx_shared_link(prop)
type_descr = prop.get_type_description(
as_ret=True, class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID)
as_ret=True, class_fmt=":class:`%s`",
collection_id=_BPY_PROP_COLLECTION_ID,
enum_descr_override=enum_descr_override,
)
descr = prop.description
if not descr:
descr = prop.name
@ -2067,6 +2112,19 @@ def write_rst_data(basepath):
EXAMPLE_SET_USED.add("bpy.data")
def pyrna_enum2sphinx_shared_link(prop):
"""
Return a reference to the enum used by ``prop`` or None when not found.
"""
if (
(prop.type == "enum") and
(pointer := prop.enum_pointer) and
(identifier := rna_enum_pointer_to_id_map.get(pointer))
):
return ":ref:`%s`" % identifier
return None
def write_rst_enum_items(basepath, key, key_no_prefix, enum_items):
"""
Write a single page for a static enum in RST.

View File

@ -242,6 +242,7 @@ class InfoPropertyRNA:
"default_str",
"default",
"enum_items",
"enum_pointer",
"min",
"max",
"array_length",
@ -285,9 +286,17 @@ class InfoPropertyRNA:
else:
self.fixed_type = None
self.enum_pointer = 0
if self.type == "enum":
self.enum_items[:] = [(item.identifier, item.name, item.description) for item in rna_prop.enum_items]
items = tuple(rna_prop.enum_items)
items_static = tuple(rna_prop.enum_items_static)
self.enum_items[:] = [(item.identifier, item.name, item.description) for item in items]
self.is_enum_flag = rna_prop.is_enum_flag
# Prioritize static items as this is never going to be allocated data and is therefor
# will be a stable match to compare against.
item = (items_static or items)
if item:
self.enum_pointer = item[0].as_pointer()
else:
self.is_enum_flag = False
@ -342,7 +351,19 @@ class InfoPropertyRNA:
return "%s=%s" % (self.identifier, default)
return self.identifier
def get_type_description(self, as_ret=False, as_arg=False, class_fmt="%s", collection_id="Collection"):
def get_type_description(
self, *,
as_ret=False,
as_arg=False,
class_fmt="%s",
collection_id="Collection",
enum_descr_override=None,
):
"""
:arg enum_descr_override: Optionally override items for enum.
Otherwise expand the literal items.
:type enum_descr_override: string or None when unset.
"""
type_str = ""
if self.fixed_type is None:
type_str += self.type
@ -357,10 +378,17 @@ class InfoPropertyRNA:
if self.type in {"float", "int"}:
type_str += " in [%s, %s]" % (range_str(self.min), range_str(self.max))
elif self.type == "enum":
enum_descr = enum_descr_override
if not enum_descr:
if self.is_enum_flag:
enum_descr = "{%s}" % ", ".join(("'%s'" % s[0]) for s in self.enum_items)
else:
enum_descr = "[%s]" % ", ".join(("'%s'" % s[0]) for s in self.enum_items)
if self.is_enum_flag:
type_str += " set in {%s}" % ", ".join(("'%s'" % s[0]) for s in self.enum_items)
type_str += " set in %s" % enum_descr
else:
type_str += " in [%s]" % ", ".join(("'%s'" % s[0]) for s in self.enum_items)
type_str += " in %s" % enum_descr
del enum_descr
if not (as_arg or as_ret):
# write default property, ignore function args for this