summaryrefslogtreecommitdiff
path: root/doc/tools
diff options
context:
space:
mode:
Diffstat (limited to 'doc/tools')
-rwxr-xr-xdoc/tools/doc_status.py32
-rwxr-xr-xdoc/tools/make_rst.py295
2 files changed, 226 insertions, 101 deletions
diff --git a/doc/tools/doc_status.py b/doc/tools/doc_status.py
index 629b5a032b..cc0733cab2 100755
--- a/doc/tools/doc_status.py
+++ b/doc/tools/doc_status.py
@@ -66,10 +66,23 @@ table_columns = [
"methods",
"constants",
"members",
- "signals",
"theme_items",
+ "signals",
+ "operators",
+ "constructors",
+]
+table_column_names = [
+ "Name",
+ "Brief Desc.",
+ "Desc.",
+ "Methods",
+ "Constants",
+ "Members",
+ "Theme Items",
+ "Signals",
+ "Operators",
+ "Constructors",
]
-table_column_names = ["Name", "Brief Desc.", "Desc.", "Methods", "Constants", "Members", "Signals", "Theme Items"]
colors = {
"name": [36], # cyan
"part_big_problem": [4, 31], # underline, red
@@ -82,7 +95,7 @@ colors = {
"state_on": [1, 35], # bold, magenta/plum
"bold": [1], # bold
}
-overall_progress_description_weigth = 10
+overall_progress_description_weight = 10
################################################################################
@@ -180,6 +193,8 @@ class ClassStatus:
"members": ClassStatusProgress(),
"theme_items": ClassStatusProgress(),
"signals": ClassStatusProgress(),
+ "operators": ClassStatusProgress(),
+ "constructors": ClassStatusProgress(),
}
def __add__(self, other):
@@ -218,12 +233,12 @@ class ClassStatus:
output["description"] = ok_string if self.has_description else missing_string
description_progress = ClassStatusProgress(
- (self.has_brief_description + self.has_description) * overall_progress_description_weigth,
- 2 * overall_progress_description_weigth,
+ (self.has_brief_description + self.has_description) * overall_progress_description_weight,
+ 2 * overall_progress_description_weight,
)
items_progress = ClassStatusProgress()
- for k in ["methods", "constants", "members", "signals", "theme_items"]:
+ for k in ["methods", "constants", "members", "theme_items", "signals", "constructors", "operators"]:
items_progress += self.progresses[k]
output[k] = self.progresses[k].to_configured_colored_string()
@@ -260,7 +275,7 @@ class ClassStatus:
elif tag.tag == "description":
status.has_description = len(tag.text.strip()) > 0
- elif tag.tag in ["methods", "signals"]:
+ elif tag.tag in ["methods", "signals", "operators", "constructors"]:
for sub_tag in list(tag):
descr = sub_tag.find("description")
status.progresses[tag.tag].increment(len(descr.text.strip()) > 0)
@@ -272,9 +287,6 @@ class ClassStatus:
elif tag.tag in ["tutorials"]:
pass # Ignore those tags for now
- elif tag.tag in ["theme_items"]:
- pass # Ignore those tags, since they seem to lack description at all
-
else:
print(tag.tag, tag.attrib)
diff --git a/doc/tools/make_rst.py b/doc/tools/make_rst.py
index fe8083f218..eba4cee33a 100755
--- a/doc/tools/make_rst.py
+++ b/doc/tools/make_rst.py
@@ -11,15 +11,54 @@ from collections import OrderedDict
# Uncomment to do type checks. I have it commented out so it works below Python 3.5
# from typing import List, Dict, TextIO, Tuple, Iterable, Optional, DefaultDict, Any, Union
-# http(s)://docs.godotengine.org/<langcode>/<tag>/path/to/page.html(#fragment-tag)
-GODOT_DOCS_PATTERN = re.compile(
- r"^http(?:s)?://docs\.godotengine\.org/(?:[a-zA-Z0-9.\-_]*)/(?:[a-zA-Z0-9.\-_]*)/(.*)\.html(#.*)?$"
-)
+# $DOCS_URL/path/to/page.html(#fragment-tag)
+GODOT_DOCS_PATTERN = re.compile(r"^\$DOCS_URL/(.*)\.html(#.*)?$")
+
+# 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 = " -.,:;!?\\/'\")]}>"
+
+# 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 = [
+ "Description",
+ "Tutorials",
+ "Properties",
+ "Constructors",
+ "Methods",
+ "Operators",
+ "Theme Properties",
+ "Signals",
+ "Enumerations",
+ "Constants",
+ "Property Descriptions",
+ "Constructor Descriptions",
+ "Method Descriptions",
+ "Operator Descriptions",
+ "Theme Property Descriptions",
+ "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.",
+]
+strings_l10n = {}
def print_error(error, state): # type: (str, State) -> None
print("ERROR: {}".format(error))
- state.errored = True
+ state.num_errors += 1
class TypeName:
@@ -42,15 +81,15 @@ class TypeName:
class PropertyDef:
def __init__(
- self, name, type_name, setter, getter, text, default_value, overridden
- ): # type: (str, TypeName, Optional[str], Optional[str], Optional[str], Optional[str], Optional[bool]) -> None
+ self, name, type_name, setter, getter, text, default_value, overrides
+ ): # type: (str, TypeName, Optional[str], Optional[str], Optional[str], Optional[str], Optional[str]) -> None
self.name = name
self.type_name = type_name
self.setter = setter
self.getter = getter
self.text = text
self.default_value = default_value
- self.overridden = overridden
+ self.overrides = overrides
class ParameterDef:
@@ -124,8 +163,7 @@ class ClassDef:
class State:
def __init__(self): # type: () -> None
- # Has any error been reported?
- self.errored = False
+ self.num_errors = 0
self.classes = OrderedDict() # type: OrderedDict[str, ClassDef]
self.current_class = "" # type: str
@@ -155,7 +193,7 @@ class State:
property_name = property.attrib["name"]
if property_name in class_def.properties:
- print_error("Duplicate property '{}', file: {}".format(property_name, class_name), self)
+ print_error('{}.xml: Duplicate property "{}".'.format(class_name, property_name), self)
continue
type_name = TypeName.from_element(property)
@@ -164,10 +202,10 @@ class State:
default_value = property.get("default") or None
if default_value is not None:
default_value = "``{}``".format(default_value)
- overridden = property.get("override") or False
+ overrides = property.get("overrides") or None
property_def = PropertyDef(
- property_name, type_name, setter, getter, property.text, default_value, overridden
+ property_name, type_name, setter, getter, property.text, default_value, overrides
)
class_def.properties[property_name] = property_def
@@ -266,7 +304,7 @@ class State:
constant_def = ConstantDef(constant_name, value, constant.text)
if enum is None:
if constant_name in class_def.constants:
- print_error("Duplicate constant '{}', file: {}".format(constant_name, class_name), self)
+ print_error('{}.xml: Duplicate constant "{}".'.format(class_name, constant_name), self)
continue
class_def.constants[constant_name] = constant_def
@@ -289,7 +327,7 @@ class State:
signal_name = signal.attrib["name"]
if signal_name in class_def.signals:
- print_error("Duplicate signal '{}', file: {}".format(signal_name, class_name), self)
+ print_error('{}.xml: Duplicate signal "{}".'.format(class_name, signal_name), self)
continue
params = parse_arguments(signal)
@@ -312,8 +350,8 @@ class State:
theme_item_id = "{}_{}".format(theme_item_data_name, theme_item_name)
if theme_item_id in class_def.theme_items:
print_error(
- "Duplicate theme property '{}' of type '{}', file: {}".format(
- theme_item_name, theme_item_data_name, class_name
+ '{}.xml: Duplicate theme item "{}" of type "{}".'.format(
+ class_name, theme_item_name, theme_item_data_name
),
self,
)
@@ -330,7 +368,7 @@ class State:
theme_item.text,
default_value,
)
- class_def.theme_items[theme_item_id] = theme_item_def
+ class_def.theme_items[theme_item_name] = theme_item_def
tutorials = class_root.find("tutorials")
if tutorials is not None:
@@ -364,6 +402,7 @@ 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.")
parser.add_argument("--filter", default="", help="The filepath pattern for XML files to filter.")
+ parser.add_argument("--lang", "-l", default="en", help="Language to use for section headings.")
group = parser.add_mutually_exclusive_group()
group.add_argument("--output", "-o", default=".", help="The directory to save output .rst files in.")
group.add_argument(
@@ -373,13 +412,32 @@ def main(): # type: () -> None
)
args = parser.parse_args()
+ # 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:
+ print("Base template strings localization requires `polib`.")
+ exit(1)
+
+ pofile = polib.pofile(lang_file)
+ for entry in pofile.translated_entries():
+ if entry.msgid in BASE_STRINGS:
+ strings_l10n[entry.msgid] = entry.msgstr
+ else:
+ print('No PO file at "{}" for language "{}".'.format(lang_file, args.lang))
+
print("Checking for errors in the XML class reference...")
file_list = [] # type: List[str]
for path in args.path:
# Cut off trailing slashes so os.path.basename doesn't choke.
- if path.endswith(os.sep):
+ if path.endswith("/") or path.endswith("\\"):
path = path[:-1]
if os.path.basename(path) == "modules":
@@ -394,7 +452,7 @@ def main(): # type: () -> None
elif os.path.isfile(path):
if not path.endswith(".xml"):
- print("Got non-.xml file '{}' in input, skipping.".format(path))
+ print('Got non-.xml file "{}" in input, skipping.'.format(path))
continue
file_list.append(path)
@@ -406,17 +464,17 @@ def main(): # type: () -> None
try:
tree = ET.parse(cur_file)
except ET.ParseError as e:
- print_error("Parse error reading file '{}': {}".format(cur_file, e), state)
+ print_error("{}.xml: Parse error while reading the file: {}".format(cur_file, e), state)
continue
doc = tree.getroot()
if "version" not in doc.attrib:
- print_error("Version missing from 'doc', file: {}".format(cur_file), state)
+ print_error('{}.xml: "version" attribute missing from "doc".'.format(cur_file), state)
continue
name = doc.attrib["name"]
if name in classes:
- print_error("Duplicate class '{}'".format(name), state)
+ print_error('{}.xml: Duplicate class "{}".'.format(cur_file, name), state)
continue
classes[name] = (doc, cur_file)
@@ -425,7 +483,7 @@ def main(): # type: () -> None
try:
state.parse_class(data[0], data[1])
except Exception as e:
- print_error("Exception while parsing class '{}': {}".format(name, e), state)
+ print_error("{}.xml: Exception while parsing class: {}".format(name, e), state)
state.sort_classes()
@@ -440,15 +498,28 @@ def main(): # type: () -> None
state.current_class = class_name
make_rst_class(class_def, state, args.dry_run, args.output)
- if not state.errored:
- print("No errors found.")
+ if state.num_errors == 0:
+ print("No errors found in the class reference XML.")
if not args.dry_run:
print("Wrote reStructuredText files for each class to: %s" % args.output)
else:
- print("Errors were found in the class reference XML. Please check the messages above.")
+ 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.")
exit(1)
+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)
+
+
def make_rst_class(class_def, state, dry_run, output_dir): # type: (ClassDef, State, bool, str) -> None
class_name = class_def.name
@@ -464,24 +535,24 @@ def make_rst_class(class_def, state, dry_run, output_dir): # type: (ClassDef, S
f.write(".. The source is found in doc/classes or modules/<name>/doc_classes.\n\n")
f.write(".. _class_" + class_name + ":\n\n")
- f.write(make_heading(class_name, "="))
+ f.write(make_heading(class_name, "=", False))
# Inheritance tree
# Ascendants
if class_def.inherits:
- inh = class_def.inherits.strip()
- f.write("**Inherits:** ")
+ inherits = class_def.inherits.strip()
+ f.write("**" + translate("Inherits:") + "** ")
first = True
- while inh in state.classes:
+ while inherits in state.classes:
if not first:
f.write(" **<** ")
else:
first = False
- f.write(make_type(inh, state))
- inode = state.classes[inh].inherits
+ f.write(make_type(inherits, state))
+ inode = state.classes[inherits].inherits
if inode:
- inh = inode.strip()
+ inherits = inode.strip()
else:
break
f.write("\n\n")
@@ -493,7 +564,7 @@ def make_rst_class(class_def, state, dry_run, output_dir): # type: (ClassDef, S
inherited.append(c.name)
if len(inherited):
- f.write("**Inherited By:** ")
+ f.write("**" + translate("Inherited By:") + "** ")
for i, child in enumerate(inherited):
if i > 0:
f.write(", ")
@@ -522,8 +593,10 @@ def make_rst_class(class_def, state, dry_run, output_dir): # type: (ClassDef, S
for property_def in class_def.properties.values():
type_rst = property_def.type_name.to_rst(state)
default = property_def.default_value
- if default is not None and property_def.overridden:
- ml.append((type_rst, property_def.name, default + " *(parent override)*"))
+ if default is not None and property_def.overrides:
+ ref = ":ref:`{1}<class_{1}_property_{0}>`".format(property_def.name, property_def.overrides)
+ # Not using translate() for now as it breaks table formatting.
+ ml.append((type_rst, property_def.name, default + " " + "(overrides %s)" % ref))
else:
ref = ":ref:`{0}<class_{1}_property_{0}>`".format(property_def.name, class_name)
ml.append((type_rst, ref, default))
@@ -626,12 +699,12 @@ def make_rst_class(class_def, state, dry_run, output_dir): # type: (ClassDef, S
f.write("\n\n")
# Property descriptions
- if any(not p.overridden for p in class_def.properties.values()) > 0:
+ if any(not p.overrides for p in class_def.properties.values()) > 0:
f.write(make_heading("Property Descriptions", "-"))
index = 0
for property_def in class_def.properties.values():
- if property_def.overridden:
+ if property_def.overrides:
continue
if index != 0:
@@ -641,12 +714,13 @@ def make_rst_class(class_def, state, dry_run, output_dir): # type: (ClassDef, S
f.write("- {} **{}**\n\n".format(property_def.type_name.to_rst(state), property_def.name))
info = []
+ # Not using translate() for now as it breaks table formatting.
if property_def.default_value is not None:
- info.append(("*Default*", property_def.default_value))
+ info.append(("*" + "Default" + "*", property_def.default_value))
if property_def.setter is not None and not property_def.setter.startswith("_"):
- info.append(("*Setter*", property_def.setter + "(value)"))
+ info.append(("*" + "Setter" + "*", property_def.setter + "(" + "value" + ")"))
if property_def.getter is not None and not property_def.getter.startswith("_"):
- info.append(("*Getter*", property_def.getter + "()"))
+ info.append(("*" + "Getter" + "*", property_def.getter + "()"))
if len(info) > 0:
format_table(f, info)
@@ -735,7 +809,8 @@ def make_rst_class(class_def, state, dry_run, output_dir): # type: (ClassDef, S
info = []
if theme_item_def.default_value is not None:
- info.append(("*Default*", theme_item_def.default_value))
+ # Not using translate() for now as it breaks table formatting.
+ info.append(("*" + "Default" + "*", theme_item_def.default_value))
if len(info) > 0:
format_table(f, info)
@@ -785,7 +860,7 @@ def escape_rst(text, until_pos=-1): # type: (str) -> str
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:
- print_error("[" + code_type + "] without a closing tag, file: {}".format(state.current_class), state)
+ 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]
@@ -804,9 +879,9 @@ def format_codeblock(code_type, post_text, indent_level, state): # types: str,
if to_skip > indent_level:
print_error(
- "Four spaces should be used for indentation within ["
+ "{}.xml: Four spaces should be used for indentation within ["
+ code_type
- + "], file: {}".format(state.current_class),
+ + "].".format(state.current_class),
state,
)
@@ -845,28 +920,23 @@ def rstize_text(text, state): # type: (str, State) -> str
if result is None:
return ""
text = pre_text + result[0]
- pos += result[1]
+ pos += result[1] - indent_level
# Handle normal text
else:
text = pre_text + "\n\n" + post_text
- pos += 2
+ pos += 2 - indent_level
next_brac_pos = text.find("[")
text = escape_rst(text, next_brac_pos)
# Handle [tags]
inside_code = False
- inside_url = False
- url_has_name = False
- url_link = ""
pos = 0
tag_depth = 0
previous_pos = 0
while True:
pos = text.find("[", pos)
- if inside_url and (pos > previous_pos):
- url_has_name = True
if pos == -1:
break
@@ -878,6 +948,7 @@ def rstize_text(text, state): # type: (str, State) -> str
post_text = text[endq_pos + 1 :]
tag_text = text[pos + 1 : endq_pos]
+ escape_pre = False
escape_post = False
if tag_text in state.classes:
@@ -886,6 +957,7 @@ def rstize_text(text, state): # type: (str, State) -> str
tag_text = "``{}``".format(tag_text)
else:
tag_text = make_type(tag_text, state)
+ escape_pre = True
escape_post = True
else: # command
cmd = tag_text
@@ -912,13 +984,14 @@ def rstize_text(text, state): # type: (str, State) -> str
or cmd.startswith("member")
or cmd.startswith("signal")
or cmd.startswith("constant")
+ or cmd.startswith("theme_item")
):
param = tag_text[space_pos + 1 :]
if param.find(".") != -1:
ss = param.split(".")
if len(ss) > 2:
- print_error("Bad reference: '{}', file: {}".format(param, state.current_class), state)
+ print_error('{}.xml: Bad reference: "{}".'.format(state.current_class, param), state)
class_param, method_param = ss
else:
@@ -931,26 +1004,31 @@ def rstize_text(text, state): # type: (str, State) -> str
if cmd.startswith("constructor"):
if method_param not in class_def.constructors:
print_error(
- "Unresolved constructor '{}', file: {}".format(param, state.current_class), state
+ '{}.xml: Unresolved constructor "{}".'.format(state.current_class, param), state
)
ref_type = "_constructor"
if cmd.startswith("method"):
if method_param not in class_def.methods:
- print_error("Unresolved method '{}', file: {}".format(param, state.current_class), state)
+ print_error('{}.xml: Unresolved method "{}".'.format(state.current_class, param), state)
ref_type = "_method"
if cmd.startswith("operator"):
if method_param not in class_def.operators:
- print_error("Unresolved operator '{}', file: {}".format(param, state.current_class), state)
+ print_error('{}.xml: Unresolved operator "{}".'.format(state.current_class, param), state)
ref_type = "_operator"
elif cmd.startswith("member"):
if method_param not in class_def.properties:
- print_error("Unresolved member '{}', file: {}".format(param, state.current_class), state)
+ print_error('{}.xml: Unresolved member "{}".'.format(state.current_class, param), state)
ref_type = "_property"
+ elif cmd.startswith("theme_item"):
+ if method_param not in class_def.theme_items:
+ print_error('{}.xml: Unresolved theme item "{}".'.format(state.current_class, param), state)
+ ref_type = "_theme_{}".format(class_def.theme_items[method_param].data_name)
+
elif cmd.startswith("signal"):
if method_param not in class_def.signals:
- print_error("Unresolved signal '{}', file: {}".format(param, state.current_class), state)
+ print_error('{}.xml: Unresolved signal "{}".'.format(state.current_class, param), state)
ref_type = "_signal"
elif cmd.startswith("constant"):
@@ -976,13 +1054,13 @@ def rstize_text(text, state): # type: (str, State) -> str
break
if not found:
- print_error("Unresolved constant '{}', file: {}".format(param, state.current_class), state)
+ print_error('{}.xml: Unresolved constant "{}".'.format(state.current_class, param), state)
ref_type = "_constant"
else:
print_error(
- "Unresolved type reference '{}' in method reference '{}', file: {}".format(
- class_param, param, state.current_class
+ '{}.xml: Unresolved type reference "{}" in method reference "{}".'.format(
+ state.current_class, class_param, param
),
state,
)
@@ -991,21 +1069,35 @@ def rstize_text(text, state): # type: (str, State) -> str
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.find("image=") == 0:
tag_text = "" # '![](' + cmd[6:] + ')'
elif cmd.find("url=") == 0:
- url_link = cmd[4:]
- tag_text = "`"
- tag_depth += 1
- inside_url = True
- url_has_name = False
- elif cmd == "/url":
- tag_text = ("" if url_has_name else url_link) + " <" + url_link + ">`__"
- tag_depth -= 1
- escape_post = True
- inside_url = False
- url_has_name = False
+ # 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]".format(state.current_class), state
+ )
+ break
+ link_title = text[endq_pos + 1 : endurl_pos]
+ tag_text = make_link(link_url, link_title)
+
+ 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)
+ previous_pos = pos
+ continue
elif cmd == "center":
tag_depth += 1
tag_text = ""
@@ -1039,40 +1131,53 @@ def rstize_text(text, state): # type: (str, State) -> str
elif cmd == "i" or cmd == "/i":
if cmd == "/i":
tag_depth -= 1
+ escape_post = True
else:
tag_depth += 1
+ escape_pre = True
tag_text = "*"
elif cmd == "b" or cmd == "/b":
if cmd == "/b":
tag_depth -= 1
+ escape_post = True
else:
tag_depth += 1
+ escape_pre = True
tag_text = "**"
elif cmd == "u" or cmd == "/u":
if cmd == "/u":
tag_depth -= 1
+ escape_post = True
else:
tag_depth += 1
+ escape_pre = True
tag_text = ""
elif cmd == "code":
tag_text = "``"
tag_depth += 1
inside_code = True
+ escape_pre = True
elif cmd == "kbd":
tag_text = ":kbd:`"
tag_depth += 1
+ escape_pre = True
elif cmd == "/kbd":
tag_text = "`"
tag_depth -= 1
+ escape_post = True
elif cmd.startswith("enum "):
tag_text = make_enum(cmd[5:], state)
+ escape_pre = True
escape_post = True
else:
tag_text = make_type(tag_text, state)
+ escape_pre = True
escape_post = True
# Properly escape things like `[Node]s`
- if escape_post and post_text and (post_text[0].isalnum() or post_text[0] == "("): # not punctuation, escape
+ 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:
post_text = "\ " + post_text
next_brac_pos = post_text.find("[", 0)
@@ -1100,7 +1205,9 @@ def rstize_text(text, state): # type: (str, State) -> str
previous_pos = pos
if tag_depth > 0:
- print_error("Tag depth mismatch: too many/little open/close tags, file: {}".format(state.current_class), state)
+ print_error(
+ "{}.xml: Tag depth mismatch: too many (or too little) open/close tags.".format(state.current_class), state
+ )
return text
@@ -1144,7 +1251,7 @@ def make_type(klass, state): # type: (str, State) -> str
link_type = link_type[:-2]
if link_type in state.classes:
return ":ref:`{}<class_{}>`".format(klass, link_type)
- print_error("Unresolved type '{}', file: {}".format(klass, state.current_class), state)
+ print_error('{}.xml: Unresolved type "{}".'.format(state.current_class, klass), state)
return klass
@@ -1168,7 +1275,7 @@ def make_enum(t, state): # type: (str, State) -> str
# 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("Unresolved enum '{}', file: {}".format(t, state.current_class), state)
+ print_error('{}.xml: Unresolved enum "{}".'.format(state.current_class, t), state)
return t
@@ -1226,7 +1333,12 @@ def make_method_signature(
return ret_type, out
-def make_heading(title, underline): # type: (str, str) -> str
+def make_heading(title, underline, l10n=True): # type: (str, str, bool) -> 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"
@@ -1235,12 +1347,12 @@ def make_footer(): # type: () -> str
# This way, we avoid bloating the generated rST with duplicate abbreviations.
# fmt: off
return (
- ".. |virtual| replace:: :abbr:`virtual (This method should typically be overridden by the user to have any effect.)`\n"
- ".. |const| replace:: :abbr:`const (This method has no side effects. It doesn't modify any of the instance's member variables.)`\n"
- ".. |vararg| replace:: :abbr:`vararg (This method accepts any number of arguments after the ones described here.)`\n"
- ".. |constructor| replace:: :abbr:`constructor (This method is used to construct a type.)`\n"
- ".. |static| replace:: :abbr:`static (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 (This method describes a valid operator to use with this type as left-hand operand.)`\n"
+ ".. |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
@@ -1252,21 +1364,22 @@ def make_link(url, title): # type: (str, str) -> str
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`
- return "`" + groups[1] + " <../" + groups[0] + ".html" + groups[1] + ">`_ in :doc:`../" + groups[0] + "`"
- # Commented out alternative: Instead just emit:
- # `Subsection in Exporting For Web`
- # return "`Subsection <../" + groups[0] + ".html" + groups[1] + ">`__ in :doc:`../" + groups[0] + "`"
+ # 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] + "`"
else:
# External link, for example:
# `http://enet.bespin.org/usergroup0.html`
if title != "":
return "`" + title + " <" + url + ">`__"
- else:
- return "`" + url + " <" + url + ">`__"
+ return "`" + url + " <" + url + ">`__"
def sanitize_operator_name(dirty_name, state): # type: (str, State) -> str
@@ -1320,7 +1433,7 @@ def sanitize_operator_name(dirty_name, state): # type: (str, State) -> str
else:
clear_name = "xxx"
- print_error("Unsupported operator type '{}', please add the missing rule.".format(dirty_name), state)
+ print_error('Unsupported operator type "{}", please add the missing rule.'.format(dirty_name), state)
return clear_name