mirror of
				https://github.com/godotengine/godot.git
				synced 2025-11-04 07:31:16 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			543 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			543 lines
		
	
	
	
		
			16 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
#!/usr/bin/env python
 | 
						|
# -*- coding: utf-8 -*-
 | 
						|
 | 
						|
import codecs
 | 
						|
import sys
 | 
						|
import os
 | 
						|
import xml.etree.ElementTree as ET
 | 
						|
 | 
						|
input_list = []
 | 
						|
 | 
						|
for arg in sys.argv[1:]:
 | 
						|
    if arg.endswith(os.sep):
 | 
						|
        arg = arg[:-1]
 | 
						|
    input_list.append(arg)
 | 
						|
 | 
						|
if len(input_list) < 1:
 | 
						|
    print('usage: makerst.py <path to folders> and/or <path to .xml files> (order of arguments irrelevant)')
 | 
						|
    print('example: makerst.py "../../modules/" "../classes" path_to/some_class.xml')
 | 
						|
    sys.exit(0)
 | 
						|
 | 
						|
 | 
						|
def validate_tag(elem, tag):
 | 
						|
    if elem.tag != tag:
 | 
						|
        print("Tag mismatch, expected '" + tag + "', got " + elem.tag)
 | 
						|
        sys.exit(255)
 | 
						|
 | 
						|
 | 
						|
class_names = []
 | 
						|
classes = {}
 | 
						|
 | 
						|
 | 
						|
def ul_string(str, ul):
 | 
						|
    str += "\n"
 | 
						|
    for i in range(len(str) - 1):
 | 
						|
        str += ul
 | 
						|
    str += "\n"
 | 
						|
    return str
 | 
						|
 | 
						|
 | 
						|
def make_class_list(class_list, columns):
 | 
						|
    f = codecs.open('class_list.rst', 'wb', 'utf-8')
 | 
						|
    prev = 0
 | 
						|
    col_max = len(class_list) / columns + 1
 | 
						|
    print(('col max is ', col_max))
 | 
						|
    col_count = 0
 | 
						|
    row_count = 0
 | 
						|
    last_initial = ''
 | 
						|
    fit_columns = []
 | 
						|
 | 
						|
    for n in range(0, columns):
 | 
						|
        fit_columns += [[]]
 | 
						|
 | 
						|
    indexers = []
 | 
						|
    last_initial = ''
 | 
						|
 | 
						|
    idx = 0
 | 
						|
    for n in class_list:
 | 
						|
        col = idx / col_max
 | 
						|
        if col >= columns:
 | 
						|
            col = columns - 1
 | 
						|
        fit_columns[col] += [n]
 | 
						|
        idx += 1
 | 
						|
        if n[:1] != last_initial:
 | 
						|
            indexers += [n]
 | 
						|
        last_initial = n[:1]
 | 
						|
 | 
						|
    row_max = 0
 | 
						|
    f.write("\n")
 | 
						|
 | 
						|
    for n in range(0, columns):
 | 
						|
        if len(fit_columns[n]) > row_max:
 | 
						|
            row_max = len(fit_columns[n])
 | 
						|
 | 
						|
    f.write("| ")
 | 
						|
    for n in range(0, columns):
 | 
						|
        f.write(" | |")
 | 
						|
 | 
						|
    f.write("\n")
 | 
						|
    f.write("+")
 | 
						|
    for n in range(0, columns):
 | 
						|
        f.write("--+-------+")
 | 
						|
    f.write("\n")
 | 
						|
 | 
						|
    for r in range(0, row_max):
 | 
						|
        s = '+ '
 | 
						|
        for c in range(0, columns):
 | 
						|
            if r >= len(fit_columns[c]):
 | 
						|
                continue
 | 
						|
 | 
						|
            classname = fit_columns[c][r]
 | 
						|
            initial = classname[0]
 | 
						|
            if classname in indexers:
 | 
						|
                s += '**' + initial + '** | '
 | 
						|
            else:
 | 
						|
                s += ' | '
 | 
						|
 | 
						|
            s += '[' + classname + '](class_' + classname.lower() + ') | '
 | 
						|
 | 
						|
        s += '\n'
 | 
						|
        f.write(s)
 | 
						|
 | 
						|
    for n in range(0, columns):
 | 
						|
        f.write("--+-------+")
 | 
						|
    f.write("\n")
 | 
						|
 | 
						|
 | 
						|
