summaryrefslogtreecommitdiff
path: root/doc/tools/makerst.py
diff options
context:
space:
mode:
Diffstat (limited to 'doc/tools/makerst.py')
-rwxr-xr-xdoc/tools/makerst.py118
1 files changed, 91 insertions, 27 deletions
diff --git a/doc/tools/makerst.py b/doc/tools/makerst.py
index ed147f31cd..a23324fd24 100755
--- a/doc/tools/makerst.py
+++ b/doc/tools/makerst.py
@@ -90,9 +90,13 @@ class EnumDef:
class ThemeItemDef:
- def __init__(self, name, type_name, default_value): # type: (str, TypeName, Optional[str]) -> None
+ def __init__(
+ self, name, type_name, data_name, text, default_value
+ ): # type: (str, TypeName, str, Optional[str], Optional[str]) -> None
self.name = name
self.type_name = type_name
+ self.data_name = data_name
+ self.text = text
self.default_value = default_value
@@ -104,11 +108,14 @@ class ClassDef:
self.properties = OrderedDict() # type: OrderedDict[str, PropertyDef]
self.methods = OrderedDict() # type: OrderedDict[str, List[MethodDef]]
self.signals = OrderedDict() # type: OrderedDict[str, SignalDef]
+ self.theme_items = OrderedDict() # type: OrderedDict[str, ThemeItemDef]
self.inherits = None # type: Optional[str]
self.brief_description = None # type: Optional[str]
self.description = None # type: Optional[str]
- self.theme_items = None # type: Optional[OrderedDict[str, List[ThemeItemDef]]]
- self.tutorials = [] # type: List[str]
+ self.tutorials = [] # type: List[Tuple[str, str]]
+
+ # Used to match the class with XML source for output filtering purposes.
+ self.filepath = "" # type: str
class State:
@@ -118,11 +125,12 @@ class State:
self.classes = OrderedDict() # type: OrderedDict[str, ClassDef]
self.current_class = "" # type: str
- def parse_class(self, class_root): # type: (ET.Element) -> None
+ def parse_class(self, class_root, filepath): # type: (ET.Element, str) -> None
class_name = class_root.attrib["name"]
class_def = ClassDef(class_name)
self.classes[class_name] = class_def
+ class_def.filepath = filepath
inherits = class_root.get("inherits")
if inherits is not None:
@@ -236,16 +244,33 @@ class State:
theme_items = class_root.find("theme_items")
if theme_items is not None:
- class_def.theme_items = OrderedDict()
for theme_item in theme_items:
assert theme_item.tag == "theme_item"
theme_item_name = theme_item.attrib["name"]
+ 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(
+ "Duplicate theme property '{}' of type '{}', file: {}".format(
+ theme_item_name, theme_item_data_name, class_name
+ ),
+ self,
+ )
+ continue
+
default_value = theme_item.get("default") or None
- theme_item_def = ThemeItemDef(theme_item_name, TypeName.from_element(theme_item), default_value)
- if theme_item_name not in class_def.theme_items:
- class_def.theme_items[theme_item_name] = []
- class_def.theme_items[theme_item_name].append(theme_item_def)
+ 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,
+ )
+ class_def.theme_items[theme_item_id] = theme_item_def
tutorials = class_root.find("tutorials")
if tutorials is not None:
@@ -253,7 +278,7 @@ class State:
assert link.tag == "link"
if link.text is not None:
- class_def.tutorials.append(link.text)
+ class_def.tutorials.append((link.text.strip(), link.get("title", "")))
def sort_classes(self): # type: () -> None
self.classes = OrderedDict(sorted(self.classes.items(), key=lambda t: t[0]))
@@ -278,6 +303,7 @@ def parse_arguments(root): # type: (ET.Element) -> List[ParameterDef]
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.")
group = parser.add_mutually_exclusive_group()
group.add_argument("--output", "-o", default=".", help="The directory to save output .rst files in.")
group.add_argument(
@@ -333,22 +359,31 @@ def main(): # type: () -> None
print_error("Duplicate class '{}'".format(name), state)
continue
- classes[name] = doc
+ classes[name] = (doc, cur_file)
for name, data in classes.items():
try:
- state.parse_class(data)
+ state.parse_class(data[0], data[1])
except Exception as e:
print_error("Exception while parsing class '{}': {}".format(name, e), state)
state.sort_classes()
+ pattern = re.compile(args.filter)
+
+ # Create the output folder recursively if it doesn't already exist.
+ os.makedirs(args.output, exist_ok=True)
+
for class_name, class_def in state.classes.items():
+ if args.filter and not pattern.search(class_def.filepath):
+ continue
state.current_class = class_name
make_rst_class(class_def, state, args.dry_run, args.output)
if not state.errored:
print("No errors found.")
+ 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.")
exit(1)
@@ -417,9 +452,8 @@ def make_rst_class(class_def, state, dry_run, output_dir): # type: (ClassDef, S
# Online tutorials
if len(class_def.tutorials) > 0:
f.write(make_heading("Tutorials", "-"))
- for t in class_def.tutorials:
- link = t.strip()
- f.write("- " + make_url(link) + "\n\n")
+ for url, title in class_def.tutorials:
+ f.write("- " + make_link(url, title) + "\n\n")
# Properties overview
if len(class_def.properties) > 0:
@@ -428,7 +462,7 @@ 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 property_def.overridden:
+ if default is not None and property_def.overridden:
ml.append((type_rst, property_def.name, default + " *(parent override)*"))
else:
ref = ":ref:`{0}<class_{1}_property_{0}>`".format(property_def.name, class_name)
@@ -445,12 +479,14 @@ def make_rst_class(class_def, state, dry_run, output_dir): # type: (ClassDef, S
format_table(f, ml)
# Theme properties
- if class_def.theme_items is not None and len(class_def.theme_items) > 0:
+ if len(class_def.theme_items) > 0:
f.write(make_heading("Theme Properties", "-"))
pl = []
- for theme_item_list in class_def.theme_items.values():
- for theme_item in theme_item_list:
- pl.append((theme_item.type_name.to_rst(state), theme_item.name, theme_item.default_value))
+ 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))
format_table(f, pl, True)
# Signals
@@ -565,6 +601,30 @@ def make_rst_class(class_def, state, dry_run, output_dir): # type: (ClassDef, S
index += 1
+ # Theme property descriptions
+ 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:
+ info.append(("*Default*", theme_item_def.default_value))
+
+ if len(info) > 0:
+ format_table(f, info)
+
+ if theme_item_def.text is not None and theme_item_def.text.strip() != "":
+ f.write(rstize_text(theme_item_def.text.strip(), state) + "\n\n")
+
+ index += 1
+
f.write(make_footer())
@@ -828,7 +888,7 @@ def rstize_text(text, state): # type: (str, State) -> str
inside_code = True
elif cmd == "gdscript":
tag_depth += 1
- tag_text = "\n .. code-tab:: gdscript GDScript\n"
+ tag_text = "\n .. code-tab:: gdscript\n"
inside_code = True
elif cmd == "csharp":
tag_depth += 1
@@ -947,6 +1007,8 @@ def format_table(f, data, remove_empty_columns=False): # type: (TextIO, Iterabl
def make_type(klass, state): # type: (str, 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]
@@ -971,9 +1033,6 @@ def make_enum(t, state): # type: (str, State) -> str
if c in state.classes and e not in state.classes[c].enums:
c = "@GlobalScope"
- if not c in state.classes and c.startswith("_"):
- c = c[1:] # Remove the underscore prefix
-
if c in state.classes and e in state.classes[c].enums:
return ":ref:`{0}<enum_{1}_{0}>`".format(e, c)
@@ -1042,12 +1101,14 @@ def make_footer(): # type: () -> str
".. |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"
+ ".. |operator| replace:: :abbr:`operator (This method describes a valid operator to use with this type as left-hand operand.)`\n"
)
# fmt: on
-def make_url(link): # type: (str) -> str
- match = GODOT_DOCS_PATTERN.search(link)
+def make_link(url, title): # type: (str, str) -> str
+ match = GODOT_DOCS_PATTERN.search(url)
if match:
groups = match.groups()
if match.lastindex == 2:
@@ -1064,7 +1125,10 @@ def make_url(link): # type: (str) -> str
else:
# External link, for example:
# `http://enet.bespin.org/usergroup0.html`
- return "`" + link + " <" + link + ">`_"
+ if title != "":
+ return "`" + title + " <" + url + ">`_"
+ else:
+ return "`" + url + " <" + url + ">`_"
if __name__ == "__main__":