2018-12-26 15:57:51 +01:00
#!/usr/bin/env python3
2016-02-07 00:22:49 +01:00
2021-10-24 08:48:03 +02:00
# This script makes RST files from the XML class reference for use with the online docs.
2018-12-28 00:37:21 +01:00
import argparse
2017-09-22 16:55:48 +02:00
import os
2022-06-11 00:16:05 +02:00
import platform
2018-06-11 13:35:44 +02:00
import re
2022-06-11 00:16:05 +02:00
import sys
2016-02-07 00:22:49 +01:00
import xml . etree . ElementTree as ET
2019-04-04 21:40:16 +02:00
from collections import OrderedDict
2022-07-28 00:54:26 +02:00
from typing import List , Dict , TextIO , Tuple , Optional , Any , Union
2016-02-07 00:22:49 +01:00
2022-07-26 17:20:51 +02:00
# Import hardcoded version information from version.py
2022-07-27 13:52:11 +02:00
root_directory = os . path . join ( os . path . dirname ( os . path . abspath ( __file__ ) ) , " ../../ " )
sys . path . append ( root_directory ) # Include the root directory
2022-07-26 17:20:51 +02:00
import version
2021-11-15 10:39:00 +01:00
# $DOCS_URL/path/to/page.html(#fragment-tag)
GODOT_DOCS_PATTERN = re . compile ( r " ^ \ $DOCS_URL/(.*) \ .html(#.*)?$ " )
2018-12-26 15:57:51 +01:00
2022-08-17 20:34:39 +02:00
# Based on reStructuredText inline markup recognition rules
2021-12-21 09:35:47 +01:00
# https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#inline-markup-recognition-rules
MARKUP_ALLOWED_PRECEDENT = " -:/ ' \" <([ { "
MARKUP_ALLOWED_SUBSEQUENT = " -.,:;!? \\ / ' \" )]}> "
2021-12-22 16:35:08 +01:00
# Used to translate section headings and other hardcoded strings when required with
# the --lang argument. The BASE_STRINGS list should be synced with what we actually
# write in this script (check `translate()` uses), and also hardcoded in
2023-06-15 16:06:22 +02:00
# `scripts/extract_classes.py` (godotengine/godot-editor-l10n repo) to include them in the source POT file.
2021-12-22 16:35:08 +01:00
BASE_STRINGS = [
2022-11-18 13:47:53 +01:00
" All classes " ,
" Globals " ,
2022-07-26 18:02:09 +02:00
" Nodes " ,
" Resources " ,
2023-04-24 21:06:55 +02:00
" Editor-only " ,
2022-11-18 13:47:53 +01:00
" Other objects " ,
" Variant types " ,
2021-12-21 10:53:07 +01:00
" Description " ,
" Tutorials " ,
" Properties " ,
" Constructors " ,
" Methods " ,
" Operators " ,
" Theme Properties " ,
" Signals " ,
" Enumerations " ,
" Constants " ,
2022-07-04 17:56:34 +02:00
" Annotations " ,
2021-12-21 10:53:07 +01:00
" Property Descriptions " ,
" Constructor Descriptions " ,
" Method Descriptions " ,
" Operator Descriptions " ,
" Theme Property Descriptions " ,
2021-12-22 16:35:08 +01:00
" Inherits: " ,
" Inherited By: " ,
" (overrides %s ) " ,
" Default " ,
" Setter " ,
" value " ,
" Getter " ,
" This method should typically be overridden by the user to have any effect. " ,
" This method has no side effects. It doesn ' t modify any of the instance ' s member variables. " ,
" This method accepts any number of arguments after the ones described here. " ,
" This method is used to construct a type. " ,
" This method doesn ' t need an instance to be called, so it can be called directly using the class name. " ,
" This method describes a valid operator to use with this type as left-hand operand. " ,
2023-06-15 16:06:22 +02:00
" This value is an integer composed as a bitmask of the following flags. " ,
2021-12-21 10:53:07 +01:00
]
2022-07-28 00:54:26 +02:00
strings_l10n : Dict [ str , str ] = { }
2018-06-11 13:35:44 +02:00
2022-07-28 00:54:26 +02:00
STYLES : Dict [ str , str ] = { }
2021-02-14 02:10:39 +01:00
2022-07-26 18:02:09 +02:00
CLASS_GROUPS : Dict [ str , str ] = {
" global " : " Globals " ,
" node " : " Nodes " ,
" resource " : " Resources " ,
2022-11-18 13:47:53 +01:00
" object " : " Other objects " ,
2023-04-24 21:06:55 +02:00
" editor " : " Editor-only " ,
2022-11-18 13:47:53 +01:00
" variant " : " Variant types " ,
}
CLASS_GROUPS_BASE : Dict [ str , str ] = {
" node " : " Node " ,
" resource " : " Resource " ,
" object " : " Object " ,
2023-04-24 21:06:55 +02:00
" variant " : " Variant " ,
2022-07-26 18:02:09 +02:00
}
2023-04-24 21:06:55 +02:00
# Sync with editor\register_editor_types.cpp
EDITOR_CLASSES : List [ str ] = [
" FileSystemDock " ,
" ScriptCreateDialog " ,
" ScriptEditor " ,
" ScriptEditorBase " ,
]
2022-07-26 18:02:09 +02:00
2018-12-28 00:37:21 +01:00
class State :
2022-07-28 00:54:26 +02:00
def __init__ ( self ) - > None :
2022-04-11 00:43:44 +02:00
self . num_errors = 0
2022-08-09 22:53:29 +02:00
self . num_warnings = 0
2022-07-28 00:54:26 +02:00
self . classes : OrderedDict [ str , ClassDef ] = OrderedDict ( )
self . current_class : str = " "
2018-12-28 00:37:21 +01:00
2022-07-28 00:54:26 +02:00
def parse_class ( self , class_root : ET . Element , filepath : str ) - > None :
2018-12-28 00:37:21 +01:00
class_name = class_root . attrib [ " name " ]
2022-08-06 01:52:27 +02:00
self . current_class = class_name
2018-12-28 00:37:21 +01:00
class_def = ClassDef ( class_name )
self . classes [ class_name ] = class_def
2021-02-14 02:10:39 +01:00
class_def . filepath = filepath
2018-12-28 00:37:21 +01:00
inherits = class_root . get ( " inherits " )
if inherits is not None :
class_def . inherits = inherits
brief_desc = class_root . find ( " brief_description " )
if brief_desc is not None and brief_desc . text :
class_def . brief_description = brief_desc . text
desc = class_root . find ( " description " )
if desc is not None and desc . text :
class_def . description = desc . text
properties = class_root . find ( " members " )
if properties is not None :
for property in properties :
assert property . tag == " member "
property_name = property . attrib [ " name " ]
if property_name in class_def . properties :
2022-08-17 20:34:39 +02:00
print_error ( f ' { class_name } .xml: Duplicate property " { property_name } " . ' , self )
2018-12-28 00:37:21 +01:00
continue
type_name = TypeName . from_element ( property )
setter = property . get ( " setter " ) or None # Use or None so '' gets turned into None.
getter = property . get ( " getter " ) or None
2019-06-01 15:42:22 +02:00
default_value = property . get ( " default " ) or None
2019-11-29 14:21:16 +01:00
if default_value is not None :
2022-08-17 20:34:39 +02:00
default_value = f " `` { default_value } `` "
2021-12-02 20:38:49 +01:00
overrides = property . get ( " overrides " ) or None
2018-12-28 00:37:21 +01:00
2020-03-30 08:28:32 +02:00
property_def = PropertyDef (
2021-12-02 20:38:49 +01:00
property_name , type_name , setter , getter , property . text , default_value , overrides
2020-03-30 08:28:32 +02:00
)
2018-12-28 00:37:21 +01:00
class_def . properties [ property_name ] = property_def
2021-11-02 20:19:40 +01:00
constructors = class_root . find ( " constructors " )
if constructors is not None :
for constructor in constructors :
assert constructor . tag == " constructor "
method_name = constructor . attrib [ " name " ]
qualifiers = constructor . get ( " qualifiers " )
return_element = constructor . find ( " return " )
if return_element is not None :
return_type = TypeName . from_element ( return_element )
else :
return_type = TypeName ( " void " )
2022-08-06 20:11:48 +02:00
params = self . parse_params ( constructor , " constructor " )
2021-11-02 20:19:40 +01:00
desc_element = constructor . find ( " description " )
method_desc = None
if desc_element is not None :
method_desc = desc_element . text
method_def = MethodDef ( method_name , return_type , params , method_desc , qualifiers )
2022-08-06 19:48:54 +02:00
method_def . definition_name = " constructor "
2021-11-02 20:19:40 +01:00
if method_name not in class_def . constructors :
class_def . constructors [ method_name ] = [ ]
class_def . constructors [ method_name ] . append ( method_def )
2018-12-28 00:37:21 +01:00
methods = class_root . find ( " methods " )
if methods is not None :
for method in methods :
assert method . tag == " method "
method_name = method . attrib [ " name " ]
qualifiers = method . get ( " qualifiers " )
return_element = method . find ( " return " )
if return_element is not None :
return_type = TypeName . from_element ( return_element )
else :
return_type = TypeName ( " void " )
2022-08-06 20:11:48 +02:00
params = self . parse_params ( method , " method " )
2018-12-28 00:37:21 +01:00
desc_element = method . find ( " description " )
method_desc = None
if desc_element is not None :
method_desc = desc_element . text
method_def = MethodDef ( method_name , return_type , params , method_desc , qualifiers )
if method_name not in class_def . methods :
class_def . methods [ method_name ] = [ ]
class_def . methods [ method_name ] . append ( method_def )
2021-11-02 20:19:40 +01:00
operators = class_root . find ( " operators " )
if operators is not None :
for operator in operators :
assert operator . tag == " operator "
method_name = operator . attrib [ " name " ]
qualifiers = operator . get ( " qualifiers " )
return_element = operator . find ( " return " )
if return_element is not None :
return_type = TypeName . from_element ( return_element )
else :
return_type = TypeName ( " void " )
2022-08-06 20:11:48 +02:00
params = self . parse_params ( operator , " operator " )
2021-11-02 20:19:40 +01:00
desc_element = operator . find ( " description " )
method_desc = None
if desc_element is not None :
method_desc = desc_element . text
method_def = MethodDef ( method_name , return_type , params , method_desc , qualifiers )
2022-08-06 19:48:54 +02:00
method_def . definition_name = " operator "
2021-11-02 20:19:40 +01:00
if method_name not in class_def . operators :
class_def . operators [ method_name ] = [ ]
class_def . operators [ method_name ] . append ( method_def )
2018-12-28 00:37:21 +01:00
constants = class_root . find ( " constants " )
if constants is not None :
for constant in constants :
assert constant . tag == " constant "
constant_name = constant . attrib [ " name " ]
value = constant . attrib [ " value " ]
enum = constant . get ( " enum " )
2022-07-28 00:54:26 +02:00
is_bitfield = constant . get ( " is_bitfield " ) == " true "
2022-06-24 11:16:37 +02:00
constant_def = ConstantDef ( constant_name , value , constant . text , is_bitfield )
2018-12-28 00:37:21 +01:00
if enum is None :
if constant_name in class_def . constants :
2022-08-17 20:34:39 +02:00
print_error ( f ' { class_name } .xml: Duplicate constant " { constant_name } " . ' , self )
2018-12-28 00:37:21 +01:00
continue
2016-02-07 00:22:49 +01:00
2018-12-28 00:37:21 +01:00
class_def . constants [ constant_name ] = constant_def
else :
if enum in class_def . enums :
enum_def = class_def . enums [ enum ]
else :
2022-11-30 14:34:42 +01:00
enum_def = EnumDef ( enum , TypeName ( " int " , enum ) , is_bitfield )
2018-12-28 00:37:21 +01:00
class_def . enums [ enum ] = enum_def
enum_def . values [ constant_name ] = constant_def
2022-07-04 17:56:34 +02:00
annotations = class_root . find ( " annotations " )
if annotations is not None :
for annotation in annotations :
assert annotation . tag == " annotation "
annotation_name = annotation . attrib [ " name " ]
qualifiers = annotation . get ( " qualifiers " )
2022-08-06 20:11:48 +02:00
params = self . parse_params ( annotation , " annotation " )
2022-07-04 17:56:34 +02:00
desc_element = annotation . find ( " description " )
annotation_desc = None
if desc_element is not None :
annotation_desc = desc_element . text
2022-08-04 21:42:20 +02:00
annotation_def = AnnotationDef ( annotation_name , params , annotation_desc , qualifiers )
2022-07-04 17:56:34 +02:00
if annotation_name not in class_def . annotations :
class_def . annotations [ annotation_name ] = [ ]
class_def . annotations [ annotation_name ] . append ( annotation_def )
2018-12-28 00:37:21 +01:00
signals = class_root . find ( " signals " )
if signals is not None :
for signal in signals :
assert signal . tag == " signal "
signal_name = signal . attrib [ " name " ]
if signal_name in class_def . signals :
2022-08-17 20:34:39 +02:00
print_error ( f ' { class_name } .xml: Duplicate signal " { signal_name } " . ' , self )
2018-12-28 00:37:21 +01:00
continue
2022-08-06 20:11:48 +02:00
params = self . parse_params ( signal , " signal " )
2018-12-28 00:37:21 +01:00
desc_element = signal . find ( " description " )
signal_desc = None
if desc_element is not None :
signal_desc = desc_element . text
signal_def = SignalDef ( signal_name , params , signal_desc )
class_def . signals [ signal_name ] = signal_def
theme_items = class_root . find ( " theme_items " )
if theme_items is not None :
for theme_item in theme_items :
assert theme_item . tag == " theme_item "
theme_item_name = theme_item . attrib [ " name " ]
2021-08-04 18:54:41 +02:00
theme_item_data_name = theme_item . attrib [ " data_type " ]
theme_item_id = " {} _ {} " . format ( theme_item_data_name , theme_item_name )
if theme_item_id in class_def . theme_items :
print_error (
2022-08-17 20:34:39 +02:00
f ' { class_name } .xml: Duplicate theme item " { theme_item_name } " of type " { theme_item_data_name } " . ' ,
2021-08-04 18:54:41 +02:00
self ,
)
continue
2019-06-01 15:42:22 +02:00
default_value = theme_item . get ( " default " ) or None
2021-08-04 18:54:41 +02:00
if default_value is not None :
2022-08-17 20:34:39 +02:00
default_value = f " `` { default_value } `` "
2021-08-04 18:54:41 +02:00
theme_item_def = ThemeItemDef (
theme_item_name ,
TypeName . from_element ( theme_item ) ,
theme_item_data_name ,
theme_item . text ,
default_value ,
)
2021-11-18 15:03:03 +01:00
class_def . theme_items [ theme_item_name ] = theme_item_def
2018-12-28 00:37:21 +01:00
tutorials = class_root . find ( " tutorials " )
if tutorials is not None :
for link in tutorials :
assert link . tag == " link "
if link . text is not None :
2021-07-29 21:55:39 +02:00
class_def . tutorials . append ( ( link . text . strip ( ) , link . get ( " title " , " " ) ) )
2018-12-28 00:37:21 +01:00
2022-08-06 01:52:27 +02:00
self . current_class = " "
2022-08-06 20:11:48 +02:00
def parse_params ( self , root : ET . Element , context : str ) - > List [ " ParameterDef " ] :
param_elements = root . findall ( " param " )
2022-08-06 01:52:27 +02:00
params : Any = [ None ] * len ( param_elements )
for param_index , param_element in enumerate ( param_elements ) :
param_name = param_element . attrib [ " name " ]
index = int ( param_element . attrib [ " index " ] )
type_name = TypeName . from_element ( param_element )
default = param_element . get ( " default " )
if param_name . strip ( ) == " " or param_name . startswith ( " _unnamed_arg " ) :
print_error (
2022-08-17 20:34:39 +02:00
f ' { self . current_class } .xml: Empty argument name in { context } " { root . attrib [ " name " ] } " at position { param_index } . ' ,
2022-08-06 01:52:27 +02:00
self ,
)
params [ index ] = ParameterDef ( param_name , type_name , default )
cast : List [ ParameterDef ] = params
return cast
2022-07-28 00:54:26 +02:00
def sort_classes ( self ) - > None :
2022-07-26 18:02:09 +02:00
self . classes = OrderedDict ( sorted ( self . classes . items ( ) , key = lambda t : t [ 0 ] . lower ( ) ) )
2018-12-28 00:37:21 +01:00
2022-07-28 00:54:26 +02:00
class TypeName :
2023-06-15 16:06:22 +02:00
def __init__ ( self , type_name : str , enum : Optional [ str ] = None , is_bitfield : bool = False ) - > None :
2022-07-28 00:54:26 +02:00
self . type_name = type_name
self . enum = enum
2023-06-15 16:06:22 +02:00
self . is_bitfield = is_bitfield
2022-07-28 00:54:26 +02:00
def to_rst ( self , state : State ) - > str :
if self . enum is not None :
2023-06-15 16:06:22 +02:00
return make_enum ( self . enum , self . is_bitfield , state )
2022-07-28 00:54:26 +02:00
elif self . type_name == " void " :
return " void "
else :
return make_type ( self . type_name , state )
@classmethod
def from_element ( cls , element : ET . Element ) - > " TypeName " :
2023-06-15 16:06:22 +02:00
return cls ( element . attrib [ " type " ] , element . get ( " enum " ) , element . get ( " is_bitfield " ) == " true " )
2022-07-28 00:54:26 +02:00
2022-08-09 22:53:29 +02:00
class DefinitionBase :
def __init__ (
self ,
definition_name : str ,
name : str ,
) - > None :
self . definition_name = definition_name
self . name = name
class PropertyDef ( DefinitionBase ) :
2022-07-28 00:54:26 +02:00
def __init__ (
self ,
name : str ,
type_name : TypeName ,
setter : Optional [ str ] ,
getter : Optional [ str ] ,
text : Optional [ str ] ,
default_value : Optional [ str ] ,
overrides : Optional [ str ] ,
) - > None :
2022-08-09 22:53:29 +02:00
super ( ) . __init__ ( " property " , name )
2022-08-06 19:48:54 +02:00
2022-07-28 00:54:26 +02:00
self . type_name = type_name
self . setter = setter
self . getter = getter
self . text = text
self . default_value = default_value
self . overrides = overrides
2022-08-09 22:53:29 +02:00
class ParameterDef ( DefinitionBase ) :
2022-07-28 00:54:26 +02:00
def __init__ ( self , name : str , type_name : TypeName , default_value : Optional [ str ] ) - > None :
2022-08-09 22:53:29 +02:00
super ( ) . __init__ ( " parameter " , name )
2022-08-06 19:48:54 +02:00
2022-07-28 00:54:26 +02:00
self . type_name = type_name
self . default_value = default_value
2022-08-09 22:53:29 +02:00
class SignalDef ( DefinitionBase ) :
2022-07-28 00:54:26 +02:00
def __init__ ( self , name : str , parameters : List [ ParameterDef ] , description : Optional [ str ] ) - > None :
2022-08-09 22:53:29 +02:00
super ( ) . __init__ ( " signal " , name )
2022-08-06 19:48:54 +02:00
2022-07-28 00:54:26 +02:00
self . parameters = parameters
self . description = description
2022-08-09 22:53:29 +02:00
class AnnotationDef ( DefinitionBase ) :
2022-08-04 21:42:20 +02:00
def __init__ (
self ,
name : str ,
parameters : List [ ParameterDef ] ,
description : Optional [ str ] ,
qualifiers : Optional [ str ] ,
) - > None :
2022-08-09 22:53:29 +02:00
super ( ) . __init__ ( " annotation " , name )
2022-08-06 19:48:54 +02:00
2022-08-04 21:42:20 +02:00
self . parameters = parameters
self . description = description
self . qualifiers = qualifiers
2022-08-09 22:53:29 +02:00
class MethodDef ( DefinitionBase ) :
2022-07-28 00:54:26 +02:00
def __init__ (
self ,
name : str ,
return_type : TypeName ,
parameters : List [ ParameterDef ] ,
description : Optional [ str ] ,
qualifiers : Optional [ str ] ,
) - > None :
2022-08-09 22:53:29 +02:00
super ( ) . __init__ ( " method " , name )
2022-08-06 19:48:54 +02:00
2022-07-28 00:54:26 +02:00
self . return_type = return_type
self . parameters = parameters
self . description = description
self . qualifiers = qualifiers
2022-08-09 22:53:29 +02:00
class ConstantDef ( DefinitionBase ) :
2022-07-28 00:54:26 +02:00
def __init__ ( self , name : str , value : str , text : Optional [ str ] , bitfield : bool ) - > None :
2022-08-09 22:53:29 +02:00
super ( ) . __init__ ( " constant " , name )
2022-08-06 19:48:54 +02:00
2022-07-28 00:54:26 +02:00
self . value = value
self . text = text
self . is_bitfield = bitfield
2022-08-09 22:53:29 +02:00
class EnumDef ( DefinitionBase ) :
2022-11-30 14:34:42 +01:00
def __init__ ( self , name : str , type_name : TypeName , bitfield : bool ) - > None :
2022-08-09 22:53:29 +02:00
super ( ) . __init__ ( " enum " , name )
2022-08-06 19:48:54 +02:00
2022-11-30 14:34:42 +01:00
self . type_name = type_name
2022-07-28 00:54:26 +02:00
self . values : OrderedDict [ str , ConstantDef ] = OrderedDict ( )
self . is_bitfield = bitfield
2022-08-09 22:53:29 +02:00
class ThemeItemDef ( DefinitionBase ) :
2022-07-28 00:54:26 +02:00
def __init__ (
self , name : str , type_name : TypeName , data_name : str , text : Optional [ str ] , default_value : Optional [ str ]
) - > None :
2022-08-09 22:53:29 +02:00
super ( ) . __init__ ( " theme item " , name )
2022-08-06 19:48:54 +02:00
2022-07-28 00:54:26 +02:00
self . type_name = type_name
self . data_name = data_name
self . text = text
self . default_value = default_value
2022-08-09 22:53:29 +02:00
class ClassDef ( DefinitionBase ) :
2022-07-28 00:54:26 +02:00
def __init__ ( self , name : str ) - > None :
2022-08-09 22:53:29 +02:00
super ( ) . __init__ ( " class " , name )
2022-08-06 19:48:54 +02:00
2022-07-28 00:54:26 +02:00
self . constants : OrderedDict [ str , ConstantDef ] = OrderedDict ( )
self . enums : OrderedDict [ str , EnumDef ] = OrderedDict ( )
self . properties : OrderedDict [ str , PropertyDef ] = OrderedDict ( )
self . constructors : OrderedDict [ str , List [ MethodDef ] ] = OrderedDict ( )
self . methods : OrderedDict [ str , List [ MethodDef ] ] = OrderedDict ( )
self . operators : OrderedDict [ str , List [ MethodDef ] ] = OrderedDict ( )
self . signals : OrderedDict [ str , SignalDef ] = OrderedDict ( )
2022-08-04 21:42:20 +02:00
self . annotations : OrderedDict [ str , List [ AnnotationDef ] ] = OrderedDict ( )
2022-07-28 00:54:26 +02:00
self . theme_items : OrderedDict [ str , ThemeItemDef ] = OrderedDict ( )
self . inherits : Optional [ str ] = None
self . brief_description : Optional [ str ] = None
self . description : Optional [ str ] = None
self . tutorials : List [ Tuple [ str , str ] ] = [ ]
# Used to match the class with XML source for output filtering purposes.
self . filepath : str = " "
2022-08-09 22:53:29 +02:00
# Entry point for the RST generator.
2022-07-28 00:54:26 +02:00
def main ( ) - > None :
2022-06-11 00:16:05 +02:00
# Enable ANSI escape code support on Windows 10 and later (for colored console output).
# <https://bugs.python.org/issue29059>
if platform . system ( ) . lower ( ) == " windows " :
2022-07-28 00:54:26 +02:00
from ctypes import windll , c_int , byref # type: ignore
2022-06-11 00:16:05 +02:00
stdout_handle = windll . kernel32 . GetStdHandle ( c_int ( - 11 ) )
mode = c_int ( 0 )
windll . kernel32 . GetConsoleMode ( c_int ( stdout_handle ) , byref ( mode ) )
mode = c_int ( mode . value | 4 )
windll . kernel32 . SetConsoleMode ( c_int ( stdout_handle ) , mode )
2018-12-28 00:37:21 +01:00
parser = argparse . ArgumentParser ( )
parser . add_argument ( " path " , nargs = " + " , help = " A path to an XML file or a directory containing XML files to parse. " )
2021-02-14 02:10:39 +01:00
parser . add_argument ( " --filter " , default = " " , help = " The filepath pattern for XML files to filter. " )
2021-12-21 10:53:07 +01:00
parser . add_argument ( " --lang " , " -l " , default = " en " , help = " Language to use for section headings. " )
2022-06-11 00:16:05 +02:00
parser . add_argument (
" --color " ,
action = " store_true " ,
help = " If passed, force colored output even if stdout is not a TTY (useful for continuous integration). " ,
)
2018-12-28 00:37:21 +01:00
group = parser . add_mutually_exclusive_group ( )
group . add_argument ( " --output " , " -o " , default = " . " , help = " The directory to save output .rst files in. " )
2020-03-30 08:28:32 +02:00
group . add_argument (
" --dry-run " ,
action = " store_true " ,
help = " If passed, no output will be generated and XML files are only checked for errors. " ,
)
2018-12-28 00:37:21 +01:00
args = parser . parse_args ( )
2016-02-07 00:22:49 +01:00
2022-06-11 00:16:05 +02:00
should_color = args . color or ( hasattr ( sys . stdout , " isatty " ) and sys . stdout . isatty ( ) )
STYLES [ " red " ] = " \x1b [91m " if should_color else " "
STYLES [ " green " ] = " \x1b [92m " if should_color else " "
2022-08-09 22:53:29 +02:00
STYLES [ " yellow " ] = " \x1b [93m " if should_color else " "
2022-06-11 00:16:05 +02:00
STYLES [ " bold " ] = " \x1b [1m " if should_color else " "
STYLES [ " regular " ] = " \x1b [22m " if should_color else " "
STYLES [ " reset " ] = " \x1b [0m " if should_color else " "
2021-12-21 10:53:07 +01:00
# Retrieve heading translations for the given language.
if not args . dry_run and args . lang != " en " :
lang_file = os . path . join (
os . path . dirname ( os . path . realpath ( __file__ ) ) , " .. " , " translations " , " {} .po " . format ( args . lang )
)
if os . path . exists ( lang_file ) :
try :
2022-08-23 15:21:46 +02:00
import polib # type: ignore
2021-12-21 10:53:07 +01:00
except ImportError :
2021-12-22 16:35:08 +01:00
print ( " Base template strings localization requires `polib`. " )
2021-12-21 10:53:07 +01:00
exit ( 1 )
pofile = polib . pofile ( lang_file )
for entry in pofile . translated_entries ( ) :
2021-12-22 16:35:08 +01:00
if entry . msgid in BASE_STRINGS :
strings_l10n [ entry . msgid ] = entry . msgstr
2021-12-21 10:53:07 +01:00
else :
2022-08-17 20:34:39 +02:00
print ( f ' No PO file at " { lang_file } " for language " { args . lang } " . ' )
2021-12-21 10:53:07 +01:00
2020-05-27 22:24:15 +02:00
print ( " Checking for errors in the XML class reference... " )
2022-07-28 00:54:26 +02:00
file_list : List [ str ] = [ ]
2016-02-07 00:22:49 +01:00
2018-12-28 00:37:21 +01:00
for path in args . path :
# Cut off trailing slashes so os.path.basename doesn't choke.
2022-01-24 10:30:07 +01:00
if path . endswith ( " / " ) or path . endswith ( " \\ " ) :
2018-12-28 00:37:21 +01:00
path = path [ : - 1 ]
2023-04-19 11:23:22 +02:00
if os . path . basename ( path ) in [ " modules " , " platform " ] :
2018-12-26 15:57:51 +01:00
for subdir , dirs , _ in os . walk ( path ) :
2020-03-30 08:28:32 +02:00
if " doc_classes " in dirs :
doc_dir = os . path . join ( subdir , " doc_classes " )
class_file_names = ( f for f in os . listdir ( doc_dir ) if f . endswith ( " .xml " ) )
2018-12-28 00:37:21 +01:00
file_list + = ( os . path . join ( doc_dir , f ) for f in class_file_names )
2016-02-07 00:22:49 +01:00
2018-12-26 15:57:51 +01:00
elif os . path . isdir ( path ) :
2020-03-30 08:28:32 +02:00
file_list + = ( os . path . join ( path , f ) for f in os . listdir ( path ) if f . endswith ( " .xml " ) )
2018-12-26 15:57:51 +01:00
elif os . path . isfile ( path ) :
if not path . endswith ( " .xml " ) :
2022-08-17 20:34:39 +02:00
print ( f ' Got non-.xml file " { path } " in input, skipping. ' )
2018-12-26 15:57:51 +01:00
continue
2016-02-07 00:22:49 +01:00
2018-12-26 15:57:51 +01:00
file_list . append ( path )
2016-02-07 00:22:49 +01:00
2022-07-28 00:54:26 +02:00
classes : Dict [ str , Tuple [ ET . Element , str ] ] = { }
2018-12-28 00:37:21 +01:00
state = State ( )
2016-10-30 19:05:14 +01:00
2018-12-26 15:57:51 +01:00
for cur_file in file_list :
try :
tree = ET . parse ( cur_file )
except ET . ParseError as e :
2022-08-17 20:34:39 +02:00
print_error ( f " { cur_file } : Parse error while reading the file: { e } " , state )
2018-12-28 00:37:21 +01:00
continue
2018-12-26 15:57:51 +01:00
doc = tree . getroot ( )
name = doc . attrib [ " name " ]
if name in classes :
2022-08-17 20:34:39 +02:00
print_error ( f ' { cur_file } : Duplicate class " { name } " . ' , state )
2018-12-26 15:57:51 +01:00
continue
2016-02-07 00:22:49 +01:00
2021-02-14 02:10:39 +01:00
classes [ name ] = ( doc , cur_file )
2016-10-30 19:05:14 +01:00
2018-12-28 00:37:21 +01:00
for name , data in classes . items ( ) :
try :
2021-02-14 02:10:39 +01:00
state . parse_class ( data [ 0 ] , data [ 1 ] )
2018-12-28 00:37:21 +01:00
except Exception as e :
2022-08-17 20:34:39 +02:00
print_error ( f " { name } .xml: Exception while parsing class: { e } " , state )
2018-12-28 00:37:21 +01:00
state . sort_classes ( )
2021-02-14 02:10:39 +01:00
pattern = re . compile ( args . filter )
2021-07-08 21:54:12 +02:00
# Create the output folder recursively if it doesn't already exist.
os . makedirs ( args . output , exist_ok = True )
2022-08-09 22:53:29 +02:00
print ( " Generating the RST class reference... " )
2022-07-26 18:02:09 +02:00
grouped_classes : Dict [ str , List [ str ] ] = { }
2018-12-28 00:37:21 +01:00
for class_name , class_def in state . classes . items ( ) :
2021-02-14 02:10:39 +01:00
if args . filter and not pattern . search ( class_def . filepath ) :
continue
2018-12-28 00:37:21 +01:00
state . current_class = class_name
make_rst_class ( class_def , state , args . dry_run , args . output )
2022-07-26 18:02:09 +02:00
group_name = get_class_group ( class_def , state )
if group_name not in grouped_classes :
grouped_classes [ group_name ] = [ ]
grouped_classes [ group_name ] . append ( class_name )
2023-04-24 21:06:55 +02:00
if is_editor_class ( class_def ) :
if " editor " not in grouped_classes :
grouped_classes [ " editor " ] = [ ]
grouped_classes [ " editor " ] . append ( class_name )
2022-07-26 18:02:09 +02:00
print ( " " )
print ( " Generating the index file... " )
make_rst_index ( grouped_classes , args . dry_run , args . output )
2022-08-09 22:53:29 +02:00
print ( " " )
if state . num_warnings > = 2 :
print (
2022-08-17 20:34:39 +02:00
f ' { STYLES [ " yellow " ] } { state . num_warnings } warnings were found in the class reference XML. Please check the messages above. { STYLES [ " reset " ] } '
2022-08-09 22:53:29 +02:00
)
elif state . num_warnings == 1 :
print (
2022-08-17 20:34:39 +02:00
f ' { STYLES [ " yellow " ] } 1 warning was found in the class reference XML. Please check the messages above. { STYLES [ " reset " ] } '
2022-08-09 22:53:29 +02:00
)
2022-04-11 00:43:44 +02:00
if state . num_errors == 0 :
2022-08-17 20:34:39 +02:00
print ( f ' { STYLES [ " green " ] } No errors found in the class reference XML. { STYLES [ " reset " ] } ' )
2021-07-08 21:54:12 +02:00
if not args . dry_run :
2022-08-17 20:34:39 +02:00
print ( f " Wrote reStructuredText files for each class to: { args . output } " )
2020-05-27 22:24:15 +02:00
else :
2022-04-11 00:43:44 +02:00
if state . num_errors > = 2 :
print (
2022-08-17 20:34:39 +02:00
f ' { STYLES [ " red " ] } { state . num_errors } errors were found in the class reference XML. Please check the messages above. { STYLES [ " reset " ] } '
2022-04-11 00:43:44 +02:00
)
else :
2022-06-11 00:16:05 +02:00
print (
2022-08-17 20:34:39 +02:00
f ' { STYLES [ " red " ] } 1 error was found in the class reference XML. Please check the messages above. { STYLES [ " reset " ] } '
2022-06-11 00:16:05 +02:00
)
2018-12-28 00:37:21 +01:00
exit ( 1 )
2020-03-30 08:28:32 +02:00
2022-08-09 22:53:29 +02:00
# Common helpers.
def print_error ( error : str , state : State ) - > None :
2022-08-17 20:34:39 +02:00
print ( f ' { STYLES [ " red " ] } { STYLES [ " bold " ] } ERROR: { STYLES [ " regular " ] } { error } { STYLES [ " reset " ] } ' )
2022-08-09 22:53:29 +02:00
state . num_errors + = 1
2022-08-17 20:34:39 +02:00
def print_warning ( warning : str , state : State ) - > None :
print ( f ' { STYLES [ " yellow " ] } { STYLES [ " bold " ] } WARNING: { STYLES [ " regular " ] } { warning } { STYLES [ " reset " ] } ' )
2022-08-09 22:53:29 +02:00
state . num_warnings + = 1
2022-07-28 00:54:26 +02:00
def translate ( string : str ) - > str :
2021-12-22 16:35:08 +01:00
""" Translate a string based on translations sourced from `doc/translations/*.po`
for a language if defined via the - - lang command line argument .
Returns the original string if no translation exists .
"""
return strings_l10n . get ( string , string )
2022-07-26 18:02:09 +02:00
def get_git_branch ( ) - > str :
if hasattr ( version , " docs " ) and version . docs != " latest " :
return version . docs
return " master "
def get_class_group ( class_def : ClassDef , state : State ) - > str :
2022-11-18 13:47:53 +01:00
group_name = " variant "
2022-07-26 18:02:09 +02:00
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
2022-11-18 13:47:53 +01:00
if inherits == " Object " :
group_name = " object "
break
2022-07-26 18:02:09 +02:00
inode = state . classes [ inherits ] . inherits
if inode :
inherits = inode . strip ( )
else :
break
return group_name
2023-04-24 21:06:55 +02:00
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
2022-08-09 22:53:29 +02:00
# Generator methods.
2022-07-28 00:54:26 +02:00
def make_rst_class ( class_def : ClassDef , state : State , dry_run : bool , output_dir : str ) - > None :
2018-12-28 00:37:21 +01:00
class_name = class_def . name
if dry_run :
2020-03-30 23:02:38 +02:00
f = open ( os . devnull , " w " , encoding = " utf-8 " )
2018-12-28 00:37:21 +01:00
else :
2022-08-17 20:34:39 +02:00
f = open ( os . path . join ( output_dir , f " class_ { class_name . lower ( ) } .rst " ) , " w " , encoding = " utf-8 " )
2018-12-28 00:37:21 +01:00
2022-07-26 17:20:51 +02:00
# Remove the "Edit on Github" button from the online docs page.
2019-07-25 15:57:43 +02:00
f . write ( " :github_url: hide \n \n " )
2018-12-28 00:37:21 +01:00
2022-07-26 17:20:51 +02:00
# Warn contributors not to edit this file directly.
# Also provide links to the source files for reference.
2022-07-26 18:02:09 +02:00
git_branch = get_git_branch ( )
2022-07-27 13:52:11 +02:00
source_xml_path = os . path . relpath ( class_def . filepath , root_directory ) . replace ( " \\ " , " / " )
2022-08-17 20:34:39 +02:00
source_github_url = f " https://github.com/godotengine/godot/tree/ { git_branch } / { source_xml_path } "
generator_github_url = f " https://github.com/godotengine/godot/tree/ { git_branch } /doc/tools/make_rst.py "
2022-07-26 17:20:51 +02:00
f . write ( " .. DO NOT EDIT THIS FILE!!! \n " )
f . write ( " .. Generated automatically from Godot engine sources. \n " )
2022-08-17 20:34:39 +02:00
f . write ( f " .. Generator: { generator_github_url } . \n " )
f . write ( f " .. XML source: { source_github_url } . \n \n " )
2022-07-26 17:20:51 +02:00
# Document reference id and header.
2022-08-17 20:34:39 +02:00
f . write ( f " .. _class_ { class_name } : \n \n " )
2021-12-21 10:53:07 +01:00
f . write ( make_heading ( class_name , " = " , False ) )
2018-12-28 00:37:21 +01:00
2022-11-30 14:34:42 +01:00
### INHERITANCE TREE ###
2018-12-28 00:37:21 +01:00
# Ascendants
if class_def . inherits :
2022-02-10 12:00:11 +01:00
inherits = class_def . inherits . strip ( )
2022-08-17 20:34:39 +02:00
f . write ( f ' ** { translate ( " Inherits: " ) } ** ' )
2018-12-28 00:37:21 +01:00
first = True
2022-02-10 12:00:11 +01:00
while inherits in state . classes :
2018-12-28 00:37:21 +01:00
if not first :
f . write ( " **<** " )
else :
first = False
2022-02-10 12:00:11 +01:00
f . write ( make_type ( inherits , state ) )
inode = state . classes [ inherits ] . inherits
2018-12-28 00:37:21 +01:00
if inode :
2022-02-10 12:00:11 +01:00
inherits = inode . strip ( )
2018-12-28 00:37:21 +01:00
else :
break
f . write ( " \n \n " )
2022-07-28 00:54:26 +02:00
# Descendants
inherited : List [ str ] = [ ]
2018-12-28 00:37:21 +01:00
for c in state . classes . values ( ) :
if c . inherits and c . inherits . strip ( ) == class_name :
inherited . append ( c . name )
if len ( inherited ) :
2022-08-17 20:34:39 +02:00
f . write ( f ' ** { translate ( " Inherited By: " ) } ** ' )
2018-12-28 00:37:21 +01:00
for i , child in enumerate ( inherited ) :
if i > 0 :
f . write ( " , " )
f . write ( make_type ( child , state ) )
f . write ( " \n \n " )
2022-11-30 14:34:42 +01:00
### INTRODUCTION ###
2022-07-19 17:17:04 +02:00
has_description = False
2018-12-28 00:37:21 +01:00
# Brief description
2022-07-19 17:17:04 +02:00
if class_def . brief_description is not None and class_def . brief_description . strip ( ) != " " :
has_description = True
2022-08-17 20:34:39 +02:00
f . write ( f " { format_text_block ( class_def . brief_description . strip ( ) , class_def , state ) } \n \n " )
2018-12-28 00:37:21 +01:00
2020-01-14 21:38:54 +01:00
# Class description
2020-03-30 08:28:32 +02:00
if class_def . description is not None and class_def . description . strip ( ) != " " :
2022-07-19 17:17:04 +02:00
has_description = True
2022-11-30 14:34:42 +01:00
f . write ( " .. rst-class:: classref-introduction-group \n \n " )
2020-03-30 08:28:32 +02:00
f . write ( make_heading ( " Description " , " - " ) )
2022-11-30 14:34:42 +01:00
2022-08-17 20:34:39 +02:00
f . write ( f " { format_text_block ( class_def . description . strip ( ) , class_def , state ) } \n \n " )
2020-01-14 21:38:54 +01:00
2022-07-19 17:17:04 +02:00
if not has_description :
f . write ( " .. container:: contribute \n \n \t " )
f . write (
translate (
" There is currently no description for this class. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`! "
)
+ " \n \n "
)
2020-01-14 21:38:54 +01:00
# Online tutorials
if len ( class_def . tutorials ) > 0 :
2022-11-30 14:34:42 +01:00
f . write ( " .. rst-class:: classref-introduction-group \n \n " )
2020-03-30 08:28:32 +02:00
f . write ( make_heading ( " Tutorials " , " - " ) )
2022-11-30 14:34:42 +01:00
2021-07-29 21:55:39 +02:00
for url , title in class_def . tutorials :
2022-08-17 20:34:39 +02:00
f . write ( f " - { make_link ( url , title ) } \n \n " )
2020-01-14 21:38:54 +01:00
2022-11-30 14:34:42 +01:00
### REFERENCE TABLES ###
# Reused container for reference tables.
2022-08-23 15:21:46 +02:00
ml : List [ Tuple [ Optional [ str ] , . . . ] ] = [ ]
2022-11-30 14:34:42 +01:00
# Properties reference table
2018-12-28 00:37:21 +01:00
if len ( class_def . properties ) > 0 :
2022-11-30 14:34:42 +01:00
f . write ( " .. rst-class:: classref-reftable-group \n \n " )
2020-03-30 08:28:32 +02:00
f . write ( make_heading ( " Properties " , " - " ) )
2022-11-30 14:34:42 +01:00
2022-08-23 15:21:46 +02:00
ml = [ ]
2018-12-28 00:37:21 +01:00
for property_def in class_def . properties . values ( ) :
type_rst = property_def . type_name . to_rst ( state )
2019-06-01 15:42:22 +02:00
default = property_def . default_value
2021-12-02 20:38:49 +01:00
if default is not None and property_def . overrides :
2022-08-17 20:34:39 +02:00
ref = f " :ref:` { property_def . overrides } <class_ { property_def . overrides } _property_ { property_def . name } >` "
2021-12-22 16:35:08 +01:00
# Not using translate() for now as it breaks table formatting.
2022-08-17 20:34:39 +02:00
ml . append ( ( type_rst , property_def . name , f " { default } (overrides { ref } ) " ) )
2019-09-03 12:42:34 +02:00
else :
2022-08-17 20:34:39 +02:00
ref = f " :ref:` { property_def . name } <class_ { class_name } _property_ { property_def . name } >` "
2019-09-03 12:42:34 +02:00
ml . append ( ( type_rst , ref , default ) )
2022-11-30 14:34:42 +01:00
2019-06-01 15:42:22 +02:00
format_table ( f , ml , True )
2018-12-28 00:37:21 +01:00
2022-11-30 14:34:42 +01:00
# Constructors, Methods, Operators reference tables
2021-11-02 20:19:40 +01:00
if len ( class_def . constructors ) > 0 :
2022-11-30 14:34:42 +01:00
f . write ( " .. rst-class:: classref-reftable-group \n \n " )
2021-11-02 20:19:40 +01:00
f . write ( make_heading ( " Constructors " , " - " ) )
2022-11-30 14:34:42 +01:00
2022-08-23 15:21:46 +02:00
ml = [ ]
2021-11-02 20:19:40 +01:00
for method_list in class_def . constructors . values ( ) :
for m in method_list :
ml . append ( make_method_signature ( class_def , m , " constructor " , state ) )
2022-11-30 14:34:42 +01:00
2021-11-02 20:19:40 +01:00
format_table ( f , ml )
2018-12-28 00:37:21 +01:00
if len ( class_def . methods ) > 0 :
2022-11-30 14:34:42 +01:00
f . write ( " .. rst-class:: classref-reftable-group \n \n " )
2020-03-30 08:28:32 +02:00
f . write ( make_heading ( " Methods " , " - " ) )
2022-11-30 14:34:42 +01:00
2022-08-23 15:21:46 +02:00
ml = [ ]
2018-12-28 00:37:21 +01:00
for method_list in class_def . methods . values ( ) :
for m in method_list :
2021-11-02 20:19:40 +01:00
ml . append ( make_method_signature ( class_def , m , " method " , state ) )
2022-11-30 14:34:42 +01:00
2021-11-02 20:19:40 +01:00
format_table ( f , ml )
if len ( class_def . operators ) > 0 :
2022-11-30 14:34:42 +01:00
f . write ( " .. rst-class:: classref-reftable-group \n \n " )
2021-11-02 20:19:40 +01:00
f . write ( make_heading ( " Operators " , " - " ) )
2022-11-30 14:34:42 +01:00
2022-08-23 15:21:46 +02:00
ml = [ ]
2021-11-02 20:19:40 +01:00
for method_list in class_def . operators . values ( ) :
for m in method_list :
ml . append ( make_method_signature ( class_def , m , " operator " , state ) )
2022-11-30 14:34:42 +01:00
2018-12-28 00:37:21 +01:00
format_table ( f , ml )
2022-11-30 14:34:42 +01:00
# Theme properties reference table
2021-08-06 17:18:22 +02:00
if len ( class_def . theme_items ) > 0 :
2022-11-30 14:34:42 +01:00
f . write ( " .. rst-class:: classref-reftable-group \n \n " )
2020-03-30 08:28:32 +02:00
f . write ( make_heading ( " Theme Properties " , " - " ) )
2022-11-30 14:34:42 +01:00
ml = [ ]
2021-08-04 18:54:41 +02:00
for theme_item_def in class_def . theme_items . values ( ) :
2022-08-17 20:34:39 +02:00
ref = f " :ref:` { theme_item_def . name } <class_ { class_name } _theme_ { theme_item_def . data_name } _ { theme_item_def . name } >` "
2022-11-30 14:34:42 +01:00
ml . append ( ( theme_item_def . type_name . to_rst ( state ) , ref , theme_item_def . default_value ) )
format_table ( f , ml , True )
2018-12-26 15:57:51 +01:00
2022-11-30 14:34:42 +01:00
### DETAILED DESCRIPTIONS ###
# Signal descriptions
2018-12-28 00:37:21 +01:00
if len ( class_def . signals ) > 0 :
2022-11-30 14:34:42 +01:00
f . write ( make_separator ( True ) )
f . write ( " .. rst-class:: classref-descriptions-group \n \n " )
2020-03-30 08:28:32 +02:00
f . write ( make_heading ( " Signals " , " - " ) )
2022-11-30 14:34:42 +01:00
2019-10-24 19:06:09 +02:00
index = 0
2018-12-28 00:37:21 +01:00
for signal in class_def . signals . values ( ) :
2019-10-24 19:06:09 +02:00
if index != 0 :
2022-11-30 14:34:42 +01:00
f . write ( make_separator ( ) )
# Create signal signature and anchor point.
2019-10-24 19:06:09 +02:00
2022-08-17 20:34:39 +02:00
f . write ( f " .. _class_ { class_name } _signal_ { signal . name } : \n \n " )
2022-11-30 14:34:42 +01:00
f . write ( " .. rst-class:: classref-signal \n \n " )
2021-11-02 20:19:40 +01:00
_ , signature = make_method_signature ( class_def , signal , " " , state )
2022-11-30 14:34:42 +01:00
f . write ( f " { signature } \n \n " )
# Add signal description, or a call to action if it's missing.
2018-12-26 15:57:51 +01:00
2020-03-30 08:28:32 +02:00
if signal . description is not None and signal . description . strip ( ) != " " :
2022-08-17 20:34:39 +02:00
f . write ( f " { format_text_block ( signal . description . strip ( ) , signal , state ) } \n \n " )
2022-11-30 14:34:42 +01:00
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 <doc_updating_the_class_reference>`! "
)
+ " \n \n "
)
2019-10-24 19:06:09 +02:00
index + = 1
2018-12-28 00:37:21 +01:00
2022-11-30 14:34:42 +01:00
# Enumeration descriptions
2018-12-28 00:37:21 +01:00
if len ( class_def . enums ) > 0 :
2022-11-30 14:34:42 +01:00
f . write ( make_separator ( True ) )
f . write ( " .. rst-class:: classref-descriptions-group \n \n " )
2020-03-30 08:28:32 +02:00
f . write ( make_heading ( " Enumerations " , " - " ) )
2022-11-30 14:34:42 +01:00
2019-10-24 19:06:09 +02:00
index = 0
2018-12-28 00:37:21 +01:00
for e in class_def . enums . values ( ) :
2019-10-24 19:06:09 +02:00
if index != 0 :
2022-11-30 14:34:42 +01:00
f . write ( make_separator ( ) )
# Create enumeration signature and anchor point.
2019-10-24 19:06:09 +02:00
2022-08-17 20:34:39 +02:00
f . write ( f " .. _enum_ { class_name } _ { e . name } : \n \n " )
2022-11-30 14:34:42 +01:00
f . write ( " .. rst-class:: classref-enumeration \n \n " )
2018-12-28 00:37:21 +01:00
2022-06-24 11:16:37 +02:00
if e . is_bitfield :
2022-08-17 20:34:39 +02:00
f . write ( f " flags ** { e . name } **: \n \n " )
2022-06-24 11:16:37 +02:00
else :
2022-08-17 20:34:39 +02:00
f . write ( f " enum ** { e . name } **: \n \n " )
2022-06-24 11:16:37 +02:00
2018-12-28 00:37:21 +01:00
for value in e . values . values ( ) :
2022-11-30 14:34:42 +01:00
# 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.
2020-03-30 08:28:32 +02:00
if value . text is not None and value . text . strip ( ) != " " :
2022-11-30 14:34:42 +01:00
f . write ( f " { format_text_block ( value . text . strip ( ) , value , state ) } " )
2019-10-24 19:06:09 +02:00
2020-03-30 08:28:32 +02:00
f . write ( " \n \n " )
2018-12-28 00:37:21 +01:00
2019-10-24 19:06:09 +02:00
index + = 1
2022-11-30 14:34:42 +01:00
# Constant descriptions
2018-12-28 00:37:21 +01:00
if len ( class_def . constants ) > 0 :
2022-11-30 14:34:42 +01:00
f . write ( make_separator ( True ) )
f . write ( " .. rst-class:: classref-descriptions-group \n \n " )
2020-03-30 08:28:32 +02:00
f . write ( make_heading ( " Constants " , " - " ) )
2022-11-30 14:34:42 +01:00
2018-12-28 00:37:21 +01:00
for constant in class_def . constants . values ( ) :
2022-11-30 14:34:42 +01:00
# Create constant signature and anchor point.
2022-08-17 20:34:39 +02:00
f . write ( f " .. _class_ { class_name } _constant_ { constant . name } : \n \n " )
2022-11-30 14:34:42 +01:00
f . write ( " .. rst-class:: classref-constant \n \n " )
f . write ( f " ** { constant . name } ** = `` { constant . value } `` \n \n " )
# Add enum constant description.
2018-12-28 00:37:21 +01:00
2020-03-30 08:28:32 +02:00
if constant . text is not None and constant . text . strip ( ) != " " :
2022-11-30 14:34:42 +01:00
f . write ( f " { format_text_block ( constant . text . strip ( ) , constant , state ) } " )
2019-10-24 19:06:09 +02:00
2020-03-30 08:28:32 +02:00
f . write ( " \n \n " )
2018-12-28 00:37:21 +01:00
2022-11-30 14:34:42 +01:00
# Annotation descriptions
2022-07-04 17:56:34 +02:00
if len ( class_def . annotations ) > 0 :
2022-11-30 14:34:42 +01:00
f . write ( make_separator ( True ) )
2022-07-04 17:56:34 +02:00
f . write ( make_heading ( " Annotations " , " - " ) )
2022-11-30 14:34:42 +01:00
2022-07-04 17:56:34 +02:00
index = 0
2022-08-23 15:21:46 +02:00
for method_list in class_def . annotations . values ( ) : # type: ignore
2022-07-04 17:56:34 +02:00
for i , m in enumerate ( method_list ) :
if index != 0 :
2022-11-30 14:34:42 +01:00
f . write ( make_separator ( ) )
# Create annotation signature and anchor point.
2022-07-04 17:56:34 +02:00
if i == 0 :
2022-08-17 20:34:39 +02:00
f . write ( f " .. _class_ { class_name } _annotation_ { m . name } : \n \n " )
2022-07-04 17:56:34 +02:00
2022-11-30 14:34:42 +01:00
f . write ( " .. rst-class:: classref-annotation \n \n " )
2022-08-04 21:42:20 +02:00
_ , signature = make_method_signature ( class_def , m , " " , state )
2022-11-30 14:34:42 +01:00
f . write ( f " { signature } \n \n " )
# Add annotation description, or a call to action if it's missing.
2022-07-04 17:56:34 +02:00
if m . description is not None and m . description . strip ( ) != " " :
2022-08-17 20:34:39 +02:00
f . write ( f " { format_text_block ( m . description . strip ( ) , m , state ) } \n \n " )
2022-07-19 17:17:04 +02:00
else :
f . write ( " .. container:: contribute \n \n \t " )
f . write (
translate (
" There is currently no description for this annotation. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`! "
)
+ " \n \n "
)
2022-07-04 17:56:34 +02:00
index + = 1
2018-12-28 00:37:21 +01:00
# Property descriptions
2021-12-02 20:38:49 +01:00
if any ( not p . overrides for p in class_def . properties . values ( ) ) > 0 :
2022-11-30 14:34:42 +01:00
f . write ( make_separator ( True ) )
f . write ( " .. rst-class:: classref-descriptions-group \n \n " )
2020-03-30 08:28:32 +02:00
f . write ( make_heading ( " Property Descriptions " , " - " ) )
2022-11-30 14:34:42 +01:00
2019-10-24 19:06:09 +02:00
index = 0
2018-12-28 00:37:21 +01:00
for property_def in class_def . properties . values ( ) :
2021-12-02 20:38:49 +01:00
if property_def . overrides :
2019-09-03 12:42:34 +02:00
continue
2019-10-24 19:06:09 +02:00
if index != 0 :
2022-11-30 14:34:42 +01:00
f . write ( make_separator ( ) )
# Create property signature and anchor point.
2019-10-24 19:06:09 +02:00
2022-08-17 20:34:39 +02:00
f . write ( f " .. _class_ { class_name } _property_ { property_def . name } : \n \n " )
2022-11-30 14:34:42 +01:00
f . write ( " .. rst-class:: classref-property \n \n " )
2018-12-28 00:37:21 +01:00
2022-11-30 14:34:42 +01:00
property_default = " "
2019-06-01 15:42:22 +02:00
if property_def . default_value is not None :
2022-11-30 14:34:42 +01:00
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 = " "
2018-12-28 00:37:21 +01:00
if property_def . setter is not None and not property_def . setter . startswith ( " _ " ) :
2022-11-30 14:34:42 +01:00
property_setter = make_setter_signature ( class_def , property_def , state )
property_setget + = f " - { property_setter } \n "
2018-12-28 00:37:21 +01:00
if property_def . getter is not None and not property_def . getter . startswith ( " _ " ) :
2022-11-30 14:34:42 +01:00
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 " )
2018-12-28 00:37:21 +01:00
2022-11-30 14:34:42 +01:00
# Add property description, or a call to action if it's missing.
2018-12-28 00:37:21 +01:00
2020-03-30 08:28:32 +02:00
if property_def . text is not None and property_def . text . strip ( ) != " " :
2022-08-17 20:34:39 +02:00
f . write ( f " { format_text_block ( property_def . text . strip ( ) , property_def , state ) } \n \n " )
2022-07-19 17:17:04 +02:00
else :
f . write ( " .. container:: contribute \n \n \t " )
f . write (
translate (
" There is currently no description for this property. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`! "
)
+ " \n \n "
)
2019-10-24 19:06:09 +02:00
index + = 1
2018-12-28 00:37:21 +01:00
2021-11-02 20:19:40 +01:00
# Constructor, Method, Operator descriptions
if len ( class_def . constructors ) > 0 :
2022-11-30 14:34:42 +01:00
f . write ( make_separator ( True ) )
f . write ( " .. rst-class:: classref-descriptions-group \n \n " )
2021-11-02 20:19:40 +01:00
f . write ( make_heading ( " Constructor Descriptions " , " - " ) )
2022-11-30 14:34:42 +01:00
2021-11-02 20:19:40 +01:00
index = 0
for method_list in class_def . constructors . values ( ) :
for i , m in enumerate ( method_list ) :
if index != 0 :
2022-11-30 14:34:42 +01:00
f . write ( make_separator ( ) )
# Create constructor signature and anchor point.
2021-11-02 20:19:40 +01:00
if i == 0 :
2022-08-17 20:34:39 +02:00
f . write ( f " .. _class_ { class_name } _constructor_ { m . name } : \n \n " )
2021-11-02 20:19:40 +01:00
2022-11-30 14:34:42 +01:00
f . write ( " .. rst-class:: classref-constructor \n \n " )
2021-11-02 20:19:40 +01:00
ret_type , signature = make_method_signature ( class_def , m , " " , state )
2022-11-30 14:34:42 +01:00
f . write ( f " { ret_type } { signature } \n \n " )
# Add constructor description, or a call to action if it's missing.
2021-11-02 20:19:40 +01:00
if m . description is not None and m . description . strip ( ) != " " :
2022-08-17 20:34:39 +02:00
f . write ( f " { format_text_block ( m . description . strip ( ) , m , state ) } \n \n " )
2022-07-19 17:17:04 +02:00
else :
f . write ( " .. container:: contribute \n \n \t " )
f . write (
translate (
" There is currently no description for this constructor. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`! "
)
+ " \n \n "
)
2021-11-02 20:19:40 +01:00
index + = 1
2018-12-28 00:37:21 +01:00
if len ( class_def . methods ) > 0 :
2022-11-30 14:34:42 +01:00
f . write ( make_separator ( True ) )
f . write ( " .. rst-class:: classref-descriptions-group \n \n " )
2020-03-30 08:28:32 +02:00
f . write ( make_heading ( " Method Descriptions " , " - " ) )
2022-11-30 14:34:42 +01:00
2019-10-24 19:06:09 +02:00
index = 0
2018-12-28 00:37:21 +01:00
for method_list in class_def . methods . values ( ) :
for i , m in enumerate ( method_list ) :
2019-10-24 19:06:09 +02:00
if index != 0 :
2022-11-30 14:34:42 +01:00
f . write ( make_separator ( ) )
# Create method signature and anchor point.
2019-10-24 19:06:09 +02:00
2018-12-28 00:37:21 +01:00
if i == 0 :
2022-08-17 20:34:39 +02:00
f . write ( f " .. _class_ { class_name } _method_ { m . name } : \n \n " )
2019-10-24 19:06:09 +02:00
2022-11-30 14:34:42 +01:00
f . write ( " .. rst-class:: classref-method \n \n " )
2021-11-02 20:19:40 +01:00
ret_type , signature = make_method_signature ( class_def , m , " " , state )
2022-11-30 14:34:42 +01:00
f . write ( f " { ret_type } { signature } \n \n " )
# Add method description, or a call to action if it's missing.
2021-11-02 20:19:40 +01:00
if m . description is not None and m . description . strip ( ) != " " :
2022-08-17 20:34:39 +02:00
f . write ( f " { format_text_block ( m . description . strip ( ) , m , state ) } \n \n " )
2022-07-19 17:17:04 +02:00
else :
f . write ( " .. container:: contribute \n \n \t " )
f . write (
translate (
" There is currently no description for this method. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`! "
)
+ " \n \n "
)
2021-11-02 20:19:40 +01:00
index + = 1
if len ( class_def . operators ) > 0 :
2022-11-30 14:34:42 +01:00
f . write ( make_separator ( True ) )
f . write ( " .. rst-class:: classref-descriptions-group \n \n " )
2021-11-02 20:19:40 +01:00
f . write ( make_heading ( " Operator Descriptions " , " - " ) )
2022-11-30 14:34:42 +01:00
2021-11-02 20:19:40 +01:00
index = 0
for method_list in class_def . operators . values ( ) :
for i , m in enumerate ( method_list ) :
if index != 0 :
2022-11-30 14:34:42 +01:00
f . write ( make_separator ( ) )
# Create operator signature and anchor point.
operator_anchor = f " .. _class_ { class_name } _operator_ { sanitize_operator_name ( m . name , state ) } "
2022-11-18 15:41:44 +01:00
for parameter in m . parameters :
2022-11-30 14:34:42 +01:00
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 " )
2021-11-02 20:19:40 +01:00
ret_type , signature = make_method_signature ( class_def , m , " " , state )
2022-11-30 14:34:42 +01:00
f . write ( f " { ret_type } { signature } \n \n " )
# Add operator description, or a call to action if it's missing.
2018-12-28 00:37:21 +01:00
2020-03-30 08:28:32 +02:00
if m . description is not None and m . description . strip ( ) != " " :
2022-08-17 20:34:39 +02:00
f . write ( f " { format_text_block ( m . description . strip ( ) , m , state ) } \n \n " )
2022-07-19 17:17:04 +02:00
else :
f . write ( " .. container:: contribute \n \n \t " )
f . write (
translate (
" There is currently no description for this operator. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`! "
)
+ " \n \n "
)
2019-10-24 19:06:09 +02:00
index + = 1
2018-12-26 15:57:51 +01:00
2021-08-06 17:18:22 +02:00
# Theme property descriptions
2021-08-04 18:54:41 +02:00
if len ( class_def . theme_items ) > 0 :
2022-11-30 14:34:42 +01:00
f . write ( make_separator ( True ) )
f . write ( " .. rst-class:: classref-descriptions-group \n \n " )
2021-08-04 18:54:41 +02:00
f . write ( make_heading ( " Theme Property Descriptions " , " - " ) )
2022-11-30 14:34:42 +01:00
2021-08-04 18:54:41 +02:00
index = 0
for theme_item_def in class_def . theme_items . values ( ) :
if index != 0 :
2022-11-30 14:34:42 +01:00
f . write ( make_separator ( ) )
# Create theme property signature and anchor point.
2021-08-04 18:54:41 +02:00
2022-08-17 20:34:39 +02:00
f . write ( f " .. _class_ { class_name } _theme_ { theme_item_def . data_name } _ { theme_item_def . name } : \n \n " )
2022-11-30 14:34:42 +01:00
f . write ( " .. rst-class:: classref-themeproperty \n \n " )
2021-08-04 18:54:41 +02:00
2022-11-30 14:34:42 +01:00
theme_item_default = " "
2021-08-04 18:54:41 +02:00
if theme_item_def . default_value is not None :
2022-11-30 14:34:42 +01:00
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 " )
2021-08-04 18:54:41 +02:00
2022-11-30 14:34:42 +01:00
# Add theme property description, or a call to action if it's missing.
2021-08-04 18:54:41 +02:00
if theme_item_def . text is not None and theme_item_def . text . strip ( ) != " " :
2022-08-17 20:34:39 +02:00
f . write ( f " { format_text_block ( theme_item_def . text . strip ( ) , theme_item_def , state ) } \n \n " )
2022-07-19 17:17:04 +02:00
else :
f . write ( " .. container:: contribute \n \n \t " )
f . write (
translate (
" There is currently no description for this theme property. Please help us by :ref:`contributing one <doc_updating_the_class_reference>`! "
)
+ " \n \n "
)
2021-08-04 18:54:41 +02:00
index + = 1
2020-07-30 11:41:12 +02:00
f . write ( make_footer ( ) )
2018-12-26 15:57:51 +01:00
2022-08-09 22:53:29 +02:00
def make_type ( klass : str , state : State ) - > str :
if klass . find ( " * " ) != - 1 : # Pointer, ignore
return klass
link_type = klass
if link_type . endswith ( " [] " ) : # Typed array, strip [] to link to contained type.
link_type = link_type [ : - 2 ]
if link_type in state . classes :
2022-08-17 20:34:39 +02:00
return f " :ref:` { klass } <class_ { link_type } >` "
print_error ( f ' { state . current_class } .xml: Unresolved type " { klass } " . ' , state )
2022-08-09 22:53:29 +02:00
return klass
2019-11-29 14:21:16 +01:00
2023-06-15 16:06:22 +02:00
def make_enum ( t : str , is_bitfield : bool , state : State ) - > str :
2022-08-09 22:53:29 +02:00
p = t . find ( " . " )
if p > = 0 :
c = t [ 0 : p ]
e = t [ p + 1 : ]
# Variant enums live in GlobalScope but still use periods.
if c == " Variant " :
c = " @GlobalScope "
e = " Variant. " + e
else :
c = state . current_class
e = t
if c in state . classes and e not in state . classes [ c ] . enums :
c = " @GlobalScope "
2019-11-29 14:21:16 +01:00
2022-08-09 22:53:29 +02:00
if c in state . classes and e in state . classes [ c ] . enums :
2023-06-15 16:06:22 +02:00
if is_bitfield :
if not state . classes [ c ] . enums [ e ] . is_bitfield :
print_error ( f ' { state . current_class } .xml: Enum " { t } " is not bitfield. ' , state )
return f " |bitfield| \ <:ref:` { e } <enum_ { c } _ { e } >` \ > "
else :
return f " :ref:` { e } <enum_ { c } _ { e } >` "
2019-11-29 14:21:16 +01:00
2022-08-09 22:53:29 +02:00
# Don't fail for `Vector3.Axis`, as this enum is a special case which is expected not to be resolved.
2022-08-17 20:34:39 +02:00
if f " { c } . { e } " != " Vector3.Axis " :
print_error ( f ' { state . current_class } .xml: Unresolved enum " { t } " . ' , state )
2019-11-29 14:21:16 +01:00
2022-08-09 22:53:29 +02:00
return t
2020-07-22 22:41:10 +02:00
2022-08-09 22:53:29 +02:00
def make_method_signature (
class_def : ClassDef , definition : Union [ AnnotationDef , MethodDef , SignalDef ] , ref_type : str , state : State
) - > Tuple [ str , str ] :
ret_type = " "
2020-07-22 22:41:10 +02:00
2022-08-23 15:21:46 +02:00
if isinstance ( definition , MethodDef ) :
2022-08-09 22:53:29 +02:00
ret_type = definition . return_type . to_rst ( state )
2020-07-22 22:41:10 +02:00
2022-08-09 22:53:29 +02:00
qualifiers = None
2022-08-23 15:21:46 +02:00
if isinstance ( definition , ( MethodDef , AnnotationDef ) ) :
2022-08-09 22:53:29 +02:00
qualifiers = definition . qualifiers
out = " "
2022-08-23 15:21:46 +02:00
if isinstance ( definition , MethodDef ) and ref_type != " " :
2022-08-09 22:53:29 +02:00
if ref_type == " operator " :
2022-08-17 20:34:39 +02:00
op_name = definition . name . replace ( " < " , " \\ < " ) # So operator "<" gets correctly displayed.
2022-11-18 15:41:44 +01:00
out + = f " :ref:` { op_name } <class_ { class_def . name } _ { ref_type } _ { sanitize_operator_name ( definition . name , state ) } "
for parameter in definition . parameters :
out + = f " _ { parameter . type_name . type_name } "
out + = f " >` "
2022-08-09 22:53:29 +02:00
else :
2022-08-17 20:34:39 +02:00
out + = f " :ref:` { definition . name } <class_ { class_def . name } _ { ref_type } _ { definition . name } >` "
2022-08-09 22:53:29 +02:00
else :
2022-08-17 20:34:39 +02:00
out + = f " ** { definition . name } ** "
2020-07-22 22:41:10 +02:00
2022-08-09 22:53:29 +02:00
out + = " **(** "
for i , arg in enumerate ( definition . parameters ) :
if i > 0 :
out + = " , "
2020-07-22 22:41:10 +02:00
else :
2022-08-09 22:53:29 +02:00
out + = " "
2022-08-17 20:34:39 +02:00
out + = f " { arg . type_name . to_rst ( state ) } { arg . name } "
2022-08-09 22:53:29 +02:00
if arg . default_value is not None :
2022-08-17 20:34:39 +02:00
out + = f " = { arg . default_value } "
2022-08-09 22:53:29 +02:00
if qualifiers is not None and " vararg " in qualifiers :
if len ( definition . parameters ) > 0 :
out + = " , ... "
else :
out + = " ... "
out + = " **)** "
if qualifiers is not None :
# Use substitutions for abbreviations. This is used to display tooltips on hover.
# See `make_footer()` for descriptions.
for qualifier in qualifiers . split ( ) :
2022-08-17 20:34:39 +02:00
out + = f " | { qualifier } | "
2022-08-09 22:53:29 +02:00
return ret_type , out
2022-11-30 14:34:42 +01:00
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 } "
2022-08-09 22:53:29 +02:00
def make_heading ( title : str , underline : str , l10n : bool = True ) - > str :
if l10n :
new_title = translate ( title )
if new_title != title :
title = new_title
underline * = 2 # Double length to handle wide chars.
2022-08-17 20:34:39 +02:00
return f " { title } \n { ( underline * len ( title ) ) } \n \n "
2022-08-09 22:53:29 +02:00
def make_footer ( ) - > str :
# Generate reusable abbreviation substitutions.
# This way, we avoid bloating the generated rST with duplicate abbreviations.
2022-08-17 20:34:39 +02:00
virtual_msg = translate ( " This method should typically be overridden by the user to have any effect. " )
const_msg = translate ( " This method has no side effects. It doesn ' t modify any of the instance ' s member variables. " )
vararg_msg = translate ( " This method accepts any number of arguments after the ones described here. " )
constructor_msg = translate ( " This method is used to construct a type. " )
static_msg = translate (
" This method doesn ' t need an instance to be called, so it can be called directly using the class name. "
)
operator_msg = translate ( " This method describes a valid operator to use with this type as left-hand operand. " )
2023-06-15 16:06:22 +02:00
bitfield_msg = translate ( " This value is an integer composed as a bitmask of the following flags. " )
2022-08-17 20:34:39 +02:00
2022-08-09 22:53:29 +02:00
return (
2022-08-17 20:34:39 +02:00
f " .. |virtual| replace:: :abbr:`virtual ( { virtual_msg } )` \n "
f " .. |const| replace:: :abbr:`const ( { const_msg } )` \n "
f " .. |vararg| replace:: :abbr:`vararg ( { vararg_msg } )` \n "
f " .. |constructor| replace:: :abbr:`constructor ( { constructor_msg } )` \n "
f " .. |static| replace:: :abbr:`static ( { static_msg } )` \n "
f " .. |operator| replace:: :abbr:`operator ( { operator_msg } )` \n "
2023-06-15 16:06:22 +02:00
f " .. |bitfield| replace:: :abbr:`BitField ( { bitfield_msg } )` \n "
2022-08-09 22:53:29 +02:00
)
2022-11-30 14:34:42 +01:00
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 "
2022-08-09 22:53:29 +02:00
def make_link ( url : str , title : str ) - > str :
match = GODOT_DOCS_PATTERN . search ( url )
if match :
groups = match . groups ( )
if match . lastindex == 2 :
# Doc reference with fragment identifier: emit direct link to section with reference to page, for example:
# `#calling-javascript-from-script in Exporting For Web`
# Or use the title if provided.
if title != " " :
2022-08-17 20:34:39 +02:00
return f " ` { title } <../ { groups [ 0 ] } .html { groups [ 1 ] } >`__ "
return f " ` { groups [ 1 ] } <../ { groups [ 0 ] } .html { groups [ 1 ] } >`__ in :doc:`../ { groups [ 0 ] } ` "
2022-08-09 22:53:29 +02:00
elif match . lastindex == 1 :
# Doc reference, for example:
# `Math`
if title != " " :
2022-08-17 20:34:39 +02:00
return f " :doc:` { title } <../ { groups [ 0 ] } >` "
return f " :doc:`../ { groups [ 0 ] } ` "
2022-08-09 22:53:29 +02:00
# External link, for example:
# `http://enet.bespin.org/usergroup0.html`
if title != " " :
2022-08-17 20:34:39 +02:00
return f " ` { title } < { url } >`__ "
return f " ` { url } < { url } >`__ "
2022-08-09 22:53:29 +02:00
2022-07-26 18:02:09 +02:00
def make_rst_index ( grouped_classes : Dict [ str , List [ str ] ] , dry_run : bool , output_dir : str ) - > None :
if dry_run :
f = open ( os . devnull , " w " , encoding = " utf-8 " )
else :
f = open ( os . path . join ( output_dir , " index.rst " ) , " w " , encoding = " utf-8 " )
# Remove the "Edit on Github" button from the online docs page.
f . write ( " :github_url: hide \n \n " )
# Warn contributors not to edit this file directly.
# Also provide links to the source files for reference.
git_branch = get_git_branch ( )
generator_github_url = f " https://github.com/godotengine/godot/tree/ { git_branch } /doc/tools/make_rst.py "
f . write ( " .. DO NOT EDIT THIS FILE!!! \n " )
f . write ( " .. Generated automatically from Godot engine sources. \n " )
f . write ( f " .. Generator: { generator_github_url } . \n \n " )
f . write ( " .. _doc_class_reference: \n \n " )
2022-11-18 13:47:53 +01:00
main_title = translate ( " All classes " )
f . write ( f " { main_title } \n " )
f . write ( f " { ' = ' * len ( main_title ) } \n \n " )
2022-07-26 18:02:09 +02:00
for group_name in CLASS_GROUPS :
if group_name in grouped_classes :
group_title = translate ( CLASS_GROUPS [ group_name ] )
f . write ( f " { group_title } \n " )
f . write ( f " { ' = ' * len ( group_title ) } \n \n " )
f . write ( " .. toctree:: \n " )
f . write ( " :maxdepth: 1 \n " )
2022-11-18 13:47:53 +01:00
f . write ( f " :name: toc-class-ref- { group_name } s \n " )
2022-07-26 18:02:09 +02:00
f . write ( " \n " )
2022-11-18 13:47:53 +01:00
if group_name in CLASS_GROUPS_BASE :
f . write ( f " class_ { CLASS_GROUPS_BASE [ group_name ] . lower ( ) } \n " )
2022-07-26 18:02:09 +02:00
for class_name in grouped_classes [ group_name ] :
2023-04-24 21:06:55 +02:00
if group_name in CLASS_GROUPS_BASE and CLASS_GROUPS_BASE [ group_name ] . lower ( ) == class_name . lower ( ) :
continue
2022-07-26 18:02:09 +02:00
f . write ( f " class_ { class_name . lower ( ) } \n " )
f . write ( " \n " )
2022-08-09 22:53:29 +02:00
# Formatting helpers.
RESERVED_FORMATTING_TAGS = [ " i " , " b " , " u " , " code " , " kbd " , " center " , " url " , " br " ]
RESERVED_CODEBLOCK_TAGS = [ " codeblocks " , " codeblock " , " gdscript " , " csharp " ]
RESERVED_CROSSLINK_TAGS = [ " method " , " member " , " signal " , " constant " , " enum " , " annotation " , " theme_item " , " param " ]
def is_in_tagset ( tag_text : str , tagset : List [ str ] ) - > bool :
for tag in tagset :
# Complete match.
if tag_text == tag :
return True
# Tag with arguments.
if tag_text . startswith ( tag + " " ) :
return True
# Tag with arguments, special case for [url].
if tag_text . startswith ( tag + " = " ) :
return True
2020-07-22 22:41:10 +02:00
2022-08-09 22:53:29 +02:00
return False
2020-07-22 22:41:10 +02:00
2022-08-09 22:53:29 +02:00
def format_text_block (
2022-08-06 19:48:54 +02:00
text : str ,
2022-08-09 22:53:29 +02:00
context : Union [ DefinitionBase , None ] ,
2022-08-06 19:48:54 +02:00
state : State ,
) - > str :
2016-10-30 18:44:57 +01:00
# Linebreak + tabs in the XML should become two line breaks unless in a "codeblock"
pos = 0
while True :
2020-03-30 08:28:32 +02:00
pos = text . find ( " \n " , pos )
2016-10-30 18:44:57 +01:00
if pos == - 1 :
break
pre_text = text [ : pos ]
2019-09-28 02:21:58 +02:00
indent_level = 0
2022-05-04 22:27:09 +02:00
while pos + 1 < len ( text ) and text [ pos + 1 ] == " \t " :
2016-10-30 18:44:57 +01:00
pos + = 1
2019-09-28 02:21:58 +02:00
indent_level + = 1
2020-03-30 08:28:32 +02:00
post_text = text [ pos + 1 : ]
2016-10-30 18:44:57 +01:00
# Handle codeblocks
2020-07-22 22:41:10 +02:00
if (
post_text . startswith ( " [codeblock] " )
or post_text . startswith ( " [gdscript] " )
or post_text . startswith ( " [csharp] " )
) :
block_type = post_text [ 1 : ] . split ( " ] " ) [ 0 ]
result = format_codeblock ( block_type , post_text , indent_level , state )
if result is None :
2018-12-28 00:37:21 +01:00
return " "
2022-08-17 20:34:39 +02:00
text = f " { pre_text } { result [ 0 ] } "
2021-12-21 09:35:47 +01:00
pos + = result [ 1 ] - indent_level
2016-10-30 18:44:57 +01:00
# Handle normal text
else :
2022-08-17 20:34:39 +02:00
text = f " { pre_text } \n \n { post_text } "
2021-12-21 09:35:47 +01:00
pos + = 2 - indent_level
2016-10-30 18:44:57 +01:00
2020-03-30 08:28:32 +02:00
next_brac_pos = text . find ( " [ " )
2019-11-29 14:21:16 +01:00
text = escape_rst ( text , next_brac_pos )
2016-10-30 18:44:57 +01:00
2022-08-09 22:53:29 +02:00
context_name = format_context_name ( context )
2016-10-30 18:44:57 +01:00
# Handle [tags]
2017-07-22 16:22:38 +02:00
inside_code = False
2022-08-09 22:53:29 +02:00
inside_code_tag = " "
inside_code_tabs = False
2016-10-30 18:44:57 +01:00
pos = 0
2018-12-28 00:37:21 +01:00
tag_depth = 0
2016-10-30 18:44:57 +01:00
while True :
2020-03-30 08:28:32 +02:00
pos = text . find ( " [ " , pos )
2016-10-30 18:44:57 +01:00
if pos == - 1 :
break
2020-03-30 08:28:32 +02:00
endq_pos = text . find ( " ] " , pos + 1 )
2016-10-30 18:44:57 +01:00
if endq_pos == - 1 :
break
pre_text = text [ : pos ]
2020-03-30 08:28:32 +02:00
post_text = text [ endq_pos + 1 : ]
tag_text = text [ pos + 1 : endq_pos ]
2016-10-30 18:44:57 +01:00
2021-12-21 09:35:47 +01:00
escape_pre = False
2017-10-21 12:33:50 +02:00
escape_post = False
2022-08-09 22:53:29 +02:00
# Tag is a reference to a class.
2022-12-05 22:58:47 +01:00
if tag_text in state . classes and not inside_code :
2019-03-29 23:37:35 +01:00
if tag_text == state . current_class :
2022-11-30 14:34:42 +01:00
# Don't create a link to the same class, format it as strong emphasis.
tag_text = f " ** { tag_text } ** "
2019-03-29 23:37:35 +01:00
else :
tag_text = make_type ( tag_text , state )
2021-12-21 09:35:47 +01:00
escape_pre = True
2017-10-21 12:33:50 +02:00
escape_post = True
2022-08-09 22:53:29 +02:00
2022-08-17 20:34:39 +02:00
# Tag is a cross-reference or a formatting directive.
2022-08-09 22:53:29 +02:00
else :
2016-10-30 18:44:57 +01:00
cmd = tag_text
2020-03-30 08:28:32 +02:00
space_pos = tag_text . find ( " " )
2022-08-06 19:48:54 +02:00
2022-08-09 22:53:29 +02:00
# Anything identified as a tag inside of a code block is valid,
# unless it's a matching closing tag.
if inside_code :
# Exiting codeblocks and inline code tags.
if inside_code_tag == cmd [ 1 : ] :
if cmd == " /codeblock " or cmd == " /gdscript " or cmd == " /csharp " :
tag_text = " "
tag_depth - = 1
inside_code = False
# Strip newline if the tag was alone on one
if pre_text [ - 1 ] == " \n " :
pre_text = pre_text [ : - 1 ]
elif cmd == " /code " :
tag_text = " `` "
tag_depth - = 1
inside_code = False
escape_post = True
2018-12-28 00:37:21 +01:00
else :
2022-08-09 22:53:29 +02:00
if cmd . startswith ( " / " ) :
print_warning (
2022-08-17 20:34:39 +02:00
f ' { state . current_class } .xml: Potential error inside of a code tag, found a string that looks like a closing tag " [ { cmd } ] " in { context_name } . ' ,
2022-08-09 22:53:29 +02:00
state ,
)
2018-12-28 00:37:21 +01:00
2022-08-17 20:34:39 +02:00
tag_text = f " [ { tag_text } ] "
2018-12-28 00:37:21 +01:00
2022-08-09 22:53:29 +02:00
# Entering codeblocks and inline code tags.
2018-12-28 00:37:21 +01:00
2022-08-09 22:53:29 +02:00
elif cmd == " codeblocks " :
tag_depth + = 1
tag_text = " \n .. tabs:: "
inside_code_tabs = True
elif cmd == " /codeblocks " :
tag_depth - = 1
tag_text = " "
inside_code_tabs = False
2022-08-06 19:48:54 +02:00
2022-08-09 22:53:29 +02:00
elif cmd == " codeblock " or cmd == " gdscript " or cmd == " csharp " :
tag_depth + = 1
2022-08-06 19:48:54 +02:00
2022-08-09 22:53:29 +02:00
if cmd == " gdscript " :
if not inside_code_tabs :
print_error (
2022-08-17 20:34:39 +02:00
f " { state . current_class } .xml: GDScript code block is used outside of [codeblocks] in { context_name } . " ,
2022-08-09 22:53:29 +02:00
state ,
)
tag_text = " \n .. code-tab:: gdscript \n "
elif cmd == " csharp " :
if not inside_code_tabs :
print_error (
2022-08-17 20:34:39 +02:00
f " { state . current_class } .xml: C# code block is used outside of [codeblocks] in { context_name } . " ,
2022-08-09 22:53:29 +02:00
state ,
)
tag_text = " \n .. code-tab:: csharp \n "
else :
tag_text = " \n :: \n "
inside_code = True
inside_code_tag = cmd
elif cmd == " code " :
tag_text = " `` "
tag_depth + = 1
inside_code = True
inside_code_tag = cmd
escape_pre = True
2023-04-26 21:36:04 +02:00
valid_context = isinstance ( context , ( MethodDef , SignalDef , AnnotationDef ) )
if valid_context :
endcode_pos = text . find ( " [/code] " , endq_pos + 1 )
if endcode_pos == - 1 :
print_error (
f " { state . current_class } .xml: Tag depth mismatch for [code]: no closing [/code] in { context_name } . " ,
state ,
)
break
inside_code_text = text [ endq_pos + 1 : endcode_pos ]
context_params : List [ ParameterDef ] = context . parameters # type: ignore
for param_def in context_params :
if param_def . name == inside_code_text :
print_warning (
f ' { state . current_class } .xml: Potential error inside of a code tag, found a string " { inside_code_text } " that matches one of the parameters in { context_name } . ' ,
state ,
)
break
2022-08-09 22:53:29 +02:00
# Cross-references to items in this or other class documentation pages.
elif is_in_tagset ( cmd , RESERVED_CROSSLINK_TAGS ) :
2022-08-26 18:04:11 +02:00
link_type : str = " "
2022-08-09 22:53:29 +02:00
link_target : str = " "
if space_pos > = 0 :
2022-08-26 18:04:11 +02:00
link_type = tag_text [ : space_pos ]
2022-08-09 22:53:29 +02:00
link_target = tag_text [ space_pos + 1 : ] . strip ( )
if link_target == " " :
2022-08-06 19:48:54 +02:00
print_error (
2022-08-17 20:34:39 +02:00
f ' { state . current_class } .xml: Empty cross-reference link " { cmd } " in { context_name } . ' ,
2022-08-06 19:48:54 +02:00
state ,
)
2022-08-09 22:53:29 +02:00
tag_text = " "
2022-08-06 19:48:54 +02:00
else :
2022-08-09 22:53:29 +02:00
if (
cmd . startswith ( " method " )
2022-08-26 18:04:11 +02:00
or cmd . startswith ( " constructor " )
or cmd . startswith ( " operator " )
2022-08-09 22:53:29 +02:00
or cmd . startswith ( " member " )
or cmd . startswith ( " signal " )
or cmd . startswith ( " annotation " )
or cmd . startswith ( " theme_item " )
2022-08-26 18:04:11 +02:00
or cmd . startswith ( " constant " )
2022-08-09 22:53:29 +02:00
) :
if link_target . find ( " . " ) != - 1 :
ss = link_target . split ( " . " )
if len ( ss ) > 2 :
print_error (
2022-08-17 20:34:39 +02:00
f ' { state . current_class } .xml: Bad reference " { link_target } " in { context_name } . ' ,
2022-08-09 22:53:29 +02:00
state ,
)
class_param , method_param = ss
else :
class_param = state . current_class
method_param = link_target
2022-08-26 18:04:11 +02:00
# Default to the tag command name. This works by default for most tags,
# but member and theme_item have special cases.
ref_type = " _ {} " . format ( link_type )
if link_type == " member " :
ref_type = " _property "
2022-08-09 22:53:29 +02:00
if class_param in state . classes :
class_def = state . classes [ class_param ]
2022-08-26 18:04:11 +02:00
if cmd . startswith ( " method " ) and method_param not in class_def . methods :
print_error (
2022-08-17 20:34:39 +02:00
f ' { state . current_class } .xml: Unresolved method reference " { link_target } " in { context_name } . ' ,
2022-08-26 18:04:11 +02:00
state ,
)
2022-08-09 22:53:29 +02:00
2022-08-26 18:04:11 +02:00
elif cmd . startswith ( " constructor " ) and method_param not in class_def . constructors :
print_error (
2022-08-17 20:34:39 +02:00
f ' { state . current_class } .xml: Unresolved constructor reference " { link_target } " in { context_name } . ' ,
2022-08-26 18:04:11 +02:00
state ,
)
2022-08-09 22:53:29 +02:00
2022-08-26 18:04:11 +02:00
elif cmd . startswith ( " operator " ) and method_param not in class_def . operators :
print_error (
2022-08-17 20:34:39 +02:00
f ' { state . current_class } .xml: Unresolved operator reference " { link_target } " in { context_name } . ' ,
2022-08-26 18:04:11 +02:00
state ,
)
elif cmd . startswith ( " member " ) and method_param not in class_def . properties :
print_error (
2022-08-17 20:34:39 +02:00
f ' { state . current_class } .xml: Unresolved member reference " { link_target } " in { context_name } . ' ,
2022-08-26 18:04:11 +02:00
state ,
)
elif cmd . startswith ( " signal " ) and method_param not in class_def . signals :
print_error (
2022-08-17 20:34:39 +02:00
f ' { state . current_class } .xml: Unresolved signal reference " { link_target } " in { context_name } . ' ,
2022-08-26 18:04:11 +02:00
state ,
)
elif cmd . startswith ( " annotation " ) and method_param not in class_def . annotations :
print_error (
2022-08-17 20:34:39 +02:00
f ' { state . current_class } .xml: Unresolved annotation reference " { link_target } " in { context_name } . ' ,
2022-08-26 18:04:11 +02:00
state ,
)
2022-08-09 22:53:29 +02:00
elif cmd . startswith ( " theme_item " ) :
if method_param not in class_def . theme_items :
print_error (
2022-08-17 20:34:39 +02:00
f ' { state . current_class } .xml: Unresolved theme item reference " { link_target } " in { context_name } . ' ,
2022-08-09 22:53:29 +02:00
state ,
)
2022-08-26 18:04:11 +02:00
else :
# Needs theme data type to be properly linked, which we cannot get without a class.
2022-08-17 20:34:39 +02:00
name = class_def . theme_items [ method_param ] . data_name
ref_type = f " _theme_ { name } "
2022-08-09 22:53:29 +02:00
elif cmd . startswith ( " constant " ) :
found = False
# Search in the current class
search_class_defs = [ class_def ]
if link_target . find ( " . " ) == - 1 :
# Also search in @GlobalScope as a last resort if no class was specified
search_class_defs . append ( state . classes [ " @GlobalScope " ] )
for search_class_def in search_class_defs :
if method_param in search_class_def . constants :
class_param = search_class_def . name
found = True
2022-08-06 19:48:54 +02:00
2022-08-09 22:53:29 +02:00
else :
for enum in search_class_def . enums . values ( ) :
if method_param in enum . values :
class_param = search_class_def . name
found = True
break
if not found :
print_error (
2022-08-17 20:34:39 +02:00
f ' { state . current_class } .xml: Unresolved constant reference " { link_target } " in { context_name } . ' ,
2022-08-09 22:53:29 +02:00
state ,
)
else :
print_error (
2022-08-17 20:34:39 +02:00
f ' { state . current_class } .xml: Unresolved type reference " { class_param } " in method reference " { link_target } " in { context_name } . ' ,
2022-08-09 22:53:29 +02:00
state ,
)
repl_text = method_param
if class_param != state . current_class :
2022-08-17 20:34:39 +02:00
repl_text = f " { class_param } . { method_param } "
tag_text = f " :ref:` { repl_text } <class_ { class_param } { ref_type } _ { method_param } >` "
2022-08-09 22:53:29 +02:00
escape_pre = True
escape_post = True
elif cmd . startswith ( " enum " ) :
2023-06-15 16:06:22 +02:00
tag_text = make_enum ( link_target , False , state )
2022-08-09 22:53:29 +02:00
escape_pre = True
escape_post = True
elif cmd . startswith ( " param " ) :
2022-08-23 15:21:46 +02:00
valid_context = isinstance ( context , ( MethodDef , SignalDef , AnnotationDef ) )
2022-08-09 22:53:29 +02:00
if not valid_context :
2022-08-06 19:48:54 +02:00
print_error (
2022-08-17 20:34:39 +02:00
f ' { state . current_class } .xml: Argument reference " { link_target } " used outside of method, signal, or annotation context in { context_name } . ' ,
2022-08-06 19:48:54 +02:00
state ,
)
2022-08-09 22:53:29 +02:00
else :
2022-08-23 15:21:46 +02:00
context_params : List [ ParameterDef ] = context . parameters # type: ignore
2022-08-09 22:53:29 +02:00
found = False
for param_def in context_params :
if param_def . name == link_target :
found = True
break
if not found :
print_error (
2022-08-17 20:34:39 +02:00
f ' { state . current_class } .xml: Unresolved argument reference " { link_target } " in { context_name } . ' ,
2022-08-09 22:53:29 +02:00
state ,
)
2022-08-17 20:34:39 +02:00
tag_text = f " `` { link_target } `` "
2022-09-12 21:51:39 +02:00
escape_pre = True
escape_post = True
2022-08-09 22:53:29 +02:00
# Formatting directives.
elif is_in_tagset ( cmd , [ " url " ] ) :
if cmd . startswith ( " url= " ) :
# URLs are handled in full here as we need to extract the optional link
# title to use `make_link`.
link_url = cmd [ 4 : ]
endurl_pos = text . find ( " [/url] " , endq_pos + 1 )
if endurl_pos == - 1 :
print_error (
2022-08-17 20:34:39 +02:00
f " { state . current_class } .xml: Tag depth mismatch for [url]: no closing [/url] in { context_name } . " ,
2022-08-09 22:53:29 +02:00
state ,
)
break
link_title = text [ endq_pos + 1 : endurl_pos ]
tag_text = make_link ( link_url , link_title )
2022-08-06 19:48:54 +02:00
2022-08-09 22:53:29 +02:00
pre_text = text [ : pos ]
post_text = text [ endurl_pos + 6 : ]
if pre_text and pre_text [ - 1 ] not in MARKUP_ALLOWED_PRECEDENT :
pre_text + = " \ "
if post_text and post_text [ 0 ] not in MARKUP_ALLOWED_SUBSEQUENT :
post_text = " \ " + post_text
text = pre_text + tag_text + post_text
pos = len ( pre_text ) + len ( tag_text )
continue
2022-08-06 19:48:54 +02:00
else :
2021-11-15 10:39:00 +01:00
print_error (
2022-08-17 20:34:39 +02:00
f ' { state . current_class } .xml: Misformatted [url] tag " { cmd } " in { context_name } . ' ,
2022-08-09 22:53:29 +02:00
state ,
2021-11-15 10:39:00 +01:00
)
2021-12-21 09:35:47 +01:00
2020-03-30 08:28:32 +02:00
elif cmd == " br " :
2016-10-30 18:44:57 +01:00
# Make a new paragraph instead of a linebreak, rst is not so linebreak friendly
2020-03-30 08:28:32 +02:00
tag_text = " \n \n "
2016-10-30 18:44:57 +01:00
# Strip potential leading spaces
2020-03-30 08:28:32 +02:00
while post_text [ 0 ] == " " :
2016-10-30 18:44:57 +01:00
post_text = post_text [ 1 : ]
2022-08-09 22:53:29 +02:00
elif cmd == " center " or cmd == " /center " :
if cmd == " /center " :
tag_depth - = 1
else :
tag_depth + = 1
tag_text = " "
2020-03-30 08:28:32 +02:00
elif cmd == " i " or cmd == " /i " :
2018-12-28 00:37:21 +01:00
if cmd == " /i " :
tag_depth - = 1
2021-12-21 09:35:47 +01:00
escape_post = True
2018-12-28 00:37:21 +01:00
else :
tag_depth + = 1
2021-12-21 09:35:47 +01:00
escape_pre = True
2020-03-30 08:28:32 +02:00
tag_text = " * "
2022-08-09 22:53:29 +02:00
2020-03-30 08:28:32 +02:00
elif cmd == " b " or cmd == " /b " :
2018-12-28 00:37:21 +01:00
if cmd == " /b " :
tag_depth - = 1
2021-12-21 09:35:47 +01:00
escape_post = True
2018-12-28 00:37:21 +01:00
else :
tag_depth + = 1
2021-12-21 09:35:47 +01:00
escape_pre = True
2020-03-30 08:28:32 +02:00
tag_text = " ** "
2022-08-09 22:53:29 +02:00
2020-03-30 08:28:32 +02:00
elif cmd == " u " or cmd == " /u " :
2018-12-28 00:37:21 +01:00
if cmd == " /u " :
tag_depth - = 1
2021-12-21 09:35:47 +01:00
escape_post = True
2018-12-28 00:37:21 +01:00
else :
tag_depth + = 1
2021-12-21 09:35:47 +01:00
escape_pre = True
2020-03-30 08:28:32 +02:00
tag_text = " "
2022-08-09 22:53:29 +02:00
elif cmd == " kbd " or cmd == " /kbd " :
2020-03-10 11:41:36 +01:00
tag_text = " ` "
2022-08-09 22:53:29 +02:00
if cmd == " /kbd " :
tag_depth - = 1
escape_post = True
else :
tag_text = " :kbd: " + tag_text
tag_depth + = 1
escape_pre = True
# Invalid syntax checks.
elif cmd . startswith ( " / " ) :
2022-08-17 20:34:39 +02:00
print_error ( f ' { state . current_class } .xml: Unrecognized closing tag " { cmd } " in { context_name } . ' , state )
2022-08-09 22:53:29 +02:00
2022-08-17 20:34:39 +02:00
tag_text = f " [ { tag_text } ] "
2022-08-09 22:53:29 +02:00
2016-10-30 18:44:57 +01:00
else :
2022-08-17 20:34:39 +02:00
print_error ( f ' { state . current_class } .xml: Unrecognized opening tag " { cmd } " in { context_name } . ' , state )
2022-08-09 22:53:29 +02:00
2022-08-17 20:34:39 +02:00
tag_text = f " `` { tag_text } `` "
2021-12-21 09:35:47 +01:00
escape_pre = True
2017-10-21 12:33:50 +02:00
escape_post = True
# Properly escape things like `[Node]s`
2021-12-21 09:35:47 +01:00
if escape_pre and pre_text and pre_text [ - 1 ] not in MARKUP_ALLOWED_PRECEDENT :
pre_text + = " \ "
if escape_post and post_text and post_text [ 0 ] not in MARKUP_ALLOWED_SUBSEQUENT :
2020-03-30 08:28:32 +02:00
post_text = " \ " + post_text
2016-10-30 18:44:57 +01:00
2020-03-30 08:28:32 +02:00
next_brac_pos = post_text . find ( " [ " , 0 )
2018-02-15 21:54:05 +01:00
iter_pos = 0
while not inside_code :
2020-03-30 08:28:32 +02:00
iter_pos = post_text . find ( " * " , iter_pos , next_brac_pos )
2018-02-15 21:54:05 +01:00
if iter_pos == - 1 :
break
2022-08-17 20:34:39 +02:00
post_text = f " { post_text [ : iter_pos ] } \ * { post_text [ iter_pos + 1 : ] } "
2018-02-15 21:54:05 +01:00
iter_pos + = 2
iter_pos = 0
while not inside_code :
2020-03-30 08:28:32 +02:00
iter_pos = post_text . find ( " _ " , iter_pos , next_brac_pos )
2018-02-15 21:54:05 +01:00
if iter_pos == - 1 :
break
if not post_text [ iter_pos + 1 ] . isalnum ( ) : # don't escape within a snake_case word
2022-08-17 20:34:39 +02:00
post_text = f " { post_text [ : iter_pos ] } \ _ { post_text [ iter_pos + 1 : ] } "
2018-02-15 21:54:05 +01:00
iter_pos + = 2
else :
iter_pos + = 1
2016-10-30 18:44:57 +01:00
text = pre_text + tag_text + post_text
pos = len ( pre_text ) + len ( tag_text )
2016-02-07 00:22:49 +01:00
2018-12-28 00:37:21 +01:00
if tag_depth > 0 :
2022-04-11 00:43:44 +02:00
print_error (
2022-08-17 20:34:39 +02:00
f " { state . current_class } .xml: Tag depth mismatch: too many (or too few) open/close tags in { context_name } . " ,
2022-08-09 22:53:29 +02:00
state ,
2022-04-11 00:43:44 +02:00
)
2018-12-28 00:37:21 +01:00
2016-10-30 18:44:57 +01:00
return text
2016-02-07 00:22:49 +01:00
2022-08-09 22:53:29 +02:00
def format_context_name ( context : Union [ DefinitionBase , None ] ) - > str :
context_name : str = " unknown context "
if context is not None :
2022-08-17 20:34:39 +02:00
context_name = f ' { context . definition_name } " { context . name } " description '
2022-08-09 22:53:29 +02:00
return context_name
def escape_rst ( text : str , until_pos : int = - 1 ) - > str :
# Escape \ character, otherwise it ends up as an escape character in rst
pos = 0
while True :
pos = text . find ( " \\ " , pos , until_pos )
if pos == - 1 :
break
2022-08-17 20:34:39 +02:00
text = f " { text [ : pos ] } \\ \\ { text [ pos + 1 : ] } "
2022-08-09 22:53:29 +02:00
pos + = 2
# Escape * character to avoid interpreting it as emphasis
pos = 0
while True :
pos = text . find ( " * " , pos , until_pos )
if pos == - 1 :
break
2022-08-17 20:34:39 +02:00
text = f " { text [ : pos ] } \ * { text [ pos + 1 : ] } "
2022-08-09 22:53:29 +02:00
pos + = 2
# Escape _ character at the end of a word to avoid interpreting it as an inline hyperlink
pos = 0
while True :
pos = text . find ( " _ " , pos , until_pos )
if pos == - 1 :
break
if not text [ pos + 1 ] . isalnum ( ) : # don't escape within a snake_case word
2022-08-17 20:34:39 +02:00
text = f " { text [ : pos ] } \ _ { text [ pos + 1 : ] } "
2022-08-09 22:53:29 +02:00
pos + = 2
else :
pos + = 1
return text
def format_codeblock ( code_type : str , post_text : str , indent_level : int , state : State ) - > Union [ Tuple [ str , int ] , None ] :
end_pos = post_text . find ( " [/ " + code_type + " ] " )
if end_pos == - 1 :
2022-08-17 20:34:39 +02:00
print_error ( f " { state . current_class } .xml: [ { code_type } ] without a closing tag. " , state )
2022-08-09 22:53:29 +02:00
return None
2022-08-17 20:34:39 +02:00
code_text = post_text [ len ( f " [ { code_type } ] " ) : end_pos ]
2022-08-09 22:53:29 +02:00
post_text = post_text [ end_pos : ]
# Remove extraneous tabs
code_pos = 0
while True :
code_pos = code_text . find ( " \n " , code_pos )
if code_pos == - 1 :
break
to_skip = 0
while code_pos + to_skip + 1 < len ( code_text ) and code_text [ code_pos + to_skip + 1 ] == " \t " :
to_skip + = 1
if to_skip > indent_level :
print_error (
2022-08-17 20:34:39 +02:00
f " { state . current_class } .xml: Four spaces should be used for indentation within [ { code_type } ]. " ,
2022-08-09 22:53:29 +02:00
state ,
)
if len ( code_text [ code_pos + to_skip + 1 : ] ) == 0 :
2022-08-17 20:34:39 +02:00
code_text = f " { code_text [ : code_pos ] } \n "
2022-08-09 22:53:29 +02:00
code_pos + = 1
else :
2022-08-17 20:34:39 +02:00
code_text = f " { code_text [ : code_pos ] } \n { code_text [ code_pos + to_skip + 1 : ] } "
2022-08-09 22:53:29 +02:00
code_pos + = 5 - to_skip
2022-08-17 20:34:39 +02:00
return ( f " \n [ { code_type } ] { code_text } { post_text } " , len ( f " \n [ { code_type } ] { code_text } " ) )
2022-08-09 22:53:29 +02:00
2022-07-28 00:54:26 +02:00
def format_table ( f : TextIO , data : List [ Tuple [ Optional [ str ] , . . . ] ] , remove_empty_columns : bool = False ) - > None :
2019-06-01 15:42:22 +02:00
if len ( data ) == 0 :
return
2019-10-24 19:06:09 +02:00
2022-11-30 14:34:42 +01:00
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.
2019-06-01 15:42:22 +02:00
column_sizes = [ 0 ] * len ( data [ 0 ] )
for row in data :
for i , text in enumerate ( row ) :
2020-03-30 08:28:32 +02:00
text_length = len ( text or " " )
2019-06-01 15:42:22 +02:00
if text_length > column_sizes [ i ] :
column_sizes [ i ] = text_length
2022-11-30 14:34:42 +01:00
# 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.
2019-06-01 15:42:22 +02:00
sep = " "
for size in column_sizes :
if size == 0 and remove_empty_columns :
continue
2022-11-30 14:34:42 +01:00
sep + = " + " + " - " * ( size + 2 ) # Content of each cell is padded by 1 on each side.
2018-09-13 02:00:26 +02:00
sep + = " + \n "
2019-10-24 19:06:09 +02:00
2022-11-30 14:34:42 +01:00
# Draw the first separator.
f . write ( f " { sep } " )
# Draw each row and close it with a separator.
2019-06-01 15:42:22 +02:00
for row in data :
row_text = " | "
for i , text in enumerate ( row ) :
if column_sizes [ i ] == 0 and remove_empty_columns :
continue
2022-08-17 20:34:39 +02:00
row_text + = f ' { ( text or " " ) . ljust ( column_sizes [ i ] ) } | '
2019-06-01 15:42:22 +02:00
row_text + = " \n "
2022-11-30 14:34:42 +01:00
f . write ( f " { row_text } " )
f . write ( f " { sep } " )
2020-03-30 08:28:32 +02:00
f . write ( " \n " )
2018-09-13 02:00:26 +02:00
2022-07-28 00:54:26 +02:00
def sanitize_operator_name ( dirty_name : str , state : State ) - > str :
2021-11-02 20:19:40 +01:00
clear_name = dirty_name . replace ( " operator " , " " )
if clear_name == " != " :
clear_name = " neq "
elif clear_name == " == " :
clear_name = " eq "
elif clear_name == " < " :
clear_name = " lt "
elif clear_name == " <= " :
clear_name = " lte "
elif clear_name == " > " :
clear_name = " gt "
elif clear_name == " >= " :
clear_name = " gte "
elif clear_name == " + " :
clear_name = " sum "
elif clear_name == " - " :
clear_name = " dif "
elif clear_name == " * " :
clear_name = " mul "
elif clear_name == " / " :
clear_name = " div "
elif clear_name == " % " :
clear_name = " mod "
2022-03-07 18:25:21 +01:00
elif clear_name == " ** " :
clear_name = " pow "
2021-11-02 20:19:40 +01:00
elif clear_name == " unary+ " :
clear_name = " unplus "
elif clear_name == " unary- " :
clear_name = " unminus "
elif clear_name == " << " :
clear_name = " bwsl "
elif clear_name == " >> " :
clear_name = " bwsr "
elif clear_name == " & " :
clear_name = " bwand "
elif clear_name == " | " :
clear_name = " bwor "
elif clear_name == " ^ " :
clear_name = " bwxor "
elif clear_name == " ~ " :
clear_name = " bwnot "
elif clear_name == " [] " :
clear_name = " idx "
else :
clear_name = " xxx "
2022-08-17 20:34:39 +02:00
print_error ( f ' Unsupported operator type " { dirty_name } " , please add the missing rule. ' , state )
2021-11-02 20:19:40 +01:00
return clear_name
2020-03-30 08:28:32 +02:00
if __name__ == " __main__ " :
2018-12-26 15:57:51 +01:00
main ( )