├── .gitignore ├── Godot.docset.7z ├── LICENSE ├── README.md ├── godotdocset.py ├── icon.png ├── schema.xsd ├── style.css └── template.jinja2 /.gitignore: -------------------------------------------------------------------------------- 1 | classes 2 | Godot.docset 3 | .vscode 4 | -------------------------------------------------------------------------------- /Godot.docset.7z: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yanpas/godotdocset/bfe0234494114eb51882ec60ba77eddb7c78debb/Godot.docset.7z -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Yan Pas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # godotdocset 2 | Dash docset generator for Godot engine xml docs 3 | 4 | Depends on jinja2 5 | 6 | ``` 7 | usage: godotdocset.py [-h] -f FROM 8 | 9 | optional arguments: 10 | -h, --help show this help message and exit 11 | -f FROM, --from FROM folder or xml file 12 | ``` 13 | 14 | Generating docset: 15 | 0. Install all deps: `pip3 install jinja2` 16 | 1. Go to https://github.com/godotengine/godot/ 17 | 2. Get doc/classes directory somehow (clone entire repo) 18 | 3. Run generator `python3 godotdocset.py -f ./classes` 19 | 4. `mv Godot.docset ~/.local/share/Zeal/Zeal/docsets/` 20 | 21 | There is Godot.docset.7z file in the project with precompiled docset. 22 | -------------------------------------------------------------------------------- /godotdocset.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # coding: utf-8 3 | 4 | ''' 5 | MIT license, author Yan Pas 6 | ''' 7 | 8 | import jinja2 9 | import xml.etree.ElementTree as etree 10 | import argparse 11 | from types import SimpleNamespace 12 | import os 13 | import os.path 14 | import sys 15 | import re 16 | from glob import glob 17 | import sqlite3 18 | import shutil 19 | 20 | TEMPLATE = "template.jinja2" 21 | VERSION = "" 22 | FROM_PATH = "" 23 | 24 | def set_glob_args_from(): 25 | global FROM_PATH 26 | ap = argparse.ArgumentParser() 27 | ap.add_argument('-f', '--from', help="folder or xml file", required=True) 28 | # ap.add_argument('-t', '--to', help="output folder", default='.') 29 | args = ap.parse_args() 30 | FROM_PATH = args.__dict__['from'] 31 | 32 | def set_glob_version(): 33 | global VERSION 34 | VERSION_PATH = os.path.abspath(os.path.join(FROM_PATH, "../../version.py")) 35 | for line in open(VERSION_PATH): 36 | if line.startswith("docs ="): 37 | VERSION = "-"+line.split("=")[1].strip().strip('\"') 38 | 39 | set_glob_args_from() 40 | set_glob_version() 41 | 42 | def linkify_text(node: etree.Element) -> str: 43 | txt = (node.text or "").strip() 44 | txt = txt.replace("\n", "
") 45 | txt = txt.replace("\t", "") 46 | txt = re.sub(r'\[(\w+?)\](.+?)\[/\1\]', r'\2', txt) # [code]bla[/code] -> ... 47 | # TODO generate valid links for methods etc. 48 | txt = re.sub(r'\[(\w+?) (\w+?)\]', r'\1 \2', txt) # [method foo] -> method foo 49 | txt = re.sub(r'\[([@a-zA-Z_]+?)\]', r'\1', txt) # [Object] -> {argt}' if argt != "var" else argt 55 | return ", ".join([f'{get_type(a.type)} {a.name}{"="+a.default if a.default else ""}' for a in args]) 56 | 57 | class DocPage: 58 | parents = {} 59 | 60 | def __init__(self, root: etree.Element): 61 | self._root = root 62 | self.title = root.attrib["name"] 63 | self.inherits = root.attrib.get("inherits") 64 | self.parents = [] 65 | DocPage.parents[self.title] = self.inherits 66 | self.brief_description = linkify_text(root.find('brief_description')) 67 | self.description = linkify_text(root.find('description')) 68 | self.populate_methods() 69 | self._j_print_args = j_print_args 70 | self.populate_signals() 71 | self.populate_fields() 72 | self.populate_consts() 73 | 74 | del self._root 75 | 76 | def build_parents(self): 77 | name = self.title 78 | if name not in DocPage.parents: 79 | print("Failed to find parent %s for %s" % (name, self.title), file=sys.stderr) 80 | return 81 | while DocPage.parents[name]: 82 | name = DocPage.parents[name] 83 | self.parents.append(name) 84 | if name not in DocPage.parents: 85 | print("Failed to find parent %s for %s" % (name, self.title), file=sys.stderr) 86 | break 87 | 88 | def populate_methods(self): 89 | self.methods = [] 90 | cnt = 1 91 | for method in self._root.findall("methods/method"): 92 | res = SimpleNamespace() 93 | res.cnt = cnt 94 | res.name = method.attrib["name"] 95 | res.__dict__["return"] = (method.find("return").attrib["type"] if method.find("return") is not None else "void") 96 | res.description = linkify_text(method.find("description")) 97 | res.arguments = [] 98 | for arg in method.findall("argument"): 99 | res.arguments.append(SimpleNamespace(index=int(arg.attrib["index"]), name=arg.attrib["name"], 100 | type=arg.attrib["type"], default=arg.attrib.get('default'))) 101 | res.arguments.sort(key=lambda el: el.index) 102 | self.methods.append(res) 103 | cnt += 1 104 | 105 | def populate_signals(self): 106 | self.signals = [] 107 | for sig in self._root.findall("signals/signal"): 108 | res = SimpleNamespace() 109 | res.name = sig.attrib['name'] 110 | res.description = linkify_text(sig.find("description")) 111 | res.arguments = [] 112 | for arg in sig.findall("argument"): 113 | res.arguments.append(SimpleNamespace(index=int(arg.attrib["index"]), name=arg.attrib["name"], 114 | type=arg.attrib["type"], default=None)) 115 | res.arguments.sort(key=lambda el: el.index) 116 | self.signals.append(res) 117 | 118 | def populate_fields(self): 119 | self.fields = [] 120 | for field in self._root.findall("members/member"): 121 | res = SimpleNamespace() 122 | res.name = field.attrib['name'] 123 | res.description = linkify_text(field) 124 | res.type = field.attrib['type'] 125 | self.fields.append(res) 126 | 127 | def populate_consts(self): 128 | self.consts = {} 129 | for const in self._root.findall("constants/constant"): 130 | ename = const.attrib.get("enum") 131 | if ename not in self.consts: 132 | self.consts[ename] = [] 133 | res = SimpleNamespace(name=const.attrib['name'], value=const.attrib['value']) 134 | res.description = linkify_text(const) 135 | self.consts[ename].append(res) 136 | 137 | def get_plist(name: str, version: str): 138 | return ''' 139 | 140 | 141 | 142 | CFBundleIdentifier 143 | {} 144 | CFBundleName 145 | {} 146 | DocSetPlatformFamily 147 | {} 148 | isDashDocset 149 | 150 | DashDocSetFamily 151 | dashtoc 152 | 153 | 154 | '''.format(name.split('_')[0]+version, name.replace('_', ' ')+version, name.split('_')[0].lower()) 155 | 156 | class DocsetMaker: 157 | outname = "Godot" 158 | v_outname = outname+VERSION 159 | rootdir = v_outname + '.docset' 160 | docdir = rootdir + '/Contents/Resources/Documents' 161 | 162 | def __enter__(self): 163 | os.makedirs(self.docdir) 164 | self.db = sqlite3.connect(DocsetMaker.v_outname + '.docset/Contents/Resources/docSet.dsidx') 165 | self.db.execute('CREATE TABLE searchIndex(id INTEGER PRIMARY KEY, name TEXT, type TEXT, path TEXT);') 166 | self.db.execute('CREATE UNIQUE INDEX anchor ON searchIndex (name, type, path);') 167 | with open(DocsetMaker.v_outname + '.docset/Contents/Info.plist', 'w') as plist: 168 | plist.write(get_plist(DocsetMaker.outname, VERSION)) 169 | self.db.execute("BEGIN") 170 | return self 171 | 172 | def __exit__(self, *oth): 173 | self.db.commit() 174 | self.db.close() 175 | 176 | def add_to_docset(self, dp: DocPage): 177 | def add_entry(name, entrytype, anchor=""): 178 | if anchor: 179 | anchor = "#" + anchor 180 | self.db.execute('INSERT OR IGNORE INTO searchIndex(name, type, path) VALUES (?,?,?);', 181 | [name, entrytype, dp.title + ".html" + anchor]) 182 | add_entry(dp.title, "Class") 183 | for e in dp.consts: 184 | def add_constants(src): 185 | for x in src: 186 | add_entry(x.name, "Constant", f'c_{x.name}') 187 | if e is None: 188 | add_constants(dp.consts[None]) 189 | else: 190 | add_entry(e, "Enum", "e_" + e) 191 | add_constants(dp.consts[e]) 192 | for e in dp.fields: 193 | add_entry(e.name, "Field", f'f_{e.name}') 194 | for e in dp.methods: 195 | add_entry(e.name, "Method", f"m_{e.name}_{e.cnt}") 196 | for e in dp.signals: 197 | add_entry(e.name, "Subroutine", "s_"+e.name) 198 | 199 | def main(): 200 | if not os.path.exists(FROM_PATH) or not os.path.isdir(FROM_PATH): 201 | exit("Directory " + FROM_PATH + " doesn't exist or is not a directory") 202 | docsetdir = DocsetMaker.v_outname + ".docset" 203 | if os.path.exists(docsetdir): 204 | print("Removing docset dir") 205 | shutil.rmtree(docsetdir) 206 | docpages = {} 207 | for fname in glob(FROM_PATH + "/*.xml"): 208 | doc = etree.parse(fname) 209 | print("parsing", fname) 210 | docpages[fname] = DocPage(doc.getroot()) 211 | tpl = jinja2.Template(open(TEMPLATE).read()) 212 | with DocsetMaker() as docset: 213 | def dump(content, name): 214 | with open(os.path.join(DocsetMaker.docdir, name[:-4] + ".html"), 'w', encoding="utf-8") as f: 215 | f.write(content) 216 | for fname, dp in docpages.items(): 217 | dp.build_parents() 218 | print("dumping", fname) 219 | docset.add_to_docset(dp) 220 | render = tpl.render(vars(dp)) 221 | dump(render, os.path.basename(fname)) 222 | shutil.copyfile('style.css', os.path.join(docset.docdir, "style.css")) 223 | shutil.copyfile("icon.png", os.path.join(docset.rootdir, "icon.png")) 224 | shutil.copyfile("icon.png", os.path.join(docset.rootdir, "icon@2.png")) 225 | 226 | if __name__ == '__main__': 227 | main() 228 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Yanpas/godotdocset/bfe0234494114eb51882ec60ba77eddb7c78debb/icon.png -------------------------------------------------------------------------------- /schema.xsd: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-size: large; 3 | font-family: sans-serif; 4 | background-color: whitesmoke 5 | } 6 | 7 | .tag_codeblock { 8 | font-family: monospace; 9 | white-space: pre; 10 | } 11 | 12 | a:visited { 13 | color: #9B59B6; 14 | } 15 | 16 | a:hover { 17 | color: #3091d1; 18 | } 19 | 20 | a { 21 | color: #2980B9; 22 | text-decoration: none; 23 | cursor: pointer 24 | } -------------------------------------------------------------------------------- /template.jinja2: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | {{title}} 8 | 9 | 10 | 11 |

