I made multitude of fixes based on the comments provided online:
[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", "Panel", "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 pyrna2sphinx(BASEPATH):
625     """ bpy.types and bpy.ops
626     """
627     structs, funcs, ops, props = rna_info.BuildRNAInfo()
628     if FILTER_BPY_TYPES is not None:
629         structs = {k: v for k, v in structs.items() if k[1] in FILTER_BPY_TYPES}
630
631     if FILTER_BPY_OPS is not None:
632         ops = {k: v for k, v in ops.items() if v.module_name in FILTER_BPY_OPS}
633
634     def write_param(ident, fw, prop, is_return=False):
635         if is_return:
636             id_name = "return"
637             id_type = "rtype"
638             kwargs = {"as_ret": True}
639             identifier = ""
640         else:
641             id_name = "arg"
642             id_type = "type"
643             kwargs = {"as_arg": True}
644             identifier = " %s" % prop.identifier
645
646         kwargs["class_fmt"] = ":class:`%s`"
647
648         kwargs["collection_id"] = _BPY_PROP_COLLECTION_ID
649
650         type_descr = prop.get_type_description(**kwargs)
651         if prop.name or prop.description:
652             fw(ident + ":%s%s: %s\n" % (id_name, identifier, ", ".join(val for val in (prop.name, prop.description) if val)))
653         fw(ident + ":%s%s: %s\n" % (id_type, identifier, type_descr))
654
655     def write_struct(struct):
656         #if not struct.identifier.startswith("Sc") and not struct.identifier.startswith("I"):
657         #    return
658
659         #if not struct.identifier == "Object":
660         #    return
661
662         filepath = os.path.join(BASEPATH, "bpy.types.%s.rst" % struct.identifier)
663         file = open(filepath, "w")
664         fw = file.write
665
666         base_id = getattr(struct.base, "identifier", "")
667
668         if _BPY_STRUCT_FAKE:
669             if not base_id:
670                 base_id = _BPY_STRUCT_FAKE
671
672         if base_id:
673             title = "%s(%s)" % (struct.identifier, base_id)
674         else:
675             title = struct.identifier
676
677         write_title(fw, title, "=")
678
679         fw(".. module:: bpy.types\n\n")
680
681         # docs first?, ok
682         write_example_ref("", fw, "bpy.types.%s" % struct.identifier)
683
684         base_ids = [base.identifier for base in struct.get_bases()]
685
686         if _BPY_STRUCT_FAKE:
687             base_ids.append(_BPY_STRUCT_FAKE)
688
689         base_ids.reverse()
690
691         if base_ids:
692             if len(base_ids) > 1:
693                 fw("base classes --- ")
694             else:
695                 fw("base class --- ")
696
697             fw(", ".join((":class:`%s`" % base_id) for base_id in base_ids))
698             fw("\n\n")
699
700         subclass_ids = [s.identifier for s in structs.values() if s.base is struct if not rna_info.rna_id_ignore(s.identifier)]
701         if subclass_ids:
702             fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in subclass_ids) + "\n\n")
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             fw(".. class:: %s(%s)\n\n" % (struct.identifier, base_id))
712         else:
713             fw(".. class:: %s\n\n" % struct.identifier)
714
715         fw("   %s\n\n" % struct.description)
716
717         # properties sorted in alphabetical order
718         sorted_struct_properties = struct.properties[:]
719         sorted_struct_properties.sort(key=lambda prop: prop.identifier)
720
721         for prop in sorted_struct_properties:
722             type_descr = prop.get_type_description(class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID)
723             # readonly properties use "data" directive, variables properties use "attribute" directive
724             if 'readonly' in type_descr:
725                 fw("   .. data:: %s\n\n" % prop.identifier)
726             else:
727                 fw("   .. attribute:: %s\n\n" % prop.identifier)
728             if prop.description:
729                 fw("      %s\n\n" % prop.description)
730             fw("      :type: %s\n\n" % type_descr)
731
732         # python attributes
733         py_properties = struct.get_py_properties()
734         py_prop = None
735         for identifier, py_prop in py_properties:
736             pyprop2sphinx("   ", fw, identifier, py_prop)
737         del py_properties, py_prop
738
739         for func in struct.functions:
740             args_str = ", ".join(prop.get_arg_default(force=False) for prop in func.args)
741
742             fw("   .. %s:: %s(%s)\n\n" % ("classmethod" if func.is_classmethod else "method", func.identifier, args_str))
743             fw("      %s\n\n" % func.description)
744
745             for prop in func.args:
746                 write_param("      ", fw, prop)
747
748             if len(func.return_values) == 1:
749                 write_param("      ", fw, func.return_values[0], is_return=True)
750             elif func.return_values:  # multiple return values
751                 fw("      :return (%s):\n" % ", ".join(prop.identifier for prop in func.return_values))
752                 for prop in func.return_values:
753                     type_descr = prop.get_type_description(as_ret=True, class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID)
754                     descr = prop.description
755                     if not descr:
756                         descr = prop.name
757                     fw("         `%s`, %s, %s\n\n" % (prop.identifier, descr, type_descr))
758
759             write_example_ref("      ", fw, "bpy.types." + struct.identifier + "." + func.identifier)
760
761             fw("\n")
762
763         # python methods
764         py_funcs = struct.get_py_functions()
765         py_func = None
766
767         for identifier, py_func in py_funcs:
768             pyfunc2sphinx("   ", fw, identifier, py_func, is_class=True)
769         del py_funcs, py_func
770
771         py_funcs = struct.get_py_c_functions()
772         py_func = None
773
774         for identifier, py_func in py_funcs:
775             py_c_func2sphinx("   ", fw, "bpy.types", struct.identifier, identifier, py_func, is_class=True)
776
777         lines = []
778
779         if struct.base or _BPY_STRUCT_FAKE:
780             bases = list(reversed(struct.get_bases()))
781
782             # props
783             lines[:] = []
784
785             if _BPY_STRUCT_FAKE:
786                 descr_items = [(key, descr) for key, descr in sorted(bpy.types.Struct.__bases__[0].__dict__.items()) if not key.startswith("__")]
787
788             if _BPY_STRUCT_FAKE:
789                 for key, descr in descr_items:
790                     if type(descr) == GetSetDescriptorType:
791                         lines.append("   * :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key))
792
793             for base in bases:
794                 for prop in base.properties:
795                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, prop.identifier))
796
797                 for identifier, py_prop in base.get_py_properties():
798                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, identifier))
799
800                 for identifier, py_prop in base.get_py_properties():
801                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, identifier))
802
803             if lines:
804                 fw(".. rubric:: Inherited Properties\n\n")
805
806                 fw(".. hlist::\n")
807                 fw("   :columns: 2\n\n")
808
809                 for line in lines:
810                     fw(line)
811                 fw("\n")
812
813             # funcs
814             lines[:] = []
815
816             if _BPY_STRUCT_FAKE:
817                 for key, descr in descr_items:
818                     if type(descr) == MethodDescriptorType:
819                         lines.append("   * :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key))
820
821             for base in bases:
822                 for func in base.functions:
823                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, func.identifier))
824                 for identifier, py_func in base.get_py_functions():
825                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, identifier))
826
827             if lines:
828                 fw(".. rubric:: Inherited Functions\n\n")
829
830                 fw(".. hlist::\n")
831                 fw("   :columns: 2\n\n")
832
833                 for line in lines:
834                     fw(line)
835                 fw("\n")
836
837             lines[:] = []
838
839         if struct.references:
840             # use this otherwise it gets in the index for a normal heading.
841             fw(".. rubric:: References\n\n")
842
843             fw(".. hlist::\n")
844             fw("   :columns: 2\n\n")
845
846             for ref in struct.references:
847                 ref_split = ref.split(".")
848                 if len(ref_split) > 2:
849                     ref = ref_split[-2] + "." + ref_split[-1]
850                 fw("   * :class:`%s`\n" % ref)
851             fw("\n")
852
853         # docs last?, disable for now
854         # write_example_ref("", fw, "bpy.types.%s" % struct.identifier)
855         file.close()
856
857     if "bpy.types" not in EXCLUDE_MODULES:
858         for struct in structs.values():
859             # TODO, rna_info should filter these out!
860             if "_OT_" in struct.identifier:
861                 continue
862             write_struct(struct)
863
864         def fake_bpy_type(class_value, class_name, descr_str, use_subclasses=True):
865             filepath = os.path.join(BASEPATH, "bpy.types.%s.rst" % class_name)
866             file = open(filepath, "w")
867             fw = file.write
868
869             write_title(fw, class_name, "=")
870
871             fw(".. module:: bpy.types\n")
872             fw("\n")
873
874             if use_subclasses:
875                 subclass_ids = [s.identifier for s in structs.values() if s.base is None if not rna_info.rna_id_ignore(s.identifier)]
876                 if subclass_ids:
877                     fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in sorted(subclass_ids)) + "\n\n")
878
879             fw(".. class:: %s\n\n" % class_name)
880             fw("   %s\n\n" % descr_str)
881             fw("   .. note::\n\n")
882             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)
883
884             descr_items = [(key, descr) for key, descr in sorted(class_value.__dict__.items()) if not key.startswith("__")]
885
886             for key, descr in descr_items:
887                 if type(descr) == MethodDescriptorType:  # GetSetDescriptorType, GetSetDescriptorType's are not documented yet
888                     py_descr2sphinx("   ", fw, descr, "bpy.types", class_name, key)
889
890             for key, descr in descr_items:
891                 if type(descr) == GetSetDescriptorType:
892                     py_descr2sphinx("   ", fw, descr, "bpy.types", class_name, key)
893             file.close()
894
895         # write fake classes
896         if _BPY_STRUCT_FAKE:
897             class_value = bpy.types.Struct.__bases__[0]
898             fake_bpy_type(class_value, _BPY_STRUCT_FAKE, "built-in base class for all classes in bpy.types.", use_subclasses=True)
899
900         if _BPY_PROP_COLLECTION_FAKE:
901             class_value = bpy.data.objects.__class__
902             fake_bpy_type(class_value, _BPY_PROP_COLLECTION_FAKE, "built-in class used for all collections.", use_subclasses=False)
903
904     # operators
905     def write_ops():
906         API_BASEURL = "http://svn.blender.org/svnroot/bf-blender/trunk/blender/release/scripts"
907         API_BASEURL_ADDON = "http://svn.blender.org/svnroot/bf-extensions/trunk/py/scripts"
908         API_BASEURL_ADDON_CONTRIB = "http://svn.blender.org/svnroot/bf-extensions/contrib/py/scripts"
909
910         op_modules = {}
911         for op in ops.values():
912             op_modules.setdefault(op.module_name, []).append(op)
913         del op
914
915         for op_module_name, ops_mod in op_modules.items():
916             filepath = os.path.join(BASEPATH, "bpy.ops.%s.rst" % op_module_name)
917             file = open(filepath, "w")
918             fw = file.write
919
920             title = "%s Operators" % op_module_name.replace("_", " ").title()
921
922             write_title(fw, title, "=")
923
924             fw(".. module:: bpy.ops.%s\n\n" % op_module_name)
925
926             ops_mod.sort(key=lambda op: op.func_name)
927
928             for op in ops_mod:
929                 args_str = ", ".join(prop.get_arg_default(force=True) for prop in op.args)
930                 fw(".. function:: %s(%s)\n\n" % (op.func_name, args_str))
931
932                 # if the description isn't valid, we output the standard warning
933                 # with a link to the wiki so that people can help
934                 if not op.description or op.description == "(undocumented operator)":
935                     operator_description = undocumented_message('bpy.ops', op.module_name, op.func_name)
936                 else:
937                     operator_description = op.description
938
939                 fw("   %s\n\n" % operator_description)
940                 for prop in op.args:
941                     write_param("   ", fw, prop)
942                 if op.args:
943                     fw("\n")
944
945                 location = op.get_location()
946                 if location != (None, None):
947                     if location[0].startswith("addons_contrib" + os.sep):
948                         url_base = API_BASEURL_ADDON_CONTRIB
949                     elif location[0].startswith("addons" + os.sep):
950                         url_base = API_BASEURL_ADDON
951                     else:
952                         url_base = API_BASEURL
953
954                     fw("   :file: `%s <%s/%s>`_:%d\n\n" % (location[0], url_base, location[0], location[1]))
955
956             file.close()
957
958     if "bpy.ops" not in EXCLUDE_MODULES:
959         write_ops()
960
961
962 def rna2sphinx(BASEPATH):
963
964     try:
965         os.mkdir(BASEPATH)
966     except:
967         pass
968
969     # conf.py - empty for now
970     filepath = os.path.join(BASEPATH, "conf.py")
971     file = open(filepath, "w")
972     fw = file.write
973
974     version_string = ".".join(str(v) for v in bpy.app.version)
975     if bpy.app.build_revision != "Unknown":
976         version_string = version_string + " r" + bpy.app.build_revision
977
978     version_string_fp = "_".join(str(v) for v in bpy.app.version)
979
980     if bpy.app.version_cycle == "release":
981         version_string_pdf = "%s%s_release" % ("_".join(str(v) for v in bpy.app.version[:2]), bpy.app.version_char)
982     else:
983         version_string_pdf = version_string_fp
984
985     fw("project = 'Blender'\n")
986     # fw("master_doc = 'index'\n")
987     fw("copyright = u'Blender Foundation'\n")
988     fw("version = '%s - API'\n" % version_string)
989     fw("release = '%s - API'\n" % version_string)
990     fw("html_theme = 'blender-org'\n")
991     fw("html_theme_path = ['../']\n")
992     fw("html_favicon = 'favicon.ico'\n")
993     # not helpful since the source us generated, adds to upload size.
994     fw("html_copy_source = False\n")
995     fw("\n")
996     # needed for latex, pdf gen
997     fw("latex_documents = [ ('contents', 'contents.tex', 'Blender Index', 'Blender Foundation', 'manual'), ]\n")
998     fw("latex_paper_size = 'a4paper'\n")
999     file.close()
1000
1001     # main page needed for sphinx (index.html)
1002     filepath = os.path.join(BASEPATH, "contents.rst")
1003     file = open(filepath, "w")
1004     fw = file.write
1005
1006     fw("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n")
1007     fw(" Blender Documentation contents\n")
1008     fw("%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n")
1009     fw("\n")
1010     fw("Welcome, this document is an API reference for Blender %s. built %s.\n" % (version_string, bpy.app.build_date))
1011     fw("\n")
1012     fw("`A PDF version of this document is also available <blender_python_reference_%s.pdf>`_\n" % version_string_pdf)
1013
1014     fw("\n")
1015
1016     if not EXCLUDE_INFO_DOCS:
1017         fw("============================\n")
1018         fw("Blender/Python Documentation\n")
1019         fw("============================\n")
1020         fw("\n")
1021         fw("\n")
1022         fw(".. toctree::\n")
1023         fw("   :maxdepth: 1\n\n")
1024         for info, info_desc in INFO_DOCS:
1025             fw("   %s <%s>\n\n" % (info_desc, info))
1026         fw("\n")
1027
1028     fw("===================\n")
1029     fw("Application Modules\n")
1030     fw("===================\n")
1031     fw("\n")
1032     fw(".. toctree::\n")
1033     fw("   :maxdepth: 1\n\n")
1034     if "bpy.context" not in EXCLUDE_MODULES:
1035         fw("   bpy.context.rst\n\n")  # note: not actually a module
1036     if "bpy.data" not in EXCLUDE_MODULES:
1037         fw("   bpy.data.rst\n\n")  # note: not actually a module
1038     if "bpy.ops" not in EXCLUDE_MODULES:
1039         fw("   bpy.ops.rst\n\n")
1040     if "bpy.types" not in EXCLUDE_MODULES:
1041         fw("   bpy.types.rst\n\n")
1042
1043     # py modules
1044     if "bpy.utils" not in EXCLUDE_MODULES:
1045         fw("   bpy.utils.rst\n\n")
1046     if "bpy.path" not in EXCLUDE_MODULES:
1047         fw("   bpy.path.rst\n\n")
1048     if "bpy.app" not in EXCLUDE_MODULES:
1049         fw("   bpy.app.rst\n\n")
1050
1051     # C modules
1052     if "bpy.props" not in EXCLUDE_MODULES:
1053         fw("   bpy.props.rst\n\n")
1054
1055     fw("==================\n")
1056     fw("Standalone Modules\n")
1057     fw("==================\n")
1058     fw("\n")
1059     fw(".. toctree::\n")
1060     fw("   :maxdepth: 1\n\n")
1061
1062     if "mathutils" not in EXCLUDE_MODULES:
1063         fw("   mathutils.rst\n\n")
1064     if "mathutils.geometry" not in EXCLUDE_MODULES:
1065         fw("   mathutils.geometry.rst\n\n")
1066     if "bgl" not in EXCLUDE_MODULES:
1067         fw("   bgl.rst\n\n")
1068     if "blf" not in EXCLUDE_MODULES:
1069         fw("   blf.rst\n\n")
1070     if "aud" not in EXCLUDE_MODULES:
1071         fw("   aud.rst\n\n")
1072     if "bpy_extras" not in EXCLUDE_MODULES:
1073         fw("   bpy_extras.rst\n\n")
1074
1075     # game engine
1076     if "bge" not in EXCLUDE_MODULES:
1077         fw("===================\n")
1078         fw("Game Engine Modules\n")
1079         fw("===================\n")
1080         fw("\n")
1081         fw(".. toctree::\n")
1082         fw("   :maxdepth: 1\n\n")
1083         fw("   bge.types.rst\n\n")
1084         fw("   bge.logic.rst\n\n")
1085         fw("   bge.render.rst\n\n")
1086         fw("   bge.texture.rst\n\n")
1087         fw("   bge.events.rst\n\n")
1088         fw("   bge.constraints.rst\n\n")
1089
1090     # rna generated change log
1091     fw("========\n")
1092     fw("API Info\n")
1093     fw("========\n")
1094     fw("\n")
1095     fw(".. toctree::\n")
1096     fw("   :maxdepth: 1\n\n")
1097     fw("   change_log.rst\n\n")
1098
1099     fw("\n")
1100     fw("\n")
1101     fw(".. note:: The Blender Python API has areas which are still in development.\n")
1102     fw("   \n")
1103     fw("   The following areas are subject to change.\n")
1104     fw("      * operator behavior, names and arguments\n")
1105     fw("      * mesh creation and editing functions\n")
1106     fw("   \n")
1107     fw("   These parts of the API are relatively stable and are unlikely to change significantly\n")
1108     fw("      * data API, access to attributes of blender data such as mesh verts, material color, timeline frames and scene objects\n")
1109     fw("      * user interface functions for defining buttons, creation of menus, headers, panels\n")
1110     fw("      * render engine integration\n")
1111     fw("      * modules: bgl, mathutils & game engine.\n")
1112     fw("\n")
1113
1114     file.close()
1115
1116     # internal modules
1117     if "bpy.ops" not in EXCLUDE_MODULES:
1118         filepath = os.path.join(BASEPATH, "bpy.ops.rst")
1119         file = open(filepath, "w")
1120         fw = file.write
1121         fw("Operators (bpy.ops)\n")
1122         fw("===================\n\n")
1123         write_example_ref("", fw, "bpy.ops")
1124         fw(".. toctree::\n")
1125         fw("   :glob:\n\n")
1126         fw("   bpy.ops.*\n\n")
1127         file.close()
1128
1129     if "bpy.types" not in EXCLUDE_MODULES:
1130         filepath = os.path.join(BASEPATH, "bpy.types.rst")
1131         file = open(filepath, "w")
1132         fw = file.write
1133         fw("Types (bpy.types)\n")
1134         fw("=================\n\n")
1135         fw(".. toctree::\n")
1136         fw("   :glob:\n\n")
1137         fw("   bpy.types.*\n\n")
1138         file.close()
1139
1140     if "bpy.data" not in EXCLUDE_MODULES:
1141         # not actually a module, only write this file so we
1142         # can reference in the TOC
1143         filepath = os.path.join(BASEPATH, "bpy.data.rst")
1144         file = open(filepath, "w")
1145         fw = file.write
1146         fw("Data Access (bpy.data)\n")
1147         fw("======================\n\n")
1148         fw(".. module:: bpy\n")
1149         fw("\n")
1150         fw("This module is used for all blender/python access.\n")
1151         fw("\n")
1152         fw(".. data:: data\n")
1153         fw("\n")
1154         fw("   Access to blenders internal data\n")
1155         fw("\n")
1156         fw("   :type: :class:`bpy.types.BlendData`\n")
1157         fw("\n")
1158         fw(".. literalinclude:: ../examples/bpy.data.py\n")
1159         file.close()
1160
1161     EXAMPLE_SET_USED.add("bpy.data")
1162
1163     module = None
1164
1165     if "bpy.context" not in EXCLUDE_MODULES:
1166         # one of a kind, context doc (uses ctypes to extract info!)
1167         pycontext2sphinx(BASEPATH)
1168
1169     # python modules
1170     if "bpy.utils" not in EXCLUDE_MODULES:
1171         from bpy import utils as module
1172         pymodule2sphinx(BASEPATH, "bpy.utils", module, "Utilities")
1173
1174     if "bpy.path" not in EXCLUDE_MODULES:
1175         from bpy import path as module
1176         pymodule2sphinx(BASEPATH, "bpy.path", module, "Path Utilities")
1177
1178     if "bpy_extras" not in EXCLUDE_MODULES:
1179         import bpy_extras as module
1180         pymodule2sphinx(BASEPATH, "bpy_extras", module, "Extra Utilities")
1181
1182     # C modules
1183     if "bpy.app" not in EXCLUDE_MODULES:
1184         from bpy import app as module
1185         pymodule2sphinx(BASEPATH, "bpy.app", module, "Application Data")
1186
1187     if "bpy.props" not in EXCLUDE_MODULES:
1188         from bpy import props as module
1189         pymodule2sphinx(BASEPATH, "bpy.props", module, "Property Definitions")
1190
1191     if "mathutils" not in EXCLUDE_MODULES:
1192         import mathutils as module
1193         pymodule2sphinx(BASEPATH, "mathutils", module, "Math Types & Utilities")
1194
1195     if "mathutils.geometry" not in EXCLUDE_MODULES:
1196         import mathutils.geometry as module
1197         pymodule2sphinx(BASEPATH, "mathutils.geometry", module, "Geometry Utilities")
1198
1199     if "blf" not in EXCLUDE_MODULES:
1200         import blf as module
1201         pymodule2sphinx(BASEPATH, "blf", module, "Font Drawing")
1202
1203     if "bgl" not in EXCLUDE_MODULES:
1204         #import bgl as module
1205         #pymodule2sphinx(BASEPATH, "bgl", module, "Blender OpenGl wrapper")
1206         #del module
1207         import shutil
1208         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bgl.rst"), BASEPATH)
1209
1210     if "aud" not in EXCLUDE_MODULES:
1211         import aud as module
1212         pymodule2sphinx(BASEPATH, "aud", module, "Audio System")
1213     del module
1214
1215     ## game engine
1216     import shutil
1217     # copy2 keeps time/date stamps
1218     if "bge" not in EXCLUDE_MODULES:
1219         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.types.rst"), BASEPATH)
1220         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.logic.rst"), BASEPATH)
1221         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.render.rst"), BASEPATH)
1222         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.texture.rst"), BASEPATH)
1223         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.events.rst"), BASEPATH)
1224         shutil.copy2(os.path.join(BASEPATH, "..", "rst", "bge.constraints.rst"), BASEPATH)
1225
1226     shutil.copy2(os.path.join(BASEPATH, "..", "rst", "change_log.rst"), BASEPATH)
1227
1228     if not EXCLUDE_INFO_DOCS:
1229         for info, info_desc in INFO_DOCS:
1230             shutil.copy2(os.path.join(BASEPATH, "..", "rst", info), BASEPATH)
1231
1232     if 0:
1233         filepath = os.path.join(BASEPATH, "bpy.rst")
1234         file = open(filepath, "w")
1235         fw = file.write
1236
1237         fw("\n")
1238
1239         title = ":mod:`bpy` --- Blender Python Module"
1240
1241         write_title(fw, title, "=")
1242
1243         fw(".. module:: bpy.types\n\n")
1244         file.close()
1245
1246     # bpy.types and bpy.ops
1247     pyrna2sphinx(BASEPATH)
1248
1249     file.close()
1250
1251
1252 def main():
1253     import shutil
1254
1255     script_dir = os.path.dirname(__file__)
1256     path_in = os.path.join(script_dir, "sphinx-in")
1257     path_out = os.path.join(script_dir, "sphinx-out")
1258     path_examples = os.path.join(script_dir, "examples")
1259     # only for partial updates
1260     path_in_tmp = path_in + "-tmp"
1261
1262     if not os.path.exists(path_in):
1263         os.mkdir(path_in)
1264
1265     for f in os.listdir(path_examples):
1266         if f.endswith(".py"):
1267             EXAMPLE_SET.add(os.path.splitext(f)[0])
1268
1269     # only for full updates
1270     if _BPY_FULL_REBUILD:
1271         shutil.rmtree(path_in, True)
1272         shutil.rmtree(path_out, True)
1273     else:
1274         # write here, then move
1275         shutil.rmtree(path_in_tmp, True)
1276
1277     rna2sphinx(path_in_tmp)
1278
1279     if not _BPY_FULL_REBUILD:
1280         import filecmp
1281
1282         # now move changed files from 'path_in_tmp' --> 'path_in'
1283         file_list_path_in = set(os.listdir(path_in))
1284         file_list_path_in_tmp = set(os.listdir(path_in_tmp))
1285
1286         # remove deprecated files that have been removed.
1287         for f in sorted(file_list_path_in):
1288             if f not in file_list_path_in_tmp:
1289                 print("\tdeprecated: %s" % f)
1290                 os.remove(os.path.join(path_in, f))
1291
1292         # freshen with new files.
1293         for f in sorted(file_list_path_in_tmp):
1294             f_from = os.path.join(path_in_tmp, f)
1295             f_to = os.path.join(path_in, f)
1296
1297             do_copy = True
1298             if f in file_list_path_in:
1299                 if filecmp.cmp(f_from, f_to):
1300                     do_copy = False
1301
1302             if do_copy:
1303                 print("\tupdating: %s" % f)
1304                 shutil.copy(f_from, f_to)
1305             '''else:
1306                 print("\tkeeping: %s" % f) # eh, not that useful'''
1307
1308     EXAMPLE_SET_UNUSED = EXAMPLE_SET - EXAMPLE_SET_USED
1309     if EXAMPLE_SET_UNUSED:
1310         print("\nUnused examples found in '%s'..." % path_examples)
1311         for f in EXAMPLE_SET_UNUSED:
1312             print("    %s.py" % f)
1313         print("  %d total\n" % len(EXAMPLE_SET_UNUSED))
1314
1315     import sys
1316     sys.exit()
1317
1318 if __name__ == '__main__':
1319     main()