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