{{title}}

12 |

13 | Inherits: 14 | {% for parent in parents -%} 15 | {{parent}} 16 | {%- if not loop.last -%} 17 |  <  18 | {%- endif -%} 19 | {%- endfor %} 20 |

21 |

Brief Description

22 |

{{brief_description}}

23 | {% if methods %} 24 |

Member Functions

25 | 26 | 27 | {% for method in methods -%} 28 | 29 | 36 | 37 | 38 | 39 | {%- endfor %} 40 | 41 |
30 | {%- if method.return in ["void", "var"] -%} 31 | {{method.return}} 32 | {%- else -%} 33 | {{method.return}} 34 | {%- endif -%} 35 | {{method.name}} ( {% call _j_print_args(method.arguments) %}{% endcall %} )
42 | {%- endif -%} 43 | {%- if signals %} 44 |

Signals

45 | {% for signal in signals -%} 46 |
  • 47 | 48 | {{signal.name}} 49 | 50 | ( {% call _j_print_args(signal.arguments) %} {% endcall %} ):
    {{signal.description}} 51 |
  • 52 | {%- endfor %} 53 | {%- endif %} 54 | {%- if fields %} 55 |

    Member Variables

    56 | {% for field in fields -%} 57 |
  • 58 | {{field.type}} 59 | 60 | {{field.name}} 61 | 62 | :
    {{field.description}} 63 |
  • 64 | {%- endfor %} 65 | {%- endif %} 66 | {%- if None in consts -%} 67 |

    Numeric Constants

    68 | {% for const in consts[None] -%} 69 |
  • 70 | {{const.name}} = {{const.value}}:
    {{const.description}} 71 |
  • 72 | {%- endfor %} 73 | {%- endif -%} 74 | {%- if (None in consts and consts|length > 1) or consts %} 75 |

    Enums

    76 | {% for enum in consts -%} 77 | {%- if enum -%} 78 |

    enum {{enum}}

    79 | {% for const in consts[enum] -%} 80 |
  • 81 | 82 | {{const.name}} = {{const.value}} 83 | :
    {{const.description}} 84 |
  • 85 | {%- endfor %} 86 | {%- endif -%} 87 | {%- endfor %} 88 | {%- endif -%} 89 |

    Description

    90 |

    {{description}}

    91 | {%- if methods -%} 92 |

    Member Function Description

    93 | {%- for method in methods -%} 94 |
  • 95 | {%- if method.return in ["void", "var"] -%} 96 | {{method.return}} 97 | {%- else -%} 98 | {{method.return}} 99 | {%- endif -%} 100 | 101 | {{method.name}} 102 | ( {%- call _j_print_args(method.arguments) -%}{%- endcall -%} ) :
    103 | {{method.description}} 104 |
  • 105 | {% endfor %} 106 | {%- endif -%} 107 | 108 | 109 | --------------------------------------------------------------------------------