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