def rstize_text(text, cclass):
 | 
						|
    # Linebreak + tabs in the XML should become two line breaks unless in a "codeblock"
 | 
						|
    pos = 0
 | 
						|
    while True:
 | 
						|
        pos = text.find('\n', pos)
 | 
						|
        if pos == -1:
 | 
						|
            break
 | 
						|
 | 
						|
        pre_text = text[:pos]
 | 
						|
        while text[pos + 1] == '\t':
 | 
						|
            pos += 1
 | 
						|
        post_text = text[pos + 1:]
 | 
						|
 | 
						|
        # Handle codeblocks
 | 
						|
        if post_text.startswith("[codeblock]"):
 | 
						|
            end_pos = post_text.find("[/codeblock]")
 | 
						|
            if end_pos == -1:
 | 
						|
                sys.exit("ERROR! [codeblock] without a closing tag!")
 | 
						|
 | 
						|
            code_text = post_text[len("[codeblock]"):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 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
 | 
						|
 | 
						|
            text = pre_text + "\n[codeblock]" + code_text + post_text
 | 
						|
            pos += len("\n[codeblock]" + code_text)
 | 
						|
 | 
						|
        # Handle normal text
 | 
						|
        else:
 | 
						|
            text = pre_text + "\n\n" + post_text
 | 
						|
            pos += 2
 | 
						|
 | 
						|
    # Escape * character to avoid interpreting it as emphasis
 | 
						|
    pos = 0
 | 
						|
    while True:
 | 
						|
        pos = text.find('*', 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)
 | 
						|
        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
 | 
						|
 | 
						|
    # Handle [tags]
 | 
						|
    inside_code = False
 | 
						|
    pos = 0
 | 
						|
    while True:
 | 
						|
        pos = text.find('[', pos)
 | 
						|
        if pos == -1:
 | 
						|
            break
 | 
						|
 | 
						|
        endq_pos = text.find(']', pos + 1)
 | 
						|
        if endq_pos == -1:
 | 
						|
            break
 | 
						|
 | 
						|
        pre_text = text[:pos]
 | 
						|
        post_text = text[endq_pos + 1:]
 | 
						|
        tag_text = text[pos + 1:endq_pos]
 | 
						|
 | 
						|
        escape_post = False
 | 
						|
 | 
						|
        if tag_text in class_names:
 | 
						|
            tag_text = make_type(tag_text)
 | 
						|
            escape_post = True
 | 
						|
        else:  # command
 | 
						|
            cmd = tag_text
 | 
						|
            space_pos = tag_text.find(' ')
 | 
						|
            if cmd == '/codeblock':
 | 
						|
                tag_text = ''
 | 
						|
                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 = '``'
 | 
						|
                inside_code = False
 | 
						|
            elif inside_code:
 | 
						|
                tag_text = '[' + tag_text + ']'
 | 
						|
            elif cmd.find('html') == 0:
 | 
						|
                cmd = tag_text[:space_pos]
 | 
						|
                param = tag_text[space_pos + 1:]
 | 
						|
                tag_text = param
 | 
						|
            elif cmd.find('method') == 0 or cmd.find('member') == 0 or cmd.find('signal') == 0:
 | 
						|
                cmd = tag_text[:space_pos]
 | 
						|
                param = tag_text[space_pos + 1:]
 | 
						|
 | 
						|
                if param.find('.') != -1:
 | 
						|
                    (class_param, method_param) = param.split('.')
 | 
						|
                    tag_text = ':ref:`' + class_param + '.' + method_param + '<class_' + class_param + '_' + method_param + '>`'
 | 
						|
                else:
 | 
						|
                    tag_text = ':ref:`' + param + '<class_' + cclass + "_" + param + '>`'
 | 
						|
                escape_post = True
 | 
						|
            elif cmd.find('image=') == 0:
 | 
						|
                tag_text = ""  # ''
 | 
						|
            elif cmd.find('url=') == 0:
 | 
						|
                tag_text = ':ref:`' + cmd[4:] + '<' + cmd[4:] + ">`"
 | 
						|
            elif cmd == '/url':
 | 
						|
                tag_text = ''
 | 
						|
                escape_post = True
 | 
						|
            elif cmd == 'center':
 | 
						|
                tag_text = ''
 | 
						|
            elif cmd == '/center':
 | 
						|
                tag_text = ''
 | 
						|
            elif cmd == 'codeblock':
 | 
						|
                tag_text = '\n::\n'
 | 
						|
                inside_code = True
 | 
						|
            elif cmd == 'br':
 | 
						|
                # Make a new paragraph instead of a linebreak, rst is not so linebreak friendly
 | 
						|
                tag_text = '\n\n'
 | 
						|
                # Strip potential leading spaces
 | 
						|
                while post_text[0] == ' ':
 | 
						|
                    post_text = post_text[1:]
 | 
						|
            elif cmd == 'i' or cmd == '/i':
 | 
						|
                tag_text = '*'
 | 
						|
            elif cmd == 'b' or cmd == '/b':
 | 
						|
                tag_text = '**'
 | 
						|
            elif cmd == 'u' or cmd == '/u':
 | 
						|
                tag_text = ''
 | 
						|
            elif cmd == 'code':
 | 
						|
                tag_text = '``'
 | 
						|
                inside_code = True
 | 
						|
            else:
 | 
						|
                tag_text = make_type(tag_text)
 | 
						|
                escape_post = True
 | 
						|
 | 
						|
        # Properly escape things like `[Node]s`
 | 
						|
        if escape_post and post_text and post_text[0].isalnum(): # not punctuation, escape
 | 
						|
            post_text = '\ ' + post_text
 | 
						|
 | 
						|
        text = pre_text + tag_text + post_text
 | 
						|
        pos = len(pre_text) + len(tag_text)
 | 
						|
 | 
						|
    return text
 | 
						|
 | 
						|
 | 
						|
def make_type(t):
 | 
						|
    global class_names
 | 
						|
    if t in class_names:
 | 
						|
        return ':ref:`' + t + '<class_' + t.lower() + '>`'
 | 
						|
    return t
 | 
						|
 | 
						|
 | 
						|
def make_method(
 | 
						|
        f,
 | 
						|
        name,
 | 
						|
        m,
 | 
						|
        declare,
 | 
						|
        cname,
 | 
						|
        event=False,
 | 
						|
        pp=None
 | 
						|
):
 | 
						|
    if (declare or pp == None):
 | 
						|
        t = '- '
 | 
						|
    else:
 | 
						|
        t = ""
 | 
						|
 | 
						|
    ret_type = 'void'
 | 
						|
    args = list(m)
 | 
						|
    mdata = {}
 | 
						|
    mdata['argidx'] = []
 | 
						|
    for a in args:
 | 
						|
        if a.tag == 'return':
 | 
						|
            idx = -1
 | 
						|
        elif a.tag == 'argument':
 | 
						|
            idx = int(a.attrib['index'])
 | 
						|
        else:
 | 
						|
            continue
 | 
						|
 | 
						|
        mdata['argidx'].append(idx)
 | 
						|
        mdata[idx] = a
 | 
						|
 | 
						|
    if not event:
 | 
						|
        if -1 in mdata['argidx']:
 | 
						|
            t += make_type(mdata[-1].attrib['type'])
 | 
						|
        else:
 | 
						|
            t += 'void'
 | 
						|
        t += ' '
 | 
						|
 | 
						|
    if declare or pp == None:
 | 
						|
 | 
						|
        s = '**' + m.attrib['name'] + '** '
 | 
						|
    else:
 | 
						|
        s = ':ref:`' + m.attrib['name'] + '<class_' + cname + "_" + m.attrib['name'] + '>` '
 | 
						|
 | 
						|
    s += '**(**'
 | 
						|
    argfound = False
 | 
						|
    for a in mdata['argidx']:
 | 
						|
        arg = mdata[a]
 | 
						|
        if a < 0:
 | 
						|
            continue
 | 
						|
        if a > 0:
 | 
						|
            s += ', '
 | 
						|
        else:
 | 
						|
            s += ' '
 | 
						|
 | 
						|
        s += make_type(arg.attrib['type'])
 | 
						|
        if 'name' in arg.attrib:
 | 
						|
            s += ' ' + arg.attrib['name']
 | 
						|
        else:
 | 
						|
            s += ' arg' + str(a)
 | 
						|
 | 
						|
        if 'default' in arg.attrib:
 | 
						|
            s += '=' + arg.attrib['default']
 | 
						|
 | 
						|
    s += ' **)**'
 | 
						|
 | 
						|
    if 'qualifiers' in m.attrib:
 | 
						|
        s += ' ' + m.attrib['qualifiers']
 | 
						|
 | 
						|
    if (not declare):
 | 
						|
        if (pp != None):
 | 
						|
            pp.append((t, s))
 | 
						|
        else:
 | 
						|
            f.write("- " + t + " " + s + "\n")
 | 
						|
    else:
 | 
						|
        f.write(t + s + "\n")
 | 
						|
 | 
						|
 | 
						|
def make_heading(title, underline):
 | 
						|
    return title + '\n' + underline * len(title) + "\n\n"
 | 
						|
 | 
						|
 | 
						|
def make_rst_class(node):
 | 
						|
    name = node.attrib['name']
 | 
						|
 | 
						|
    f = codecs.open("class_" + name.lower() + '.rst', 'wb', 'utf-8')
 | 
						|
 | 
						|
    # Warn contributors not to edit this file directly
 | 
						|
    f.write(".. Generated automatically by doc/tools/makerst.py in Godot's source tree.\n")
 | 
						|
    f.write(".. DO NOT EDIT THIS FILE, but the " + name + ".xml source instead.\n")
 | 
						|
    f.write(".. The source is found in doc/classes or modules/<name>/doc_classes.\n\n")
 | 
						|
 | 
						|
    f.write(".. _class_" + name + ":\n\n")
 | 
						|
    f.write(make_heading(name, '='))
 | 
						|
 | 
						|
    if 'inherits' in node.attrib:
 | 
						|
        inh = node.attrib['inherits'].strip()
 | 
						|
        f.write('**Inherits:** ')
 | 
						|
        first = True
 | 
						|
        while (inh in classes):
 | 
						|
            if (not first):
 | 
						|
                f.write(" **<** ")
 | 
						|
            else:
 | 
						|
                first = False
 | 
						|
 | 
						|
            f.write(make_type(inh))
 | 
						|
            inode = classes[inh]
 | 
						|
            if ('inherits' in inode.attrib):
 | 
						|
                inh = inode.attrib['inherits'].strip()
 | 
						|
            else:
 | 
						|
                inh = None
 | 
						|
 | 
						|
        f.write("\n\n")
 | 
						|
 | 
						|
    inherited = []
 | 
						|
    for cn in classes:
 | 
						|
        c = classes[cn]
 | 
						|
        if 'inherits' in c.attrib:
 | 
						|
            if (c.attrib['inherits'].strip() == name):
 | 
						|
                inherited.append(c.attrib['name'])
 | 
						|
 | 
						|
    if (len(inherited)):
 | 
						|
        f.write('**Inherited By:** ')
 | 
						|
        for i in range(len(inherited)):
 | 
						|
            if (i > 0):
 | 
						|
                f.write(", ")
 | 
						|
            f.write(make_type(inherited[i]))
 | 
						|
        f.write("\n\n")
 | 
						|
    if 'category' in node.attrib:
 | 
						|
        f.write('**Category:** ' + node.attrib['category'].strip() + "\n\n")
 | 
						|
 | 
						|
    f.write(make_heading('Brief Description', '-'))
 | 
						|
    briefd = node.find('brief_description')
 | 
						|
    if briefd != None:
 | 
						|
        f.write(rstize_text(briefd.text.strip(), name) + "\n\n")
 | 
						|
 | 
						|
    methods = node.find('methods')
 | 
						|
 | 
						|
    if methods != None and len(list(methods)) > 0:
 | 
						|
        f.write(make_heading('Member Functions', '-'))
 | 
						|
        ml = []
 | 
						|
        for m in list(methods):
 | 
						|
            make_method(f, node.attrib['name'], m, False, name, False, ml)
 | 
						|
        longest_t = 0
 | 
						|
        longest_s = 0
 | 
						|
        for s in ml:
 | 
						|
            sl = len(s[0])
 | 
						|
            if (sl > longest_s):
 | 
						|
                longest_s = sl
 | 
						|
            tl = len(s[1])
 | 
						|
            if (tl > longest_t):
 | 
						|
                longest_t = tl
 | 
						|
 | 
						|
        sep = "+"
 | 
						|
        for i in range(longest_s + 2):
 | 
						|
            sep += "-"
 | 
						|
        sep += "+"
 | 
						|
        for i in range(longest_t + 2):
 | 
						|
            sep += "-"
 | 
						|
        sep += "+\n"
 | 
						|
        f.write(sep)
 | 
						|
        for s in ml:
 | 
						|
            rt = s[0]
 | 
						|
            while (len(rt) < longest_s):
 | 
						|
                rt += " "
 | 
						|
            st = s[1]
 | 
						|
            while (len(st) < longest_t):
 | 
						|
                st += " "
 | 
						|
            f.write("| " + rt + " | " + st + " |\n")
 | 
						|
            f.write(sep)
 | 
						|
        f.write('\n')
 | 
						|
 | 
						|
    events = node.find('signals')
 | 
						|
    if events != None and len(list(events)) > 0:
 | 
						|
        f.write(make_heading('Signals', '-'))
 | 
						|
        for m in list(events):
 | 
						|
            f.write(".. _class_" + name + "_" + m.attrib['name'] + ":\n\n")
 | 
						|
            make_method(f, node.attrib['name'], m, True, name, True)
 | 
						|
            f.write('\n')
 | 
						|
            d = m.find('description')
 | 
						|
            if d == None or d.text.strip() == '':
 | 
						|
                continue
 | 
						|
            f.write(rstize_text(d.text.strip(), name))
 | 
						|
            f.write("\n\n")
 | 
						|
 | 
						|
        f.write('\n')
 | 
						|
 | 
						|
    members = node.find('members')
 | 
						|
    if members != None and len(list(members)) > 0:
 | 
						|
        f.write(make_heading('Member Variables', '-'))
 | 
						|
 | 
						|
        for c in list(members):
 | 
						|
            # Leading two spaces necessary to prevent breaking the <ul>
 | 
						|
            f.write("  .. _class_" + name + "_" + c.attrib['name'] + ":\n\n")
 | 
						|
            s = '- '
 | 
						|
            s += make_type(c.attrib['type']) + ' '
 | 
						|
            s += '**' + c.attrib['name'] + '**'
 | 
						|
            if c.text.strip() != '':
 | 
						|
                s += ' - ' + rstize_text(c.text.strip(), name)
 | 
						|
            f.write(s + '\n\n')
 | 
						|
        f.write('\n')
 | 
						|
 | 
						|
    constants = node.find('constants')
 | 
						|
    if constants != None and len(list(constants)) > 0:
 | 
						|
        f.write(make_heading('Numeric Constants', '-'))
 | 
						|
        for c in list(constants):
 | 
						|
            s = '- '
 | 
						|
            s += '**' + c.attrib['name'] + '**'
 | 
						|
            if 'value' in c.attrib:
 | 
						|
                s += ' = **' + c.attrib['value'] + '**'
 | 
						|
            if c.text.strip() != '':
 | 
						|
                s += ' --- ' + rstize_text(c.text.strip(), name)
 | 
						|
            f.write(s + '\n')
 | 
						|
        f.write('\n')
 | 
						|
 | 
						|
    descr = node.find('description')
 | 
						|
    if descr != None and descr.text.strip() != '':
 | 
						|
        f.write(make_heading('Description', '-'))
 | 
						|
        f.write(rstize_text(descr.text.strip(), name) + "\n\n")
 | 
						|
 | 
						|
    methods = node.find('methods')
 | 
						|
    if methods != None and len(list(methods)) > 0:
 | 
						|
        f.write(make_heading('Member Function Description', '-'))
 | 
						|
        for m in list(methods):
 | 
						|
            f.write(".. _class_" + name + "_" + m.attrib['name'] + ":\n\n")
 | 
						|
            make_method(f, node.attrib['name'], m, True, name)
 | 
						|
            f.write('\n')
 | 
						|
            d = m.find('description')
 | 
						|
            if d == None or d.text.strip() == '':
 | 
						|
                continue
 | 
						|
            f.write(rstize_text(d.text.strip(), name))
 | 
						|
            f.write("\n\n")
 | 
						|
        f.write('\n')
 | 
						|
 | 
						|
 | 
						|
file_list = []
 | 
						|
 | 
						|
for path in input_list:
 | 
						|
    if os.path.basename(path) == 'modules':
 | 
						|
        for subdir, dirs, _ in os.walk(path):
 | 
						|
            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')]
 | 
						|
                file_list += [os.path.join(doc_dir, f) for f in class_file_names]
 | 
						|
    elif not os.path.isfile(path):
 | 
						|
        file_list += [os.path.join(path, f) for f in os.listdir(path) if f.endswith('.xml')]
 | 
						|
    elif os.path.isfile(path) and path.endswith('.xml'):
 | 
						|
        file_list.append(path)
 | 
						|
 | 
						|
for file in file_list:
 | 
						|
    tree = ET.parse(file)
 | 
						|
    doc = tree.getroot()
 | 
						|
 | 
						|
    if 'version' not in doc.attrib:
 | 
						|
        print("Version missing from 'doc'")
 | 
						|
        sys.exit(255)
 | 
						|
 | 
						|
    version = doc.attrib['version']
 | 
						|
    if doc.attrib['name'] in class_names:
 | 
						|
        continue
 | 
						|
    class_names.append(doc.attrib['name'])
 | 
						|
    classes[doc.attrib['name']] = doc
 | 
						|
 | 
						|
class_names.sort()
 | 
						|
 | 
						|
# Don't make class list for Sphinx, :toctree: handles it
 | 
						|
# make_class_list(class_names, 2)
 | 
						|
 | 
						|
for cn in class_names:
 | 
						|
    c = classes[cn]
 | 
						|
    make_rst_class(c)
 |