-
Notifications
You must be signed in to change notification settings - Fork 12
/
pypredef_gen.py
1182 lines (997 loc) · 49.1 KB
/
pypredef_gen.py
1
2
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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# Contributor(s): Campbell Barton, Witold Jaworski
# ***** End GPL LICENSE BLOCK *****
'''
Creates Python predefinition (*.pypredef) files for Blender API
The *.pypredef files are useful for syntax checking and
auto-completion of expressions in Eclipse IDE (with PyDev plugin)
This program is based on Campbell Barton's sphinx_doc_gen.py script
@author: Witold Jaworski (http://www.airplanes3d.net)
'''
#FIXES/UPDATES:
#2012-03-01: In Blender 2.62 the description of the @roll argument in EditBone.transform()
# method has an unexpected empty line, which break processing. Added handling
# for that case in process_line() method
#2013-03-01: In Blender 2.66 two methods of the bpy.types.Space obejects are reported as
# Python - implemented methods, while tehy are not:
# draw_handler_add() and draw_handler_remove()
# I have added additional try.. except caluse to hande such errors in the future
# (However, as long as the descriptions of these methods are wrong, tehy are not documented)
#2013-03-13: Updates by Brian Henke:
# * add a no-op (pass) to functions; most were passing because they have comments but the parser fails when there are none.
# * Remove the "import" class because it is a reserved keyword.
#2013-03-14: Further updates: I have found another function (py_c_func2predef()) which was ommited in
# the Brian update. I added the "pass" statement generation there.
#2016-June: modifications by Robert Forsman for use with pycharm
script_help_msg = '''
Usage:
- Run this script from blenders root path:
.\blender.exe -b -P doc\python_api\pypredef_gen.py
This will generate PyDev python predefiniton files (for Eclipse) in doc\python_api\pypredef\,
assuming that .\blender.exe is the blender executable, and you have placed this script in
.\doc\python_api\ Blender's subdirectory.
'''
'''
Comments to using the pypredef files in Eclipse:
1. Add the directory that contains the *.pypredef files to PYTHONPATH of your project:
- Open Project-->Properties window.
- Select PyDev - PYTHONPATH option.
- In External Libraries tab add this directory to the list.
2. In the same tab (External Libraries) press the button [Force restore internal info]
NOTE: The completion may sometimes appear after a 1-2s. delay.
When you type "." it doesn't, But when you remove it, and type again ".", it appears!
NOTE: In case of specific bpy.app submodule, use it in your code in the following way:
from bpy import app
c = app.build_time
(because plain "import bpy.app" does not work)!
'''
import sys
# Switch for quick testing
# select modules to build:
INCLUDE_MODULES = (
"bpy",
"bpy.app",
"bpy.path",
"bpy.props",
"bpy.utils",
"bmesh",
"bmesh.types",
"bge",
"aud",
"bgl",
"blf",
"mathutils",
"mathutils.geometry"
)
_BPY_STRUCT_FAKE = "bpy_struct"
_BPY_FULL_REBUILD = False
_IDENT = " "
#dictionary, used to correct some type descriptions:
TYPE_ABERRATIONS = {
"boolean" : "bool",
"integer" : "int",
"enum" : "str",
"string" : "str",
"Matrix" : "mathutils.Matrix",
"Vector" : "mathutils.Vector",
"Quaternion": "mathutils.Quaternion",
"Color" : "mathutils.Color",
"Euler" : "mathutils.Euler",
"subclass of bpy_struct" : "bpy_struct",
"subclass of bpy.types.bpy_struct" : "bpy_struct",
"bpy.types.FCurve or list if index is -1 with an array property." : "bpy.types.FCurve",
"float triplet": "(float, float, float)",
"string in ['XYZ', 'XZY', 'YXZ', 'YZX', 'ZXY', 'ZYX']" : "str #in ['XYZ', 'XZY', 'YXZ', 'YZX', 'ZXY', 'ZYX']",
"tuple of 2 floats":"(float, float)",
"mathutils.Vector's" : "mathutils.Vector",
"list of mathutils.Vector's":"[mathutils.Vector]",
"tuple, pair of floats":"(float, float)",
"tuple of mathutils.Vector's":"(mathutils.Vector, mathutils.Vector)",
"mathutils.Vector or None":"mathutils.Vector",
"list of strigs":"[str]",
"list of strings":"[str]",
"FCurve or list if index is -1 with an array property":"FCurve",
"list of key, value tuples": ("[(str, types.%s)]" % _BPY_STRUCT_FAKE)
}
import os
import sys
import inspect
import types
import bpy
import rna_info
#---- learning types "by example"
# lame, the standard types modelule does not contains many type classes
# so we have to define it "by example":
class _example:
@property
def a_property(self):
return None
@classmethod
def a_classmethod(cls):
return None
@staticmethod
def a_staticmethod():
return None
PropertyType = type(_example.a_property)
ClassMethodType = type(_example.__dict__["a_classmethod"])
StaticMethodType = type(_example.__dict__["a_staticmethod"])
ClassMethodDescriptorType = type(dict.__dict__['fromkeys'])
MethodDescriptorType = type(dict.get)
GetSetDescriptorType = type(int.real)
#---- end "learning by example"
def write_indented_lines(ident, fn, text, strip=True):
''' Helper function. Apply same indentation to all lines in a multilines text.
Details:
@ident (string): the required prefix (spaces)
@fn (function): the print() or file.write() function
@text (string): the lines that have to be shifted right
@strip (boolean): True, when the lines should be stripped
from leading and trailing spaces
'''
if text is None:
return
for l in text.split("\n"):
if strip:
fn(ident + l.strip() + "\n")
else:
fn(ident + l + "\n")
#Helper functions, that transforms the RST doctext like this:
# .. method:: from_pydata(vertices, edges, faces)
#
# Make a mesh from a list of verts/edges/faces
# Until we have a nicer way to make geometry, use this.
#
# :arg vertices: float triplets each representing (X, Y, Z) eg: [(0.0, 1.0, 0.5), ...].
# :type vertices: iterable object
# :arg edges: int pairs, each pair contains two indices to the *vertices* argument. eg: [(1, 2), ...]
# :type edges: iterable object
# :arg faces: iterator of faces, each faces contains three or four indices to the *vertices* argument. eg: [(5, 6, 8, 9), (1, 2, 3), ...]
# :type faces: iterable object
#
#into pypredef header definition list, which contains following text:
#
# def from_pydata(vertices, edges, faces):
# ''' Make a mesh from a list of verts/edges/faces
# Until we have a nicer way to make geometry, use this.
# Arguments:
# @vertices (iterable object): float triplets each representing (X, Y, Z) eg: [(0.0, 1.0, 0.5), ...].
# @edges (iterable object): int pairs, each pair contains two indices to the *vertices* argument. eg: [(1, 2), ...]
# @faces (iterable object): iterator of faces, each faces contains three or four indices to the *vertices* argument. eg: [(5, 6, 8, 9), (1, 2, 3), ...]
# '''
#Some blender built-in functions have nothing, but such formatted docstring (in bpy.props, for example)
def rst2list(doc):
'''Method tries convert given doctext into list of definition elements
Arguments:
@doc (string) - the documentation text of the member (preferably in sphinx RST syntax)
Returns: dictionary with identified elements of the definition (keys: "@def","@description","@returns", and zero or more function arguments)
each dictionary item is a small dictionary, which content depends on the keyword:
"@def":
"prototype" : function declaration - "<name>([<arg1[,..]])"
"description": (optional) general description of the function
"type": (optional) type of the returned value - usually for the properties
then the list of arguments follows (if it exists)
[argument name:]
"name": argument's name (just to make the printing easier)
"type": argument's type (may be a class name)
"description": argument's description
"ord": nr kolejny
["@returns":]
optional: what function/property returns:
"description": description of the content
"type": the name of returned type
"ord": nr kolejny
["@note":]
optional: note, added to description (below argument list)
"description": description of the content
"ord": nr kolejny
["@seealso":]
optional: reference, added to description (below argument list)
"description": description of the content
"ord": nr kolejny
'''
def process_line(line, definition, last_entry):
'''Helper function, that analyzes the line and tries to place the
information it contains into "working definition"
Arguments:
@line (string): single line of the description
@definition (dictionary of dictionaries): working definition of the member
@last_entry (string): the key in definition, which was used lately (before this call)
Returns: updated last_entry (string)
'''
def type_name(line):
''' Helper function, that tries to extract the name of Python type
Arguments:
@line (string): text (single line) to analyze (the expression that begins with :type: or :rtype:)
returns the identified type or None, when it cannot identify it!
'''
expr = line.split(" ",1) #split ":type: float" into (':type:','float')
if len(expr) < 2: return None #we cannot identify it!
result = expr[1].strip()
if result in TYPE_ABERRATIONS:
return TYPE_ABERRATIONS[result]
else:
return result
line = line.lstrip(" ")
line = line.replace(":class:","").replace("`","") #replace occurences of ":class:`<TypeName>`"
# with "<TypeName>"
line = line.replace(":exc:","").replace("`","") #replace occurences of ":exc:`<TypeName>`"
# with "<TypeName>"
if line.startswith(".. method::") or line.startswith(".. function::") or line.startswith(".. classmethod::"):
prototype = (line.split("::",1)[1]).lstrip(" ")
last_entry = "@def"
definition["@def"].setdefault("prototype",prototype)
elif line.startswith(":arg"):
expr = line.split(" ",2)
name = expr[1].rstrip(":")
if len(expr) == 3:
definition.setdefault(name,{"name":name, "description":expr[2], "ord":len(definition)})
else:
definition.setdefault(name,{"name":name, "description":"", "ord":len(definition)})
last_entry = name
elif line.startswith(":type:"): #property type
expr = type_name(line)
if expr: definition["@def"].setdefault("type",expr)
last_entry = "@def"
elif line.startswith(":return:"): #return description
expr = line.split(" ",1)
name = "@returns"
definition.setdefault(name,{"name": "returns", "description":expr[1], "ord":len(definition)})
last_entry = name
elif line.startswith(":rtype:"): #type, returned by the function
expr = type_name(line)
if last_entry != "@returns": last_entry = "@def"
if expr: definition[last_entry].setdefault("type",expr)
elif line.startswith(":type"): #argument's type
expr = line.split(" ",2)
name = expr[1].rstrip(":")
try:
definition[name].setdefault("type",expr[2])
last_entry = name
except:
print("Missing argument declaration for '%s'" % name)
elif line.startswith(".. note:: "): #note to member description
line = line.replace(".. note:: ","")
name = "@note"
definition.setdefault(name,{"description":line, "ord":len(definition)})
last_entry = name
elif line.startswith(".. seealso::"): #reference to external resource
line = line.replace(".. seealso:: ","")
name = "@seealso"
definition.setdefault(name,{"description":line, "ord":len(definition)})
last_entry = name
elif line.startswith(".. literalinclude::"):
pass #skip this line
else: #this is just second line of description for the last entry
# (whole member, or just an single argument)
if last_entry in definition and line != "" and not line.startswith("Undocumented"):
item = definition[last_entry]
if "description" not in item:
item.setdefault("description",line)
else:
item["description"] = item["description"] + line + "\n"
return last_entry
#--------------------------------- process_line
lines = doc.split("\n")
last_key = "@def"
definition = {last_key:{"description":"", "ord":0}} #at the beginning: empty description of function definition
for line in lines:
last_key = process_line(line,definition,last_key)
#now let's check the result, stored in <definition> dictionary:
return definition
def get_item(dictionary,key):
'''Helper function. Returns the dictionary element at key, or None
Arguments:
@dictionary: the dictionary which will be searched
@key: the key in the dictionary
'''
if key in dictionary:
return dictionary[key]
else:
return None
def rna2list(info):
''' Prepares list of definition elements
Arguments:
@info (one of rna_info.Info*RNA types) - the descriptor of Struct, Operator, Function or Property
Returns: dictionary of the same structure, like the one returned by rst2list()
"@def":
"prototype" : used in structs and functions
for struct: declaration "class AClass(ABaseClass):"
for function or operator: declaration - "<name>([<arg1[,..]])"
for property: declaration - "<name> = <TypeReturned> [# (read only)]"
"decorator": (optional) "@classmethod" or "@staticmethod"
"description": (optional) general description of the element
"hint" : (optional) formatting hint for the doc2definition() function: "property" for RNA properties, "class" for RNA structs
then the list of function's arguments follows (if they exist)
[argument name:]
"name": argument's name (just to make the printing easier)
"type": argument's type (may be a class name)
"description": argument's description
"ord": ordinal number
["@returns":]
optional: what function/property returns:
"description": description of the content
"type": the name of returned type
"ord": ordinal number (for functions)
["@note":]
optional: note, added to description (below argument list)
"description": description of the content
"ord": ordinal number
["@seealso":]
optional: reference, added to description (below argument list)
"description": description of the content
"ord": oridinal number
'''
def type_name(name, include_namespace=False):
''' Helper function, that corrects some wrong type names
Arguments:
@name (string): "raw" name, received from RNA
@include_namespace: True, when append the bpy.types. prefix
returns the corrected type name (string)
'''
if name in TYPE_ABERRATIONS:
name = TYPE_ABERRATIONS[name]
if include_namespace:
name = "types." + name
return name
def get_argitem(arg, prev_ord, is_return=False):
'''Helper function, that creates an argument definition subdictionary
Arguments:
@arg (rna_info.InfoPropertyRNA): descriptor of the argument
@prev_ord (int): previous order index (to set the value for the "ord" key)
Returns: an definistion subdictionary (keys: "name", "type", "description", "ord")
'''
if arg.fixed_type:
arg_type = arg.fixed_type.identifier
else:
arg_type = arg.type
if is_return:
description = arg.get_type_description(as_ret = True) #without default value!
else:
description = arg.get_type_description(as_arg = True) #without default value!
if arg.collection_type == None:
description = description.replace(arg_type, "", 1) #remove the first occurence of type name - it repeats the declaration!
if description.startswith(","): #it may happen, when the arg_type was at the begining of the string:
description = (description[1:]) #skip the leading colon
if description.startswith(" "):
description = (description[1:]) #skip first space
#add some human comments (if it exists):
if arg.description:
description = arg.description + "\n" + _IDENT + description
if is_return:
return {"name":"returns", "description":description, "type":type_name(arg_type, arg.fixed_type != None), "ord":(prev_ord + 1)}
else:
return {"name":arg.identifier, "description":description, "type":type_name(arg_type), "ord":(prev_ord + 1)}
def get_return(returns, prev_ord):
'''Helper function, that creates the return definition subdictionary ("@returns")
Arguments:
@returns (list of rna_info.InfoPropertyRNA): descriptor of the return values
@prev_ord (int): previous order index (to set the value for the "ord" key)
Returns: an definistion subdictionary (keys: type", "description", "ord")
'''
if len(returns) == 1:
return get_argitem(returns[0],prev_ord,is_return = True)
else: #many different values:
description = "\n("
for ret in returns:
item = get_argitem(ret, prev_ord, is_return = True)
description = description + "\n{0}{1}({2}):{3}".format(_IDENT, ret.identifier, item.pop("type"), item.pop("description"))
#give just the description, not the type!
description = description + "\n)"
return {"name":"returns", "description":description, "ord":(prev_ord + 1)}
definition = {"@def":{"description":"", "ord":0}} #at the beginning: empty description of function definition
if type(info) == rna_info.InfoStructRNA:
#base class of this struct:
base_id = getattr(info.base,"identifier",_BPY_STRUCT_FAKE)
prototype = "class {0}(types.{1}):".format(info.identifier, base_id)
definition["@def"].setdefault("prototype",prototype)
definition["@def"]["description"] = info.description
definition["@def"].setdefault("hint","class")
elif type(info) == rna_info.InfoPropertyRNA:
if info.collection_type:
prop_type = info.collection_type.identifier
elif info.fixed_type:
prop_type = info.fixed_type.identifier
else:
prop_type = info.type
prototype = "{0} = {1}".format(info.identifier, type_name(prop_type, info.fixed_type != None))
if info.is_readonly:
prototype = prototype + " # (read only)"
definition["@def"].setdefault("prototype",prototype)
definition["@def"].setdefault("hint","property")
if info.description:
definition["@def"]["description"] = info.description
definition.setdefault("@returns",{"name" : "returns", "description" : info.get_type_description(as_ret = True), "ord" : 1})
elif type(info) == rna_info.InfoFunctionRNA:
args_str = ", ".join(prop.get_arg_default(force=False) for prop in info.args)
prototype = "{0}({1})".format(info.identifier, args_str)
definition["@def"].setdefault("prototype",prototype)
if info.is_classmethod: definition["@def"].setdefault("decorator","@classmethod\n")
definition["@def"]["description"] = info.description
#append arguments:
for arg in info.args:
definition.setdefault(arg.identifier, get_argitem(arg,len(definition)))
#append returns (operators have none):
if info.return_values:
definition.setdefault("@returns",get_return(info.return_values,len(definition)))
elif type(info) == rna_info.InfoOperatorRNA:
args_str = ", ".join(prop.get_arg_default(force=False) for prop in info.args)
prototype = "{0}({1})".format(info.func_name, args_str)
definition["@def"].setdefault("prototype",prototype)
# definition["@def"].setdefault("decorator","@staticmethod\n")
if info.description and info.description != "(undocumented operator)":
definition["@def"]["description"] = info.description
else: #just empty line
definition["@def"]["description"] = "undocumented"
#append arguments:
for arg in info.args:
definition.setdefault(arg.identifier, get_argitem(arg,len(definition)))
else:
raise TypeError("type was not InfoFunctionRNA, InfoStructRNA, InfoPropertyRNA or InfoOperatorRNA")
return definition
def doc2definition(doc,docstring_ident=_IDENT):
'''Method converts given doctext into declaration and docstring comment
Details:
@doc (string or list) - the documentation text of the member (preferably in sphinx RST syntax)
or ready dictionary of dictionaries, like the result of rst2list() (see above)
@docstring_ident (string) - the amount of spaces before docstring markings
@function - function, that should be used to get the list
Returns: dictionary with following elements:
"declaration": function declaration (may be omitted for attributes docstrings)
"docstring": properly idented docstring (leading and trailing comment markings included)
"returns": type, returned by property/function (to use in eventual return statement)
'''
def pop(definition,key):
'''Removes the given element form the dictionary
Arguments:
@definition: dictionary[key]
@key: the key in the definition dictionary
'''
if key in definition:
return definition.pop(key)
else:
return None
def format_arg(data):
'''Returns line of text, describing an argument or return statement
Arguments:
data (dictionary): a "subdictionary" of <definition>, describing single item:
("ord", "name", ["description"],["type"])
'''
if "type" in data and "description" in data:
return "@{name} ({type}): {description}".format(**data)
elif "type" in data:
return "@{name} ({type}): <not documented>".format(**data)
elif "description" in data:
return "@{name}: {description}".format(**data)
else:
return "@{name}: <not documented>".format(**data)
def get(definition,key,subkey):
''' Returns the given value from the definition dictionary, or None
when it does not exists
Arguments:
@definition: dictionary[key] of dictionaries[subkey]
@key: the key in the definition dictionary
@subkey: the key in the definition[key] subdictionary
'''
if key in definition:
if subkey in definition[key]:
return definition[key][subkey]
else:
return None
else:
return None
if doc is None:
return {"docstring" : docstring_ident + "\n"}
if type(doc) is str :
definition = rst2list(doc)
else:
definition = doc #assume, that doc is the ready definition list!
rtype = get(definition,"@def","type")
if rtype is None:
rtype = get(definition,"@returns","type") #for functions
_returns = pop(definition, "@returns")
_note = pop(definition,"@note")
_seealso = pop(definition, "@seealso")
declaration = get(definition, "@def","prototype")
decorator = get(definition, "@def", "decorator")
hint = get(definition, "@def", "hint")
if declaration:
if hint in ("property", "class"):
pass #no prefix needed
elif decorator:
declaration = decorator + "def " + declaration +":"
else:
declaration = "def " + declaration +":"
_def = pop(definition, "@def") #remove the definition from the list....
ident = docstring_ident + _IDENT #all next row will have additional ident, to match the first line
lines = [] #lines of the docstring text
al = lines.append #trick, to re-use the write_indented_lines to add the line
if "description" in _def:
write_indented_lines(ident,al,_def["description"],False) #fill the <lines> list
if lines:
lines[0] = lines[0][len(ident):] #skip the ident in the first and the last line:
# (the docstring's prefix " '''" will be placed there)
if definition.keys(): #Are named arguments there?
write_indented_lines(ident,al,"Arguments:",False)
for tuple in sorted(definition.items(),key = lambda item: item[1]["ord"]): #sort the lines in the original sequence
#first item of the <tuple> is the key, second - the value (dictionary describing a single element)
write_indented_lines(ident,al,format_arg(tuple[1]),False)
#end for
al("\n")
if _returns:
write_indented_lines(ident,al,format_arg(_returns),False)
if _note and "description" in _note:
write_indented_lines(ident,al,"Note: " + _note["description"],False)
if _seealso and "description" in _seealso:
write_indented_lines(ident,al,"(seealso " + _seealso["description"]+")\n",False)
if not lines:
lines.append("<not documented>\n")
result = {"docstring" : docstring_ident + "'''" + "".join(lines)+ docstring_ident + "'''\n"}
if declaration:
result.setdefault("declaration",declaration)
if rtype:
result.setdefault("returns",rtype)
return result
def pyfunc2predef(ident, fw, identifier, py_func, is_class=True):
''' Creates declaration of a function or class method
Details:
@ident (string): the required prefix (spaces)
@fw (function): the unified shortcut to print() or file.write() function
@identifier (string): the name of the member
@py_func (<py function>): the method, that is being described here
@is_class (boolean): True, when it is a class member
'''
try:
arguments = inspect.getargspec(py_func)
if len(arguments.args) == 0 and is_class:
fw(ident+"@staticmethod\n")
elif len(arguments.args)==0: #global function (is_class = false)
pass
elif arguments.args[0] == "cls" and is_class:
fw(ident+"@classmethod\n")
else: #global function
pass
definition = doc2definition(py_func.__doc__) #parse the eventual RST sphinx markup
if "declaration" in definition:
write_indented_lines(ident,fw, definition["declaration"],False)
else:
arg_str = inspect.formatargspec(*arguments)
fmt = ident + "def %s%s:\n"
fw(fmt % (identifier, arg_str))
if "docstring" in definition:
write_indented_lines(ident,fw,definition["docstring"],False)
if "returns" in definition:
write_indented_lines(ident+_IDENT,fw,"return " + definition["returns"],False)
else:
write_indented_lines(ident+_IDENT,fw,"pass",False)
fw(ident + "\n")
except:
msg = "#unable to describe the '%s' method due to internal error\n\n" % identifier
fw(ident + msg)
def py_descr2predef(ident, fw, descr, module_name, type_name, identifier):
''' Creates declaration of a function or class method
Details:
@ident (string): the required prefix (spaces)
@fw (function): the unified shortcut to print() or file.write() function
@descr(<type descriptor>): an object, describing the member
@module_name (string): the name of this module
@type_name (string): the name of the containing class
@identifier (string): the name of the member
'''
if identifier.startswith("_"):
return
if type(descr) in (types.GetSetDescriptorType, types.MemberDescriptorType): #an attribute of the module or class
definition = doc2definition(descr.__doc__,"") #parse the eventual RST sphinx markup
if "returns" in definition:
returns = definition["returns"]
else:
returns = "None" #we have to assign just something, to be properly parsed!
fw(ident + identifier + " = " + returns + "\n")
if "docstring" in definition:
write_indented_lines(ident,fw,definition["docstring"],False)
elif type(descr) in (MethodDescriptorType, ClassMethodDescriptorType):
py_c_func2predef(ident,fw,module_name,type_name,identifier,descr,True)
else:
raise TypeError("type was not MemberDescriptiorType, GetSetDescriptorType, MethodDescriptorType or ClassMethodDescriptorType")
fw("\n")
def py_c_func2predef(ident, fw, module_name, type_name, identifier, py_func, is_class=True):
''' Creates declaration of a function or class method
Details:
@ident (string): the required prefix (spaces)
@fw (function): the unified shortcut to print() or file.write() function
@type_name (string): the name of the class
@py_func (<py function>): the method, that is being described here
@is_class (boolean): True, when it is a class member
'''
definition = doc2definition(py_func.__doc__) #parse the eventual RST sphinx markup
if type(py_func)== ClassMethodDescriptorType:
fw(ident+"@classmethod\n")
if "declaration" in definition:
write_indented_lines(ident,fw, definition["declaration"],False)
else:
fw(ident + "def %s(*argv):\n" % identifier)#*argv, because we do not know about its arguments....
if "docstring" in definition:
write_indented_lines(ident,fw,definition["docstring"],False)
if "returns" in definition:
write_indented_lines(ident+_IDENT,fw,"return " + definition["returns"],False)
else:
write_indented_lines(ident+_IDENT,fw,"pass",False)
fw(ident + "\n")
def pyprop2predef(ident, fw, identifier, py_prop):
''' Creates declaration of a property
Details:
@ident (string): the required prefix (spaces)
@fw (function): the unified shortcut to print() or file.write() function
@identifier (string): the name of the property
@py_prop (<py property>): the property, that is being described here
'''
definition = doc2definition(py_prop.__doc__,"") #parse the eventual RST sphinx markup
if "returns" in definition:
declaration = identifier + " = " + definition["returns"]
else:
declaration = identifier + " = None" #we have to assign just something, to be properly parsed!
# readonly properties use "data" directive, variables use "attribute" directive
if py_prop.fset is None: declaration = declaration + " # (readonly)"
fw(ident + declaration + "\n")
if "docstring" in definition:
write_indented_lines(ident, fw, definition["docstring"], False)
fw(ident + "\n")
def pyclass2predef(fw, module_name, type_name, value):
''' Creates declaration of a class
Details:
@fw (function): the unified shortcut to print() or file.write() function
@module_name (string): the name of the module, that contains this class
@type_name (string): the name of the class
@value (<class type>): the descriptor of this type
'''
fw("class %s:\n" % type_name)
definition = doc2definition(value.__doc__) #parse the eventual RST sphinx markup
if "docstring" in definition:
write_indented_lines("", fw, definition["docstring"], False)
descr_items = [(key, descr) for key, descr in sorted(value.__dict__.items()) if not key.startswith("__")]
for key, descr in descr_items:
if type(descr) == ClassMethodDescriptorType:
py_descr2predef(_IDENT, fw, descr, module_name, type_name, key)
for key, descr in descr_items:
if type(descr) == MethodDescriptorType:
py_descr2predef(_IDENT, fw, descr, module_name, type_name, key)
for key, descr in descr_items:
if type(descr) in {types.FunctionType, types.MethodType}:
pyfunc2predef(_IDENT, fw, key, descr)
for key, descr in descr_items:
if type(descr) == types.GetSetDescriptorType:
py_descr2predef(_IDENT, fw, descr, module_name, type_name, key)
for key, descr in descr_items:
if type(descr) == PropertyType:
pyprop2predef(_IDENT, fw, key, descr)
fw("\n\n")
def pymodule2predef(BASEPATH, module_name, module, title):
attribute_set = set()
filepath = os.path.join(BASEPATH, module_name + ".py")
file = open(filepath, "w")
fw = file.write
#fw = print
#The description of this module:
if module.__doc__:
title = title + "\n" + module.__doc__
definition = doc2definition(title,"") #skip the leading spaces at the first line...
fw(definition["docstring"])
fw("\n\n")
# write members of the module
# only tested with PyStructs which are not exactly modules
# List the properties, first:
for key, descr in sorted(type(module).__dict__.items()):
if key.startswith("__"):
continue
# naughty, we also add getset's into PyStructs, this is not typical py but also not incorrect.
if type(descr) == types.GetSetDescriptorType : # 'bpy_app_type' name is only used for examples and messages
py_descr2predef("", fw, descr, module_name, "bpy_app_type", key)
attribute_set.add(key)
# Then list the attributes:
for key, descr in sorted(type(module).__dict__.items()):
if key.startswith("__"):
continue
# naughty, we also add getset's into PyStructs, this is not typical py but also not incorrect.
if type(descr) == types.MemberDescriptorType: # 'bpy_app_type' name is only used for examples and messages
py_descr2predef("", fw, descr, module_name, "", key)
attribute_set.add(key)
del key, descr
#list classes:
classes = []
for attribute in sorted(dir(module)):
if not attribute.startswith("_"):
if attribute in attribute_set: #skip the processed items:
continue
if attribute.startswith("n_"): # annoying exception, needed for bpy.app
continue
value = getattr(module, attribute)
value_type = type(value)
if value_type == types.FunctionType:
pyfunc2predef("", fw, attribute, value, is_class=False)
elif value_type in (types.BuiltinMethodType, types.BuiltinFunctionType): # both the same at the moment but to be future proof
# note: can't get args from these, so dump the string as is
# this means any module used like this must have fully formatted docstrings.
py_c_func2predef("", fw, module_name, module, attribute, value, is_class=False)
elif value_type == type:
classes.append((attribute, value))
elif value_type in (bool, int, float, str, tuple):
# constant, not much fun we can do here except to list it.
# TODO, figure out some way to document these!
fw("{0} = {1} #constant value \n\n".format(attribute,repr(value)))
else:
print("\tnot documenting %s.%s" % (module_name, attribute))
continue
attribute_set.add(attribute)
# TODO, more types...
# write collected classes now
for (type_name, value) in classes:
pyclass2predef(fw, module_name, type_name, value)
file.close()
def rna_property2predef(ident, fw, descr):
''' Creates declaration of a property
Details:
@ident (string): the required prefix (spaces)
@fw (function): the unified shortcut to print() or file.write() function
@descr (rna_info.InfoPropertyRNA): descriptor of the property
'''
definition = doc2definition(rna2list(descr),docstring_ident="")
write_indented_lines(ident,fw, definition["declaration"],False)
if "docstring" in definition:
write_indented_lines(ident, fw, definition["docstring"], False)
def rna_function2predef(ident, fw, descr):
''' Creates declaration of a function or operator
Details:
@ident (string): the required prefix (spaces)
@fw (function): the unified shortcut to print() or file.write() function
@descr (rna_info.InfoFunctionRNA or rna_info.InfoOperatorRNA): method's descriptor
'''
definition = doc2definition(rna2list(descr))
write_indented_lines(ident,fw,definition["declaration"],False) #may contain two lines: decorator and declaration
if "docstring" in definition:
write_indented_lines(ident, fw, definition["docstring"], False)
if "returns" in definition:
write_indented_lines(ident+_IDENT,fw,"return " + definition["returns"],False)
else:
write_indented_lines(ident+_IDENT,fw,"pass",False)
fw("\n")
def rna_struct2predef(ident, fw, descr):
''' Creates declaration of a bpy structure
Details:
@ident (string): the required prefix (spaces)
@fw (function): the unified shortcut to print() or file.write() function
@descr (rna_info.InfoStructRNA): the descriptor of a Blender Python class
'''
print("class %s:\n" % descr.identifier)
definition = doc2definition(rna2list(descr))
write_indented_lines(ident,fw, definition["declaration"],False)
if "docstring" in definition:
write_indented_lines(ident, fw, definition["docstring"], False)
#native properties
ident = ident + _IDENT
properties = descr.properties
properties.sort(key= lambda prop: prop.identifier)
for prop in properties:
rna_property2predef(ident,fw,prop)
#Python properties
properties = descr.get_py_properties()
for identifier, prop in properties:
pyprop2predef(ident,fw,identifier,prop)
#Blender native functions
functions = descr.functions
for function in functions:
rna_function2predef(ident, fw, function)
functions = descr.get_py_functions()
for identifier, function in functions:
pyfunc2predef(ident, fw, identifier, function, is_class = True)
def ops_struct2predef(ident, fw, module, operators):
''' Creates "pseudostructure" for a given module of operators
Details:
@ident (string): the required prefix (spaces)
@fw (function): the unified shortcut to print() or file.write() function
@module (string): one of bpy.ops names ("actions", for example)
@operators (list of rna_info.InfoOperatorRNA): operators, grouped in this module
'''
fmt = ident + "class {0}:\n"
fw(fmt.format(module)) #"action" -> "class action:\n"
ident = ident+_IDENT
fw(ident+"'''Spcecial class, created just to reflect content of bpy.ops.{0}'''\n\n".format(module))
operators.sort(key=lambda op: op.func_name)
for operator in operators:
rna_function2predef(ident, fw, operator)
def bpy_base2predef(ident, fw):
''' Creates a structure for the Blender base class
Details:
@ident (string): the required prefix (spaces)
@fw (function): the unified shortcut to print() or file.write() function\n
'''
fmt = ident + "class %s:\n"
fw(fmt % _BPY_STRUCT_FAKE)
ident = ident + _IDENT
fw(ident + "'''built-in base class for all classes in bpy.types.\n\n")
fmt = ident + _IDENT + "Note that bpy.types.%s is not actually available from within blender, it only exists for the purpose of documentation.\n" + ident + "'''\n\n"
fw(fmt % _BPY_STRUCT_FAKE)
descr_items = [(key, descr) for key, descr in sorted(bpy.types.Struct.__bases__[0].__dict__.items()) if not key.startswith("__")]
for key, descr in descr_items:
if type(descr) == MethodDescriptorType: # GetSetDescriptorType, GetSetDescriptorType's are not documented yet
py_descr2predef(ident, fw, descr, "bpy.types", _BPY_STRUCT_FAKE, key)
for key, descr in descr_items:
if type(descr) == types.GetSetDescriptorType:
py_descr2predef(ident, fw, descr, "bpy.types", _BPY_STRUCT_FAKE, key)
fw("\n\n")
def bpy2predef(BASEPATH, title):
''' Creates the bpy.predef file. It contains the bpy.dta, bpy.ops, bpy.types
Arguments:
BASEPATH (string): path for the output file
title(string): descriptive title (the comment for the whole module)
'''
def property2predef(ident, fw, module, name):
''' writes definition of a named property
Details:
@ident (string): the required prefix (spaces)
@fw (function): the unified shortcut to print() or file.write() function
@module (string): one of bpy.ops names ("actions", for example)
@name (string): name of the property
'''
value = getattr(module, name, None)
if value:
value_type = getattr(value, "rna_type", None)
if value_type:
fw("{0} = types.{1}\n".format(name, value_type.identifier))
else:
pyclass2predef(fw, modulr, name, value)
fw("\n\n")
#read all data:
structs, funcs, ops, props = rna_info.BuildRNAInfo()
#open the file: