b20cd74f82a29700e5067dd44ac55a15d96f9377
[blender-staging.git] / source / blender / python / doc / 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 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/doc/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 For PDF generation
33
34     sphinx-build -b latex source/blender/python/doc/sphinx-in source/blender/python/doc/sphinx-out
35     cd source/blender/python/doc/sphinx-out
36     make
37 '''
38
39 # import rpdb2; rpdb2.start_embedded_debugger('test')
40
41 import os
42 import inspect
43 import bpy
44 import rna_info
45 reload(rna_info)
46
47 # lame, python wont give some access
48 ClassMethodDescriptorType = type(dict.__dict__['fromkeys'])
49 MethodDescriptorType = type(dict.get)
50 GetSetDescriptorType = type(int.real)
51
52 EXAMPLE_SET = set()
53 EXAMPLE_SET_USED = set()
54
55 _BPY_STRUCT_FAKE = "bpy_struct"
56 _BPY_FULL_REBUILD = False
57
58 def undocumented_message(module_name, type_name, identifier):
59     message = "Undocumented (`contribute " \
60         "<http://wiki.blender.org/index.php/Dev:2.5/Py/API/Documentation/Contribute" \
61         "?action=edit&section=new&preload=Dev:2.5/Py/API/Documentation/Contribute/Howto-message" \
62         "&preloadtitle=%s.%s.%s>`_)\n\n" % (module_name, type_name, identifier)
63     return message
64
65
66 def range_str(val):
67     '''
68     Converts values to strings for the range directive.
69     (unused function it seems)
70     '''
71     if val < -10000000: return '-inf'
72     if val >  10000000: return 'inf'
73     if type(val)==float:
74         return '%g'  % val
75     else:
76         return str(val)
77
78
79 def write_example_ref(ident, fw, example_id, ext="py"):
80     if example_id in EXAMPLE_SET:
81         fw("%s.. literalinclude:: ../examples/%s.%s\n\n" % (ident, example_id, ext))
82         EXAMPLE_SET_USED.add(example_id)
83     else:
84         if bpy.app.debug:
85             print("\tskipping example:", example_id)
86
87
88 def write_indented_lines(ident, fn, text, strip=True):
89     '''
90     Apply same indentation to all lines in a multilines text.
91     '''
92     if text is None:
93         return
94     for l in text.split("\n"):
95         if strip:
96             fn(ident + l.strip() + "\n")
97         else:
98             fn(ident + l + "\n")
99
100
101 def pymethod2sphinx(ident, fw, identifier, py_func):
102     '''
103     class method to sphinx
104     '''
105     arg_str = inspect.formatargspec(*inspect.getargspec(py_func))
106     if arg_str.startswith("(self, "):
107         arg_str = "(" + arg_str[7:]
108         func_type = "method"
109     elif arg_str.startswith("(cls, "):
110         arg_str = "(" + arg_str[6:]
111         func_type = "classmethod"
112     else:
113         func_type = "staticmethod"
114
115     fw(ident + ".. %s:: %s%s\n\n" % (func_type, identifier, arg_str))
116     if py_func.__doc__:
117         write_indented_lines(ident + "   ", fw, py_func.__doc__)
118         fw("\n")
119
120
121 def pyfunc2sphinx(ident, fw, identifier, py_func, is_class=True):
122     '''
123     function or class method to sphinx
124     '''
125     arg_str = inspect.formatargspec(*inspect.getargspec(py_func))
126
127     if not is_class:
128         func_type = "function"
129         
130         # ther rest are class methods
131     elif arg_str.startswith("(self, "):
132         arg_str = "(" + arg_str[7:]
133         func_type = "method"
134     elif arg_str.startswith("(cls, "):
135         arg_str = "(" + arg_str[6:]
136         func_type = "classmethod"
137     else:
138         func_type = "staticmethod"
139
140     fw(ident + ".. %s:: %s%s\n\n" % (func_type, identifier, arg_str))
141     if py_func.__doc__:
142         write_indented_lines(ident + "   ", fw, py_func.__doc__.strip())
143         fw("\n")
144
145
146 def py_descr2sphinx(ident, fw, descr, module_name, type_name, identifier):    
147     if identifier.startswith("_"):
148         return
149     
150     doc = descr.__doc__
151     if not doc:
152         doc = undocumented_message(module_name, type_name, identifier)
153     
154     if type(descr) == GetSetDescriptorType:
155         fw(ident + ".. attribute:: %s\n\n" % identifier)
156         write_indented_lines(ident + "   ", fw, doc, False)
157     elif type(descr) in (MethodDescriptorType, ClassMethodDescriptorType):
158         write_indented_lines(ident, fw, doc, False)
159     else:
160         raise TypeError("type was not GetSetDescriptorType, MethodDescriptorType or ClassMethodDescriptorType")
161
162     write_example_ref(ident, fw, module_name + "." + type_name + "." + identifier)
163     fw("\n")
164
165
166 def py_c_func2sphinx(ident, fw, module_name, type_name, identifier, py_func, is_class=True):
167     '''
168     c defined function to sphinx.
169     '''
170     
171     # dump the docstring, assume its formatted correctly
172     if py_func.__doc__:
173         write_indented_lines(ident, fw, py_func.__doc__, False)
174         fw("\n")
175     else:
176         fw(ident + ".. function:: %s()\n\n" % identifier)
177         fw(ident + "   " + undocumented_message(module_name, type_name, identifier))
178
179
180 def pyprop2sphinx(ident, fw, identifier, py_prop):
181     '''
182     python property to sphinx
183     '''
184     # readonly properties use "data" directive, variables use "attribute" directive
185     if py_prop.fset is None:
186         fw(ident + ".. data:: %s\n\n" % identifier)
187     else:
188         fw(ident + ".. attribute:: %s\n\n" % identifier)
189     write_indented_lines(ident + "   ", fw, py_prop.__doc__)
190     if py_prop.fset is None:
191         fw(ident + "   (readonly)\n\n")
192
193
194 def pymodule2sphinx(BASEPATH, module_name, module, title):
195     import types
196     attribute_set = set()
197     filepath = os.path.join(BASEPATH, module_name + ".rst")
198     
199     file = open(filepath, "w")
200
201     fw = file.write
202     
203     fw(title + "\n")
204     fw(("=" * len(title)) + "\n\n")
205     
206     fw(".. module:: %s\n\n" % module_name)
207     
208     if module.__doc__:
209         # Note, may contain sphinx syntax, dont mangle!
210         fw(module.__doc__.strip())
211         fw("\n\n")
212         
213     write_example_ref("", fw, module_name)
214     
215     # write members of the module
216     # only tested with PyStructs which are not exactly modules
217     for key, descr in sorted(type(module).__dict__.items()):
218         if type(descr) == types.MemberDescriptorType:
219             if descr.__doc__:
220                 fw(".. data:: %s\n\n" % key)
221                 write_indented_lines("   ", fw, descr.__doc__, False)
222                 attribute_set.add(key)
223                 fw("\n")
224     del key, descr
225     
226     classes = []
227
228     for attribute in sorted(dir(module)):
229         if not attribute.startswith("_"):
230
231             if attribute in attribute_set:
232                 continue
233
234             if attribute.startswith("n_"): # annoying exception, needed for bpy.app
235                 continue
236             
237             value = getattr(module, attribute)
238
239             value_type = type(value)
240
241             if value_type == types.FunctionType:
242                 pyfunc2sphinx("", fw, attribute, value, is_class=False)
243             elif value_type in (types.BuiltinMethodType, types.BuiltinFunctionType): # both the same at the moment but to be future proof
244                 # note: can't get args from these, so dump the string as is
245                 # this means any module used like this must have fully formatted docstrings.
246                 py_c_func2sphinx("", fw, module_name, module, attribute, value, is_class=False)
247             elif value_type == type:
248                 classes.append((attribute, value))
249             elif value_type in (bool, int, float, str, tuple):
250                 # constant, not much fun we can do here except to list it.
251                 # TODO, figure out some way to document these!
252                 fw(".. data:: %s\n\n" % attribute)
253                 write_indented_lines("   ", fw, "constant value %s" % repr(value), False)
254                 fw("\n")
255             else:
256                 print("\tnot documenting %s.%s" % (module_name, attribute))
257                 continue
258
259             attribute_set.add(attribute)
260             # TODO, more types...
261
262     # write collected classes now
263     for (type_name, value) in classes:
264         # May need to be its own function
265         fw(".. class:: %s\n\n" % type_name)
266         if value.__doc__:
267             write_indented_lines("   ", fw, value.__doc__, False)
268             fw("\n")
269         write_example_ref("   ", fw, module_name + "." + type_name)
270
271         descr_items = [(key, descr) for key, descr in sorted(value.__dict__.items()) if not key.startswith("__")]
272
273         for key, descr in descr_items:
274             if type(descr) == ClassMethodDescriptorType:
275                 py_descr2sphinx("   ", fw, descr, module_name, type_name, key)
276
277         for key, descr in descr_items:
278             if type(descr) == MethodDescriptorType:
279                 py_descr2sphinx("   ", fw, descr, module_name, type_name, key)
280
281         for key, descr in descr_items:
282             if type(descr) == GetSetDescriptorType:
283                 py_descr2sphinx("   ", fw, descr, module_name, type_name, key)
284
285         fw("\n\n")
286
287     file.close()
288
289
290
291 def rna2sphinx(BASEPATH):
292
293     structs, funcs, ops, props = rna_info.BuildRNAInfo()
294
295     try:
296         os.mkdir(BASEPATH)
297     except:
298         pass
299
300     # conf.py - empty for now
301     filepath = os.path.join(BASEPATH, "conf.py")
302     file = open(filepath, "w")
303     fw = file.write
304
305
306     version_string = bpy.app.version_string.split("(")[0]
307     if bpy.app.build_revision != "Unknown":
308         version_string = version_string + " r" + bpy.app.build_revision
309     
310     # for use with files
311     version_string_fp = "_".join(str(v) for v in bpy.app.version)
312     
313     fw("project = 'Blender'\n")
314     # fw("master_doc = 'index'\n")
315     fw("copyright = u'Blender Foundation'\n")
316     fw("version = '%s - UNSTABLE API'\n" % version_string)
317     fw("release = '%s - UNSTABLE API'\n" % version_string)
318     fw("html_theme = 'blender-org'\n")
319     fw("html_theme_path = ['../']\n")
320     fw("html_favicon = 'favicon.ico'\n")
321     # not helpful since the source us generated, adds to upload size.
322     fw("html_copy_source = False\n")
323     fw("\n")
324     # needed for latex, pdf gen
325     fw("latex_documents = [ ('contents', 'contents.tex', 'Blender Index', 'Blender Foundation', 'manual'), ]\n")
326     fw("latex_paper_size = 'a4paper'\n")
327     file.close()
328
329
330     filepath = os.path.join(BASEPATH, "contents.rst")
331     file = open(filepath, "w")
332     fw = file.write
333     
334     fw("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n")
335     fw(" Blender Documentation contents\n")
336     fw("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n")
337     fw("\n")
338     fw("This document is an API reference for Blender %s. built %s.\n" % (version_string, bpy.app.build_date))
339     fw("\n")
340     fw("An introduction to Blender and Python can be found at <http://wiki.blender.org/index.php/Dev:2.5/Py/API/Intro>\n")
341     fw("\n")
342     fw("`A PDF version of this document is also available <blender_python_reference_%s.pdf>`__\n" % version_string_fp)
343     fw("\n")
344     fw(".. warning:: The Python API in Blender is **UNSTABLE**, It should only be used for testing, any script written now may break in future releases.\n")
345     fw("   \n")
346     fw("   The following areas are subject to change.\n")
347     fw("      * operator names and arguments\n")
348     fw("      * render api\n")
349     fw("      * function calls with the data api (any function calls with values accessed from bpy.data), including functions for importing and exporting meshes\n")
350     fw("      * class registration (Operator, Panels, Menus, Headers)\n")
351     fw("      * modules: bpy.props, blf)\n")
352     fw("      * members in the bpy.context have to be reviewed\n")
353     fw("      * python defined modal operators, especially drawing callbacks are highly experemental\n")
354     fw("   \n")
355     fw("   These parts of the API are relatively stable and are unlikely to change significantly\n")
356     fw("      * data API, access to attributes of blender data such as mesh verts, material color, timeline frames and scene objects\n")
357     fw("      * user interface functions for defining buttons, creation of menus, headers, panels\n")
358     fw("      * modules: bgl, mathutils and geometry\n")
359     fw("      * game engine modules\n")
360     fw("\n")
361
362     fw("===================\n")
363     fw("Application Modules\n")
364     fw("===================\n")
365     fw("\n")
366     fw(".. toctree::\n")
367     fw("   :maxdepth: 1\n\n")
368     fw("   bpy.data.rst\n\n") # note: not actually a module
369     fw("   bpy.ops.rst\n\n")
370     fw("   bpy.types.rst\n\n")
371     
372     # py modules
373     fw("   bpy.utils.rst\n\n")
374     fw("   bpy.path.rst\n\n")
375     fw("   bpy.app.rst\n\n")
376     
377     # C modules
378     fw("   bpy.props.rst\n\n")
379
380     fw("==================\n")
381     fw("Standalone Modules\n")
382     fw("==================\n")
383     fw("\n")
384     fw(".. toctree::\n")
385     fw("   :maxdepth: 1\n\n")
386
387
388     fw("   mathutils.rst\n\n")
389     fw("   blf.rst\n\n")
390     fw("   aud.rst\n\n")
391     
392     # game engine
393     fw("===================\n")
394     fw("Game Engine Modules\n")
395     fw("===================\n")
396     fw("\n")
397     fw(".. toctree::\n")
398     fw("   :maxdepth: 1\n\n")
399     fw("   bge.types.rst\n\n")
400     fw("   bge.logic.rst\n\n")
401     fw("   bge.render.rst\n\n")
402     fw("   bge.events.rst\n\n")
403
404     file.close()
405
406
407     # internal modules
408     filepath = os.path.join(BASEPATH, "bpy.ops.rst")
409     file = open(filepath, "w")
410     fw = file.write
411     fw("Operators (bpy.ops)\n")
412     fw("===================\n\n")
413     fw(".. toctree::\n")
414     fw("   :glob:\n\n")
415     fw("   bpy.ops.*\n\n")
416     file.close()
417
418     filepath = os.path.join(BASEPATH, "bpy.types.rst")
419     file = open(filepath, "w")
420     fw = file.write
421     fw("Types (bpy.types)\n")
422     fw("=================\n\n")
423     fw(".. toctree::\n")
424     fw("   :glob:\n\n")
425     fw("   bpy.types.*\n\n")
426     file.close()
427
428
429     # not actually a module, only write this file so we
430     # can reference in the TOC
431     filepath = os.path.join(BASEPATH, "bpy.data.rst")
432     file = open(filepath, "w")
433     fw = file.write
434     fw("Data Access (bpy.data)\n")
435     fw("======================\n\n")
436     fw(".. module:: bpy\n")
437     fw("\n")
438     fw("This module is used for all blender/python access.\n")
439     fw("\n")
440     fw(".. literalinclude:: ../examples/bpy.data.py\n")
441     fw("\n")
442     fw(".. data:: data\n")
443     fw("\n")
444     fw("   Access to blenders internal data\n")
445     fw("\n")
446     fw("   :type: :class:`bpy.types.BlendData`\n")
447     file.close()
448
449     EXAMPLE_SET_USED.add("bpy.data")
450
451
452     # python modules
453     from bpy import utils as module
454     pymodule2sphinx(BASEPATH, "bpy.utils", module, "Utilities (bpy.utils)")
455
456     from bpy import path as module
457     pymodule2sphinx(BASEPATH, "bpy.path", module, "Path Utilities (bpy.path)")
458
459     # C modules
460     from bpy import app as module
461     pymodule2sphinx(BASEPATH, "bpy.app", module, "Application Data (bpy.app)")
462
463     from bpy import props as module
464     pymodule2sphinx(BASEPATH, "bpy.props", module, "Property Definitions (bpy.props)")
465     
466     import mathutils as module
467     pymodule2sphinx(BASEPATH, "mathutils", module, "Math Types & Utilities (mathutils)")
468     del module
469
470     import blf as module
471     pymodule2sphinx(BASEPATH, "blf", module, "Font Drawing (blf)")
472     del module
473     
474     import aud as module
475     pymodule2sphinx(BASEPATH, "aud", module, "Audio System (aud)")
476     del module
477
478     # game engine
479     import shutil
480     # copy2 keeps time/date stamps
481     shutil.copy2(os.path.join(BASEPATH, "../../../../gameengine/PyDoc/bge.types.rst"), BASEPATH)
482     shutil.copy2(os.path.join(BASEPATH, "../../../../gameengine/PyDoc/bge.logic.rst"), BASEPATH)
483     shutil.copy2(os.path.join(BASEPATH, "../../../../gameengine/PyDoc/bge.render.rst"), BASEPATH)
484     shutil.copy2(os.path.join(BASEPATH, "../../../../gameengine/PyDoc/bge.events.rst"), BASEPATH)
485
486
487     if 0:
488         filepath = os.path.join(BASEPATH, "bpy.rst")
489         file = open(filepath, "w")
490         fw = file.write
491         
492         fw("\n")
493
494         title = ":mod:`bpy` --- Blender Python Module"
495         fw("%s\n%s\n\n" % (title, "=" * len(title)))
496         fw(".. module:: bpy.types\n\n")
497         file.close()
498
499     def write_param(ident, fw, prop, is_return=False):
500         if is_return:
501             id_name = "return"
502             id_type = "rtype"
503             kwargs = {"as_ret": True, "class_fmt": ":class:`%s`"}
504             identifier = ""
505         else:
506             id_name = "arg"
507             id_type = "type"
508             kwargs = {"as_arg": True, "class_fmt": ":class:`%s`"}
509             identifier = " %s" % prop.identifier
510
511         type_descr = prop.get_type_description(**kwargs)
512         if prop.name or prop.description:
513             fw(ident + ":%s%s: %s\n" % (id_name, identifier, ", ".join(val for val in (prop.name, prop.description) if val)))
514         fw(ident + ":%s%s: %s\n" % (id_type, identifier, type_descr))
515
516     def write_struct(struct):
517         #if not struct.identifier.startswith("Sc") and not struct.identifier.startswith("I"):
518         #    return
519
520         #if not struct.identifier == "Object":
521         #    return
522
523         filepath = os.path.join(BASEPATH, "bpy.types.%s.rst" % struct.identifier)
524         file = open(filepath, "w")
525         fw = file.write
526         
527         base_id = getattr(struct.base, "identifier", "")
528
529         if _BPY_STRUCT_FAKE:
530             if not base_id:
531                 base_id = _BPY_STRUCT_FAKE
532
533         if base_id:
534             title = "%s(%s)" % (struct.identifier, base_id)
535         else:
536             title = struct.identifier
537
538         fw("%s\n%s\n\n" % (title, "=" * len(title)))
539         
540         fw(".. module:: bpy.types\n\n")
541         
542         base_ids = [base.identifier for base in struct.get_bases()]
543
544         if _BPY_STRUCT_FAKE:
545             base_ids.append(_BPY_STRUCT_FAKE)
546
547         base_ids.reverse()
548             
549         if base_ids:
550             if len(base_ids) > 1:
551                 fw("base classes --- ")
552             else:
553                 fw("base class --- ")
554
555             fw(", ".join((":class:`%s`" % base_id) for base_id in base_ids))
556             fw("\n\n")
557         
558         subclass_ids = [s.identifier for s in structs.values() if s.base is struct if not rna_info.rna_id_ignore(s.identifier)]
559         if subclass_ids:
560             fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in subclass_ids) + "\n\n")
561         
562         base_id = getattr(struct.base, "identifier", "")
563         
564         if _BPY_STRUCT_FAKE:
565             if not base_id:
566                 base_id = _BPY_STRUCT_FAKE
567
568         if base_id:
569             fw(".. class:: %s(%s)\n\n" % (struct.identifier, base_id))
570         else:
571             fw(".. class:: %s\n\n" % struct.identifier)
572
573         fw("   %s\n\n" % struct.description)
574         
575         # properties sorted in alphabetical order
576         sorted_struct_properties = struct.properties[:]
577         sorted_struct_properties.sort(key=lambda prop: prop.identifier)
578
579         for prop in sorted_struct_properties:
580             type_descr = prop.get_type_description(class_fmt=":class:`%s`")
581             # readonly properties use "data" directive, variables properties use "attribute" directive
582             if 'readonly' in type_descr:
583                 fw("   .. data:: %s\n\n" % prop.identifier)
584             else:
585                 fw("   .. attribute:: %s\n\n" % prop.identifier)
586             if prop.description:
587                 fw("      %s\n\n" % prop.description)
588             fw("      :type: %s\n\n" % type_descr)
589         
590         # python attributes
591         py_properties = struct.get_py_properties()
592         py_prop = None
593         for identifier, py_prop in py_properties:
594             pyprop2sphinx("   ", fw, identifier, py_prop)
595         del py_properties, py_prop
596
597         for func in struct.functions:
598             args_str = ", ".join(prop.get_arg_default(force=False) for prop in func.args)
599
600             fw("   .. %s:: %s(%s)\n\n" % ("classmethod" if func.is_classmethod else "method", func.identifier, args_str))
601             fw("      %s\n\n" % func.description)
602             
603             for prop in func.args:
604                 write_param("      ", fw, prop)
605
606             if len(func.return_values) == 1:
607                 write_param("      ", fw, func.return_values[0], is_return=True)
608             elif func.return_values: # multiple return values
609                 fw("      :return (%s):\n" % ", ".join(prop.identifier for prop in func.return_values))
610                 for prop in func.return_values:
611                     type_descr = prop.get_type_description(as_ret=True, class_fmt=":class:`%s`")
612                     descr = prop.description
613                     if not descr:
614                         descr = prop.name
615                     fw("         `%s`, %s, %s\n\n" % (prop.identifier, descr, type_descr))
616
617             fw("\n")
618
619
620         # python methods
621         py_funcs = struct.get_py_functions()
622         py_func = None
623         
624         for identifier, py_func in py_funcs:
625             pyfunc2sphinx("   ", fw, identifier, py_func, is_class=True)
626         del py_funcs, py_func
627
628         lines = []
629
630         if struct.base or _BPY_STRUCT_FAKE:
631             bases = list(reversed(struct.get_bases()))
632
633             # props
634             lines[:] = []
635             
636             if _BPY_STRUCT_FAKE:
637                 descr_items = [(key, descr) for key, descr in sorted(bpy.types.Struct.__bases__[0].__dict__.items()) if not key.startswith("__")]
638             
639             if _BPY_STRUCT_FAKE:
640                 for key, descr in descr_items:
641                     if type(descr) == GetSetDescriptorType:
642                         lines.append("   * :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key))
643
644             for base in bases:
645                 for prop in base.properties:
646                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, prop.identifier))
647
648                 for identifier, py_prop in base.get_py_properties():
649                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, identifier))
650                     
651                 for identifier, py_prop in base.get_py_properties():
652                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, identifier))
653             
654             if lines:
655                 fw(".. rubric:: Inherited Properties\n\n")
656
657                 fw(".. hlist::\n")
658                 fw("   :columns: 2\n\n")
659
660                 for line in lines:
661                     fw(line)
662                 fw("\n")
663
664
665             # funcs
666             lines[:] = []
667
668             if _BPY_STRUCT_FAKE:
669                 for key, descr in descr_items:
670                     if type(descr) == MethodDescriptorType:
671                         lines.append("   * :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key))
672
673             for base in bases:
674                 for func in base.functions:
675                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, func.identifier))
676                 for identifier, py_func in base.get_py_functions():
677                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, identifier))
678
679             if lines:
680                 fw(".. rubric:: Inherited Functions\n\n")
681
682                 fw(".. hlist::\n")
683                 fw("   :columns: 2\n\n")
684
685                 for line in lines:
686                     fw(line)
687                 fw("\n")
688             
689             lines[:] = []
690
691
692         if struct.references:
693             # use this otherwise it gets in the index for a normal heading.
694             fw(".. rubric:: References\n\n")
695
696             fw(".. hlist::\n")
697             fw("   :columns: 2\n\n")
698
699             for ref in struct.references:
700                 ref_split = ref.split(".")
701                 if len(ref_split) > 2:
702                     ref = ref_split[-2] + "." + ref_split[-1]
703                 fw("   * :class:`%s`\n" % ref)
704             fw("\n")
705
706
707     for struct in structs.values():
708         # TODO, rna_info should filter these out!
709         if "_OT_" in struct.identifier:
710             continue
711         write_struct(struct)
712         
713     # special case, bpy_struct
714     if _BPY_STRUCT_FAKE:
715         filepath = os.path.join(BASEPATH, "bpy.types.%s.rst" % _BPY_STRUCT_FAKE)
716         file = open(filepath, "w")
717         fw = file.write
718
719         fw("%s\n" % _BPY_STRUCT_FAKE)
720         fw("=" * len(_BPY_STRUCT_FAKE) + "\n")
721         fw("\n")
722         fw(".. module:: bpy.types\n")
723         fw("\n")
724
725         subclass_ids = [s.identifier for s in structs.values() if s.base is None if not rna_info.rna_id_ignore(s.identifier)]
726         if subclass_ids:
727             fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in sorted(subclass_ids)) + "\n\n")
728
729         fw(".. class:: %s\n\n" % _BPY_STRUCT_FAKE)
730         fw("   built-in base class for all classes in bpy.types.\n\n")
731         fw("   .. note::\n\n")
732         fw("      Note that bpy.types.%s is not actually available from within blender, it only exists for the purpose of documentation.\n\n" % _BPY_STRUCT_FAKE)
733
734         descr_items = [(key, descr) for key, descr in sorted(bpy.types.Struct.__bases__[0].__dict__.items()) if not key.startswith("__")]
735
736         for key, descr in descr_items:
737             if type(descr) == MethodDescriptorType: # GetSetDescriptorType, GetSetDescriptorType's are not documented yet
738                 py_descr2sphinx("   ", fw, descr, "bpy.types", _BPY_STRUCT_FAKE, key)
739
740         for key, descr in descr_items:
741             if type(descr) == GetSetDescriptorType:
742                 py_descr2sphinx("   ", fw, descr, "bpy.types", _BPY_STRUCT_FAKE, key)
743
744
745     # operators
746     def write_ops():
747         API_BASEURL='https://svn.blender.org/svnroot/bf-blender/trunk/blender/release/scripts'
748         fw = None
749         last_mod = ''
750         
751         for op_key in sorted(ops.keys()):
752             op = ops[op_key]
753             
754             if last_mod != op.module_name:
755                 filepath = os.path.join(BASEPATH, "bpy.ops.%s.rst" % op.module_name)
756                 file = open(filepath, "w")
757                 fw = file.write
758                 
759                 title = "%s Operators"  % (op.module_name[0].upper() + op.module_name[1:])
760                 fw("%s\n%s\n\n" % (title, "=" * len(title)))
761                 
762                 fw(".. module:: bpy.ops.%s\n\n" % op.module_name)
763                 last_mod = op.module_name
764
765             args_str = ", ".join(prop.get_arg_default(force=True) for prop in op.args)
766             fw(".. function:: %s(%s)\n\n" % (op.func_name, args_str))
767
768             # if the description isn't valid, we output the standard warning 
769             # with a link to the wiki so that people can help
770             if not op.description or op.description == "(undocumented operator)":
771                 operator_description = undocumented_message('bpy.ops',op.module_name,op.func_name)
772             else:
773                 operator_description = op.description
774
775             fw("   %s\n\n" % operator_description)
776             for prop in op.args:
777                 write_param("   ", fw, prop)
778             if op.args:
779                 fw("\n")
780
781             location = op.get_location()
782             if location != (None, None):
783                 fw("   :file: `%s <%s/%s>`_:%d\n\n" % (location[0],API_BASEURL,location[0],location[1]))
784     
785     write_ops()
786
787     file.close()
788
789 def main():
790     import bpy
791     if 'bpy' not in dir():
792         print("\nError, this script must run from inside blender2.5")
793         print(script_help_msg)
794     else:
795         import shutil
796
797         path_in = 'source/blender/python/doc/sphinx-in'
798         path_out = 'source/blender/python/doc/sphinx-out'
799         path_examples = 'source/blender/python/doc/examples'
800         # only for partial updates
801         path_in_tmp = path_in + "-tmp"
802
803         if not os.path.exists(path_in):
804             os.mkdir(path_in)
805
806         for f in os.listdir(path_examples):
807             if f.endswith(".py"):
808                 EXAMPLE_SET.add(os.path.splitext(f)[0])
809
810
811         # only for full updates
812         if _BPY_FULL_REBUILD:
813             shutil.rmtree(path_in, True)
814             shutil.rmtree(path_out, True)
815         else:
816             # write here, then move
817             shutil.rmtree(path_in_tmp, True)
818
819         rna2sphinx(path_in_tmp)
820
821         if not _BPY_FULL_REBUILD:
822             import filecmp
823
824             # now move changed files from 'path_in_tmp' --> 'path_in'
825             file_list_path_in = set(os.listdir(path_in))
826             file_list_path_in_tmp = set(os.listdir(path_in_tmp))
827             
828             # remove deprecated files that have been removed.
829             for f in sorted(file_list_path_in):
830                 if f not in file_list_path_in_tmp:
831                     print("\tdeprecated: %s" % f)
832                     os.remove(os.path.join(path_in, f))
833
834             # freshen with new files.
835             for f in sorted(file_list_path_in_tmp):
836                 f_from = os.path.join(path_in_tmp, f)
837                 f_to = os.path.join(path_in, f)
838
839                 do_copy = True
840                 if f in file_list_path_in:
841                     if filecmp.cmp(f_from, f_to):
842                         do_copy = False
843                 
844                 if do_copy:
845                     print("\tupdating: %s" % f)
846                     shutil.copy(f_from, f_to)
847                 '''else:
848                     print("\tkeeping: %s" % f) # eh, not that useful'''
849
850
851         EXAMPLE_SET_UNUSED = EXAMPLE_SET - EXAMPLE_SET_USED
852         if EXAMPLE_SET_UNUSED:
853             print("\nUnused examples found in '%s'..." % path_examples)
854             for f in EXAMPLE_SET_UNUSED:
855                 print("    %s.py" % f)
856             print("  %d total\n" % len(EXAMPLE_SET_UNUSED))
857
858     import sys
859     sys.exit()
860
861 if __name__ == '__main__':
862     main()