svn merge -r40197:40311 ^/trunk/blender
[blender.git] / doc / python_api / 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 # <pep8 compliant>
22
23 script_help_msg = '''
24 Usage:
25
26 For HTML generation
27 -------------------
28 - Run this script from blenders root path once you have compiled blender
29
30     ./blender.bin --background --python doc/python_api/sphinx_doc_gen.py
31
32   This will generate python files in doc/python_api/sphinx-in/
33   providing ./blender.bin is or links to the blender executable
34
35 - Generate html docs by running...
36
37     cd doc/python_api
38     sphinx-build sphinx-in sphinx-out
39
40   This requires sphinx 1.0.7 to be installed.
41
42 For PDF generation
43 ------------------
44 - After you have built doc/python_api/sphinx-in (see above), run:
45
46     sphinx-build -b latex doc/python_api/sphinx-in doc/python_api/sphinx-out
47     cd doc/python_api/sphinx-out
48     make
49 '''
50
51 # Check we're running in blender
52 if __import__("sys").modules.get("bpy") is None:
53     print("\nError, this script must run from inside blender2.5")
54     print(script_help_msg)
55
56     import sys
57     sys.exit()
58
59
60 # Switch for quick testing
61 if 1:
62     # full build
63     EXCLUDE_INFO_DOCS = False
64     EXCLUDE_MODULES = ()
65     FILTER_BPY_TYPES = None
66     FILTER_BPY_OPS = None
67
68 else:
69     EXCLUDE_INFO_DOCS = False
70     # for testing so doc-builds dont take so long.
71     EXCLUDE_MODULES = (
72         "bpy.context",
73         "bpy.app",
74         "bpy.path",
75         "bpy.data",
76         "bpy.props",
77         "bpy.utils",
78         "bpy.context",
79         # "bpy.types",  # supports filtering
80         "bpy.ops",  # supports filtering
81         "bpy_extras",
82         "bge",
83         "aud",
84         "bgl",
85         "blf",
86         "mathutils",
87         "mathutils.geometry",
88     )
89
90     FILTER_BPY_TYPES = ("bpy_struct", "Operator", "ID")  # allow
91     FILTER_BPY_OPS = ("import.scene", )  # allow
92
93     # for quick rebuilds
94     """
95 rm -rf /b/doc/python_api/sphinx-* && \
96 ./blender.bin --background --factory-startup --python  doc/python_api/sphinx_doc_gen.py && \
97 sphinx-build doc/python_api/sphinx-in doc/python_api/sphinx-out
98
99     """
100
101 # extra info, not api reference docs
102 # stored in ./rst/info/
103 INFO_DOCS = (
104     ("info_quickstart.rst", "Blender/Python Quickstart: new to blender/scripting and want to get you're feet wet?"),
105     ("info_overview.rst", "Blender/Python API Overview: a more complete explanation of python integration"),
106     ("info_best_practice.rst", "Best Practice: Conventions to follow for writing good scripts"),
107     ("info_tips_and_tricks.rst", "Tips and Tricks: Hints to help you while writeing scripts for blender"),
108     ("info_gotcha.rst", "Gotcha's: some of the problems you may come up against when writing scripts"),
109     )
110
111 # import rpdb2; rpdb2.start_embedded_debugger('test')
112
113 import os
114 import inspect
115 import bpy
116 import rna_info
117
118 # lame, python wont give some access
119 ClassMethodDescriptorType = type(dict.__dict__['fromkeys'])
120 MethodDescriptorType = type(dict.get)
121 GetSetDescriptorType = type(int.real)
122
123 EXAMPLE_SET = set()
124 EXAMPLE_SET_USED = set()
125
126 _BPY_STRUCT_FAKE = "bpy_struct"
127 _BPY_PROP_COLLECTION_FAKE = "bpy_prop_collection"
128 _BPY_FULL_REBUILD = False
129
130 if _BPY_PROP_COLLECTION_FAKE:
131     _BPY_PROP_COLLECTION_ID = ":class:`%s`" % _BPY_PROP_COLLECTION_FAKE
132 else:
133     _BPY_PROP_COLLECTION_ID = "collection"
134
135
136 def undocumented_message(module_name, type_name, identifier):
137     if str(type_name).startswith('<module'):
138         preloadtitle = '%s.%s' % (module_name, identifier)
139     else:
140         preloadtitle = '%s.%s.%s' % (module_name, type_name, identifier)
141     message = "Undocumented (`contribute "\
142         "<http://wiki.blender.org/index.php/Dev:2.5/Py/API/Documentation/Contribute"\
143         "?action=edit&section=new&preload=Dev:2.5/Py/API/Documentation/Contribute/Howto-message"\
144         "&preloadtitle=%s>`_)\n\n" % preloadtitle
145     return message
146
147
148 def range_str(val):
149     '''
150     Converts values to strings for the range directive.
151     (unused function it seems)
152     '''
153     if val < -10000000:
154         return '-inf'
155     elif val > 10000000:
156         return 'inf'
157     elif type(val) == float:
158         return '%g' % val
159     else:
160         return str(val)
161
162
163 def example_extract_docstring(filepath):
164     file = open(filepath, 'r')
165     line = file.readline()
166     line_no = 0
167     text = []
168     if line.startswith('"""'):  # assume nothing here
169         line_no += 1
170     else:
171         file.close()
172         return "", 0
173
174     for line in file.readlines():
175         line_no += 1
176         if line.startswith('"""'):
177             break
178         else:
179             text.append(line.rstrip())
180
181     line_no += 1
182     file.close()
183     return "\n".join(text), line_no
184
185
186 def write_title(fw, text, heading_char):
187     fw("%s\n%s\n\n" % (text, len(text) * heading_char))
188
189
190 def write_example_ref(ident, fw, example_id, ext="py"):
191     if example_id in EXAMPLE_SET:
192
193         # extract the comment
194         filepath = "../examples/%s.%s" % (example_id, ext)
195         filepath_full = os.path.join(os.path.dirname(fw.__self__.name), filepath)
196
197         text, line_no = example_extract_docstring(filepath_full)
198
199         for line in text.split("\n"):
200             fw("%s\n" % (ident + line).rstrip())
201         fw("\n")
202
203         fw("%s.. literalinclude:: %s\n" % (ident, filepath))
204         if line_no > 0:
205             fw("%s   :lines: %d-\n" % (ident, line_no))
206         fw("\n")
207         EXAMPLE_SET_USED.add(example_id)
208     else:
209         if bpy.app.debug:
210             print("\tskipping example:", example_id)
211
212     # Support for numbered files bpy.types.Operator -> bpy.types.Operator.1.py
213     i = 1
214     while True:
215         example_id_num = "%s.%d" % (example_id, i)
216         if example_id_num in EXAMPLE_SET:
217             write_example_ref(ident, fw, example_id_num, ext)
218             i += 1
219         else:
220             break
221
222
223 def write_indented_lines(ident, fn, text, strip=True):
224     '''
225     Apply same indentation to all lines in a multilines text.
226     '''
227     if text is None:
228         return
229
230     lines = text.split("\n")
231
232     # strip empty lines from the start/end
233     while lines and not lines[0].strip():
234         del lines[0]
235     while lines and not lines[-1].strip():
236         del lines[-1]
237
238     if strip:
239         ident_strip = 1000
240         for l in lines:
241             if l.strip():
242                 ident_strip = min(ident_strip, len(l) - len(l.lstrip()))
243         for l in lines:
244             fn(ident + l[ident_strip:] + "\n")
245     else:
246         for l in lines:
247             fn(ident + l + "\n")
248
249
250 def pymethod2sphinx(ident, fw, identifier, py_func):
251     '''
252     class method to sphinx
253     '''
254     arg_str = inspect.formatargspec(*inspect.getargspec(py_func))
255     if arg_str.startswith("(self, "):
256         arg_str = "(" + arg_str[7:]
257         func_type = "method"
258     elif arg_str.startswith("(cls, "):
259         arg_str = "(" + arg_str[6:]
260         func_type = "classmethod"
261     else:
262         func_type = "staticmethod"
263
264     fw(ident + ".. %s:: %s%s\n\n" % (func_type, identifier, arg_str))
265     if py_func.__doc__:
266         write_indented_lines(ident + "   ", fw, py_func.__doc__)
267         fw("\n")
268
269
270 def pyfunc2sphinx(ident, fw, identifier, py_func, is_class=True):
271     '''
272     function or class method to sphinx
273     '''
274     arg_str = inspect.formatargspec(*inspect.getargspec(py_func))
275
276     if not is_class:
277         func_type = "function"
278
279         # ther rest are class methods
280     elif arg_str.startswith("(self, "):
281         arg_str = "(" + arg_str[7:]
282         func_type = "method"
283     elif arg_str.startswith("(cls, "):
284         arg_str = "(" + arg_str[6:]
285         func_type = "classmethod"
286     else:
287         func_type = "staticmethod"
288
289     fw(ident + ".. %s:: %s%s\n\n" % (func_type, identifier, arg_str))
290     if py_func.__doc__:
291         write_indented_lines(ident + "   ", fw, py_func.__doc__)
292         fw("\n")
293
294
295 def py_descr2sphinx(ident, fw, descr, module_name, type_name, identifier):
296     if identifier.startswith("_"):
297         return
298
299     doc = descr.__doc__
300     if not doc:
301         doc = undocumented_message(module_name, type_name, identifier)
302
303     if type(descr) == GetSetDescriptorType:
304         fw(ident + ".. attribute:: %s\n\n" % identifier)
305         write_indented_lines(ident + "   ", fw, doc, False)
306         fw("\n")
307     elif type(descr) in (MethodDescriptorType, ClassMethodDescriptorType):
308         write_indented_lines(ident, fw, doc, False)
309         fw("\n")
310     else:
311         raise TypeError("type was not GetSetDescriptorType, MethodDescriptorType or ClassMethodDescriptorType")
312
313     write_example_ref(ident + "   ", fw, module_name + "." + type_name + "." + identifier)
314     fw("\n")
315
316
317 def py_c_func2sphinx(ident, fw, module_name, type_name, identifier, py_func, is_class=True):
318     '''
319     c defined function to sphinx.
320     '''
321
322     # dump the docstring, assume its formatted correctly
323     if py_func.__doc__:
324         write_indented_lines(ident, fw, py_func.__doc__, False)
325         fw("\n")
326     else:
327         fw(ident + ".. function:: %s()\n\n" % identifier)
328         fw(ident + "   " + undocumented_message(module_name, type_name, identifier))
329
330     if is_class:
331         write_example_ref(ident + "   ", fw, module_name + "." + type_name + "." + identifier)
332     else:
333         write_example_ref(ident + "   ", fw, module_name + "." + identifier)
334
335     fw("\n")
336
337
338 def pyprop2sphinx(ident, fw, identifier, py_prop):
339     '''
340     python property to sphinx
341     '''
342     # readonly properties use "data" directive, variables use "attribute" directive
343     if py_prop.fset is None:
344         fw(ident + ".. data:: %s\n\n" % identifier)
345     else:
346         fw(ident + ".. attribute:: %s\n\n" % identifier)
347     write_indented_lines(ident + "   ", fw, py_prop.__doc__)
348     if py_prop.fset is None:
349         fw(ident + "   (readonly)\n\n")
350
351
352 def pymodule2sphinx(BASEPATH, module_name, module, title):
353     import types
354     attribute_set = set()
355     filepath = os.path.join(BASEPATH, module_name + ".rst")
356
357     module_all = getattr(module, "__all__", None)
358     module_dir = sorted(dir(module))
359
360     if module_all:
361         module_dir = module_all
362
363     file = open(filepath, "w")
364
365     fw = file.write
366
367     write_title(fw, "%s (%s)" % (title, module_name), "=")
368
369     fw(".. module:: %s\n\n" % module_name)
370
371     if module.__doc__:
372         # Note, may contain sphinx syntax, dont mangle!
373         fw(module.__doc__.strip())
374         fw("\n\n")
375
376     write_example_ref("", fw, module_name)
377
378     # write submodules
379     # we could also scan files but this ensures __all__ is used correctly
380     if module_all is not None:
381         submod_name = None
382         submod = None
383         submod_ls = []
384         for submod_name in module_all:
385             ns = {}
386             exec_str = "from %s import %s as submod" % (module.__name__, submod_name)
387             exec(exec_str, ns, ns)
388             submod = ns["submod"]
389             if type(submod) == types.ModuleType:
390                 submod_ls.append((submod_name, submod))
391
392         del submod_name
393         del submod
394
395         if submod_ls:
396             fw(".. toctree::\n")
397             fw("   :maxdepth: 1\n\n")
398
399             for submod_name, submod in submod_ls:
400                 submod_name_full = "%s.%s" % (module_name, submod_name)
401                 fw("   %s.rst\n\n" % submod_name_full)
402
403                 pymodule2sphinx(BASEPATH, submod_name_full, submod, "%s submodule" % module_name)
404         del submod_ls
405     # done writing submodules!
406
407     # write members of the module
408     # only tested with PyStructs which are not exactly modules
409     for key, descr in sorted(type(module).__dict__.items()):
410         if key.startswith("__"):
411             continue
412         # naughty, we also add getset's into PyStructs, this is not typical py but also not incorrect.
413         if type(descr) == types.GetSetDescriptorType:  # 'bpy_app_type' name is only used for examples and messages
414             py_descr2sphinx("", fw, descr, module_name, "bpy_app_type", key)
415             attribute_set.add(key)
416     for key, descr in sorted(type(module).__dict__.items()):
417         if key.startswith("__"):
418             continue
419
420         if type(descr) == types.MemberDescriptorType:
421             if descr.__doc__:
422                 fw(".. data:: %s\n\n" % key)
423                 write_indented_lines("   ", fw, descr.__doc__, False)
424                 fw("\n")
425                 attribute_set.add(key)
426
427     del key, descr
428
429     classes = []
430     submodules = []
431
432     for attribute in module_dir:
433         if not attribute.startswith("_"):
434             if attribute in attribute_set:
435                 continue
436
437             if attribute.startswith("n_"):  # annoying exception, needed for bpy.app
438                 continue
439
440             value = getattr(module, attribute)
441
442             value_type = type(value)
443
444             if value_type == types.FunctionType:
445                 pyfunc2sphinx("", fw, attribute, value, is_class=False)
446             elif value_type in (types.BuiltinMethodType, types.BuiltinFunctionType):  # both the same at the moment but to be future proof
447                 # note: can't get args from these, so dump the string as is
448                 # this means any module used like this must have fully formatted docstrings.
449                 py_c_func2sphinx("", fw, module_name, None, attribute, value, is_class=False)
450             elif value_type == type:
451                 classes.append((attribute, value))
452             elif issubclass(value_type, types.ModuleType):
453                 submodules.append((attribute, value))
454             elif value_type in (bool, int, float, str, tuple):
455                 # constant, not much fun we can do here except to list it.
456                 # TODO, figure out some way to document these!
457                 fw(".. data:: %s\n\n" % attribute)
458                 write_indented_lines("   ", fw, "constant value %s" % repr(value), False)
459                 fw("\n")
460             else:
461                 print("\tnot documenting %s.%s of %r type" % (module_name, attribute, value_type.__name__))
462                 continue
463
464             attribute_set.add(attribute)
465             # TODO, more types...
466
467     # TODO, bpy_extras does this already, mathutils not.
468     """
469     if submodules:
470         fw("\n"
471            "**********\n"
472            "Submodules\n"
473            "**********\n"
474            "\n"
475            )
476         for attribute, submod in submodules:
477             fw("* :mod:`%s.%s`\n" % (module_name, attribute))
478         fw("\n")
479     """
480
481     # write collected classes now
482     for (type_name, value) in classes:
483         # May need to be its own function
484         fw(".. class:: %s\n\n" % type_name)
485         if value.__doc__:
486             write_indented_lines("   ", fw, value.__doc__, False)
487             fw("\n")
488         write_example_ref("   ", fw, module_name + "." + type_name)
489
490         descr_items = [(key, descr) for key, descr in sorted(value.__dict__.items()) if not key.startswith("__")]
491
492         for key, descr in descr_items:
493             if type(descr) == ClassMethodDescriptorType:
494                 py_descr2sphinx("   ", fw, descr, module_name, type_name, key)
495
496         for key, descr in descr_items:
497             if type(descr) == MethodDescriptorType:
498                 py_descr2sphinx("   ", fw, descr, module_name, type_name, key)
499
500         for key, descr in descr_items:
501             if type(descr) == GetSetDescriptorType:
502                 py_descr2sphinx("   ", fw, descr, module_name, type_name, key)
503
504         fw("\n\n")
505
506     file.close()
507
508
509 def pycontext2sphinx(BASEPATH):
510     # Only use once. very irregular
511
512     filepath = os.path.join(BASEPATH, "bpy.context.rst")
513     file = open(filepath, "w")
514     fw = file.write
515     fw("Context Access (bpy.context)\n")
516     fw("============================\n\n")
517     fw(".. module:: bpy.context\n")
518     fw("\n")
519     fw("The context members available depend on the area of blender which is currently being accessed.\n")
520     fw("\n")
521     fw("Note that all context values are readonly, but may be modified through the data api or by running operators\n\n")
522
523     # nasty, get strings directly from blender because there is no other way to get it
524     import ctypes
525
526     context_strings = (
527         "screen_context_dir",
528         "view3d_context_dir",
529         "buttons_context_dir",
530         "image_context_dir",
531         "node_context_dir",
532         "text_context_dir",
533     )
534
535     # Changes in blender will force errors here
536     type_map = {
537         "active_base": ("ObjectBase", False),
538         "active_bone": ("Bone", False),
539         "active_object": ("Object", False),
540         "active_pose_bone": ("PoseBone", False),
541         "armature": ("Armature", False),
542         "bone": ("Bone", False),
543         "brush": ("Brush", False),
544         "camera": ("Camera", False),
545         "cloth": ("ClothModifier", False),
546         "collision": ("CollisionModifier", False),
547         "curve": ("Curve", False),
548         "edit_bone": ("EditBone", False),
549         "edit_image": ("Image", False),
550         "edit_object": ("Object", False),
551         "edit_text": ("Text", False),
552         "editable_bones": ("EditBone", True),
553         "fluid": ("FluidSimulationModifier", False),
554         "image_paint_object": ("Object", False),
555         "lamp": ("Lamp", False),
556         "lattice": ("Lattice", False),
557         "material": ("Material", False),
558         "material_slot": ("MaterialSlot", False),
559         "mesh": ("Mesh", False),
560         "meta_ball": ("MetaBall", False),
561         "object": ("Object", False),
562         "particle_edit_object": ("Object", False),
563         "particle_system": ("ParticleSystem", False),
564         "particle_system_editable": ("ParticleSystem", False),
565         "pose_bone": ("PoseBone", False),
566         "scene": ("Scene", False),
567         "sculpt_object": ("Object", False),
568         "selectable_bases": ("ObjectBase", True),
569         "selectable_objects": ("Object", True),
570         "selected_bases": ("ObjectBase", True),
571         "selected_bones": ("Bone", True),
572         "selected_editable_bases": ("ObjectBase", True),
573         "selected_editable_bones": ("Bone", True),
574         "selected_editable_objects": ("Object", True),
575         "selected_editable_sequences": ("Sequence", True),
576         "selected_nodes": ("Node", True),
577         "selected_objects": ("Object", True),
578         "selected_pose_bones": ("PoseBone", True),
579         "selected_sequences": ("Sequence", True),
580         "sequences": ("Sequence", True),
581         "smoke": ("SmokeModifier", False),
582         "soft_body": ("SoftBodyModifier", False),
583         "speaker": ("Speaker", False),
584         "texture": ("Texture", False),
585         "texture_slot": ("MaterialTextureSlot", False),
586         "vertex_paint_object": ("Object", False),
587         "visible_bases": ("ObjectBase", True),
588         "visible_bones": ("Object", True),
589         "visible_objects": ("Object", True),
590         "visible_pose_bones": ("PoseBone", True),
591         "weight_paint_object": ("Object", False),
592         "world": ("World", False),
593     }
594
595     unique = set()
596     blend_cdll = ctypes.CDLL("")
597     for ctx_str in context_strings:
598         subsection = "%s Context" % ctx_str.split("_")[0].title()
599         fw("\n%s\n%s\n\n" % (subsection, (len(subsection) * '-')))
600
601         attr = ctypes.addressof(getattr(blend_cdll, ctx_str))
602         c_char_p_p = ctypes.POINTER(ctypes.c_char_p)
603         char_array = c_char_p_p.from_address(attr)
604         i = 0
605         while char_array[i] is not None:
606             member = ctypes.string_at(char_array[i]).decode()
607             fw(".. data:: %s\n\n" % member)
608             member_type, is_seq = type_map[member]
609             fw("   :type: %s :class:`bpy.types.%s`\n\n" % ("sequence of " if is_seq else "", member_type))
610             unique.add(member)
611             i += 1
612
613     # generate typemap...
614     # for member in sorted(unique):
615     #     print('        "%s": ("", False),' % member)
616     if len(type_map) > len(unique):
617         raise Exception("Some types are not used: %s" % str([member for member in type_map if member not in unique]))
618     else:
619         pass  # will have raised an error above
620
621     file.close()
622
623
624 def pyrna_enum2sphinx(prop, use_empty_descriptions=False):
625     """ write a bullet point list of enum + descrptons
626     """
627
628     if use_empty_descriptions:
629         ok = True
630     else:
631         ok = False
632         for identifier, name, description in prop.enum_items:
633             if description:
634                 ok = True
635                 break
636
637     if ok:
638         return "".join(["* ``%s`` %s.\n" %
639                         (identifier,
640                          ", ".join(val for val in (name, description) if val),
641                          )
642                         for identifier, name, description in prop.enum_items
643                         ])
644     else:
645         return ""
646
647
648 def pyrna2sphinx(BASEPATH):
649     """ bpy.types and bpy.ops
650     """
651     structs, funcs, ops, props = rna_info.BuildRNAInfo()
652     if FILTER_BPY_TYPES is not None:
653         structs = {k: v for k, v in structs.items() if k[1] in FILTER_BPY_TYPES}
654
655     if FILTER_BPY_OPS is not None:
656         ops = {k: v for k, v in ops.items() if v.module_name in FILTER_BPY_OPS}
657
658     def write_param(ident, fw, prop, is_return=False):
659         if is_return:
660             id_name = "return"
661             id_type = "rtype"
662             kwargs = {"as_ret": True}
663             identifier = ""
664         else:
665             id_name = "arg"
666             id_type = "type"
667             kwargs = {"as_arg": True}
668             identifier = " %s" % prop.identifier
669
670         kwargs["class_fmt"] = ":class:`%s`"
671
672         kwargs["collection_id"] = _BPY_PROP_COLLECTION_ID
673
674         type_descr = prop.get_type_description(**kwargs)
675
676         enum_text = pyrna_enum2sphinx(prop)
677
678         if prop.name or prop.description or enum_text:
679             fw(ident + ":%s%s:\n\n" % (id_name, identifier))
680
681             if prop.name or prop.description:
682                 fw(ident + "   " + ", ".join(val for val in (prop.name, prop.description) if val) + "\n\n")
683
684             # special exception, cant use genric code here for enums
685             if enum_text:
686                 write_indented_lines(ident + "   ", fw, enum_text)
687                 fw("\n")
688             del enum_text
689             # end enum exception
690
691         fw(ident + ":%s%s: %s\n" % (id_type, identifier, type_descr))
692
693     def write_struct(struct):
694         #if not struct.identifier.startswith("Sc") and not struct.identifier.startswith("I"):
695         #    return
696
697         #if not struct.identifier == "Object":
698         #    return
699
700         filepath = os.path.join(BASEPATH, "bpy.types.%s.rst" % struct.identifier)
701         file = open(filepath, "w")
702         fw = file.write
703
704         base_id = getattr(struct.base, "identifier", "")
705
706         if _BPY_STRUCT_FAKE:
707             if not base_id:
708                 base_id = _BPY_STRUCT_FAKE
709
710         if base_id:
711             title = "%s(%s)" % (struct.identifier, base_id)
712         else:
713             title = struct.identifier
714
715         write_title(fw, title, "=")
716
717         fw(".. module:: bpy.types\n\n")
718
719         # docs first?, ok
720         write_example_ref("", fw, "bpy.types.%s" % struct.identifier)
721
722         base_ids = [base.identifier for base in struct.get_bases()]
723
724         if _BPY_STRUCT_FAKE:
725             base_ids.append(_BPY_STRUCT_FAKE)
726
727         base_ids.reverse()
728
729         if base_ids:
730             if len(base_ids) > 1:
731                 fw("base classes --- ")
732             else:
733                 fw("base class --- ")
734
735             fw(", ".join((":class:`%s`" % base_id) for base_id in base_ids))
736             fw("\n\n")
737
738         subclass_ids = [s.identifier for s in structs.values() if s.base is struct if not rna_info.rna_id_ignore(s.identifier)]
739         if subclass_ids:
740             fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in subclass_ids) + "\n\n")
741
742         base_id = getattr(struct.base, "identifier", "")
743
744         if _BPY_STRUCT_FAKE:
745             if not base_id:
746                 base_id = _BPY_STRUCT_FAKE
747
748         if base_id:
749             fw(".. class:: %s(%s)\n\n" % (struct.identifier, base_id))
750         else:
751             fw(".. class:: %s\n\n" % struct.identifier)
752
753         fw("   %s\n\n" % struct.description)
754
755         # properties sorted in alphabetical order
756         sorted_struct_properties = struct.properties[:]
757         sorted_struct_properties.sort(key=lambda prop: prop.identifier)
758
759         for prop in sorted_struct_properties:
760             type_descr = prop.get_type_description(class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID)
761             # readonly properties use "data" directive, variables properties use "attribute" directive
762             if 'readonly' in type_descr:
763                 fw("   .. data:: %s\n\n" % prop.identifier)
764             else:
765                 fw("   .. attribute:: %s\n\n" % prop.identifier)
766             if prop.description:
767                 fw("      %s\n\n" % prop.description)
768             
769             # special exception, cant use genric code here for enums
770             if prop.type == "enum":
771                 enum_text = pyrna_enum2sphinx(prop)
772                 if enum_text:
773                     write_indented_lines("      ", fw, enum_text)
774                     fw("\n")
775                 del enum_text
776             # end enum exception
777
778             fw("      :type: %s\n\n" % type_descr)
779
780         # python attributes
781         py_properties = struct.get_py_properties()
782         py_prop = None
783         for identifier, py_prop in py_properties:
784             pyprop2sphinx("   ", fw, identifier, py_prop)
785         del py_properties, py_prop
786
787         for func in struct.functions:
788             args_str = ", ".join(prop.get_arg_default(force=False) for prop in func.args)
789
790             fw("   .. %s:: %s(%s)\n\n" % ("classmethod" if func.is_classmethod else "method", func.identifier, args_str))
791             fw("      %s\n\n" % func.description)
792
793             for prop in func.args:
794                 write_param("      ", fw, prop)
795
796             if len(func.return_values) == 1:
797                 write_param("      ", fw, func.return_values[0], is_return=True)
798             elif func.return_values:  # multiple return values
799                 fw("      :return (%s):\n" % ", ".join(prop.identifier for prop in func.return_values))
800                 for prop in func.return_values:
801                     # TODO, pyrna_enum2sphinx for multiple return values... actually dont think we even use this but still!!!
802                     type_descr = prop.get_type_description(as_ret=True, class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID)
803                     descr = prop.description
804                     if not descr:
805                         descr = prop.name
806                     fw("         `%s`, %s, %s\n\n" % (prop.identifier, descr, type_descr))
807
808             write_example_ref("      ", fw, "bpy.types." + struct.identifier + "." + func.identifier)
809
810             fw("\n")
811
812         # python methods
813         py_funcs = struct.get_py_functions()
814         py_func = None
815
816         for identifier, py_func in py_funcs:
817             pyfunc2sphinx("   ", fw, identifier, py_func, is_class=True)
818         del py_funcs, py_func
819
820         py_funcs = struct.get_py_c_functions()
821         py_func = None
822
823         for identifier, py_func in py_funcs:
824             py_c_func2sphinx("   ", fw, "bpy.types", struct.identifier, identifier, py_func, is_class=True)
825
826         lines = []
827
828         if struct.base or _BPY_STRUCT_FAKE:
829             bases = list(reversed(struct.get_bases()))
830
831             # props
832             lines[:] = []
833
834             if _BPY_STRUCT_FAKE:
835                 descr_items = [(key, descr) for key, descr in sorted(bpy.types.Struct.__bases__[0].__dict__.items()) if not key.startswith("__")]
836
837             if _BPY_STRUCT_FAKE:
838                 for key, descr in descr_items:
839                     if type(descr) == GetSetDescriptorType:
840                         lines.append("   * :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key))
841
842             for base in bases:
843                 for prop in base.properties:
844                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, prop.identifier))
845
846                 for identifier, py_prop in base.get_py_properties():
847                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, identifier))
848
849                 for identifier, py_prop in base.get_py_properties():
850                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, identifier))
851
852             if lines:
853                 fw(".. rubric:: Inherited Properties\n\n")
854
855                 fw(".. hlist::\n")
856                 fw("   :columns: 2\n\n")
857
858                 for line in lines:
859                     fw(line)
860                 fw("\n")
861
862             # funcs
863             lines[:] = []
864
865             if _BPY_STRUCT_FAKE:
866                 for key, descr in descr_items:
867                     if type(descr) == MethodDescriptorType:
868                         lines.append("   * :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key))
869
870             for base in bases:
871                 for func in base.functions:
872                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, func.identifier))
873                 for identifier, py_func in base.get_py_functions():
874                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, identifier))
875
876             if lines:
877                 fw(".. rubric:: Inherited Functions\n\n")
878
879                 fw(".. hlist::\n")
880                 fw("   :columns: 2\n\n")
881
882                 for line in lines:
883                     fw(line)
884                 fw("\n")
885
886             lines[:] = []
887
888         if struct.references:
889             # use this otherwise it gets in the index for a normal heading.
890             fw(".. rubric:: References\n\n")
891
892             fw(".. hlist::\n")
893             fw("   :columns: 2\n\n")
894
895             for ref in struct.references:
896                 ref_split = ref.split(".")
897                 if len(ref_split) > 2:
898                     ref = ref_split[-2] + "." + ref_split[-1]
899                 fw("   * :class:`%s`\n" % ref)
900             fw("\n")
901
902         # docs last?, disable for now
903         # write_example_ref("", fw, "bpy.types.%s" % struct.identifier)
904         file.close()
905
906     if "bpy.types" not in EXCLUDE_MODULES:
907         for struct in structs.values():
908             # TODO, rna_info should filter these out!
909             if "_OT_" in struct.identifier:
910                 continue
911             write_struct(struct)
912
913         def fake_bpy_type(class_value, class_name, descr_str, use_subclasses=True):
914             filepath = os.path.join(BASEPATH, "bpy.types.%s.rst" % class_name)
915             file = open(filepath, "w")
916             fw = file.write
917
918             write_title(fw, class_name, "=")
919
920             fw(".. module:: bpy.types\n")
921             fw("\n")
922
923             if use_subclasses:
924                 subclass_ids = [s.identifier for s in structs.values() if s.base is None if not rna_info.rna_id_ignore(s.identifier)]
925                 if subclass_ids:
926                     fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in sorted(subclass_ids)) + "\n\n")
927
928             fw(".. class:: %s\n\n" % class_name)
929             fw("   %s\n\n" % descr_str)
930             fw("   .. note::\n\n")
931             fw("      Note that bpy.types.%s is not actually available from within blender, it only exists for the purpose of documentation.\n\n" % class_name)
932
933             descr_items = [(key, descr) for key, descr in sorted(class_value.__dict__.items()) if not key.startswith("__")]
934
935             for key, descr in descr_items:
936                 if type(descr) == MethodDescriptorType:  # GetSetDescriptorType, GetSetDescriptorType's are not documented yet
937                     py_descr2sphinx("   ", fw, descr, "bpy.types", class_name, key)
938
939             for key, descr in descr_items:
940                 if type(descr) == GetSetDescriptorType:
941                     py_descr2sphinx("   ", fw, descr, "bpy.types", class_name, key)
942             file.close()
943
944         # write fake classes
945         if _BPY_STRUCT_FAKE:
946             class_value = bpy.types.Struct.__bases__[0]
947             fake_bpy_type(class_value, _BPY_STRUCT_FAKE, "built-in base class for all classes in bpy.types.", use_subclasses=True)
948
949         if _BPY_PROP_COLLECTION_FAKE:
950             class_value = bpy.data.objects.__class__
951             fake_bpy_type(class_value, _BPY_PROP_COLLECTION_FAKE, "built-in class used for all collections.", use_subclasses=False)
952
953     # operators
954     def write_ops():
955         API_BASEURL = "http://svn.blender.org/svnroot/bf-blender/trunk/blender/release/scripts"
956         API_BASEURL_ADDON = "http://svn.blender.org/svnroot/bf-extensions/trunk/py/scripts"
957         API_BASEURL_ADDON_CONTRIB = "http://svn.blender.org/svnroot/bf-extensions/contrib/py/scripts"
958
959         op_modules = {}
960         for op in ops.values():
961             op_modules.setdefault(op.module_name, []).append(op)
962         del op
963
964         for op_module_name, ops_mod in op_modules.items():
965             filepath = os.path.join(BASEPATH, "bpy.ops.%s.rst" % op_module_name)
966             file = open(filepath, "w")
967             fw = file.write
968
969             title = "%s Operators" % op_module_name.replace("_", " ").title()
970
971             write_title(fw, title, "=")
972
973             fw(".. module:: bpy.ops.%s\n\n" % op_module_name)
974
975             ops_mod.sort(key=lambda op: op.func_name)
976
977             for op in ops_mod:
978                 args_str = ", ".join(prop.get_arg_default(force=True) for prop in op.args)
979                 fw(".. function:: %s(%s)\n\n" % (op.func_name, args_str))
980
981                 # if the description isn't valid, we output the standard warning
982                 # with a link to the wiki so that people can help
983                 if not op.description or op.description == "(undocumented operator)":
984                     operator_description = undocumented_message('bpy.ops', op.module_name, op.func_name)
985                 else:
986                     operator_description = op.description
987
988                 fw("   %s\n\n" % operator_description)
989                 for prop in op.args:
990                     write_param("   ", fw, prop)
991                 if op.args:
992                     fw("\n")
993
994                 location = op.get_location()
995                 if location != (None, None):
996                     if location[0].startswith("addons_contrib" + os.sep):
997                         url_base = API_BASEURL_ADDON_CONTRIB
998                     elif location[0].startswith("addons" + os.sep):
999                         url_base = API_BASEURL_ADDON
1000                     else:
1001                         url_base = API_BASEURL
1002
1003                     fw("   :file: `%s <%s/%s>`_:%d\n\n" % (location[0], url_base, location[0], location[1]))
1004
1005             file.close()
1006
1007     if "bpy.ops" not in EXCLUDE_MODULES:
1008         write_ops()
1009
1010
1011 def rna2sphinx(BASEPATH):
1012
1013     try:
1014         os.mkdir(BASEPATH)
1015     except:
1016         pass
1017
1018     # conf.py - empty for now
1019     filepath = os.path.join(BASEPATH, "conf.py")
1020     file = open(filepath, "w")
1021     fw = file.write
1022
1023     version_string = ".".join(str(v) for v in bpy.app.version)
1024     if bpy.app.build_revision != "Unknown":
1025         version_string = version_string + " r" + bpy.app.build_revision
1026
1027     version_string_fp = "_".join(str(v) for v in bpy.app.version)
1028
1029     if bpy.app.version_cycle == "release":
1030         version_string_pdf = "%s%s_release" % ("_".join(str(v) for v in bpy.app.version[:2]), bpy.app.version_char)
1031     else:
1032         version_string_pdf = version_string_fp
1033
1034     fw("project = 'Blender'\n")
1035     # fw("master_doc = 'index'\n")
1036     fw("copyright = u'Blender Foundation'\n")
1037     fw("version = '%s - API'\n" % version_string)
1038     fw("release = '%s - API'\n" % version_string)
1039     fw("html_theme = 'blender-org'\n")
1040     fw("html_theme_path = ['../']\n")
1041     fw("html_favicon = 'favicon.ico'\n")
1042     # not helpful since the source us generated, adds to upload size.
1043     fw("html_copy_source = False\n")
1044     fw("\n")
1045     # needed for latex, pdf gen
1046     fw("latex_documents = [ ('contents', 'contents.tex', 'Blender Index', 'Blender Foundation', 'manual'), ]\n")
1047     fw("latex_paper_size = 'a4paper'\n")
1048     file.close()
1049
1050     # main page needed for sphinx (index.html)
1051     filepath = os.path.join(BASEPATH, "contents.rst")
1052     file = open(filepath, "w")
1053     fw = file.write
1054
1055     fw("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n")
1056     fw(" Blender Documentation contents\n")
1057     fw("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n")
1058     fw("\n")
1059     fw("Welcome, this document is an API reference for Blender %s. built %s.\n" % (version_string, bpy.app.build_date))
1060     fw("\n")
1061     fw("`A PDF version of this document is also available <blender_python_reference_%s.pdf>`_\n" % version_string_pdf)
1062
1063     fw("\n")
1064
1065     if not EXCLUDE_INFO_DOCS:
1066         fw("============================\n")
1067         fw("Blender/Python Documentation\n")
1068         fw("============================\n")
1069         fw("\n")
1070         fw("\n")
1071         fw(".. toctree::\n")
1072         fw("   :maxdepth: 1\n\n")
1073         for info, info_desc in INFO_DOCS:
1074             fw("   %s <%s>\n\n" % (info_desc, info))
1075         fw("\n")
1076
1077     fw("===================\n")
1078     fw("Application Modules\n")
1079     fw("===================\n")
1080     fw("\n")
1081     fw(".. toctree::\n")
1082     fw("   :maxdepth: 1\n\n")
1083     if "bpy.context" not in EXCLUDE_MODULES:
1084         fw("   bpy.context.rst\n\n")  # note: not actually a module
1085     if "bpy.data" not in EXCLUDE_MODULES:
1086         fw("   bpy.data.rst\n\n")  # note: not actually a module
1087     if "bpy.ops" not in EXCLUDE_MODULES:
1088         fw("   bpy.ops.rst\n\n")
1089     if "bpy.types" not in EXCLUDE_MODULES:
1090         fw("   bpy.types.rst\n\n")
1091
1092     # py modules
1093     if "bpy.utils" not in EXCLUDE_MODULES:
1094         fw("   bpy.utils.rst\n\n")
1095     if "bpy.path" not in EXCLUDE_MODULES:
1096         fw("   bpy.path.rst\n\n")
1097     if "bpy.app" not in EXCLUDE_MODULES:
1098         fw("   bpy.app.rst\n\n")
1099
1100     # C modules
1101     if "bpy.props" not in EXCLUDE_MODULES:
1102         fw("   bpy.props.rst\n\n")
1103
1104     fw("==================\n")
1105     fw("Standalone Modules\n")
1106     fw("==================\n")
1107     fw("\n")
1108     fw(".. toctree::\n")
1109     fw("   :maxdepth: 1\n\n")
1110
1111     if "mathutils" not in EXCLUDE_MODULES:
1112         fw("   mathutils.rst\n\n")
1113     if "mathutils.geometry" not in EXCLUDE_MODULES:
1114         fw("   mathutils.geometry.rst\n\n")
1115     if "bgl" not in EXCLUDE_MODULES:
1116         fw("   bgl.rst\n\n")
1117     if "blf" not in EXCLUDE_MODULES:
1118         fw("   blf.rst\n\n")
1119     if "aud" not in EXCLUDE_MODULES:
1120         fw("   aud.rst\n\n")
1121     if "bpy_extras" not in EXCLUDE_MODULES:
1122         fw("   bpy_extras.rst\n\n")
1123
1124     # game engine
1125     if "bge" not in EXCLUDE_MODULES:
1126         fw("===================\n")
1127         fw("Game Engine Modules\n")
1128         fw("===================\n")
1129         fw("\n")
1130         fw(".. toctree::\n")
1131         fw("   :maxdepth: 1\n\n")
1132         fw("   bge.types.rst\n\n")
1133         fw("   bge.logic.rst\n\n")
1134         fw("   bge.render.rst\n\n")
1135         fw("   bge.texture.rst\n\n")
1136         fw("   bge.events.rst\n\n")
1137         fw("   bge.constraints.rst\n\n")
1138
1139     # rna generated change log
1140     fw("========\n")
1141     fw("API Info\n")
1142     fw("========\n")
1143     fw("\n")
1144     fw(".. toctree::\n")
1145     fw("   :maxdepth: 1\n\n")
1146     fw("   change_log.rst\n\n")
1147
1148     fw("\n")
1149     fw("\n")
1150     fw(".. note:: The Blender Python API has areas which are still in development.\n")
1151     fw("   \n")
1152     fw("   The following areas are subject to change.\n")
1153     fw("      * operator behavior, names and arguments\n")
1154     fw("      * mesh creation and editing functions\n")
1155     fw("   \n")
1156     fw("   These parts of the API are relatively stable and are unlikely to change significantly\n")
1157     fw("      * data API, access to attributes of blender data such as mesh verts, material color, timeline frames and scene objects\n")
1158     fw("      * user interface functions for defining buttons, creation of menus, headers, panels\n")
1159     fw("      * render engine integration\n")
1160     fw("      * modules: bgl, mathutils & game engine.\n")
1161     fw("\n")
1162
1163     file.close()
1164
1165     # internal modules
1166     if "bpy.ops" not in EXCLUDE_MODULES:
1167         filepath = os.path.join(BASEPATH, "bpy.ops.rst")
1168         file = open(filepath, "w")
1169         fw = file.write
1170         fw("Operators (bpy.ops)\n")
1171         fw("===================\n\n")
1172         write_example_ref("", fw, "bpy.ops")
1173         fw(".. toctree::\n")
1174         fw("   :glob:\n\n")
1175         fw("   bpy.ops.*\n\n")
1176         file.close()
1177
1178     if "bpy.types" not in EXCLUDE_MODULES:
1179         filepath = os.path.join(BASEPATH, "bpy.types.rst")
1180         file = open(filepath, "w")
1181         fw = file.write
1182         fw("Types (bpy.types)\n")
1183         fw("=================\n\n")
1184         fw(".. toctree::\n")
1185         fw("   :glob:\n\n")
1186         fw("   bpy.types.*\n\n")
1187         file.close()
1188
1189     if "bpy.data" not in EXCLUDE_MODULES:
1190         # not actually a module, only write this file so we
1191         # can reference in the TOC
1192         filepath = os.path.join(BASEPATH, "bpy.data.rst")
1193         file = open(filepath, "w")
1194         fw = file.write
1195         fw("Data Access (bpy.data)\n")
1196         fw("======================\n\n")
1197         fw(".. module:: bpy\n")
1198         fw("\n")
1199         fw("This module is used for all blender/python access.\n")
1200         fw("\n")
1201         fw(".. data:: data\n")
1202         fw("\n")
1203         fw("   Access to blenders internal data\n")
1204         fw("\n")
1205         fw("   :type: :class:`bpy.types.BlendData`\n")
1206         fw("\n")
1207         fw(".. literalinclude:: ../examples/bpy.data.py\n")
1208         file.close()
1209
1210     EXAMPLE_SET_USED.add("bpy.data")
1211
1212     module = None
1213
1214     if "bpy.context" not in EXCLUDE_MODULES:
1215         # one of a kind, context doc (uses ctypes to extract info!)
1216         pycontext2sphinx(BASEPATH)
1217
1218     # python modules
1219     if "bpy.utils" not in EXCLUDE_MODULES:
1220         from bpy import utils as module
1221         pymodule2sphinx(BASEPATH, "bpy.utils", module, "Utilities")
1222
1223     if "bpy.path" not in EXCLUDE_MODULES:
1224         from bpy import path as module
1225         pymodule2sphinx(BASEPATH, "bpy.path", module, "Path Utilities")
1226
1227     if "bpy_extras" not in EXCLUDE_MODULES:
1228         import bpy_extras as module
1229         pymodule2sphinx(BASEPATH, "bpy_extras", module, "Extra Utilities")
1230
1231     # C modules
1232     if "bpy.app" not in EXCLUDE_MODULES:
1233         from bpy import app as module
1234         pymodule2sphinx(BASEPATH, "bpy.app", module, "Application Data")
1235
1236     if "bpy.props" not in EXCLUDE_MODULES:
1237         from bpy import props as module
1238         pymodule2sphinx(BASEPATH, "bpy.props", module, "Property Definitions")
1239
1240     if "mathutils" not in EXCLUDE_MODULES:
1241         import mathutils as module
1242         pymodule2sphinx(BASEPATH, "mathutils", module, "Math Types & Utilities")
1243
1244     if "mathutils.geometry" not in EXCLUDE_MODULES:
1245         import mathutils.geometry as module
1246         pymodule2sphinx(BASEPATH, "mathutils.geometry", module, "Geometry Utilities")
1247
1248     if "blf" not in EXCLUDE_MODULES:
1249         import blf as module
1250         pymodule2sphinx(BASEPATH, "blf", module, "Font Drawing")
1251
1252     if "bgl" not in EXCLUDE_MODULES:
1253         #import bgl as module
1254         #pymodule2sphinx(BASEPATH, "bgl", module, "Blender OpenGl wrapper")
1255         #del module
1256         import shutil
1257         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bgl.rst"), BASEPATH)
1258
1259     if "aud" not in EXCLUDE_MODULES:
1260         import aud as module
1261         pymodule2sphinx(BASEPATH, "aud", module, "Audio System")
1262     del module
1263
1264     ## game engine
1265     import shutil
1266     # copy2 keeps time/date stamps
1267     if "bge" not in EXCLUDE_MODULES:
1268         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.types.rst"), BASEPATH)
1269         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.logic.rst"), BASEPATH)
1270         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.render.rst"), BASEPATH)
1271         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.texture.rst"), BASEPATH)
1272         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.events.rst"), BASEPATH)
1273         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.constraints.rst"), BASEPATH)
1274
1275     shutil.copy2(os.path.join(BASEPATH, "..", "rst", "change_log.rst"), BASEPATH)
1276
1277     if not EXCLUDE_INFO_DOCS:
1278         for info, info_desc in INFO_DOCS:
1279             shutil.copy2(os.path.join(BASEPATH, "..", "rst", info), BASEPATH)
1280
1281     if 0:
1282         filepath = os.path.join(BASEPATH, "bpy.rst")
1283         file = open(filepath, "w")
1284         fw = file.write
1285
1286         fw("\n")
1287
1288         title = ":mod:`bpy` --- Blender Python Module"
1289
1290         write_title(fw, title, "=")
1291
1292         fw(".. module:: bpy.types\n\n")
1293         file.close()
1294
1295     # bpy.types and bpy.ops
1296     pyrna2sphinx(BASEPATH)
1297
1298     file.close()
1299
1300
1301 def main():
1302     import shutil
1303
1304     script_dir = os.path.dirname(__file__)
1305     path_in = os.path.join(script_dir, "sphinx-in")
1306     path_out = os.path.join(script_dir, "sphinx-out")
1307     path_examples = os.path.join(script_dir, "examples")
1308     # only for partial updates
1309     path_in_tmp = path_in + "-tmp"
1310
1311     if not os.path.exists(path_in):
1312         os.mkdir(path_in)
1313
1314     for f in os.listdir(path_examples):
1315         if f.endswith(".py"):
1316             EXAMPLE_SET.add(os.path.splitext(f)[0])
1317
1318     # only for full updates
1319     if _BPY_FULL_REBUILD:
1320         shutil.rmtree(path_in, True)
1321         shutil.rmtree(path_out, True)
1322     else:
1323         # write here, then move
1324         shutil.rmtree(path_in_tmp, True)
1325
1326     rna2sphinx(path_in_tmp)
1327
1328     if not _BPY_FULL_REBUILD:
1329         import filecmp
1330
1331         # now move changed files from 'path_in_tmp' --> 'path_in'
1332         file_list_path_in = set(os.listdir(path_in))
1333         file_list_path_in_tmp = set(os.listdir(path_in_tmp))
1334
1335         # remove deprecated files that have been removed.
1336         for f in sorted(file_list_path_in):
1337             if f not in file_list_path_in_tmp:
1338                 print("\tdeprecated: %s" % f)
1339                 os.remove(os.path.join(path_in, f))
1340
1341         # freshen with new files.
1342         for f in sorted(file_list_path_in_tmp):
1343             f_from = os.path.join(path_in_tmp, f)
1344             f_to = os.path.join(path_in, f)
1345
1346             do_copy = True
1347             if f in file_list_path_in:
1348                 if filecmp.cmp(f_from, f_to):
1349                     do_copy = False
1350
1351             if do_copy:
1352                 print("\tupdating: %s" % f)
1353                 shutil.copy(f_from, f_to)
1354             '''else:
1355                 print("\tkeeping: %s" % f) # eh, not that useful'''
1356
1357     EXAMPLE_SET_UNUSED = EXAMPLE_SET - EXAMPLE_SET_USED
1358     if EXAMPLE_SET_UNUSED:
1359         print("\nUnused examples found in '%s'..." % path_examples)
1360         for f in EXAMPLE_SET_UNUSED:
1361             print("    %s.py" % f)
1362         print("  %d total\n" % len(EXAMPLE_SET_UNUSED))
1363
1364     import sys
1365     sys.exit()
1366
1367 if __name__ == '__main__':
1368     main()