copy of docs from 2.4x for python modules that have been kept
[blender.git] / source / blender / python / sphinx_doc_gen.py
1  # ***** BEGIN GPL LICENSE BLOCK *****
2  #
3  # This program is free software; you can redistribute it and/or
4  # modify it under the terms of the GNU General Public License
5  # as published by the Free Software Foundation; either version 2
6  # of the License, or (at your option) any later version.
7  #
8  # This program is distributed in the hope that it will be useful,
9  # but WITHOUT ANY WARRANTY; without even the implied warranty of
10  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  # GNU General Public License for more details.
12  #
13  # You should have received a copy of the GNU General Public License
14  # along with this program; if not, write to the Free Software Foundation,
15  # Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
16  #
17  # Contributor(s): Campbell Barton
18  #
19  # #**** END GPL LICENSE BLOCK #****
20
21 script_help_msg = '''
22 Usage,
23 run this script from blenders root path once you have compiled blender
24     ./blender.bin -b -P /b/source/blender/python/sphinx_doc_gen.py
25
26 This will generate python files in "./source/blender/python/doc/sphinx-in"
27 Generate html docs  by running...
28     
29     sphinx-build source/blender/python/doc/sphinx-in source/blender/python/doc/sphinx-out
30 '''
31
32 import os
33 import inspect
34 import bpy
35 import rna_info
36 reload(rna_info)
37
38 def range_str(val):
39     if val < -10000000: return '-inf'
40     if val >  10000000: return 'inf'
41     if type(val)==float:
42         return '%g'  % val
43     else:
44         return str(val)
45
46 def write_indented_lines(ident, fn, text):
47     if text is None:
48         return
49     for l in text.split("\n"):
50         fn(ident + l.strip() + "\n")
51
52
53 def pymethod2sphinx(ident, fw, identifier, py_func):
54     '''
55     class method to sphinx
56     '''
57     arg_str = inspect.formatargspec(*inspect.getargspec(py_func))
58     if arg_str.startswith("(self, "):
59         arg_str = "(" + arg_str[7:]
60         func_type = "method"
61     elif arg_str.startswith("(cls, "):
62         arg_str = "(" + arg_str[6:]
63         func_type = "classmethod"
64     else:
65         func_type = "staticmethod"
66
67     fw(ident + ".. %s:: %s%s\n\n" % (func_type, identifier, arg_str))
68     if py_func.__doc__:
69         write_indented_lines(ident + "   ", fw, py_func.__doc__)
70         fw("\n")
71
72
73 def pyfunc2sphinx(ident, fw, identifier, py_func, is_class=True):
74     '''
75     function or class method to sphinx
76     '''
77     arg_str = inspect.formatargspec(*inspect.getargspec(py_func))
78
79     if not is_class:
80         func_type = "function"
81         
82         # ther rest are class methods
83     elif arg_str.startswith("(self, "):
84         arg_str = "(" + arg_str[7:]
85         func_type = "method"
86     elif arg_str.startswith("(cls, "):
87         arg_str = "(" + arg_str[6:]
88         func_type = "classmethod"
89     else:
90         func_type = "staticmethod"
91
92     fw(ident + ".. %s:: %s%s\n\n" % (func_type, identifier, arg_str))
93     if py_func.__doc__:
94         write_indented_lines(ident + "   ", fw, py_func.__doc__.strip())
95         fw("\n")
96
97 def py_c_func2sphinx(ident, fw, identifier, py_func, is_class=True):
98     '''
99     c defined function to sphinx.
100     '''
101     
102     # dump the docstring, assume its formatted correctly
103     if py_func.__doc__:
104         for l in py_func.__doc__.split("\n"):
105             fw(ident + l + "\n")
106         fw("\n")
107     else:
108         fw(ident + ".. function:: %s()\n\n" % identifier)
109         fw(ident + "   Undocumented function.\n\n" % identifier)
110
111
112 def pyprop2sphinx(ident, fw, identifier, py_prop):
113     '''
114     python property to sphinx
115     '''
116     fw(ident + ".. attribute:: %s\n\n" % identifier)
117     write_indented_lines(ident + "   ", fw, py_prop.__doc__)
118     if py_prop.fset is None:
119         fw(ident + "   (readonly)\n\n")
120
121
122 def pymodule2sphinx(BASEPATH, module_name, module, title):
123     import types
124
125     filepath = os.path.join(BASEPATH, module_name + ".rst")
126     
127     file = open(filepath, "w")
128     print(filepath)
129     print(filepath)
130     fw = file.write
131     
132     fw(title + "\n")
133     fw(("=" * len(title)) + "\n\n")
134     
135     fw(".. module:: %s\n\n" % module_name)
136     
137     if module.__doc__:
138         # Note, may contain sphinx syntax, dont mangle!
139         fw(module.__doc__.strip())
140         fw("\n\n")
141
142     for attribute in dir(module):
143         if not attribute.startswith("_"):
144             value = getattr(module, attribute)
145
146             value_type = type(value)
147             print(attribute, value_type)
148             if value_type == types.FunctionType:
149                 pyfunc2sphinx("", fw, attribute, value, is_class=False)
150             elif value_type in (types.BuiltinMethodType, types.BuiltinFunctionType): # both the same at the moment but to be future proof
151                 # note: can't get args from these, so dump the string as is
152                 # this means any module used like this must have fully formatted docstrings.
153                 py_c_func2sphinx("", fw, attribute, value, is_class=False)
154
155             # TODO, more types...
156
157     file.close()
158
159
160 def rna2sphinx(BASEPATH):
161
162     structs, funcs, ops, props = rna_info.BuildRNAInfo()
163
164     try:
165         os.mkdir(BASEPATH)
166     except:
167         pass
168
169     # conf.py - empty for now
170     filepath = os.path.join(BASEPATH, "conf.py")
171     file = open(filepath, "w")
172     fw = file.write
173     
174     fw("project = 'Blender 3D'\n")
175     # fw("master_doc = 'index'\n")
176     fw("copyright = u'Blender Foundation'\n")
177     fw("version = '2.5'\n")
178     fw("release = '2.5'\n")
179     file.close()
180
181
182     filepath = os.path.join(BASEPATH, "contents.rst")
183     file = open(filepath, "w")
184     fw = file.write
185     
186     fw("\n")
187     fw(".. toctree::\n")
188     fw("   :glob:\n\n")
189     fw("   bpy.ops.*\n\n")
190     fw("   bpy.types.*\n\n")
191     
192     # py modules
193     fw("   bpy.utils\n\n")
194     fw("   bpy.app\n\n")
195     
196     # C modules
197     fw("   bpy.props\n\n")
198     
199     file.close()
200
201     # python modules
202     from bpy import utils as module
203     pymodule2sphinx(BASEPATH, "bpy.utils", module, "Blender Python Utilities")
204     from bpy import app as module
205     pymodule2sphinx(BASEPATH, "bpy.app", module, "Blender Python Application Constants")
206
207     from bpy import props as module
208     pymodule2sphinx(BASEPATH, "bpy.props", module, "Blender Python Property Definitions")
209     del module
210
211
212     if 0:
213         filepath = os.path.join(BASEPATH, "bpy.rst")
214         file = open(filepath, "w")
215         fw = file.write
216         
217         fw("\n")
218
219         title = ":mod:`bpy` --- Blender Python Module"
220         fw("%s\n%s\n\n" % (title, "=" * len(title)))
221         fw(".. module:: bpy.types\n\n")
222         file.close()
223
224     def write_param(ident, fw, prop, is_return=False):
225         if is_return:
226             id_name = "return"
227             id_type = "rtype"
228             kwargs = {"as_ret": True, "class_fmt": ":class:`%s`"}
229             identifier = ""
230         else:
231             id_name = "arg"
232             id_type = "type"
233             kwargs = {"as_arg": True, "class_fmt": ":class:`%s`"}
234             identifier = " %s" % prop.identifier
235
236         type_descr = prop.get_type_description(**kwargs)
237         if prop.name or prop.description:
238             fw(ident + ":%s%s: %s\n" % (id_name, identifier, ", ".join([val for val in (prop.name, prop.description) if val])))
239         fw(ident + ":%s%s: %s\n" % (id_type, identifier, type_descr))
240
241     def write_struct(struct):
242         #if not struct.identifier.startswith("Sc") and not struct.identifier.startswith("I"):
243         #    return
244
245         #if not struct.identifier == "Object":
246         #    return
247
248         filepath = os.path.join(BASEPATH, "bpy.types.%s.rst" % struct.identifier)
249         file = open(filepath, "w")
250         fw = file.write
251         
252         if struct.base: 
253             title = "%s(%s)" % (struct.identifier, struct.base.identifier)
254         else:
255             title = struct.identifier
256
257         fw("%s\n%s\n\n" % (title, "=" * len(title)))
258         
259         fw(".. module:: bpy.types\n\n")
260         
261         bases = struct.get_bases()
262         if bases:
263             if len(bases) > 1:
264                 fw("base classes --- ")
265             else:
266                 fw("base class --- ")
267
268             fw(", ".join([(":class:`%s`" % base.identifier) for base in reversed(bases)]))
269             fw("\n\n")
270         
271         subclasses = [s for s in structs.values() if s.base is struct]
272         
273         if subclasses:
274             fw("subclasses --- \n")
275             fw(", ".join([(":class:`%s`" % s.identifier) for s in subclasses]))
276             fw("\n\n")
277
278
279         if struct.base:
280             fw(".. class:: %s(%s)\n\n" % (struct.identifier, struct.base.identifier))
281         else:
282             fw(".. class:: %s\n\n" % struct.identifier)
283
284         fw("   %s\n\n" % struct.description)
285
286         for prop in struct.properties:
287             fw("   .. attribute:: %s\n\n" % prop.identifier)
288             if prop.description:
289                 fw("      %s\n\n" % prop.description)
290             type_descr = prop.get_type_description(class_fmt=":class:`%s`")
291             fw("      *type* %s\n\n" % type_descr)
292         
293         # python attributes
294         py_properties = struct.get_py_properties()
295         py_prop = None
296         for identifier, py_prop in py_properties:
297             pyprop2sphinx("   ", fw, identifier, py_prop)
298         del py_properties, py_prop
299
300         for func in struct.functions:
301             args_str = ", ".join([prop.get_arg_default(force=False) for prop in func.args])
302
303             fw("   .. method:: %s(%s)\n\n" % (func.identifier, args_str))
304             fw("      %s\n\n" % func.description)
305             
306             for prop in func.args:
307                 write_param("      ", fw, prop)
308
309             if len(func.return_values) == 1:
310                 write_param("      ", fw, func.return_values[0], is_return=True)
311             elif func.return_values: # multiple return values
312                 fw("      :return (%s):\n" % ", ".join([prop.identifier for prop in func.return_values]))
313                 for prop in func.return_values:
314                     type_descr = prop.get_type_description(as_ret=True, class_fmt=":class:`%s`")
315                     descr = prop.description
316                     if not descr:
317                         descr = prop.name
318                     fw("         `%s`, %s, %s\n\n" % (prop.identifier, descr, type_descr))
319
320             fw("\n")
321
322
323         # python methods
324         py_funcs = struct.get_py_functions()
325         py_func = None
326         
327         for identifier, py_func in py_funcs:
328             pyfunc2sphinx("   ", fw, identifier, py_func, is_class=True)
329         del py_funcs, py_func
330
331         if struct.references:
332             # use this otherwise it gets in the index for a normal heading.
333             fw(".. rubric:: References\n\n")
334
335             for ref in struct.references:
336                 ref_split = ref.split(".")
337                 if len(ref_split) > 2:
338                     ref = ref_split[-2] + "." + ref_split[-1]
339                 fw("* :class:`%s`\n" % ref)
340             fw("\n")
341
342
343     for struct in structs.values():
344         write_struct(struct)
345
346     # oeprators
347     def write_ops():
348         fw = None
349         
350         last_mod = ''
351         
352         for op_key in sorted(ops.keys()):
353             op = ops[op_key]
354             
355             if last_mod != op.module_name:
356                 filepath = os.path.join(BASEPATH, "bpy.ops.%s.rst" % op.module_name)
357                 file = open(filepath, "w")
358                 fw = file.write
359                 
360                 title = "%s Operators"  % (op.module_name[0].upper() + op.module_name[1:])
361                 fw("%s\n%s\n\n" % (title, "=" * len(title)))
362                 
363                 fw(".. module:: bpy.ops.%s\n\n" % op.module_name)
364                 last_mod = op.module_name
365             
366             args_str = ", ".join([prop.get_arg_default(force=True) for prop in op.args])
367             fw(".. function:: %s(%s)\n\n" % (op.func_name, args_str))
368             if op.description:
369                 fw("   %s\n\n" % op.description)
370             for prop in op.args:
371                 write_param("   ", fw, prop)
372             if op.args:
373                 fw("\n")
374
375             location = op.get_location()
376             if location != (None, None):
377                 fw("   *python operator source --- `%s:%d`* \n\n" % location)
378     
379     write_ops()
380
381     file.close()
382
383 if __name__ == '__main__':
384     if 'bpy' not in dir():
385         print("\nError, this script must run from inside blender2.5")
386         print(script_help_msg)
387     else:
388         # os.system("rm source/blender/python/doc/sphinx-in/*.rst")
389         # os.system("rm -rf source/blender/python/doc/sphinx-out/*")
390         rna2sphinx('source/blender/python/doc/sphinx-in')
391
392     import sys
393     sys.exit()