diff options
Diffstat (limited to 'doc/tools/make_rst.py')
-rwxr-xr-x | doc/tools/make_rst.py | 213 |
1 files changed, 154 insertions, 59 deletions
diff --git a/doc/tools/make_rst.py b/doc/tools/make_rst.py index c3a21f3d7b..4435d52527 100755 --- a/doc/tools/make_rst.py +++ b/doc/tools/make_rst.py @@ -68,6 +68,8 @@ BASE_STRINGS = [ "This value is an integer composed as a bitmask of the following flags.", "There is currently no description for this class. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!", "There is currently no description for this signal. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!", + "There is currently no description for this enum. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!", + "There is currently no description for this constant. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!", "There is currently no description for this annotation. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!", "There is currently no description for this property. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!", "There is currently no description for this constructor. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!", @@ -105,6 +107,7 @@ EDITOR_CLASSES: List[str] = [ CLASSES_WITH_CSHARP_DIFFERENCES: List[str] = [ "@GlobalScope", "String", + "StringName", "NodePath", "Signal", "Callable", @@ -140,6 +143,9 @@ class State: self.classes: OrderedDict[str, ClassDef] = OrderedDict() self.current_class: str = "" + # Additional content and structure checks and validators. + self.script_language_parity_check: ScriptLanguageParityCheck = ScriptLanguageParityCheck() + def parse_class(self, class_root: ET.Element, filepath: str) -> None: class_name = class_root.attrib["name"] self.current_class = class_name @@ -542,6 +548,9 @@ class ClassDef(DefinitionBase): def __init__(self, name: str) -> None: super().__init__("class", name) + self.class_group = "variant" + self.editor_class = self._is_editor_class() + self.constants: OrderedDict[str, ConstantDef] = OrderedDict() self.enums: OrderedDict[str, EnumDef] = OrderedDict() self.properties: OrderedDict[str, PropertyDef] = OrderedDict() @@ -559,6 +568,65 @@ class ClassDef(DefinitionBase): # Used to match the class with XML source for output filtering purposes. self.filepath: str = "" + def _is_editor_class(self) -> bool: + if self.name.startswith("Editor"): + return True + if self.name in EDITOR_CLASSES: + return True + + return False + + def update_class_group(self, state: State) -> None: + group_name = "variant" + + if self.name.startswith("@"): + group_name = "global" + elif self.inherits: + inherits = self.inherits.strip() + + while inherits in state.classes: + if inherits == "Node": + group_name = "node" + break + if inherits == "Resource": + group_name = "resource" + break + if inherits == "Object": + group_name = "object" + break + + inode = state.classes[inherits].inherits + if inode: + inherits = inode.strip() + else: + break + + self.class_group = group_name + + +# Checks if code samples have both GDScript and C# variations. +# For simplicity we assume that a GDScript example is always present, and ignore contexts +# which don't necessarily need C# examples. +class ScriptLanguageParityCheck: + def __init__(self) -> None: + self.hit_map: OrderedDict[str, List[Tuple[DefinitionBase, str]]] = OrderedDict() + self.hit_count = 0 + + def add_hit(self, class_name: str, context: DefinitionBase, error: str, state: State) -> None: + if class_name in ["@GDScript", "@GlobalScope"]: + return # We don't expect these contexts to have parity. + + class_def = state.classes[class_name] + if class_def.class_group == "variant" and class_def.name != "Object": + return # Variant types are replaced with native types in C#, we don't expect parity. + + self.hit_count += 1 + + if class_name not in self.hit_map: + self.hit_map[class_name] = [] + + self.hit_map[class_name].append((context, error)) + # Entry point for the RST generator. def main() -> None: @@ -589,6 +657,11 @@ def main() -> None: action="store_true", help="If passed, no output will be generated and XML files are only checked for errors.", ) + parser.add_argument( + "--verbose", + action="store_true", + help="If passed, enables verbose printing.", + ) args = parser.parse_args() should_color = args.color or (hasattr(sys.stdout, "isatty") and sys.stdout.isatty()) @@ -683,15 +756,15 @@ def main() -> None: if args.filter and not pattern.search(class_def.filepath): continue state.current_class = class_name - make_rst_class(class_def, state, args.dry_run, args.output) - group_name = get_class_group(class_def, state) + class_def.update_class_group(state) + make_rst_class(class_def, state, args.dry_run, args.output) - if group_name not in grouped_classes: - grouped_classes[group_name] = [] - grouped_classes[group_name].append(class_name) + if class_def.class_group not in grouped_classes: + grouped_classes[class_def.class_group] = [] + grouped_classes[class_def.class_group].append(class_name) - if is_editor_class(class_def): + if class_def.editor_class: if "editor" not in grouped_classes: grouped_classes["editor"] = [] grouped_classes["editor"].append(class_name) @@ -703,6 +776,26 @@ def main() -> None: print("") + # Print out checks. + + if state.script_language_parity_check.hit_count > 0: + if not args.verbose: + print( + f'{STYLES["yellow"]}{state.script_language_parity_check.hit_count} code samples failed parity check. Use --verbose to get more information.{STYLES["reset"]}' + ) + else: + print( + f'{STYLES["yellow"]}{state.script_language_parity_check.hit_count} code samples failed parity check:{STYLES["reset"]}' + ) + + for class_name in state.script_language_parity_check.hit_map.keys(): + class_hits = state.script_language_parity_check.hit_map[class_name] + print(f'{STYLES["yellow"]}- {len(class_hits)} hits in class "{class_name}"{STYLES["reset"]}') + + for context, error in class_hits: + print(f" - {error} in {format_context_name(context)}") + print("") + # Print out warnings and errors, or lack thereof, and exit with an appropriate code. if state.num_warnings >= 2: @@ -759,46 +852,6 @@ def get_git_branch() -> str: return "master" -def get_class_group(class_def: ClassDef, state: State) -> str: - group_name = "variant" - class_name = class_def.name - - if class_name.startswith("@"): - group_name = "global" - elif class_def.inherits: - inherits = class_def.inherits.strip() - - while inherits in state.classes: - if inherits == "Node": - group_name = "node" - break - if inherits == "Resource": - group_name = "resource" - break - if inherits == "Object": - group_name = "object" - break - - inode = state.classes[inherits].inherits - if inode: - inherits = inode.strip() - else: - break - - return group_name - - -def is_editor_class(class_def: ClassDef) -> bool: - class_name = class_def.name - - if class_name.startswith("Editor"): - return True - if class_name in EDITOR_CLASSES: - return True - - return False - - # Generator methods. @@ -1051,6 +1104,14 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: if value.text is not None and value.text.strip() != "": f.write(f"{format_text_block(value.text.strip(), value, state)}") + else: + f.write(".. container:: contribute\n\n\t") + f.write( + translate( + "There is currently no description for this enum. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!" + ) + + "\n\n" + ) f.write("\n\n") @@ -1074,6 +1135,14 @@ def make_rst_class(class_def: ClassDef, state: State, dry_run: bool, output_dir: if constant.text is not None and constant.text.strip() != "": f.write(f"{format_text_block(constant.text.strip(), constant, state)}") + else: + f.write(".. container:: contribute\n\n\t") + f.write( + translate( + "There is currently no description for this constant. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`!" + ) + + "\n\n" + ) f.write("\n\n") @@ -1641,7 +1710,7 @@ def parse_link_target(link_target: str, state: State, context_name: str) -> List def format_text_block( text: str, - context: Union[DefinitionBase, None], + context: DefinitionBase, state: State, ) -> str: # Linebreak + tabs in the XML should become two line breaks unless in a "codeblock" @@ -1690,6 +1759,10 @@ def format_text_block( inside_code_tag = "" inside_code_tabs = False ignore_code_warnings = False + code_warning_if_intended_string = "If this is intended, use [code skip-lint]...[/code]." + + has_codeblocks_gdscript = False + has_codeblocks_csharp = False pos = 0 tag_depth = 0 @@ -1748,7 +1821,7 @@ def format_text_block( else: if not ignore_code_warnings and tag_state.closing: print_warning( - f'{state.current_class}.xml: Found a code string that looks like a closing tag "[{tag_state.raw}]" in {context_name}.', + f'{state.current_class}.xml: Found a code string that looks like a closing tag "[{tag_state.raw}]" in {context_name}. {code_warning_if_intended_string}', state, ) @@ -1758,6 +1831,17 @@ def format_text_block( elif tag_state.name == "codeblocks": if tag_state.closing: + if not has_codeblocks_gdscript or not has_codeblocks_csharp: + state.script_language_parity_check.add_hit( + state.current_class, + context, + "Only one script language sample found in [codeblocks]", + state, + ) + + has_codeblocks_gdscript = False + has_codeblocks_csharp = False + tag_depth -= 1 tag_text = "" inside_code_tabs = False @@ -1775,6 +1859,8 @@ def format_text_block( f"{state.current_class}.xml: GDScript code block is used outside of [codeblocks] in {context_name}.", state, ) + else: + has_codeblocks_gdscript = True tag_text = "\n .. code-tab:: gdscript\n" elif tag_state.name == "csharp": if not inside_code_tabs: @@ -1782,8 +1868,17 @@ def format_text_block( f"{state.current_class}.xml: C# code block is used outside of [codeblocks] in {context_name}.", state, ) + else: + has_codeblocks_csharp = True tag_text = "\n .. code-tab:: csharp\n" else: + state.script_language_parity_check.add_hit( + state.current_class, + context, + "Code sample is formatted with [codeblock] where [codeblocks] should be used", + state, + ) + tag_text = "\n::\n" inside_code = True @@ -1815,7 +1910,7 @@ def format_text_block( if inside_code_text in state.classes: print_warning( - f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches one of the known classes in {context_name}.', + f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches one of the known classes in {context_name}. {code_warning_if_intended_string}', state, ) @@ -1825,49 +1920,49 @@ def format_text_block( if target_name in class_def.methods: print_warning( - f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} method in {context_name}.', + f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} method in {context_name}. {code_warning_if_intended_string}', state, ) elif target_name in class_def.constructors: print_warning( - f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} constructor in {context_name}.', + f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} constructor in {context_name}. {code_warning_if_intended_string}', state, ) elif target_name in class_def.operators: print_warning( - f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} operator in {context_name}.', + f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} operator in {context_name}. {code_warning_if_intended_string}', state, ) elif target_name in class_def.properties: print_warning( - f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} member in {context_name}.', + f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} member in {context_name}. {code_warning_if_intended_string}', state, ) elif target_name in class_def.signals: print_warning( - f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} signal in {context_name}.', + f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} signal in {context_name}. {code_warning_if_intended_string}', state, ) elif target_name in class_def.annotations: print_warning( - f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} annotation in {context_name}.', + f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} annotation in {context_name}. {code_warning_if_intended_string}', state, ) elif target_name in class_def.theme_items: print_warning( - f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} theme item in {context_name}.', + f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} theme item in {context_name}. {code_warning_if_intended_string}', state, ) elif target_name in class_def.constants: print_warning( - f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} constant in {context_name}.', + f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} constant in {context_name}. {code_warning_if_intended_string}', state, ) @@ -1875,7 +1970,7 @@ def format_text_block( for enum in class_def.enums.values(): if target_name in enum.values: print_warning( - f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} enum value in {context_name}.', + f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches the {target_class_name}.{target_name} enum value in {context_name}. {code_warning_if_intended_string}', state, ) break @@ -1886,7 +1981,7 @@ def format_text_block( for param_def in context_params: if param_def.name == inside_code_text: print_warning( - f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches one of the parameters in {context_name}.', + f'{state.current_class}.xml: Found a code string "{inside_code_text}" that matches one of the parameters in {context_name}. {code_warning_if_intended_string}', state, ) break |