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
2018-06-11 13:35:44 +02:00
import re
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
2016-02-07 00:22:49 +01:00
2018-12-26 15:57:51 +01:00
# Uncomment to do type checks. I have it commented out so it works below Python 3.5
2020-03-30 08:28:32 +02:00
# from typing import List, Dict, TextIO, Tuple, Iterable, Optional, DefaultDict, Any, Union
2016-02-07 00:22:49 +01:00
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
2021-12-21 09:35:47 +01:00
# Based on reStructedText inline markup recognition rules
# 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
# `doc/translations/extract.py` to include them in the source POT file.
BASE_STRINGS = [
2021-12-21 10:53:07 +01:00
" Description " ,
" Tutorials " ,
" Properties " ,
" Constructors " ,
" Methods " ,
" Operators " ,
" Theme Properties " ,
" Signals " ,
" Enumerations " ,
" Constants " ,
" 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. " ,
2021-12-21 10:53:07 +01:00
]
2021-12-22 16:35:08 +01:00
strings_l10n = { }
2021-12-21 10:53:07 +01:00
2018-06-11 13:35:44 +02:00
2018-12-28 00:37:21 +01:00
def print_error ( error , state ) : # type: (str, State) -> None
2019-12-04 08:41:38 +01:00
print ( " ERROR: {} " . format ( error ) )
2022-04-11 00:43:44 +02:00
state . num_errors + = 1
2018-12-28 00:37:21 +01:00
class TypeName :
def __init__ ( self , type_name , enum = None ) : # type: (str, Optional[str]) -> None
self . type_name = type_name
self . enum = enum
def to_rst ( self , state ) : # type: ("State") -> str
if self . enum is not None :
return make_enum ( self . enum , state )
elif self . type_name == " void " :
return " void "
else :
return make_type ( self . type_name , state )
@classmethod
def from_element ( cls , element ) : # type: (ET.Element) -> "TypeName"
return cls ( element . attrib [ " type " ] , element . get ( " enum " ) )
class PropertyDef :
2020-03-30 08:28:32 +02:00
def __init__ (
2021-12-02 20:38:49 +01:00
self , name , type_name , setter , getter , text , default_value , overrides
) : # type: (str, TypeName, Optional[str], Optional[str], Optional[str], Optional[str], Optional[str]) -> None
2018-12-28 00:37:21 +01:00
self . name = name
self . type_name = type_name
self . setter = setter
self . getter = getter
self . text = text
2019-06-01 15:42:22 +02:00
self . default_value = default_value
2021-12-02 20:38:49 +01:00
self . overrides = overrides
2018-12-28 00:37:21 +01:00
2020-03-30 08:28:32 +02:00
2018-12-28 00:37:21 +01:00
class ParameterDef :
def __init__ ( self , name , type_name , default_value ) : # type: (str, TypeName, Optional[str]) -> None
self . name = name
self . type_name = type_name
self . default_value = default_value
class SignalDef :
def __init__ ( self , name , parameters , description ) : # type: (str, List[ParameterDef], Optional[str]) -> None
self . name = name
self . parameters = parameters
self . description = description
class MethodDef :
2020-03-30 08:28:32 +02:00
def __init__ (
self , name , return_type , parameters , description , qualifiers
) : # type: (str, TypeName, List[ParameterDef], Optional[str], Optional[str]) -> None
2018-12-28 00:37:21 +01:00
self . name = name
self . return_type = return_type
self . parameters = parameters
self . description = description
self . qualifiers = qualifiers
class ConstantDef :
def __init__ ( self , name , value , text ) : # type: (str, str, Optional[str]) -> None
self . name = name
self . value = value
self . text = text
class EnumDef :
def __init__ ( self , name ) : # type: (str) -> None
self . name = name
self . values = OrderedDict ( ) # type: OrderedDict[str, ConstantDef]
class ThemeItemDef :
2021-08-04 18:54:41 +02:00
def __init__ (
self , name , type_name , data_name , text , default_value
) : # type: (str, TypeName, str, Optional[str], Optional[str]) -> None
2018-12-28 00:37:21 +01:00
self . name = name
self . type_name = type_name
2021-08-04 18:54:41 +02:00
self . data_name = data_name
self . text = text
2019-06-01 15:42:22 +02:00
self . default_value = default_value
2018-12-28 00:37:21 +01:00
class ClassDef :
def __init__ ( self , name ) : # type: (str) -> None
self . name = name
self . constants = OrderedDict ( ) # type: OrderedDict[str, ConstantDef]
self . enums = OrderedDict ( ) # type: OrderedDict[str, EnumDef]
self . properties = OrderedDict ( ) # type: OrderedDict[str, PropertyDef]
2021-11-02 20:19:40 +01:00
self . constructors = OrderedDict ( ) # type: OrderedDict[str, List[MethodDef]]
2018-12-28 00:37:21 +01:00
self . methods = OrderedDict ( ) # type: OrderedDict[str, List[MethodDef]]
2021-11-02 20:19:40 +01:00
self . operators = OrderedDict ( ) # type: OrderedDict[str, List[MethodDef]]
2018-12-28 00:37:21 +01:00
self . signals = OrderedDict ( ) # type: OrderedDict[str, SignalDef]
2021-08-04 18:54:41 +02:00
self . theme_items = OrderedDict ( ) # type: OrderedDict[str, ThemeItemDef]
2018-12-28 00:37:21 +01:00
self . inherits = None # type: Optional[str]
self . brief_description = None # type: Optional[str]
self . description = None # type: Optional[str]
2021-07-29 21:55:39 +02:00
self . tutorials = [ ] # type: List[Tuple[str, str]]
2018-12-28 00:37:21 +01:00
2021-02-14 02:10:39 +01:00
# Used to match the class with XML source for output filtering purposes.
self . filepath = " " # type: str
2018-12-28 00:37:21 +01:00
class State :
def __init__ ( self ) : # type: () -> None
2022-04-11 00:43:44 +02:00
self . num_errors = 0
2018-12-28 00:37:21 +01:00
self . classes = OrderedDict ( ) # type: OrderedDict[str, ClassDef]
self . current_class = " " # type: str
2021-02-14 02:10:39 +01:00
def parse_class ( self , class_root , filepath ) : # type: (ET.Element, str) -> None
2018-12-28 00:37:21 +01:00
class_name = class_root . attrib [ " name " ]
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-04-11 00:43:44 +02:00
print_error ( ' {} .xml: Duplicate property " {} " . ' . format ( class_name , 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 :
2020-03-30 08:28:32 +02:00
default_value = " `` {} `` " . format ( 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 " )
params = parse_arguments ( constructor )
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 )
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 " )
params = parse_arguments ( method )
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 " )
params = parse_arguments ( operator )
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 )
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 " )
constant_def = ConstantDef ( constant_name , value , constant . text )
if enum is None :
if constant_name in class_def . constants :
2022-04-11 00:43:44 +02:00
print_error ( ' {} .xml: Duplicate constant " {} " . ' . format ( class_name , 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 :
enum_def = EnumDef ( enum )
class_def . enums [ enum ] = enum_def
enum_def . values [ constant_name ] = constant_def
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-04-11 00:43:44 +02:00
print_error ( ' {} .xml: Duplicate signal " {} " . ' . format ( class_name , signal_name ) , self )
2018-12-28 00:37:21 +01:00
continue
params = parse_arguments ( signal )
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-04-11 00:43:44 +02:00
' {} .xml: Duplicate theme item " {} " of type " {} " . ' . format (
class_name , theme_item_name , 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 :
default_value = " `` {} `` " . format ( default_value )
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
def sort_classes ( self ) : # type: () -> None
self . classes = OrderedDict ( sorted ( self . classes . items ( ) , key = lambda t : t [ 0 ] ) )
def parse_arguments ( root ) : # type: (ET.Element) -> List[ParameterDef]
param_elements = root . findall ( " argument " )
params = [ None ] * len ( param_elements ) # type: Any
for param_element in 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 " )
params [ index ] = ParameterDef ( param_name , type_name , default )
cast = params # type: List[ParameterDef]
return cast
def main ( ) : # type: () -> None
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. " )
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
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 :
import polib
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-04-11 00:43:44 +02:00
print ( ' No PO file at " {} " for language " {} " . ' . format ( lang_file , 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... " )
2018-12-26 15:57:51 +01:00
file_list = [ ] # type: 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 ]
2020-03-30 08:28:32 +02:00
if os . path . basename ( path ) == " modules " :
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-04-11 00:43:44 +02:00
print ( ' Got non-.xml file " {} " in input, skipping. ' . format ( path ) )
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
2018-12-28 00:37:21 +01:00
classes = { } # type: Dict[str, ET.Element]
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-04-11 00:43:44 +02:00
print_error ( " {} .xml: Parse error while reading the file: {} " . format ( cur_file , e ) , state )
2018-12-28 00:37:21 +01:00
continue
2018-12-26 15:57:51 +01:00
doc = tree . getroot ( )
2020-03-30 08:28:32 +02:00
if " version " not in doc . attrib :
2022-04-11 00:43:44 +02:00
print_error ( ' {} .xml: " version " attribute missing from " doc " . ' . format ( cur_file ) , state )
2018-12-28 00:37:21 +01:00
continue
2018-12-26 15:57:51 +01:00
name = doc . attrib [ " name " ]
if name in classes :
2022-04-11 00:43:44 +02:00
print_error ( ' {} .xml: Duplicate class " {} " . ' . format ( cur_file , 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-04-11 00:43:44 +02:00
print_error ( " {} .xml: Exception while parsing class: {} " . format ( name , 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 )
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-04-11 00:43:44 +02:00
if state . num_errors == 0 :
print ( " No errors found in the class reference XML. " )
2021-07-08 21:54:12 +02:00
if not args . dry_run :
print ( " Wrote reStructuredText files for each class to: %s " % 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 (
" %d errors were found in the class reference XML. Please check the messages above. " % state . num_errors
)
else :
print ( " 1 error was found in the class reference XML. Please check the messages above. " )
2018-12-28 00:37:21 +01:00
exit ( 1 )
2020-03-30 08:28:32 +02:00
2021-12-22 16:35:08 +01:00
def translate ( string ) : # type: (str) -> str
""" 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 )
2018-12-28 00:37:21 +01:00
def make_rst_class ( class_def , state , dry_run , output_dir ) : # type: (ClassDef, State, bool, str) -> None
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 :
2020-03-30 08:28:32 +02:00
f = open ( os . path . join ( output_dir , " class_ " + class_name . lower ( ) + " .rst " ) , " w " , encoding = " utf-8 " )
2018-12-28 00:37:21 +01:00
# Warn contributors not to edit this file directly
2019-07-25 15:57:43 +02:00
f . write ( " :github_url: hide \n \n " )
2021-10-24 08:48:03 +02:00
f . write ( " .. Generated automatically by doc/tools/make_rst.py in Godot ' s source tree. \n " )
2018-12-28 00:37:21 +01:00
f . write ( " .. DO NOT EDIT THIS FILE, but the " + class_name + " .xml source instead. \n " )
f . write ( " .. The source is found in doc/classes or modules/<name>/doc_classes. \n \n " )
f . write ( " .. _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
# Inheritance tree
# Ascendants
if class_def . inherits :
2022-02-10 12:00:11 +01:00
inherits = class_def . inherits . strip ( )
2021-12-22 16:35:08 +01:00
f . write ( " ** " + 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 " )
# Descendents
inherited = [ ]
for c in state . classes . values ( ) :
if c . inherits and c . inherits . strip ( ) == class_name :
inherited . append ( c . name )
if len ( inherited ) :
2021-12-22 16:35:08 +01:00
f . write ( " ** " + 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 " )
# Brief description
if class_def . brief_description is not None :
f . write ( rstize_text ( class_def . brief_description . strip ( ) , state ) + " \n \n " )
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 ( ) != " " :
f . write ( make_heading ( " Description " , " - " ) )
2020-01-14 21:38:54 +01:00
f . write ( rstize_text ( class_def . description . strip ( ) , state ) + " \n \n " )
# Online tutorials
if len ( class_def . tutorials ) > 0 :
2020-03-30 08:28:32 +02:00
f . write ( make_heading ( " Tutorials " , " - " ) )
2021-07-29 21:55:39 +02:00
for url , title in class_def . tutorials :
f . write ( " - " + make_link ( url , title ) + " \n \n " )
2020-01-14 21:38:54 +01:00
2018-12-28 00:37:21 +01:00
# Properties overview
if len ( class_def . properties ) > 0 :
2020-03-30 08:28:32 +02:00
f . write ( make_heading ( " Properties " , " - " ) )
2019-09-03 12:42:34 +02:00
ml = [ ] # type: List[Tuple[str, str, str]]
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 :
ref = " :ref:` {1} <class_ {1} _property_ {0} >` " . format ( property_def . name , property_def . overrides )
2021-12-22 16:35:08 +01:00
# Not using translate() for now as it breaks table formatting.
ml . append ( ( type_rst , property_def . name , default + " " + " (overrides %s ) " % ref ) )
2019-09-03 12:42:34 +02:00
else :
ref = " :ref:` {0} <class_ {1} _property_ {0} >` " . format ( property_def . name , class_name )
ml . append ( ( type_rst , ref , default ) )
2019-06-01 15:42:22 +02:00
format_table ( f , ml , True )
2018-12-28 00:37:21 +01:00
2021-11-02 20:19:40 +01:00
# Constructors, Methods, Operators overview
if len ( class_def . constructors ) > 0 :
f . write ( make_heading ( " Constructors " , " - " ) )
ml = [ ]
for method_list in class_def . constructors . values ( ) :
for m in method_list :
ml . append ( make_method_signature ( class_def , m , " constructor " , state ) )
format_table ( f , ml )
2018-12-28 00:37:21 +01:00
if len ( class_def . methods ) > 0 :
2020-03-30 08:28:32 +02:00
f . write ( make_heading ( " Methods " , " - " ) )
2018-12-28 00:37:21 +01:00
ml = [ ]
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 ) )
format_table ( f , ml )
if len ( class_def . operators ) > 0 :
f . write ( make_heading ( " Operators " , " - " ) )
ml = [ ]
for method_list in class_def . operators . values ( ) :
for m in method_list :
ml . append ( make_method_signature ( class_def , m , " operator " , state ) )
2018-12-28 00:37:21 +01:00
format_table ( f , ml )
# Theme properties
2021-08-06 17:18:22 +02:00
if len ( class_def . theme_items ) > 0 :
2020-03-30 08:28:32 +02:00
f . write ( make_heading ( " Theme Properties " , " - " ) )
2019-06-01 15:42:22 +02:00
pl = [ ]
2021-08-04 18:54:41 +02:00
for theme_item_def in class_def . theme_items . values ( ) :
ref = " :ref:` {0} <class_ {2} _theme_ {1} _ {0} >` " . format (
theme_item_def . name , theme_item_def . data_name , class_name
)
pl . append ( ( theme_item_def . type_name . to_rst ( state ) , ref , theme_item_def . default_value ) )
2019-06-01 15:42:22 +02:00
format_table ( f , pl , True )
2018-12-26 15:57:51 +01:00
2018-12-28 00:37:21 +01:00
# Signals
if len ( class_def . signals ) > 0 :
2020-03-30 08:28:32 +02:00
f . write ( make_heading ( " Signals " , " - " ) )
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 :
2020-03-30 08:28:32 +02:00
f . write ( " ---- \n \n " )
2019-10-24 19:06:09 +02:00
2018-12-28 00:37:21 +01:00
f . write ( " .. _class_ {} _signal_ {} : \n \n " . format ( class_name , signal . name ) )
2021-11-02 20:19:40 +01:00
_ , signature = make_method_signature ( class_def , signal , " " , state )
2018-12-28 00:37:21 +01:00
f . write ( " - {} \n \n " . format ( signature ) )
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 ( ) != " " :
f . write ( rstize_text ( signal . description . strip ( ) , state ) + " \n \n " )
2019-10-24 19:06:09 +02:00
index + = 1
2018-12-28 00:37:21 +01:00
# Enums
if len ( class_def . enums ) > 0 :
2020-03-30 08:28:32 +02:00
f . write ( make_heading ( " Enumerations " , " - " ) )
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 :
2020-03-30 08:28:32 +02:00
f . write ( " ---- \n \n " )
2019-10-24 19:06:09 +02:00
2018-12-28 00:37:21 +01:00
f . write ( " .. _enum_ {} _ {} : \n \n " . format ( class_name , e . name ) )
# Sphinx seems to divide the bullet list into individual <ul> tags if we weave the labels into it.
# As such I'll put them all above the list. Won't be perfect but better than making the list visually broken.
# As to why I'm not modifying the reference parser to directly link to the _enum label:
# If somebody gets annoyed enough to fix it, all existing references will magically improve.
for value in e . values . values ( ) :
f . write ( " .. _class_ {} _constant_ {} : \n \n " . format ( class_name , value . name ) )
f . write ( " enum ** {} **: \n \n " . format ( e . name ) )
for value in e . values . values ( ) :
f . write ( " - ** {} ** = ** {} ** " . format ( value . name , value . value ) )
2020-03-30 08:28:32 +02:00
if value . text is not None and value . text . strip ( ) != " " :
f . write ( " --- " + rstize_text ( value . text . strip ( ) , 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
2018-12-28 00:37:21 +01:00
# Constants
if len ( class_def . constants ) > 0 :
2020-03-30 08:28:32 +02:00
f . write ( make_heading ( " Constants " , " - " ) )
2018-12-28 00:37:21 +01:00
# Sphinx seems to divide the bullet list into individual <ul> tags if we weave the labels into it.
# As such I'll put them all above the list. Won't be perfect but better than making the list visually broken.
for constant in class_def . constants . values ( ) :
f . write ( " .. _class_ {} _constant_ {} : \n \n " . format ( class_name , constant . name ) )
for constant in class_def . constants . values ( ) :
f . write ( " - ** {} ** = ** {} ** " . format ( constant . name , constant . value ) )
2020-03-30 08:28:32 +02:00
if constant . text is not None and constant . text . strip ( ) != " " :
f . write ( " --- " + rstize_text ( constant . text . strip ( ) , 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
# Property descriptions
2021-12-02 20:38:49 +01:00
if any ( not p . overrides for p in class_def . properties . values ( ) ) > 0 :
2020-03-30 08:28:32 +02:00
f . write ( make_heading ( " Property Descriptions " , " - " ) )
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 :
2020-03-30 08:28:32 +02:00
f . write ( " ---- \n \n " )
2019-10-24 19:06:09 +02:00
2018-12-28 00:37:21 +01:00
f . write ( " .. _class_ {} _property_ {} : \n \n " . format ( class_name , property_def . name ) )
2020-03-30 08:28:32 +02:00
f . write ( " - {} ** {} ** \n \n " . format ( property_def . type_name . to_rst ( state ) , property_def . name ) )
2018-12-28 00:37:21 +01:00
2019-06-01 15:42:22 +02:00
info = [ ]
2021-12-22 16:35:08 +01:00
# Not using translate() for now as it breaks table formatting.
2019-06-01 15:42:22 +02:00
if property_def . default_value is not None :
2021-12-22 16:35:08 +01:00
info . append ( ( " * " + " Default " + " * " , property_def . default_value ) )
2018-12-28 00:37:21 +01:00
if property_def . setter is not None and not property_def . setter . startswith ( " _ " ) :
2021-12-22 16:35:08 +01:00
info . append ( ( " * " + " Setter " + " * " , property_def . setter + " ( " + " value " + " ) " ) )
2018-12-28 00:37:21 +01:00
if property_def . getter is not None and not property_def . getter . startswith ( " _ " ) :
2021-12-22 16:35:08 +01:00
info . append ( ( " * " + " Getter " + " * " , property_def . getter + " () " ) )
2018-12-28 00:37:21 +01:00
2019-06-01 15:42:22 +02:00
if len ( info ) > 0 :
format_table ( f , info )
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 ( ) != " " :
f . write ( rstize_text ( property_def . text . strip ( ) , state ) + " \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 :
f . write ( make_heading ( " Constructor Descriptions " , " - " ) )
index = 0
for method_list in class_def . constructors . values ( ) :
for i , m in enumerate ( method_list ) :
if index != 0 :
f . write ( " ---- \n \n " )
if i == 0 :
f . write ( " .. _class_ {} _constructor_ {} : \n \n " . format ( class_name , m . name ) )
ret_type , signature = make_method_signature ( class_def , m , " " , state )
f . write ( " - {} {} \n \n " . format ( ret_type , signature ) )
if m . description is not None and m . description . strip ( ) != " " :
f . write ( rstize_text ( m . description . strip ( ) , state ) + " \n \n " )
index + = 1
2018-12-28 00:37:21 +01:00
if len ( class_def . methods ) > 0 :
2020-03-30 08:28:32 +02:00
f . write ( make_heading ( " Method Descriptions " , " - " ) )
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 :
2020-03-30 08:28:32 +02:00
f . write ( " ---- \n \n " )
2019-10-24 19:06:09 +02:00
2018-12-28 00:37:21 +01:00
if i == 0 :
f . write ( " .. _class_ {} _method_ {} : \n \n " . format ( class_name , m . name ) )
2019-10-24 19:06:09 +02:00
2021-11-02 20:19:40 +01:00
ret_type , signature = make_method_signature ( class_def , m , " " , state )
f . write ( " - {} {} \n \n " . format ( ret_type , signature ) )
if m . description is not None and m . description . strip ( ) != " " :
f . write ( rstize_text ( m . description . strip ( ) , state ) + " \n \n " )
index + = 1
if len ( class_def . operators ) > 0 :
f . write ( make_heading ( " Operator Descriptions " , " - " ) )
index = 0
for method_list in class_def . operators . values ( ) :
for i , m in enumerate ( method_list ) :
if index != 0 :
f . write ( " ---- \n \n " )
if i == 0 :
f . write (
" .. _class_ {} _operator_ {} _ {} : \n \n " . format (
class_name , sanitize_operator_name ( m . name , state ) , m . return_type . type_name
)
)
ret_type , signature = make_method_signature ( class_def , m , " " , state )
2018-12-28 00:37:21 +01:00
f . write ( " - {} {} \n \n " . format ( ret_type , signature ) )
2020-03-30 08:28:32 +02:00
if m . description is not None and m . description . strip ( ) != " " :
f . write ( rstize_text ( m . description . strip ( ) , state ) + " \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 :
f . write ( make_heading ( " Theme Property Descriptions " , " - " ) )
index = 0
for theme_item_def in class_def . theme_items . values ( ) :
if index != 0 :
f . write ( " ---- \n \n " )
f . write ( " .. _class_ {} _theme_ {} _ {} : \n \n " . format ( class_name , theme_item_def . data_name , theme_item_def . name ) )
f . write ( " - {} ** {} ** \n \n " . format ( theme_item_def . type_name . to_rst ( state ) , theme_item_def . name ) )
info = [ ]
if theme_item_def . default_value is not None :
2021-12-22 16:35:08 +01:00
# Not using translate() for now as it breaks table formatting.
info . append ( ( " * " + " Default " + " * " , theme_item_def . default_value ) )
2021-08-04 18:54:41 +02:00
if len ( info ) > 0 :
format_table ( f , info )
if theme_item_def . text is not None and theme_item_def . text . strip ( ) != " " :
f . write ( rstize_text ( theme_item_def . text . strip ( ) , state ) + " \n \n " )
index + = 1
2020-07-30 11:41:12 +02:00
f . write ( make_footer ( ) )
2018-12-26 15:57:51 +01:00
2019-11-29 14:21:16 +01:00
def escape_rst ( text , until_pos = - 1 ) : # type: (str) -> str
# Escape \ character, otherwise it ends up as an escape character in rst
pos = 0
while True :
2020-03-30 08:28:32 +02:00
pos = text . find ( " \\ " , pos , until_pos )
2019-11-29 14:21:16 +01:00
if pos == - 1 :
break
2020-03-30 08:28:32 +02:00
text = text [ : pos ] + " \\ \\ " + text [ pos + 1 : ]
2019-11-29 14:21:16 +01:00
pos + = 2
# Escape * character to avoid interpreting it as emphasis
pos = 0
while True :
2020-03-30 08:28:32 +02:00
pos = text . find ( " * " , pos , until_pos )
2019-11-29 14:21:16 +01:00
if pos == - 1 :
break
2020-03-30 08:28:32 +02:00
text = text [ : pos ] + " \ * " + text [ pos + 1 : ]
2019-11-29 14:21:16 +01:00
pos + = 2
# Escape _ character at the end of a word to avoid interpreting it as an inline hyperlink
pos = 0
while True :
2020-03-30 08:28:32 +02:00
pos = text . find ( " _ " , pos , until_pos )
2019-11-29 14:21:16 +01:00
if pos == - 1 :
break
if not text [ pos + 1 ] . isalnum ( ) : # don't escape within a snake_case word
2020-03-30 08:28:32 +02:00
text = text [ : pos ] + " \ _ " + text [ pos + 1 : ]
2019-11-29 14:21:16 +01:00
pos + = 2
else :
pos + = 1
return text
2020-07-22 22:41:10 +02:00
def format_codeblock ( code_type , post_text , indent_level , state ) : # types: str, str, int, state
end_pos = post_text . find ( " [/ " + code_type + " ] " )
if end_pos == - 1 :
2022-04-11 00:43:44 +02:00
print_error ( " {} .xml: [ " + code_type + " ] without a closing tag. " . format ( state . current_class ) , state )
2020-07-22 22:41:10 +02:00
return None
code_text = post_text [ len ( " [ " + code_type + " ] " ) : end_pos ]
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-04-11 00:43:44 +02:00
" {} .xml: Four spaces should be used for indentation within [ "
2020-07-22 22:41:10 +02:00
+ code_type
2022-04-11 00:43:44 +02:00
+ " ]. " . format ( state . current_class ) ,
2020-07-22 22:41:10 +02:00
state ,
)
if len ( code_text [ code_pos + to_skip + 1 : ] ) == 0 :
code_text = code_text [ : code_pos ] + " \n "
code_pos + = 1
else :
code_text = code_text [ : code_pos ] + " \n " + code_text [ code_pos + to_skip + 1 : ]
code_pos + = 5 - to_skip
return [ " \n [ " + code_type + " ] " + code_text + post_text , len ( " \n [ " + code_type + " ] " + code_text ) ]
2018-12-28 00:37:21 +01:00
def rstize_text ( text , state ) : # type: (str, 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
2020-03-30 08:28:32 +02:00
while 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 " "
2020-07-22 22:41:10 +02:00
text = 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 :
text = 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
# Handle [tags]
2017-07-22 16:22:38 +02:00
inside_code = False
2016-10-30 18:44:57 +01:00
pos = 0
2018-12-28 00:37:21 +01:00
tag_depth = 0
2019-05-16 15:11:58 +02:00
previous_pos = 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
2018-12-28 00:37:21 +01:00
if tag_text in state . classes :
2019-03-29 23:37:35 +01:00
if tag_text == state . current_class :
# We don't want references to the same class
2020-03-30 08:28:32 +02:00
tag_text = " `` {} `` " . format ( 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
2016-10-30 18:57:40 +01:00
else : # command
2016-10-30 18:44:57 +01:00
cmd = tag_text
2020-03-30 08:28:32 +02:00
space_pos = tag_text . find ( " " )
2020-07-22 22:41:10 +02:00
if cmd == " /codeblock " or cmd == " /gdscript " or cmd == " /csharp " :
2020-03-30 08:28:32 +02:00
tag_text = " "
2018-12-28 00:37:21 +01:00
tag_depth - = 1
2017-07-22 16:22:38 +02:00
inside_code = False
# Strip newline if the tag was alone on one
2020-03-30 08:28:32 +02:00
if pre_text [ - 1 ] == " \n " :
2017-07-22 16:22:38 +02:00
pre_text = pre_text [ : - 1 ]
2020-03-30 08:28:32 +02:00
elif cmd == " /code " :
tag_text = " `` "
2018-12-28 00:37:21 +01:00
tag_depth - = 1
2017-07-22 16:22:38 +02:00
inside_code = False
2017-11-18 01:29:32 +01:00
escape_post = True
2017-07-22 16:22:38 +02:00
elif inside_code :
2020-03-30 08:28:32 +02:00
tag_text = " [ " + tag_text + " ] "
elif cmd . find ( " html " ) == 0 :
param = tag_text [ space_pos + 1 : ]
2016-10-30 18:44:57 +01:00
tag_text = param
2020-03-30 08:28:32 +02:00
elif (
cmd . startswith ( " method " )
or cmd . startswith ( " member " )
or cmd . startswith ( " signal " )
or cmd . startswith ( " constant " )
2021-11-18 15:03:03 +01:00
or cmd . startswith ( " theme_item " )
2020-03-30 08:28:32 +02:00
) :
param = tag_text [ space_pos + 1 : ]
if param . find ( " . " ) != - 1 :
ss = param . split ( " . " )
2017-11-17 17:32:07 +01:00
if len ( ss ) > 2 :
2022-04-11 00:43:44 +02:00
print_error ( ' {} .xml: Bad reference: " {} " . ' . format ( state . current_class , param ) , state )
2018-12-28 00:37:21 +01:00
class_param , method_param = ss
else :
class_param = state . current_class
method_param = param
ref_type = " "
if class_param in state . classes :
class_def = state . classes [ class_param ]
2021-11-02 20:19:40 +01:00
if cmd . startswith ( " constructor " ) :
if method_param not in class_def . constructors :
print_error (
2022-04-11 00:43:44 +02:00
' {} .xml: Unresolved constructor " {} " . ' . format ( state . current_class , param ) , state
2021-11-02 20:19:40 +01:00
)
ref_type = " _constructor "
2018-12-28 00:37:21 +01:00
if cmd . startswith ( " method " ) :
if method_param not in class_def . methods :
2022-04-11 00:43:44 +02:00
print_error ( ' {} .xml: Unresolved method " {} " . ' . format ( state . current_class , param ) , state )
2018-12-28 00:37:21 +01:00
ref_type = " _method "
2021-11-02 20:19:40 +01:00
if cmd . startswith ( " operator " ) :
if method_param not in class_def . operators :
2022-04-11 00:43:44 +02:00
print_error ( ' {} .xml: Unresolved operator " {} " . ' . format ( state . current_class , param ) , state )
2021-11-02 20:19:40 +01:00
ref_type = " _operator "
2018-12-28 00:37:21 +01:00
elif cmd . startswith ( " member " ) :
if method_param not in class_def . properties :
2022-04-11 00:43:44 +02:00
print_error ( ' {} .xml: Unresolved member " {} " . ' . format ( state . current_class , param ) , state )
2018-12-28 00:37:21 +01:00
ref_type = " _property "
2021-11-18 15:03:03 +01:00
elif cmd . startswith ( " theme_item " ) :
if method_param not in class_def . theme_items :
2022-04-11 00:43:44 +02:00
print_error ( ' {} .xml: Unresolved theme item " {} " . ' . format ( state . current_class , param ) , state )
2021-11-19 10:41:11 +01:00
ref_type = " _theme_ {} " . format ( class_def . theme_items [ method_param ] . data_name )
2021-11-18 15:03:03 +01:00
2018-12-28 00:37:21 +01:00
elif cmd . startswith ( " signal " ) :
if method_param not in class_def . signals :
2022-04-11 00:43:44 +02:00
print_error ( ' {} .xml: Unresolved signal " {} " . ' . format ( state . current_class , param ) , state )
2018-12-28 00:37:21 +01:00
ref_type = " _signal "
elif cmd . startswith ( " constant " ) :
found = False
2019-03-27 20:01:16 +01:00
# Search in the current class
search_class_defs = [ class_def ]
2020-03-30 08:28:32 +02:00
if param . find ( " . " ) == - 1 :
2019-03-27 20:01:16 +01:00
# 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
else :
for enum in search_class_def . enums . values ( ) :
if method_param in enum . values :
class_param = search_class_def . name
found = True
break
2018-12-28 00:37:21 +01:00
if not found :
2022-04-11 00:43:44 +02:00
print_error ( ' {} .xml: Unresolved constant " {} " . ' . format ( state . current_class , param ) , state )
2018-12-28 00:37:21 +01:00
ref_type = " _constant "
2016-10-30 18:44:57 +01:00
else :
2020-03-30 08:28:32 +02:00
print_error (
2022-04-11 00:43:44 +02:00
' {} .xml: Unresolved type reference " {} " in method reference " {} " . ' . format (
state . current_class , class_param , param
2020-03-30 08:28:32 +02:00
) ,
state ,
)
2018-12-28 00:37:21 +01:00
repl_text = method_param
if class_param != state . current_class :
repl_text = " {} . {} " . format ( class_param , method_param )
2020-03-30 08:28:32 +02:00
tag_text = " :ref:` {} <class_ {} {} _ {} >` " . format ( repl_text , class_param , ref_type , method_param )
2021-12-22 17:50:40 +01:00
escape_pre = True
2017-10-21 12:33:50 +02:00
escape_post = True
2020-03-30 08:28:32 +02:00
elif cmd . find ( " image= " ) == 0 :
2016-10-30 18:57:40 +01:00
tag_text = " " # '![](' + cmd[6:] + ')'
2020-03-30 08:28:32 +02:00
elif cmd . find ( " url= " ) == 0 :
2021-11-15 10:39:00 +01:00
# 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-04-11 00:43:44 +02:00
" {} .xml: Tag depth mismatch for [url]: no closing [/url] " . format ( state . current_class ) , state
2021-11-15 10:39:00 +01:00
)
break
link_title = text [ endq_pos + 1 : endurl_pos ]
tag_text = make_link ( link_url , link_title )
pre_text = text [ : pos ]
2021-12-21 09:35:47 +01:00
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
2021-11-15 10:39:00 +01:00
pos = len ( pre_text ) + len ( tag_text )
previous_pos = pos
continue
2020-03-30 08:28:32 +02:00
elif cmd == " center " :
2018-12-28 00:37:21 +01:00
tag_depth + = 1
2020-03-30 08:28:32 +02:00
tag_text = " "
elif cmd == " /center " :
2018-12-28 00:37:21 +01:00
tag_depth - = 1
2020-03-30 08:28:32 +02:00
tag_text = " "
elif cmd == " codeblock " :
2018-12-28 00:37:21 +01:00
tag_depth + = 1
2020-03-30 08:28:32 +02:00
tag_text = " \n :: \n "
2017-07-22 16:22:38 +02:00
inside_code = True
2020-07-22 22:41:10 +02:00
elif cmd == " gdscript " :
tag_depth + = 1
2021-07-12 12:57:31 +02:00
tag_text = " \n .. code-tab:: gdscript \n "
2020-07-22 22:41:10 +02:00
inside_code = True
elif cmd == " csharp " :
tag_depth + = 1
tag_text = " \n .. code-tab:: csharp \n "
inside_code = True
elif cmd == " codeblocks " :
tag_depth + = 1
tag_text = " \n .. tabs:: "
elif cmd == " /codeblocks " :
tag_depth - = 1
tag_text = " "
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 : ]
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 = " * "
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 = " ** "
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 = " "
elif cmd == " code " :
tag_text = " `` "
2018-12-28 00:37:21 +01:00
tag_depth + = 1
2017-07-22 16:22:38 +02:00
inside_code = True
2021-12-21 09:35:47 +01:00
escape_pre = True
2020-03-10 11:41:36 +01:00
elif cmd == " kbd " :
tag_text = " :kbd:` "
tag_depth + = 1
2021-12-21 09:35:47 +01:00
escape_pre = True
2020-03-10 11:41:36 +01:00
elif cmd == " /kbd " :
tag_text = " ` "
tag_depth - = 1
2021-12-21 09:35:47 +01:00
escape_post = True
2020-03-30 08:28:32 +02:00
elif cmd . startswith ( " enum " ) :
2018-12-28 00:37:21 +01:00
tag_text = make_enum ( cmd [ 5 : ] , state )
2021-12-21 09:35:47 +01:00
escape_pre = True
2020-01-24 13:08:36 +01:00
escape_post = True
2016-10-30 18:44:57 +01:00
else :
2018-12-28 00:37:21 +01:00
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
# 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
2020-03-30 08:28:32 +02:00
post_text = 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
2020-03-30 08:28:32 +02:00
post_text = 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 )
2019-05-16 15:11:58 +02:00
previous_pos = pos
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 (
" {} .xml: Tag depth mismatch: too many (or too little) open/close tags. " . format ( state . current_class ) , state
)
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
2019-06-01 15:42:22 +02:00
def format_table ( f , data , remove_empty_columns = False ) : # type: (TextIO, Iterable[Tuple[str, ...]]) -> None
if len ( data ) == 0 :
return
2019-10-24 19:06:09 +02:00
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
sep = " "
for size in column_sizes :
if size == 0 and remove_empty_columns :
continue
sep + = " + " + " - " * ( size + 2 )
2018-09-13 02:00:26 +02:00
sep + = " + \n "
f . write ( sep )
2019-10-24 19:06:09 +02:00
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
2020-03-30 08:28:32 +02:00
row_text + = " " + ( text or " " ) . ljust ( column_sizes [ i ] ) + " | "
2019-06-01 15:42:22 +02:00
row_text + = " \n "
f . write ( row_text )
2018-09-13 02:00:26 +02:00
f . write ( sep )
2020-03-30 08:28:32 +02:00
f . write ( " \n " )
2018-09-13 02:00:26 +02:00
2020-04-24 17:43:10 +02:00
def make_type ( klass , state ) : # type: (str, State) -> str
2021-08-23 19:53:27 +02:00
if klass . find ( " * " ) != - 1 : # Pointer, ignore
return klass
2020-04-24 17:43:10 +02:00
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 :
return " :ref:` {} <class_ {} >` " . format ( klass , link_type )
2022-04-11 00:43:44 +02:00
print_error ( ' {} .xml: Unresolved type " {} " . ' . format ( state . current_class , klass ) , state )
2020-04-24 17:43:10 +02:00
return klass
2016-02-07 00:22:49 +01:00
2018-05-12 20:08:39 +02:00
2018-12-28 00:37:21 +01:00
def make_enum ( t , state ) : # type: (str, State) -> str
2017-12-21 20:23:21 +01:00
p = t . find ( " . " )
if p > = 0 :
c = t [ 0 : p ]
2020-03-30 08:28:32 +02:00
e = t [ p + 1 : ]
2018-05-12 20:08:39 +02:00
# Variant enums live in GlobalScope but still use periods.
if c == " Variant " :
c = " @GlobalScope "
e = " Variant. " + e
else :
2018-12-28 00:37:21 +01:00
c = state . current_class
2018-05-12 20:08:39 +02:00
e = t
2018-12-28 00:37:21 +01:00
if c in state . classes and e not in state . classes [ c ] . enums :
c = " @GlobalScope "
2016-02-07 00:22:49 +01:00
2018-12-28 00:37:21 +01:00
if c in state . classes and e in state . classes [ c ] . enums :
return " :ref:` {0} <enum_ {1} _ {0} >` " . format ( e , c )
2019-12-04 08:41:38 +01:00
# Don't fail for `Vector3.Axis`, as this enum is a special case which is expected not to be resolved.
if " {} . {} " . format ( c , e ) != " Vector3.Axis " :
2022-04-11 00:43:44 +02:00
print_error ( ' {} .xml: Unresolved enum " {} " . ' . format ( state . current_class , t ) , state )
2019-12-04 08:41:38 +01:00
2018-12-28 00:37:21 +01:00
return t
2018-05-12 20:08:39 +02:00
2016-10-30 18:44:57 +01:00
2020-03-30 08:28:32 +02:00
def make_method_signature (
2021-11-02 20:19:40 +01:00
class_def , method_def , ref_type , state
) : # type: (ClassDef, Union[MethodDef, SignalDef], str, State) -> Tuple[str, str]
2018-12-28 00:37:21 +01:00
ret_type = " "
2016-10-30 18:44:57 +01:00
2018-12-28 00:37:21 +01:00
if isinstance ( method_def , MethodDef ) :
ret_type = method_def . return_type . to_rst ( state )
2021-10-05 16:26:28 +02:00
2018-12-28 00:37:21 +01:00
out = " "
2016-10-30 18:44:57 +01:00
2021-11-02 20:19:40 +01:00
if ref_type != " " :
if ref_type == " operator " :
out + = " :ref:` {0} <class_ {1} _ {2} _ {3} _ {4} >` " . format (
method_def . name ,
class_def . name ,
ref_type ,
sanitize_operator_name ( method_def . name , state ) ,
method_def . return_type . type_name ,
)
else :
out + = " :ref:` {0} <class_ {1} _ {2} _ {0} >` " . format ( method_def . name , class_def . name , ref_type )
2016-10-30 18:44:57 +01:00
else :
2018-12-28 00:37:21 +01:00
out + = " ** {} ** " . format ( method_def . name )
2016-10-30 18:44:57 +01:00
2020-03-30 08:28:32 +02:00
out + = " **(** "
2018-12-28 00:37:21 +01:00
for i , arg in enumerate ( method_def . parameters ) :
if i > 0 :
2020-03-30 08:28:32 +02:00
out + = " , "
2016-10-30 18:44:57 +01:00
else :
2020-03-30 08:28:32 +02:00
out + = " "
2016-10-30 18:44:57 +01:00
2018-12-28 00:37:21 +01:00
out + = " {} {} " . format ( arg . type_name . to_rst ( state ) , arg . name )
2016-10-30 18:44:57 +01:00
2018-12-28 00:37:21 +01:00
if arg . default_value is not None :
2020-03-30 08:28:32 +02:00
out + = " = " + arg . default_value
2016-10-30 18:44:57 +01:00
2020-03-30 08:28:32 +02:00
if isinstance ( method_def , MethodDef ) and method_def . qualifiers is not None and " vararg " in method_def . qualifiers :
2019-03-05 20:42:09 +01:00
if len ( method_def . parameters ) > 0 :
2020-03-30 08:28:32 +02:00
out + = " , ... "
2019-03-05 20:42:09 +01:00
else :
2020-03-30 08:28:32 +02:00
out + = " ... "
2019-03-05 20:42:09 +01:00
2020-03-30 08:28:32 +02:00
out + = " **)** "
2016-10-30 18:44:57 +01:00
2018-12-28 00:37:21 +01:00
if isinstance ( method_def , MethodDef ) and method_def . qualifiers is not None :
2020-07-30 11:41:12 +02:00
# Use substitutions for abbreviations. This is used to display tooltips on hover.
# See `make_footer()` for descriptions.
for qualifier in method_def . qualifiers . split ( ) :
out + = " | " + qualifier + " | "
2018-09-13 02:00:26 +02:00
2018-12-28 00:37:21 +01:00
return ret_type , out
2018-09-13 02:00:26 +02:00
2021-12-21 10:53:07 +01:00
def make_heading ( title , underline , l10n = True ) : # type: (str, str, bool) -> str
if l10n :
2021-12-22 16:35:08 +01:00
new_title = translate ( title )
if new_title != title :
title = new_title
2021-12-21 10:53:07 +01:00
underline * = 2 # Double length to handle wide chars.
2020-03-30 08:28:32 +02:00
return title + " \n " + ( underline * len ( title ) ) + " \n \n "
2016-02-07 12:07:24 +01:00
2020-07-30 11:41:12 +02:00
def make_footer ( ) : # type: () -> str
# Generate reusable abbreviation substitutions.
# This way, we avoid bloating the generated rST with duplicate abbreviations.
# fmt: off
return (
2021-12-22 16:35:08 +01:00
" .. |virtual| replace:: :abbr:`virtual ( " + translate ( " This method should typically be overridden by the user to have any effect. " ) + " )` \n "
" .. |const| replace:: :abbr:`const ( " + translate ( " This method has no side effects. It doesn ' t modify any of the instance ' s member variables. " ) + " )` \n "
" .. |vararg| replace:: :abbr:`vararg ( " + translate ( " This method accepts any number of arguments after the ones described here. " ) + " )` \n "
" .. |constructor| replace:: :abbr:`constructor ( " + translate ( " This method is used to construct a type. " ) + " )` \n "
" .. |static| replace:: :abbr:`static ( " + translate ( " This method doesn ' t need an instance to be called, so it can be called directly using the class name. " ) + " )` \n "
" .. |operator| replace:: :abbr:`operator ( " + translate ( " This method describes a valid operator to use with this type as left-hand operand. " ) + " )` \n "
2020-07-30 11:41:12 +02:00
)
# fmt: on
2021-07-29 21:55:39 +02:00
def make_link ( url , title ) : # type: (str, str) -> str
match = GODOT_DOCS_PATTERN . search ( url )
2019-06-11 10:51:10 +02:00
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`
2021-11-15 10:39:00 +01:00
# Or use the title if provided.
if title != " " :
return " ` " + title + " <../ " + groups [ 0 ] + " .html " + groups [ 1 ] + " >`__ "
return " ` " + groups [ 1 ] + " <../ " + groups [ 0 ] + " .html " + groups [ 1 ] + " >`__ in :doc:`../ " + groups [ 0 ] + " ` "
2019-06-11 10:51:10 +02:00
elif match . lastindex == 1 :
# Doc reference, for example:
# `Math`
2021-11-15 10:39:00 +01:00
if title != " " :
return " :doc:` " + title + " <../ " + groups [ 0 ] + " >` "
2019-06-11 10:51:10 +02:00
return " :doc:`../ " + groups [ 0 ] + " ` "
else :
# External link, for example:
# `http://enet.bespin.org/usergroup0.html`
2021-07-29 21:55:39 +02:00
if title != " " :
2021-10-05 15:31:25 +02:00
return " ` " + title + " < " + url + " >`__ "
2021-11-15 10:39:00 +01:00
return " ` " + url + " < " + url + " >`__ "
2019-06-11 10:51:10 +02:00
2021-11-02 20:19:40 +01:00
def sanitize_operator_name ( dirty_name , state ) : # type: (str, State) -> str
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 "
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-04-11 00:43:44 +02:00
print_error ( ' Unsupported operator type " {} " , please add the missing rule. ' . format ( dirty_name ) , 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 ( )