Skip to content

Commit 4a35f10

Browse files
committed
update v1.3
1 parent 393c7cb commit 4a35f10

39 files changed

+3232
-0
lines changed
Lines changed: 340 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,340 @@
1+
tool
2+
extends Reference
3+
4+
var _pending_docs := {}
5+
var _docs_queue := []
6+
7+
const _GD_TYPES = [
8+
"", "bool", "int", "float",
9+
"String", "Vector2", "Rect2", "Vector3",
10+
"Transform2D", "Plane", "Quat", "AABB",
11+
"Basis", "Transform", "Color", "NodePath",
12+
"RID", "Object", "Dictionary", "Array",
13+
"PoolByteArray", "PoolIntArray", "PoolRealArray", "PoolStringArray",
14+
"PoolVector2Array", "PoolVector3Array", "PoolColorArray"
15+
]
16+
17+
var plugin: EditorPlugin
18+
19+
20+
func _update() -> void:
21+
var time := OS.get_ticks_msec()
22+
while not _docs_queue.empty() and OS.get_ticks_msec() - time < 5:
23+
var name: String = _docs_queue.pop_front()
24+
var doc: ClassDocItem = _pending_docs[name]
25+
var should_first_gen := _generate(doc)
26+
27+
if should_first_gen.empty():
28+
_pending_docs.erase(name)
29+
else:
30+
_docs_queue.push_back(name)
31+
_docs_queue.erase(should_first_gen)
32+
_docs_queue.push_front(should_first_gen)
33+
34+
35+
func generate(name: String, base: String, script_path: String) -> ClassDocItem:
36+
if name in _pending_docs:
37+
return _pending_docs[name]
38+
39+
var doc := ClassDocItem.new({
40+
name = name,
41+
base = base,
42+
path = script_path
43+
})
44+
45+
_pending_docs[name] = doc
46+
_docs_queue.append(name)
47+
return doc
48+
49+
50+
func _generate(doc: ClassDocItem) -> String:
51+
var script: GDScript = load(doc.path)
52+
var code_lines := script.source_code.split("\n")
53+
54+
var inherits := doc.base
55+
var parent_props := []
56+
var parent_methods := []
57+
var parent_constants := []
58+
while inherits != "" and inherits in plugin.class_docs:
59+
if inherits in _pending_docs:
60+
return inherits
61+
62+
for prop in plugin.class_docs[inherits].properties:
63+
parent_props.append(prop.name)
64+
for method in plugin.class_docs[inherits].methods:
65+
parent_methods.append(method.name)
66+
for constant in plugin.class_docs[inherits].constants:
67+
parent_constants.append(constant.name)
68+
inherits = plugin.get_parent_class(inherits)
69+
70+
for method in script.get_script_method_list():
71+
if method.name.begins_with("_") or method.name in parent_methods:
72+
continue
73+
doc.methods.append(_create_method_doc(method.name, script, method))
74+
75+
for property in script.get_script_property_list():
76+
if property.name.begins_with("_") or property.name in parent_props:
77+
continue
78+
doc.properties.append(_create_property_doc(property.name, script, property))
79+
80+
for _signal in script.get_script_signal_list():
81+
var signal_doc := SignalDocItem.new({
82+
"name": _signal.name
83+
})
84+
doc.signals.append(signal_doc)
85+
86+
for arg in _signal.args:
87+
signal_doc.args.append(ArgumentDocItem.new({
88+
"name": arg.name,
89+
"type": _type_string(
90+
arg.type,
91+
arg["class_name"]
92+
) if arg.type != TYPE_NIL else "Variant"
93+
}))
94+
95+
for constant in script.get_script_constant_map():
96+
if constant.begins_with("_") or constant in parent_constants:
97+
continue
98+
var value = script.get_script_constant_map()[constant]
99+
100+
# Check if constant is an enumerator.
101+
var is_enum := false
102+
if typeof(value) == TYPE_DICTIONARY:
103+
is_enum = true
104+
for i in value.size():
105+
if typeof(value.keys()[i]) != TYPE_STRING or typeof(value.values()[i]) != TYPE_INT:
106+
is_enum = false
107+
break
108+
109+
if is_enum:
110+
for _enum in value:
111+
doc.constants.append(ConstantDocItem.new({
112+
"name": _enum,
113+
"value": value[_enum],
114+
"enumeration": constant
115+
}))
116+
else:
117+
doc.constants.append(ConstantDocItem.new({
118+
"name": constant,
119+
"value": value
120+
}))
121+
122+
var comment_block := ""
123+
var annotations := {}
124+
var reading_block := false
125+
var enum_block := false
126+
for line in code_lines:
127+
var indented: bool = line.begins_with(" ") or line.begins_with("\t")
128+
if line.begins_with("##"):
129+
reading_block = true
130+
else:
131+
reading_block = false
132+
comment_block = comment_block.trim_suffix("\n")
133+
134+
if line.begins_with("enum"):
135+
enum_block = true
136+
if line.find("}") != -1 and enum_block:
137+
enum_block = false
138+
139+
if line.find("##") != -1 and not reading_block:
140+
var offset := 3 if line.find("## ") != -1 else 2
141+
comment_block = line.right(line.find("##") + offset)
142+
143+
if reading_block:
144+
if line.begins_with("## "):
145+
line = line.trim_prefix("## ")
146+
else:
147+
line = line.trim_prefix("##")
148+
if line.begins_with("@"):
149+
var annote: Array = line.split(" ", true, 1)
150+
if annote[0] == "@tutorial" and annote.size() == 2:
151+
if annotations.has("@tutorial"):
152+
annotations["@tutorial"].append(annote[1])
153+
annote[1] = annotations["@tutorial"]
154+
else:
155+
annote[1] = [annote[1]]
156+
annotations[annote[0]] = null if annote.size() == 1 else annote[1]
157+
else:
158+
comment_block += line + "\n"
159+
160+
elif not comment_block.empty():
161+
var doc_item: DocItem
162+
163+
# Class document
164+
if line.begins_with("extends") or line.begins_with("tool") or line.begins_with("class_name"):
165+
if annotations.has("@doc-ignore"):
166+
return ""
167+
if annotations.has("@contribute"):
168+
doc.contriute_url = annotations["@contribute"]
169+
if annotations.has("@tutorial"):
170+
doc.tutorials = annotations["@tutorial"]
171+
var doc_split = comment_block.split("\n", true, 1)
172+
doc.brief = doc_split[0]
173+
if doc_split.size() == 2:
174+
doc.description = doc_split[1]
175+
doc_item = doc
176+
177+
# Method document
178+
elif line.find("func ") != -1 and not indented:
179+
var regex := RegEx.new()
180+
regex.compile("func ([a-zA-Z0-9_]+)")
181+
var method := regex.search(line).get_string(1)
182+
var method_doc := doc.get_method_doc(method)
183+
184+
if not method_doc and method:
185+
method_doc = _create_method_doc(method, script)
186+
if method_doc:
187+
doc.methods.append(method_doc)
188+
189+
if method_doc:
190+
if annotations.has("@args"):
191+
var params = annotations["@args"].split(",")
192+
for i in min(params.size(), method_doc.args.size()):
193+
method_doc.args[i].name = params[i].strip_edges()
194+
if annotations.has("@arg-defaults"):
195+
var params = annotations["@arg-defaults"].split(",")
196+
for i in min(params.size(), method_doc.args.size()):
197+
method_doc.args[i].default = params[i].strip_edges().replace(";", ",")
198+
if annotations.has("@arg-types"):
199+
var params = annotations["@arg-types"].split(",")
200+
for i in min(params.size(), method_doc.args.size()):
201+
method_doc.args[i].type = params[i].strip_edges()
202+
if annotations.has("@arg-enums"):
203+
var params = annotations["@arg-enums"].split(",")
204+
for i in min(params.size(), method_doc.args.size()):
205+
method_doc.args[i].enumeration = params[i].strip_edges()
206+
if annotations.has("@return"):
207+
method_doc.return_type = annotations["@return"]
208+
if annotations.has("@return-enum"):
209+
method_doc.return_enum = annotations["@return-enum"]
210+
method_doc.is_virtual = annotations.has("@virtual")
211+
method_doc.description = comment_block
212+
doc_item = method_doc
213+
214+
# Property document
215+
elif line.find("var ") != -1 and not indented:
216+
var regex := RegEx.new()
217+
regex.compile("var ([a-zA-Z0-9_]+)")
218+
var prop := regex.search(line).get_string(1)
219+
var prop_doc := doc.get_property_doc(prop)
220+
if not prop_doc and prop:
221+
prop_doc = _create_property_doc(prop, script)
222+
if prop_doc:
223+
doc.properties.append(prop_doc)
224+
225+
if prop_doc:
226+
if annotations.has("@type"):
227+
prop_doc.type = annotations["@type"]
228+
if annotations.has("@default"):
229+
prop_doc.default = annotations["@default"]
230+
if annotations.has("@enum"):
231+
prop_doc.enumeration = annotations["@enum"]
232+
if annotations.has("@setter"):
233+
prop_doc.setter = annotations["@setter"]
234+
if annotations.has("@getter"):
235+
prop_doc.getter = annotations["@getter"]
236+
prop_doc.description = comment_block
237+
doc_item = prop_doc
238+
239+
# Signal document
240+
elif line.find("signal") != -1 and not indented:
241+
var regex := RegEx.new()
242+
regex.compile("signal ([a-zA-Z0-9_]+)")
243+
var signl := regex.search(line).get_string(1)
244+
var signal_doc := doc.get_signal_doc(signl)
245+
if signal_doc:
246+
if annotations.has("@arg-types"):
247+
var params = annotations["@arg-types"].split(",")
248+
for i in min(params.size(), signal_doc.args.size()):
249+
signal_doc.args[i].type = params[i].strip_edges()
250+
if annotations.has("@arg-enums"):
251+
var params = annotations["@arg-enums"].split(",")
252+
for i in min(params.size(), signal_doc.args.size()):
253+
signal_doc.args[i].enumeration = params[i].strip_edges()
254+
signal_doc.description = comment_block
255+
doc_item = signal_doc
256+
257+
# Constant document
258+
elif line.find("const") != -1 and not indented:
259+
var regex := RegEx.new()
260+
regex.compile("const ([a-zA-Z0-9_]+)")
261+
var constant := regex.search(line).get_string(1)
262+
var const_doc := doc.get_constant_doc(constant)
263+
if const_doc:
264+
const_doc.description = comment_block
265+
doc_item = const_doc
266+
267+
# Enumerator document
268+
elif enum_block: # Handle enumerators
269+
for enum_doc in doc.constants:
270+
if line.find(enum_doc.name) != -1:
271+
enum_doc.description = comment_block
272+
doc_item = enum_doc
273+
break
274+
275+
# Meta annotations
276+
if doc_item:
277+
for annote in annotations:
278+
if annote.find("@meta-") == 0:
279+
var key: String = annote.right("@meta-".length())
280+
doc_item.meta[key] = annotations[annote].strip_edges()
281+
282+
comment_block = ""
283+
annotations.clear()
284+
return ""
285+
286+
func _create_method_doc(name: String, script: Script = null, method := {}) -> MethodDocItem:
287+
if method.empty():
288+
var methods := script.get_script_method_list()
289+
for m in methods:
290+
if m.name == name:
291+
method = m
292+
break
293+
294+
if not method.has("name"):
295+
return null
296+
297+
var method_doc := MethodDocItem.new({
298+
"name": method.name,
299+
"return_type": _type_string(
300+
method["return"]["type"],
301+
method["return"]["class_name"]
302+
) if method["return"]["type"] != TYPE_NIL else "void",
303+
})
304+
for arg in method.args:
305+
method_doc.args.append(ArgumentDocItem.new({
306+
"name": arg.name,
307+
"type": _type_string(
308+
arg.type,
309+
arg["class_name"]
310+
) if arg.type != TYPE_NIL else "Variant"
311+
}))
312+
return method_doc
313+
314+
315+
func _create_property_doc(name: String, script: Script = null, property := {}) -> PropertyDocItem:
316+
if property.empty():
317+
var properties := script.get_script_property_list()
318+
for p in properties:
319+
if p.name == name:
320+
property = p
321+
break
322+
323+
if not property.has("name"):
324+
return null
325+
326+
var property_doc := PropertyDocItem.new({
327+
"name": name,
328+
"type": _type_string(
329+
property.type,
330+
property["class_name"]
331+
) if property.type != TYPE_NIL else "Variant"
332+
})
333+
return property_doc
334+
335+
336+
func _type_string(type: int, _class_name: String) -> String:
337+
if type == TYPE_OBJECT:
338+
return _class_name
339+
else:
340+
return _GD_TYPES[type]
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
## The base class for every document exporter.
2+
## @contribute https://placeholder_contribute.com
3+
tool
4+
extends Reference
5+
class_name DocExporter
6+
7+
## @virtual
8+
## @args doc
9+
## @arg-types ClassDocItem
10+
## This function gets called to generate a document string from a [ClassDocItem].
11+
func _generate(doc: ClassDocItem) -> String:
12+
return ""

0 commit comments

Comments
 (0)