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
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 " ,
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. " ,
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
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-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 " )
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-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 :
2022-06-24 11:16:37 +02:00
enum_def = EnumDef ( 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-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
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-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
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 (
' {} .xml: Empty argument name in {} " {} " at position {} . ' . format (
self . current_class , context , root . attrib [ " name " ] , param_index
) ,
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 :
2018-12-28 00:37:21 +01:00
self . classes = OrderedDict ( sorted ( self . classes . items ( ) , key = lambda t : t [ 0 ] ) )
2022-07-28 00:54:26 +02:00
class TypeName :
def __init__ ( self , type_name : str , enum : Optional [ str ] = None ) - > None :
self . type_name = type_name
self . enum = enum
def to_rst ( self , state : 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 : ET . Element ) - > " TypeName " :
return cls ( element . attrib [ " type " ] , element . get ( " enum " ) )
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-07-28 00:54:26 +02:00
def __init__ ( self , name : str , bitfield : bool ) - > None :
2022-08-09 22:53:29 +02:00
super ( ) . __init__ ( " enum " , name )
2022-08-06 19:48:54 +02:00
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 :
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... " )
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 ]
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
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-06-10 22:58:09 +02:00
print_error ( " {} : 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-06-10 22:58:09 +02:00
print_error ( ' {} : " 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-06-10 22:58:09 +02:00
print_error ( ' {} : 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 )
2022-08-09 22:53:29 +02:00
print ( " Generating the RST class reference... " )
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-08-09 22:53:29 +02:00
print ( " " )
if state . num_warnings > = 2 :
print (
" {} {} warnings were found in the class reference XML. Please check the messages above. {} " . format (
STYLES [ " yellow " ] , state . num_warnings , STYLES [ " reset " ]
)
)
elif state . num_warnings == 1 :
print (
" {} 1 warning was found in the class reference XML. Please check the messages above. {} " . format (
STYLES [ " yellow " ] , STYLES [ " reset " ]
)
)
2022-04-11 00:43:44 +02:00
if state . num_errors == 0 :
2022-06-11 00:16:05 +02:00
print ( " {} No errors found in the class reference XML. {} " . format ( STYLES [ " green " ] , STYLES [ " reset " ] ) )
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 (
2022-06-11 00:16:05 +02:00
" {} {} errors were found in the class reference XML. Please check the messages above. {} " . format (
STYLES [ " red " ] , state . num_errors , STYLES [ " reset " ]
)
2022-04-11 00:43:44 +02:00
)
else :
2022-06-11 00:16:05 +02:00
print (
" {} 1 error was found in the class reference XML. Please check the messages above. {} " . format (
STYLES [ " red " ] , STYLES [ " reset " ]
)
)
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 :
print ( " {} {} ERROR: {} {} {} " . format ( STYLES [ " red " ] , STYLES [ " bold " ] , STYLES [ " regular " ] , error , STYLES [ " reset " ] ) )
state . num_errors + = 1
def print_warning ( error : str , state : State ) - > None :
print ( " {} {} WARNING: {} {} {} " . format ( STYLES [ " yellow " ] , STYLES [ " bold " ] , STYLES [ " regular " ] , error , STYLES [ " reset " ] ) )
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-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 :
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
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.
git_branch = " master "
if hasattr ( version , " docs " ) and version . docs != " latest " :
git_branch = version . docs
2022-07-27 13:52:11 +02:00
source_xml_path = os . path . relpath ( class_def . filepath , root_directory ) . replace ( " \\ " , " / " )
2022-07-26 17:20:51 +02:00
source_github_url = " https://github.com/godotengine/godot/tree/ {} / {} " . format ( git_branch , source_xml_path )
generator_github_url = " https://github.com/godotengine/godot/tree/ {} /doc/tools/make_rst.py " . format ( git_branch )
f . write ( " .. DO NOT EDIT THIS FILE!!! \n " )
f . write ( " .. Generated automatically from Godot engine sources. \n " )
f . write ( " .. Generator: " + generator_github_url + " . \n " )
f . write ( " .. XML source: " + source_github_url + " . \n \n " )
# Document reference id and header.
2018-12-28 00:37:21 +01:00
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 " )
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 ) :
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 :
2022-08-09 22:53:29 +02:00
f . write ( 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 ( ) != " " :
f . write ( make_heading ( " Description " , " - " ) )
2022-08-09 22:53:29 +02:00
f . write ( format_text_block ( class_def . description . strip ( ) , class_def , state ) + " \n \n " )
2020-01-14 21:38:54 +01:00
# 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 " , " - " ) )
2022-07-28 00:54:26 +02:00
ml : List [ Tuple [ Optional [ 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 " , " - " ) )
2022-07-28 00:54:26 +02:00
ml : List [ Tuple [ Optional [ str ] , . . . ] ] = [ ]
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 ) )
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 " , " - " ) )
2022-07-28 00:54:26 +02:00
ml : List [ Tuple [ Optional [ str ] , . . . ] ] = [ ]
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 ) )
format_table ( f , ml )
if len ( class_def . operators ) > 0 :
f . write ( make_heading ( " Operators " , " - " ) )
2022-07-28 00:54:26 +02:00
ml : List [ Tuple [ Optional [ str ] , . . . ] ] = [ ]
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 ) )
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 " , " - " ) )
2022-07-28 00:54:26 +02:00
pl : List [ Tuple [ Optional [ str ] , . . . ] ] = [ ]
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 ( ) != " " :
2022-08-09 22:53:29 +02:00
f . write ( format_text_block ( signal . description . strip ( ) , signal , 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 ) )
2022-06-24 11:16:37 +02:00
if e . is_bitfield :
f . write ( " flags ** {} **: \n \n " . format ( e . name ) )
else :
f . write ( " enum ** {} **: \n \n " . format ( e . name ) )
2018-12-28 00:37:21 +01:00
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 ( ) != " " :
2022-05-04 22:27:09 +02:00
# If value.text contains a bullet point list, each entry needs additional indentation
2022-08-09 22:53:29 +02:00
f . write ( " --- " + indent_bullets ( 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
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 ( ) != " " :
2022-08-09 22:53:29 +02:00
f . write ( " --- " + 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-08-04 21:42:20 +02:00
# Annotations
2022-07-04 17:56:34 +02:00
if len ( class_def . annotations ) > 0 :
f . write ( make_heading ( " Annotations " , " - " ) )
index = 0
for method_list in class_def . annotations . values ( ) :
for i , m in enumerate ( method_list ) :
if index != 0 :
f . write ( " ---- \n \n " )
if i == 0 :
2022-08-26 18:04:11 +02:00
f . write ( " .. _class_ {} _annotation_ {} : \n \n " . format ( class_name , m . name ) )
2022-07-04 17:56:34 +02:00
2022-08-04 21:42:20 +02:00
_ , signature = make_method_signature ( class_def , m , " " , state )
f . write ( " - {} \n \n " . format ( signature ) )
2022-07-04 17:56:34 +02:00
if m . description is not None and m . description . strip ( ) != " " :
2022-08-09 22:53:29 +02:00
f . write ( format_text_block ( m . description . strip ( ) , m , state ) + " \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 :
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
2022-07-28 00:54:26 +02:00
info : List [ Tuple [ Optional [ str ] , . . . ] ] = [ ]
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 ( ) != " " :
2022-08-09 22:53:29 +02:00
f . write ( format_text_block ( property_def . text . strip ( ) , property_def , 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 ( ) != " " :
2022-08-09 22:53:29 +02:00
f . write ( format_text_block ( m . description . strip ( ) , m , state ) + " \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 :
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 ( ) != " " :
2022-08-09 22:53:29 +02:00
f . write ( format_text_block ( m . description . strip ( ) , m , state ) + " \n \n " )
2021-11-02 20:19:40 +01:00
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 ( ) != " " :
2022-08-09 22:53:29 +02:00
f . write ( format_text_block ( m . description . strip ( ) , m , 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 ( ) != " " :
2022-08-09 22:53:29 +02:00
f . write ( format_text_block ( theme_item_def . text . strip ( ) , theme_item_def , state ) + " \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 :
return " :ref:` {} <class_ {} >` " . format ( klass , link_type )
print_error ( ' {} .xml: Unresolved type " {} " . ' . format ( state . current_class , klass ) , state )
return klass
2019-11-29 14:21:16 +01:00
2022-08-09 22:53:29 +02:00
def make_enum ( t : str , state : State ) - > str :
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 :
return " :ref:` {0} <enum_ {1} _ {0} >` " . format ( e , c )
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.
if " {} . {} " . format ( c , e ) != " Vector3.Axis " :
print_error ( ' {} .xml: Unresolved enum " {} " . ' . format ( state . current_class , 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-09 22:53:29 +02:00
is_method_def = isinstance ( definition , MethodDef )
if is_method_def :
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
if is_method_def or isinstance ( definition , AnnotationDef ) :
qualifiers = definition . qualifiers
out = " "
if is_method_def and ref_type != " " :
if ref_type == " operator " :
out + = " :ref:` {0} <class_ {1} _ {2} _ {3} _ {4} >` " . format (
definition . name . replace ( " < " , " \\ < " ) , # So operator "<" gets correctly displayed.
class_def . name ,
ref_type ,
sanitize_operator_name ( definition . name , state ) ,
definition . return_type . type_name ,
2020-07-22 22:41:10 +02:00
)
2022-08-09 22:53:29 +02:00
else :
out + = " :ref:` {0} <class_ {1} _ {2} _ {0} >` " . format ( definition . name , class_def . name , ref_type )
else :
out + = " ** {} ** " . format ( 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 + = " "
out + = " {} {} " . format ( arg . type_name . to_rst ( state ) , arg . name )
if arg . default_value is not None :
out + = " = " + arg . default_value
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 ( ) :
out + = " | " + qualifier + " | "
return ret_type , out
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.
return title + " \n " + ( underline * len ( title ) ) + " \n \n "
def make_footer ( ) - > str :
# Generate reusable abbreviation substitutions.
# This way, we avoid bloating the generated rST with duplicate abbreviations.
# fmt: off
return (
" .. |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 "
)
# fmt: on
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 != " " :
return " ` " + title + " <../ " + groups [ 0 ] + " .html " + groups [ 1 ] + " >`__ "
return " ` " + groups [ 1 ] + " <../ " + groups [ 0 ] + " .html " + groups [ 1 ] + " >`__ in :doc:`../ " + groups [ 0 ] + " ` "
elif match . lastindex == 1 :
# Doc reference, for example:
# `Math`
if title != " " :
return " :doc:` " + title + " <../ " + groups [ 0 ] + " >` "
return " :doc:`../ " + groups [ 0 ] + " ` "
# External link, for example:
# `http://enet.bespin.org/usergroup0.html`
if title != " " :
return " ` " + title + " < " + url + " >`__ "
return " ` " + url + " < " + url + " >`__ "
# 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 " "
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
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.
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 :
2022-08-09 22:53:29 +02:00
# Don't create a link to the same class, format it as inline code.
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
2022-08-09 22:53:29 +02:00
# Tag is a cross-reference or a formating directive.
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 (
' {} .xml: Potential error inside of a code tag, found a string that looks like a closing tag " [ {} ] " in {} . ' . format (
state . current_class , cmd , context_name
) ,
state ,
)
2018-12-28 00:37:21 +01:00
2022-08-09 22:53:29 +02:00
tag_text = " [ " + 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 (
" {} .xml: GDScript code block is used outside of [codeblocks] in {} . " . format (
state . current_class , cmd , context_name
) ,
state ,
)
tag_text = " \n .. code-tab:: gdscript \n "
elif cmd == " csharp " :
if not inside_code_tabs :
print_error (
" {} .xml: C# code block is used outside of [codeblocks] in {} . " . format (
state . current_class , cmd , context_name
) ,
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
# 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-09 22:53:29 +02:00
' {} .xml: Empty cross-reference link " {} " in {} . ' . format ( state . current_class , cmd , 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 (
' {} .xml: Bad reference " {} " in {} . ' . format (
state . current_class , link_target , context_name
) ,
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 (
' {} .xml: Unresolved method reference " {} " in {} . ' . format (
state . current_class , link_target , context_name
) ,
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 (
' {} .xml: Unresolved constructor reference " {} " in {} . ' . format (
state . current_class , link_target , context_name
) ,
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 (
' {} .xml: Unresolved operator reference " {} " in {} . ' . format (
state . current_class , link_target , context_name
) ,
state ,
)
elif cmd . startswith ( " member " ) and method_param not in class_def . properties :
print_error (
' {} .xml: Unresolved member reference " {} " in {} . ' . format (
state . current_class , link_target , context_name
) ,
state ,
)
elif cmd . startswith ( " signal " ) and method_param not in class_def . signals :
print_error (
' {} .xml: Unresolved signal reference " {} " in {} . ' . format (
state . current_class , link_target , context_name
) ,
state ,
)
elif cmd . startswith ( " annotation " ) and method_param not in class_def . annotations :
print_error (
' {} .xml: Unresolved annotation reference " {} " in {} . ' . format (
state . current_class , link_target , context_name
) ,
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 (
' {} .xml: Unresolved theme item reference " {} " in {} . ' . format (
state . current_class , link_target , context_name
) ,
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.
ref_type = " _theme_ {} " . format ( class_def . theme_items [ method_param ] . data_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 (
' {} .xml: Unresolved constant reference " {} " in {} . ' . format (
state . current_class , link_target , context_name
) ,
state ,
)
else :
print_error (
' {} .xml: Unresolved type reference " {} " in method reference " {} " in {} . ' . format (
state . current_class , class_param , link_target , context_name
) ,
state ,
)
repl_text = method_param
if class_param != state . current_class :
repl_text = " {} . {} " . format ( class_param , method_param )
tag_text = " :ref:` {} <class_ {} {} _ {} >` " . format ( repl_text , class_param , ref_type , method_param )
escape_pre = True
escape_post = True
elif cmd . startswith ( " enum " ) :
tag_text = make_enum ( link_target , state )
escape_pre = True
escape_post = True
elif cmd . startswith ( " param " ) :
valid_context = (
isinstance ( context , MethodDef )
or isinstance ( context , SignalDef )
or isinstance ( context , AnnotationDef )
2022-08-06 19:48:54 +02:00
)
2022-08-09 22:53:29 +02:00
if not valid_context :
2022-08-06 19:48:54 +02:00
print_error (
2022-08-09 22:53:29 +02:00
' {} .xml: Argument reference " {} " used outside of method, signal, or annotation context in {} . ' . format (
state . current_class , link_target , context_name
2022-08-06 19:48:54 +02:00
) ,
state ,
)
2022-08-09 22:53:29 +02:00
else :
context_params : List [ ParameterDef ] = context . parameters
found = False
for param_def in context_params :
if param_def . name == link_target :
found = True
break
if not found :
print_error (
' {} .xml: Unresolved argument reference " {} " in {} . ' . format (
state . current_class , link_target , context_name
) ,
state ,
)
tag_text = " `` {} `` " . format ( link_target )
# 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 (
" {} .xml: Tag depth mismatch for [url]: no closing [/url] in {} . " . format (
state . current_class , context_name
) ,
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-09 22:53:29 +02:00
' {} .xml: Misformatted [url] tag " {} " in {} . ' . format ( state . current_class , cmd , context_name ) ,
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 ( " / " ) :
print_error (
' {} .xml: Unrecognized closing tag " {} " in {} . ' . format ( state . current_class , cmd , context_name ) , state
)
tag_text = " [ " + tag_text + " ] "
2016-10-30 18:44:57 +01:00
else :
2022-08-09 22:53:29 +02:00
print_error (
' {} .xml: Unrecognized opening tag " {} " in {} . ' . format ( state . current_class , cmd , context_name ) , state
)
tag_text = " `` {} `` " . format ( 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
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 )
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-09 22:53:29 +02:00
" {} .xml: Tag depth mismatch: too many (or too little) open/close tags in {} . " . format (
state . current_class , context_name
) ,
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 :
context_name = ' {} " {} " description ' . format ( context . definition_name , context . name )
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
text = text [ : pos ] + " \\ \\ " + text [ pos + 1 : ]
pos + = 2
# Escape * character to avoid interpreting it as emphasis
pos = 0
while True :
pos = text . find ( " * " , pos , until_pos )
if pos == - 1 :
break
text = text [ : pos ] + " \ * " + text [ pos + 1 : ]
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
text = text [ : pos ] + " \ _ " + text [ pos + 1 : ]
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 :
print_error ( " {} .xml: [ " + code_type + " ] without a closing tag. " . format ( state . current_class ) , state )
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 (
" {} .xml: Four spaces should be used for indentation within [ {} ]. " . format (
state . current_class , code_type
) ,
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 ) )
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
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
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-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
2022-07-28 00:54:26 +02:00
def indent_bullets ( text : str ) - > str :
2022-05-04 22:27:09 +02:00
# Take the text and check each line for a bullet point represented by "-".
# Where found, indent the given line by a further "\t".
# Used to properly indent bullet points contained in the description for enum values.
# Ignore the first line - text will be prepended to it so bullet points wouldn't work anyway.
bullet_points = " - "
lines = text . splitlines ( keepends = True )
for line_index , line in enumerate ( lines [ 1 : ] , start = 1 ) :
pos = 0
while pos < len ( line ) and line [ pos ] == " \t " :
pos + = 1
if pos < len ( line ) and line [ pos ] in bullet_points :
lines [ line_index ] = line [ : pos ] + " \t " + line [ pos : ]
return " " . join ( lines )
2020-03-30 08:28:32 +02:00
if __name__ == " __main__ " :
2018-12-26 15:57:51 +01:00
main ( )