From 9e71cacd6164b41fa33f4e3367b7d57929de7741 Mon Sep 17 00:00:00 2001 From: Yuri Sizov Date: Wed, 30 Nov 2022 16:34:42 +0300 Subject: Improve layout of generated class references RST pages - General improvements to the page layout - Improved formatting for all class (properties, signals, annotations, theme properties, methods, operators, constructors, enumerations, constants) - Added extra styling opportunities throughout the generated document - Made generated tables more flexible to their content - Changed highlighting for self-reference from code to strong --- doc/tools/make_rst.py | 319 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 236 insertions(+), 83 deletions(-) (limited to 'doc/tools') diff --git a/doc/tools/make_rst.py b/doc/tools/make_rst.py index e5a0bbb008..8960c66acc 100755 --- a/doc/tools/make_rst.py +++ b/doc/tools/make_rst.py @@ -240,7 +240,7 @@ class State: enum_def = class_def.enums[enum] else: - enum_def = EnumDef(enum, is_bitfield) + enum_def = EnumDef(enum, TypeName("int", enum), is_bitfield) class_def.enums[enum] = enum_def enum_def.values[constant_name] = constant_def @@ -458,9 +458,10 @@ class ConstantDef(DefinitionBase): class EnumDef(DefinitionBase): - def __init__(self, name: str, bitfield: bool) -> None: + def __init__(self, name: str, type_name: TypeName, bitfield: bool) -> None: super().__init__("enum", name) + self.type_name = type_name self.values: OrderedDict[str, ConstantDef] = OrderedDict() self.is_bitfield = bitfield @@ -754,7 +755,8 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: f.write(f".. _class_{class_name}:\n\n") f.write(make_heading(class_name, "=", False)) - # Inheritance tree + ### INHERITANCE TREE ### + # Ascendants if class_def.inherits: inherits = class_def.inherits.strip() @@ -788,6 +790,8 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: f.write(make_type(child, state)) f.write("\n\n") + ### INTRODUCTION ### + has_description = False # Brief description @@ -800,7 +804,9 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: if class_def.description is not None and class_def.description.strip() != "": has_description = True + f.write(".. rst-class:: classref-introduction-group\n\n") f.write(make_heading("Description", "-")) + f.write(f"{format_text_block(class_def.description.strip(), class_def, state)}\n\n") if not has_description: @@ -814,14 +820,22 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: # Online tutorials if len(class_def.tutorials) > 0: + f.write(".. rst-class:: classref-introduction-group\n\n") f.write(make_heading("Tutorials", "-")) + for url, title in class_def.tutorials: f.write(f"- {make_link(url, title)}\n\n") - # Properties overview + ### REFERENCE TABLES ### + + # Reused container for reference tables. ml: List[Tuple[Optional[str], ...]] = [] + + # Properties reference table if len(class_def.properties) > 0: + f.write(".. rst-class:: classref-reftable-group\n\n") f.write(make_heading("Properties", "-")) + ml = [] for property_def in class_def.properties.values(): type_rst = property_def.type_name.to_rst(state) @@ -833,76 +847,108 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: else: ref = f":ref:`{property_def.name}`" ml.append((type_rst, ref, default)) + format_table(f, ml, True) - # Constructors, Methods, Operators overview + # Constructors, Methods, Operators reference tables if len(class_def.constructors) > 0: + f.write(".. rst-class:: classref-reftable-group\n\n") f.write(make_heading("Constructors", "-")) + ml = [] for method_list in class_def.constructors.values(): for m in method_list: ml.append(make_method_signature(class_def, m, "constructor", state)) + format_table(f, ml) if len(class_def.methods) > 0: + f.write(".. rst-class:: classref-reftable-group\n\n") f.write(make_heading("Methods", "-")) + ml = [] for method_list in class_def.methods.values(): for m in method_list: ml.append(make_method_signature(class_def, m, "method", state)) + format_table(f, ml) if len(class_def.operators) > 0: + f.write(".. rst-class:: classref-reftable-group\n\n") f.write(make_heading("Operators", "-")) + ml = [] for method_list in class_def.operators.values(): for m in method_list: ml.append(make_method_signature(class_def, m, "operator", state)) + format_table(f, ml) - # Theme properties + # Theme properties reference table if len(class_def.theme_items) > 0: + f.write(".. rst-class:: classref-reftable-group\n\n") f.write(make_heading("Theme Properties", "-")) - pl: List[Tuple[Optional[str], ...]] = [] + + ml = [] for theme_item_def in class_def.theme_items.values(): ref = f":ref:`{theme_item_def.name}`" - pl.append((theme_item_def.type_name.to_rst(state), ref, theme_item_def.default_value)) - format_table(f, pl, True) + ml.append((theme_item_def.type_name.to_rst(state), ref, theme_item_def.default_value)) + + format_table(f, ml, True) - # Signals + ### DETAILED DESCRIPTIONS ### + + # Signal descriptions if len(class_def.signals) > 0: + f.write(make_separator(True)) + f.write(".. rst-class:: classref-descriptions-group\n\n") f.write(make_heading("Signals", "-")) + index = 0 for signal in class_def.signals.values(): if index != 0: - f.write("----\n\n") + f.write(make_separator()) + + # Create signal signature and anchor point. f.write(f".. _class_{class_name}_signal_{signal.name}:\n\n") + f.write(".. rst-class:: classref-signal\n\n") + _, signature = make_method_signature(class_def, signal, "", state) - f.write(f"- {signature}\n\n") + f.write(f"{signature}\n\n") + + # Add signal description, or a call to action if it's missing. if signal.description is not None and signal.description.strip() != "": f.write(f"{format_text_block(signal.description.strip(), signal, state)}\n\n") + else: + f.write(".. container:: contribute\n\n\t") + f.write( + translate( + "There is currently no description for this signal. Please help us by :ref:`contributing one `!" + ) + + "\n\n" + ) index += 1 - # Enums + # Enumeration descriptions if len(class_def.enums) > 0: + f.write(make_separator(True)) + f.write(".. rst-class:: classref-descriptions-group\n\n") f.write(make_heading("Enumerations", "-")) + index = 0 for e in class_def.enums.values(): if index != 0: - f.write("----\n\n") + f.write(make_separator()) + + # Create enumeration signature and anchor point. f.write(f".. _enum_{class_name}_{e.name}:\n\n") - # Sphinx seems to divide the bullet list into individual
    tags if we weave the labels into it. - # As such I'll put them all above the list. Won't be perfect but better than making the list visually broken. - # As to why I'm not modifying the reference parser to directly link to the _enum label: - # If somebody gets annoyed enough to fix it, all existing references will magically improve. - for value in e.values.values(): - f.write(f".. _class_{class_name}_constant_{value.name}:\n\n") + f.write(".. rst-class:: classref-enumeration\n\n") if e.is_bitfield: f.write(f"flags **{e.name}**:\n\n") @@ -910,45 +956,66 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: f.write(f"enum **{e.name}**:\n\n") for value in e.values.values(): - f.write(f"- **{value.name}** = **{value.value}**") + # Also create signature and anchor point for each enum constant. + + f.write(f".. _class_{class_name}_constant_{value.name}:\n\n") + f.write(".. rst-class:: classref-enumeration-constant\n\n") + + f.write(f"{e.type_name.to_rst(state)} **{value.name}** = ``{value.value}``\n\n") + + # Add enum constant description. + if value.text is not None and value.text.strip() != "": - # If value.text contains a bullet point list, each entry needs additional indentation - f.write(f" --- {indent_bullets(format_text_block(value.text.strip(), value, state))}") + f.write(f"{format_text_block(value.text.strip(), value, state)}") f.write("\n\n") index += 1 - # Constants + # Constant descriptions if len(class_def.constants) > 0: + f.write(make_separator(True)) + f.write(".. rst-class:: classref-descriptions-group\n\n") f.write(make_heading("Constants", "-")) - # Sphinx seems to divide the bullet list into individual
      tags if we weave the labels into it. - # As such I'll put them all above the list. Won't be perfect but better than making the list visually broken. + for constant in class_def.constants.values(): + # Create constant signature and anchor point. + f.write(f".. _class_{class_name}_constant_{constant.name}:\n\n") + f.write(".. rst-class:: classref-constant\n\n") + + f.write(f"**{constant.name}** = ``{constant.value}``\n\n") + + # Add enum constant description. - for constant in class_def.constants.values(): - f.write(f"- **{constant.name}** = **{constant.value}**") if constant.text is not None and constant.text.strip() != "": - f.write(f" --- {format_text_block(constant.text.strip(), constant, state)}") + f.write(f"{format_text_block(constant.text.strip(), constant, state)}") f.write("\n\n") - # Annotations + # Annotation descriptions if len(class_def.annotations) > 0: + f.write(make_separator(True)) f.write(make_heading("Annotations", "-")) + index = 0 for method_list in class_def.annotations.values(): # type: ignore for i, m in enumerate(method_list): if index != 0: - f.write("----\n\n") + f.write(make_separator()) + + # Create annotation signature and anchor point. if i == 0: f.write(f".. _class_{class_name}_annotation_{m.name}:\n\n") + f.write(".. rst-class:: classref-annotation\n\n") + _, signature = make_method_signature(class_def, m, "", state) - f.write(f"- {signature}\n\n") + f.write(f"{signature}\n\n") + + # Add annotation description, or a call to action if it's missing. if m.description is not None and m.description.strip() != "": f.write(f"{format_text_block(m.description.strip(), m, state)}\n\n") @@ -965,7 +1032,10 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: # Property descriptions if any(not p.overrides for p in class_def.properties.values()) > 0: + f.write(make_separator(True)) + f.write(".. rst-class:: classref-descriptions-group\n\n") f.write(make_heading("Property Descriptions", "-")) + index = 0 for property_def in class_def.properties.values(): @@ -973,22 +1043,36 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: continue if index != 0: - f.write("----\n\n") + f.write(make_separator()) + + # Create property signature and anchor point. f.write(f".. _class_{class_name}_property_{property_def.name}:\n\n") - f.write(f"- {property_def.type_name.to_rst(state)} **{property_def.name}**\n\n") + f.write(".. rst-class:: classref-property\n\n") - info: List[Tuple[Optional[str], ...]] = [] - # Not using translate() for now as it breaks table formatting. + property_default = "" if property_def.default_value is not None: - info.append(("*Default*", property_def.default_value)) + property_default = f" = {property_def.default_value}" + f.write(f"{property_def.type_name.to_rst(state)} **{property_def.name}**{property_default}\n\n") + + # Create property setter and getter records. + + property_setget = "" + if property_def.setter is not None and not property_def.setter.startswith("_"): - info.append(("*Setter*", f"{property_def.setter}(value)")) + property_setter = make_setter_signature(class_def, property_def, state) + property_setget += f"- {property_setter}\n" + if property_def.getter is not None and not property_def.getter.startswith("_"): - info.append(("*Getter*", f"{property_def.getter}()")) + property_getter = make_getter_signature(class_def, property_def, state) + property_setget += f"- {property_getter}\n" + + if property_setget != "": + f.write(".. rst-class:: classref-property-setget\n\n") + f.write(property_setget) + f.write("\n") - if len(info) > 0: - format_table(f, info) + # Add property description, or a call to action if it's missing. if property_def.text is not None and property_def.text.strip() != "": f.write(f"{format_text_block(property_def.text.strip(), property_def, state)}\n\n") @@ -1005,19 +1089,28 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: # Constructor, Method, Operator descriptions if len(class_def.constructors) > 0: + f.write(make_separator(True)) + f.write(".. rst-class:: classref-descriptions-group\n\n") f.write(make_heading("Constructor Descriptions", "-")) + index = 0 for method_list in class_def.constructors.values(): for i, m in enumerate(method_list): if index != 0: - f.write("----\n\n") + f.write(make_separator()) + + # Create constructor signature and anchor point. if i == 0: f.write(f".. _class_{class_name}_constructor_{m.name}:\n\n") + f.write(".. rst-class:: classref-constructor\n\n") + ret_type, signature = make_method_signature(class_def, m, "", state) - f.write(f"- {ret_type} {signature}\n\n") + f.write(f"{ret_type} {signature}\n\n") + + # Add constructor description, or a call to action if it's missing. if m.description is not None and m.description.strip() != "": f.write(f"{format_text_block(m.description.strip(), m, state)}\n\n") @@ -1033,19 +1126,28 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: index += 1 if len(class_def.methods) > 0: + f.write(make_separator(True)) + f.write(".. rst-class:: classref-descriptions-group\n\n") f.write(make_heading("Method Descriptions", "-")) + index = 0 for method_list in class_def.methods.values(): for i, m in enumerate(method_list): if index != 0: - f.write("----\n\n") + f.write(make_separator()) + + # Create method signature and anchor point. if i == 0: f.write(f".. _class_{class_name}_method_{m.name}:\n\n") + f.write(".. rst-class:: classref-method\n\n") + ret_type, signature = make_method_signature(class_def, m, "", state) - f.write(f"- {ret_type} {signature}\n\n") + f.write(f"{ret_type} {signature}\n\n") + + # Add method description, or a call to action if it's missing. if m.description is not None and m.description.strip() != "": f.write(f"{format_text_block(m.description.strip(), m, state)}\n\n") @@ -1061,20 +1163,31 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: index += 1 if len(class_def.operators) > 0: + f.write(make_separator(True)) + f.write(".. rst-class:: classref-descriptions-group\n\n") f.write(make_heading("Operator Descriptions", "-")) + index = 0 for method_list in class_def.operators.values(): for i, m in enumerate(method_list): if index != 0: - f.write("----\n\n") - out = f".. _class_{class_name}_operator_{sanitize_operator_name(m.name, state)}" + f.write(make_separator()) + + # Create operator signature and anchor point. + + operator_anchor = f".. _class_{class_name}_operator_{sanitize_operator_name(m.name, state)}" for parameter in m.parameters: - out += f"_{parameter.type_name.type_name}" - out += f":\n\n" - f.write(out) + operator_anchor += f"_{parameter.type_name.type_name}" + operator_anchor += f":\n\n" + f.write(operator_anchor) + + f.write(".. rst-class:: classref-operator\n\n") + ret_type, signature = make_method_signature(class_def, m, "", state) - f.write(f"- {ret_type} {signature}\n\n") + f.write(f"{ret_type} {signature}\n\n") + + # Add operator description, or a call to action if it's missing. if m.description is not None and m.description.strip() != "": f.write(f"{format_text_block(m.description.strip(), m, state)}\n\n") @@ -1091,23 +1204,27 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: # Theme property descriptions if len(class_def.theme_items) > 0: + f.write(make_separator(True)) + f.write(".. rst-class:: classref-descriptions-group\n\n") f.write(make_heading("Theme Property Descriptions", "-")) + index = 0 for theme_item_def in class_def.theme_items.values(): if index != 0: - f.write("----\n\n") + f.write(make_separator()) + + # Create theme property signature and anchor point. f.write(f".. _class_{class_name}_theme_{theme_item_def.data_name}_{theme_item_def.name}:\n\n") - f.write(f"- {theme_item_def.type_name.to_rst(state)} **{theme_item_def.name}**\n\n") + f.write(".. rst-class:: classref-themeproperty\n\n") - info = [] + theme_item_default = "" if theme_item_def.default_value is not None: - # Not using translate() for now as it breaks table formatting. - info.append(("*Default*", theme_item_def.default_value)) + theme_item_default = f" = {theme_item_def.default_value}" + f.write(f"{theme_item_def.type_name.to_rst(state)} **{theme_item_def.name}**{theme_item_default}\n\n") - if len(info) > 0: - format_table(f, info) + # Add theme property description, or a call to action if it's missing. if theme_item_def.text is not None and theme_item_def.text.strip() != "": f.write(f"{format_text_block(theme_item_def.text.strip(), theme_item_def, state)}\n\n") @@ -1216,6 +1333,39 @@ def make_method_signature( return ret_type, out +def make_setter_signature(class_def: ClassDef, property_def: PropertyDef, state: State) -> str: + if property_def.setter is None: + return "" + + # If setter is a method available as a method definition, we use that. + if property_def.setter in class_def.methods: + setter = class_def.methods[property_def.setter][0] + # Otherwise we fake it with the information we have available. + else: + setter_params: List[ParameterDef] = [] + setter_params.append(ParameterDef("value", property_def.type_name, None)) + setter = MethodDef(property_def.setter, TypeName("void"), setter_params, None, None) + + ret_type, signature = make_method_signature(class_def, setter, "", state) + return f"{ret_type} {signature}" + + +def make_getter_signature(class_def: ClassDef, property_def: PropertyDef, state: State) -> str: + if property_def.getter is None: + return "" + + # If getter is a method available as a method definition, we use that. + if property_def.getter in class_def.methods: + getter = class_def.methods[property_def.getter][0] + # Otherwise we fake it with the information we have available. + else: + getter_params: List[ParameterDef] = [] + getter = MethodDef(property_def.getter, property_def.type_name, getter_params, None, None) + + ret_type, signature = make_method_signature(class_def, getter, "", state) + return f"{ret_type} {signature}" + + def make_heading(title: str, underline: str, l10n: bool = True) -> str: if l10n: new_title = translate(title) @@ -1247,6 +1397,14 @@ def make_footer() -> str: ) +def make_separator(section_level: bool = False) -> str: + separator_class = "item" + if section_level: + separator_class = "section" + + return f".. rst-class:: classref-{separator_class}-separator\n\n----\n\n" + + def make_link(url: str, title: str) -> str: match = GODOT_DOCS_PATTERN.search(url) if match: @@ -1409,8 +1567,8 @@ def format_text_block( # Tag is a reference to a class. if tag_text in state.classes: if tag_text == state.current_class: - # Don't create a link to the same class, format it as inline code. - tag_text = f"``{tag_text}``" + # Don't create a link to the same class, format it as strong emphasis. + tag_text = f"**{tag_text}**" else: tag_text = make_type(tag_text, state) escape_pre = True @@ -1872,6 +2030,11 @@ def format_table(f: TextIO, data: List[Tuple[Optional[str], ...]], remove_empty_ if len(data) == 0: return + f.write(".. table::\n") + f.write(" :widths: auto\n\n") + + # Calculate the width of each column first, we will use this information + # to properly format RST-style tables. column_sizes = [0] * len(data[0]) for row in data: for i, text in enumerate(row): @@ -1879,14 +2042,21 @@ def format_table(f: TextIO, data: List[Tuple[Optional[str], ...]], remove_empty_ if text_length > column_sizes[i]: column_sizes[i] = text_length + # Each table row is wrapped in two separators, consecutive rows share the same separator. + # All separators, or rather borders, have the same shape and content. We compose it once, + # then reuse it. + sep = "" for size in column_sizes: if size == 0 and remove_empty_columns: continue - sep += "+" + "-" * (size + 2) + sep += "+" + "-" * (size + 2) # Content of each cell is padded by 1 on each side. sep += "+\n" - f.write(sep) + # Draw the first separator. + f.write(f" {sep}") + + # Draw each row and close it with a separator. for row in data: row_text = "|" for i, text in enumerate(row): @@ -1894,8 +2064,10 @@ def format_table(f: TextIO, data: List[Tuple[Optional[str], ...]], remove_empty_ continue row_text += f' {(text or "").ljust(column_sizes[i])} |' row_text += "\n" - f.write(row_text) - f.write(sep) + + f.write(f" {row_text}") + f.write(f" {sep}") + f.write("\n") @@ -1957,24 +2129,5 @@ def sanitize_operator_name(dirty_name: str, state: State) -> str: return clear_name -def indent_bullets(text: str) -> str: - # Take the text and check each line for a bullet point represented by "-". - # Where found, indent the given line by a further "\t". - # Used to properly indent bullet points contained in the description for enum values. - # Ignore the first line - text will be prepended to it so bullet points wouldn't work anyway. - bullet_points = "-" - - lines = text.splitlines(keepends=True) - for line_index, line in enumerate(lines[1:], start=1): - pos = 0 - while pos < len(line) and line[pos] == "\t": - pos += 1 - - if pos < len(line) and line[pos] in bullet_points: - lines[line_index] = f"{line[:pos]}\t{line[pos:]}" - - return "".join(lines) - - if __name__ == "__main__": main() -- cgit v1.2.3