Tests: Disable failing import/export tests for until they're fixed
[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
25 API dump in RST files
26 ---------------------
27   Run this script from blenders root path once you have compiled blender
28
29     ./blender.bin --background -noaudio --python doc/python_api/sphinx_doc_gen.py
30
31   This will generate python files in doc/python_api/sphinx-in/
32   providing ./blender.bin is or links to the blender executable
33
34   To choose sphinx-in directory:
35     ./blender.bin --background --python doc/python_api/sphinx_doc_gen.py -- --output ../python_api
36
37   For quick builds:
38     ./blender.bin --background --python doc/python_api/sphinx_doc_gen.py -- --partial bmesh.*
39
40
41 Sphinx: HTML generation
42 -----------------------
43   After you have built doc/python_api/sphinx-in (see above),
44   generate html docs by running:
45
46     cd doc/python_api
47     sphinx-build sphinx-in sphinx-out
48
49   This requires sphinx 1.0.7 to be installed.
50
51
52 Sphinx: PDF generation
53 ----------------------
54   After you have built doc/python_api/sphinx-in (see above),
55   generate the pdf doc by running:
56
57     sphinx-build -b latex doc/python_api/sphinx-in doc/python_api/sphinx-out
58     cd doc/python_api/sphinx-out
59     make
60
61 """
62
63 try:
64     import bpy  # blender module
65 except ImportError:
66     print("\nERROR: this script must run from inside Blender")
67     print(SCRIPT_HELP_MSG)
68     import sys
69     sys.exit()
70
71 import rna_info     # blender module
72
73
74 def rna_info_BuildRNAInfo_cache():
75     if rna_info_BuildRNAInfo_cache.ret is None:
76         rna_info_BuildRNAInfo_cache.ret = rna_info.BuildRNAInfo()
77     return rna_info_BuildRNAInfo_cache.ret
78 rna_info_BuildRNAInfo_cache.ret = None
79 # --- end rna_info cache
80
81 # import rpdb2; rpdb2.start_embedded_debugger('test')
82 import os
83 import sys
84 import inspect
85 import shutil
86 import logging
87
88 from platform import platform
89 PLATFORM = platform().split('-')[0].lower()    # 'linux', 'darwin', 'windows'
90
91 SCRIPT_DIR = os.path.abspath(os.path.dirname(__file__))
92
93
94 def handle_args():
95     '''
96     Parse the args passed to Blender after "--", ignored by Blender
97     '''
98     import argparse
99
100     # When --help is given, print the usage text
101     parser = argparse.ArgumentParser(
102         formatter_class=argparse.RawTextHelpFormatter,
103         usage=SCRIPT_HELP_MSG
104     )
105
106     # optional arguments
107     parser.add_argument("-p", "--partial",
108                         dest="partial",
109                         type=str,
110                         default="",
111                         help="Use a wildcard to only build specific module(s)\n"
112                              "Example: --partial bmesh*\n",
113                         required=False)
114
115     parser.add_argument("-f", "--fullrebuild",
116                         dest="full_rebuild",
117                         default=False,
118                         action='store_true',
119                         help="Rewrite all rst files in sphinx-in/ "
120                              "(default=False)",
121                         required=False)
122
123     parser.add_argument("-b", "--bpy",
124                         dest="bpy",
125                         default=False,
126                         action='store_true',
127                         help="Write the rst file of the bpy module "
128                              "(default=False)",
129                         required=False)
130
131     parser.add_argument("-o", "--output",
132                         dest="output_dir",
133                         type=str,
134                         default=SCRIPT_DIR,
135                         help="Path of the API docs (default=<script dir>)",
136                         required=False)
137
138     parser.add_argument("-T", "--sphinx-theme",
139                         dest="sphinx_theme",
140                         type=str,
141                         default='default',
142                         help=
143                         # see SPHINX_THEMES below
144                         "Sphinx theme (default='default')\n"
145                         "Available themes\n"
146                         "----------------\n"
147                         "(Blender Foundation) blender-org\n"    # naiad
148                         "(Sphinx) agogo, basic, epub, haiku, nature, "
149                         "scrolls, sphinxdoc, traditional\n",
150 #                        choices=['naiad', 'blender-org'] +      # bf
151 #                                ['agogo', 'basic', 'epub',
152 #                                 'haiku', 'nature', 'scrolls',
153 #                                 'sphinxdoc', 'traditional'],   # sphinx
154                         required=False)
155
156     parser.add_argument("-N", "--sphinx-named-output",
157                         dest="sphinx_named_output",
158                         default=False,
159                         action='store_true',
160                         help="Add the theme name to the html dir name.\n"
161                              "Example: \"sphinx-out_haiku\" (default=False)",
162                         required=False)
163
164     parser.add_argument("-B", "--sphinx-build",
165                         dest="sphinx_build",
166                         default=False,
167                         action='store_true',
168                         help="Build the html docs by running:\n"
169                              "sphinx-build SPHINX_IN SPHINX_OUT\n"
170                              "(default=False; does not depend on -P)",
171                         required=False)
172
173     parser.add_argument("-P", "--sphinx-build-pdf",
174                         dest="sphinx_build_pdf",
175                         default=False,
176                         action='store_true',
177                         help="Build the pdf by running:\n"
178                              "sphinx-build -b latex SPHINX_IN SPHINX_OUT_PDF\n"
179                              "(default=False; does not depend on -B)",
180                         required=False)
181
182     parser.add_argument("-R", "--pack-reference",
183                         dest="pack_reference",
184                         default=False,
185                         action='store_true',
186                         help="Pack all necessary files in the deployed dir.\n"
187                              "(default=False; use with -B and -P)",
188                         required=False)
189
190     parser.add_argument("-l", "--log",
191                         dest="log",
192                         default=False,
193                         action='store_true',
194                         help=(
195                         "Log the output of the api dump and sphinx|latex "
196                         "warnings and errors (default=False).\n"
197                         "If given, save logs in:\n"
198                         "* OUTPUT_DIR/.bpy.log\n"
199                         "* OUTPUT_DIR/.sphinx-build.log\n"
200                         "* OUTPUT_DIR/.sphinx-build_pdf.log\n"
201                         "* OUTPUT_DIR/.latex_make.log",
202                         ),
203                         required=False)
204
205     # parse only the args passed after '--'
206     argv = []
207     if "--" in sys.argv:
208         argv = sys.argv[sys.argv.index("--") + 1:]  # get all args after "--"
209
210     return parser.parse_args(argv)
211
212
213 ARGS = handle_args()
214
215 # ----------------------------------BPY-----------------------------------------
216
217 BPY_LOGGER = logging.getLogger('bpy')
218 BPY_LOGGER.setLevel(logging.DEBUG)
219
220 """
221 # for quick rebuilds
222 rm -rf /b/doc/python_api/sphinx-* && \
223 ./blender.bin -b -noaudio --factory-startup -P doc/python_api/sphinx_doc_gen.py && \
224 sphinx-build doc/python_api/sphinx-in doc/python_api/sphinx-out
225
226 or
227
228 ./blender.bin -b -noaudio --factory-startup -P doc/python_api/sphinx_doc_gen.py -- -f -B
229 """
230
231 # Switch for quick testing so doc-builds don't take so long
232 if not ARGS.partial:
233     # full build
234     FILTER_BPY_OPS = None
235     FILTER_BPY_TYPES = None
236     EXCLUDE_INFO_DOCS = False
237     EXCLUDE_MODULES = ()
238
239 else:
240     # can manually edit this too:
241     #FILTER_BPY_OPS = ("import.scene", )  # allow
242     #FILTER_BPY_TYPES = ("bpy_struct", "Operator", "ID")  # allow
243     EXCLUDE_INFO_DOCS = True
244     EXCLUDE_MODULES = [
245         "aud",
246         "bge",
247         "bge.constraints",
248         "bge.events",
249         "bge.logic",
250         "bge.render",
251         "bge.texture",
252         "bge.types",
253         "bgl",
254         "blf",
255         "bmesh",
256         "bmesh.ops",
257         "bmesh.types",
258         "bmesh.utils",
259         "bmesh.geometry",
260         "bpy.app",
261         "bpy.app.handlers",
262         "bpy.app.translations",
263         "bpy.context",
264         "bpy.data",
265         "bpy.ops",  # supports filtering
266         "bpy.path",
267         "bpy.props",
268         "bpy.types",  # supports filtering
269         "bpy.utils",
270         "bpy_extras",
271         "gpu",
272         "mathutils",
273         "mathutils.geometry",
274         "mathutils.kdtree",
275         "mathutils.noise",
276         "freestyle",
277         "freestyle.chainingiterators",
278         "freestyle.functions",
279         "freestyle.predicates",
280         "freestyle.shaders",
281         "freestyle.types",
282         "freestyle.utils",
283         ]
284
285     # ------
286     # Filter
287     #
288     # TODO, support bpy.ops and bpy.types filtering
289     import fnmatch
290     m = None
291     EXCLUDE_MODULES = [m for m in EXCLUDE_MODULES if not fnmatch.fnmatchcase(m, ARGS.partial)]
292
293     # special support for bpy.types.XXX
294     FILTER_BPY_OPS = tuple([m[8:] for m in ARGS.partial.split(":") if m.startswith("bpy.ops.")])
295     if FILTER_BPY_OPS:
296         EXCLUDE_MODULES.remove("bpy.ops")
297
298     FILTER_BPY_TYPES = tuple([m[10:] for m in ARGS.partial.split(":") if m.startswith("bpy.types.")])
299     if FILTER_BPY_TYPES:
300         EXCLUDE_MODULES.remove("bpy.types")
301
302     print(FILTER_BPY_TYPES)
303
304     EXCLUDE_INFO_DOCS = (not fnmatch.fnmatchcase("info", ARGS.partial))
305
306     del m
307     del fnmatch
308
309     BPY_LOGGER.debug("Partial Doc Build, Skipping: %s\n" % "\n                             ".join(sorted(EXCLUDE_MODULES)))
310
311     #
312     # done filtering
313     # --------------
314
315 try:
316     __import__("aud")
317 except ImportError:
318     BPY_LOGGER.debug("Warning: Built without 'aud' module, docs incomplete...")
319     EXCLUDE_MODULES = list(EXCLUDE_MODULES) + ["aud"]
320
321 try:
322     __import__("freestyle")
323 except ImportError:
324     BPY_LOGGER.debug("Warning: Built without 'freestyle' module, docs incomplete...")
325     EXCLUDE_MODULES = list(EXCLUDE_MODULES) + ["freestyle",
326                                                "freestyle.chainingiterators",
327                                                "freestyle.functions",
328                                                "freestyle.predicates",
329                                                "freestyle.shaders",
330                                                "freestyle.types",
331                                                "freestyle.utils"]
332
333 # examples
334 EXAMPLES_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "examples"))
335 EXAMPLE_SET = set()
336 for f in os.listdir(EXAMPLES_DIR):
337     if f.endswith(".py"):
338         EXAMPLE_SET.add(os.path.splitext(f)[0])
339 EXAMPLE_SET_USED = set()
340
341 # rst files dir
342 RST_DIR = os.path.abspath(os.path.join(SCRIPT_DIR, "rst"))
343
344 # extra info, not api reference docs
345 # stored in ./rst/info_*
346 INFO_DOCS = (
347     ("info_quickstart.rst", "Blender/Python Quickstart: new to blender/scripting and want to get your feet wet?"),
348     ("info_overview.rst", "Blender/Python API Overview: a more complete explanation of python integration"),
349     ("info_tutorial_addon.rst", "Blender/Python Addon Tutorial: a step by step guide on how to write an addon from scratch"),
350     ("info_api_reference.rst", "Blender/Python API Reference Usage: examples of how to use the API reference docs"),
351     ("info_best_practice.rst", "Best Practice: Conventions to follow for writing good scripts"),
352     ("info_tips_and_tricks.rst", "Tips and Tricks: Hints to help you while writing scripts for blender"),
353     ("info_gotcha.rst", "Gotcha's: some of the problems you may come up against when writing scripts"),
354     )
355
356 # only support for properties atm.
357 RNA_BLACKLIST = {
358     # XXX messes up PDF!, really a bug but for now just workaround.
359     "UserPreferencesSystem": {"language", }
360     }
361
362 MODULE_GROUPING = {
363     "bmesh.types": (
364                     ("Base Mesh Type", '-'),
365                     "BMesh",
366                     ("Mesh Elements", '-'),
367                     "BMVert",
368                     "BMEdge",
369                     "BMFace",
370                     "BMLoop",
371                     ("Sequence Accessors", '-'),
372                     "BMElemSeq",
373                     "BMVertSeq",
374                     "BMEdgeSeq",
375                     "BMFaceSeq",
376                     "BMLoopSeq",
377                     "BMIter",
378                     ("Selection History", '-'),
379                     "BMEditSelSeq",
380                     "BMEditSelIter",
381                     ("Custom-Data Layer Access", '-'),
382                     "BMLayerAccessVert",
383                     "BMLayerAccessEdge",
384                     "BMLayerAccessFace",
385                     "BMLayerAccessLoop",
386                     "BMLayerCollection",
387                     "BMLayerItem",
388                     ("Custom-Data Layer Types", '-'),
389                     "BMLoopUV",
390                     "BMDeformVert"
391                     )
392     }
393
394 # --------------------configure compile time options----------------------------
395
396 # -------------------------------BLENDER----------------------------------------
397
398 blender_version_strings = [str(v) for v in bpy.app.version]
399
400 # converting bytes to strings, due to #30154
401 BLENDER_REVISION = str(bpy.app.build_hash, 'utf_8')
402 BLENDER_DATE = str(bpy.app.build_date, 'utf_8')
403
404 BLENDER_VERSION_DOTS = ".".join(blender_version_strings)    # '2.62.1'
405 if BLENDER_REVISION != "Unknown":
406     BLENDER_VERSION_DOTS += " " + BLENDER_REVISION          # '2.62.1 SHA1'
407
408 BLENDER_VERSION_PATH = "_".join(blender_version_strings)    # '2_62_1'
409 if bpy.app.version_cycle == "release":
410     BLENDER_VERSION_PATH = "%s%s_release" % ("_".join(blender_version_strings[:2]),
411                                              bpy.app.version_char)   # '2_62_release'
412
413 # --------------------------DOWNLOADABLE FILES----------------------------------
414
415 REFERENCE_NAME = "blender_python_reference_%s" % BLENDER_VERSION_PATH
416 REFERENCE_PATH = os.path.join(ARGS.output_dir, REFERENCE_NAME)
417 BLENDER_PDF_FILENAME = "%s.pdf" % REFERENCE_NAME
418 BLENDER_ZIP_FILENAME = "%s.zip" % REFERENCE_NAME
419
420 # -------------------------------SPHINX-----------------------------------------
421
422 SPHINX_THEMES = {'bf': ['blender-org'],  # , 'naiad',
423                  'sphinx': ['agogo',
424                             'basic',
425                             'default',
426                             'epub',
427                             'haiku',
428                             'nature',
429                             'scrolls',
430                             'sphinxdoc',
431                             'traditional']}
432
433 available_themes = SPHINX_THEMES['bf'] + SPHINX_THEMES['sphinx']
434 if ARGS.sphinx_theme not in available_themes:
435     print("Please choose a theme among: %s" % ', '.join(available_themes))
436     sys.exit()
437
438 if ARGS.sphinx_theme in SPHINX_THEMES['bf']:
439     SPHINX_THEME_DIR = os.path.join(ARGS.output_dir, ARGS.sphinx_theme)
440     SPHINX_THEME_SVN_DIR = os.path.join(SCRIPT_DIR, ARGS.sphinx_theme)
441
442 SPHINX_IN = os.path.join(ARGS.output_dir, "sphinx-in")
443 SPHINX_IN_TMP = SPHINX_IN + "-tmp"
444 SPHINX_OUT = os.path.join(ARGS.output_dir, "sphinx-out")
445 if ARGS.sphinx_named_output:
446     SPHINX_OUT += "_%s" % ARGS.sphinx_theme
447
448 # html build
449 if ARGS.sphinx_build:
450     SPHINX_BUILD = ["sphinx-build", SPHINX_IN, SPHINX_OUT]
451
452     if ARGS.log:
453         SPHINX_BUILD_LOG = os.path.join(ARGS.output_dir, ".sphinx-build.log")
454         SPHINX_BUILD = ["sphinx-build",
455                         "-w", SPHINX_BUILD_LOG,
456                         SPHINX_IN, SPHINX_OUT]
457
458 # pdf build
459 if ARGS.sphinx_build_pdf:
460     SPHINX_OUT_PDF = os.path.join(ARGS.output_dir, "sphinx-out_pdf")
461     SPHINX_BUILD_PDF = ["sphinx-build",
462                         "-b", "latex",
463                         SPHINX_IN, SPHINX_OUT_PDF]
464     SPHINX_MAKE_PDF = ["make", "-C", SPHINX_OUT_PDF]
465     SPHINX_MAKE_PDF_STDOUT = None
466
467     if ARGS.log:
468         SPHINX_BUILD_PDF_LOG = os.path.join(ARGS.output_dir, ".sphinx-build_pdf.log")
469         SPHINX_BUILD_PDF = ["sphinx-build", "-b", "latex",
470                             "-w", SPHINX_BUILD_PDF_LOG,
471                             SPHINX_IN, SPHINX_OUT_PDF]
472
473         sphinx_make_pdf_log = os.path.join(ARGS.output_dir, ".latex_make.log")
474         SPHINX_MAKE_PDF_STDOUT = open(sphinx_make_pdf_log, "w", encoding="utf-8")
475
476 # --------------------------------API DUMP--------------------------------------
477
478 # lame, python wont give some access
479 ClassMethodDescriptorType = type(dict.__dict__['fromkeys'])
480 MethodDescriptorType = type(dict.get)
481 GetSetDescriptorType = type(int.real)
482 StaticMethodType = type(staticmethod(lambda: None))
483 from types import (MemberDescriptorType,
484                    MethodType,
485                    )
486
487 _BPY_STRUCT_FAKE = "bpy_struct"
488 _BPY_PROP_COLLECTION_FAKE = "bpy_prop_collection"
489
490 if _BPY_PROP_COLLECTION_FAKE:
491     _BPY_PROP_COLLECTION_ID = ":class:`%s`" % _BPY_PROP_COLLECTION_FAKE
492 else:
493     _BPY_PROP_COLLECTION_ID = "collection"
494
495
496 def is_struct_seq(value):
497     return isinstance(value, tuple) and type(tuple) != tuple and hasattr(value, "n_fields")
498
499
500 def undocumented_message(module_name, type_name, identifier):
501     return "Undocumented"
502
503     """
504     if str(type_name).startswith('<module'):
505         preloadtitle = '%s.%s' % (module_name, identifier)
506     else:
507         preloadtitle = '%s.%s.%s' % (module_name, type_name, identifier)
508     message = ("Undocumented (`contribute "
509                "<http://wiki.blender.org/index.php/"
510                "Dev:2.5/Py/API/Generating_API_Reference/Contribute"
511                "?action=edit"
512                "&section=new"
513                "&preload=Dev:2.5/Py/API/Generating_API_Reference/Contribute/Howto-message"
514                "&preloadtitle=%s>`_)\n\n" % preloadtitle)
515     return message
516     """
517
518
519 def range_str(val):
520     '''
521     Converts values to strings for the range directive.
522     (unused function it seems)
523     '''
524     if val < -10000000:
525         return '-inf'
526     elif val > 10000000:
527         return 'inf'
528     elif type(val) == float:
529         return '%g' % val
530     else:
531         return str(val)
532
533
534 def example_extract_docstring(filepath):
535     file = open(filepath, "r", encoding="utf-8")
536     line = file.readline()
537     line_no = 0
538     text = []
539     if line.startswith('"""'):  # assume nothing here
540         line_no += 1
541     else:
542         file.close()
543         return "", 0
544
545     for line in file.readlines():
546         line_no += 1
547         if line.startswith('"""'):
548             break
549         else:
550             text.append(line.rstrip())
551
552     line_no += 1
553     file.close()
554     return "\n".join(text), line_no
555
556
557 def title_string(text, heading_char, double=False):
558     filler = len(text) * heading_char
559
560     if double:
561         return "%s\n%s\n%s\n\n" % (filler, text, filler)
562     else:
563         return "%s\n%s\n\n" % (text, filler)
564
565
566 def write_example_ref(ident, fw, example_id, ext="py"):
567     if example_id in EXAMPLE_SET:
568
569         # extract the comment
570         filepath = os.path.join("..", "examples", "%s.%s" % (example_id, ext))
571         filepath_full = os.path.join(os.path.dirname(fw.__self__.name), filepath)
572
573         text, line_no = example_extract_docstring(filepath_full)
574
575         for line in text.split("\n"):
576             fw("%s\n" % (ident + line).rstrip())
577         fw("\n")
578
579         fw("%s.. literalinclude:: %s\n" % (ident, filepath))
580         if line_no > 0:
581             fw("%s   :lines: %d-\n" % (ident, line_no))
582         fw("\n")
583         EXAMPLE_SET_USED.add(example_id)
584     else:
585         if bpy.app.debug:
586             BPY_LOGGER.debug("\tskipping example: " + example_id)
587
588     # Support for numbered files bpy.types.Operator -> bpy.types.Operator.1.py
589     i = 1
590     while True:
591         example_id_num = "%s.%d" % (example_id, i)
592         if example_id_num in EXAMPLE_SET:
593             write_example_ref(ident, fw, example_id_num, ext)
594             i += 1
595         else:
596             break
597
598
599 def write_indented_lines(ident, fn, text, strip=True):
600     '''
601     Apply same indentation to all lines in a multilines text.
602     '''
603     if text is None:
604         return
605
606     lines = text.split("\n")
607
608     # strip empty lines from the start/end
609     while lines and not lines[0].strip():
610         del lines[0]
611     while lines and not lines[-1].strip():
612         del lines[-1]
613
614     if strip:
615         # set indentation to <indent>
616         ident_strip = 1000
617         for l in lines:
618             if l.strip():
619                 ident_strip = min(ident_strip, len(l) - len(l.lstrip()))
620         for l in lines:
621             fn(ident + l[ident_strip:] + "\n")
622     else:
623         # add <indent> number of blanks to the current indentation
624         for l in lines:
625             fn(ident + l + "\n")
626
627
628 def pymethod2sphinx(ident, fw, identifier, py_func):
629     '''
630     class method to sphinx
631     '''
632     arg_str = inspect.formatargspec(*inspect.getargspec(py_func))
633     if arg_str.startswith("(self, "):
634         arg_str = "(" + arg_str[7:]
635         func_type = "method"
636     elif arg_str.startswith("(cls, "):
637         arg_str = "(" + arg_str[6:]
638         func_type = "classmethod"
639     else:
640         func_type = "staticmethod"
641
642     fw(ident + ".. %s:: %s%s\n\n" % (func_type, identifier, arg_str))
643     if py_func.__doc__:
644         write_indented_lines(ident + "   ", fw, py_func.__doc__)
645         fw("\n")
646
647
648 def pyfunc2sphinx(ident, fw, module_name, type_name, identifier, py_func, is_class=True):
649     '''
650     function or class method to sphinx
651     '''
652
653     if type(py_func) == MethodType:
654         return
655
656     arg_str = inspect.formatargspec(*inspect.getargspec(py_func))
657
658     if not is_class:
659         func_type = "function"
660
661         # ther rest are class methods
662     elif arg_str.startswith("(self, "):
663         arg_str = "(" + arg_str[7:]
664         func_type = "method"
665     elif arg_str.startswith("(cls, "):
666         arg_str = "(" + arg_str[6:]
667         func_type = "classmethod"
668     else:
669         func_type = "staticmethod"
670
671     fw(ident + ".. %s:: %s%s\n\n" % (func_type, identifier, arg_str))
672     if py_func.__doc__:
673         write_indented_lines(ident + "   ", fw, py_func.__doc__)
674         fw("\n")
675
676     if is_class:
677         write_example_ref(ident + "   ", fw, module_name + "." + type_name + "." + identifier)
678     else:
679         write_example_ref(ident + "   ", fw, module_name + "." + identifier)
680
681
682 def py_descr2sphinx(ident, fw, descr, module_name, type_name, identifier):
683     if identifier.startswith("_"):
684         return
685
686     doc = descr.__doc__
687     if not doc:
688         doc = undocumented_message(module_name, type_name, identifier)
689
690     if type(descr) == GetSetDescriptorType:
691         fw(ident + ".. attribute:: %s\n\n" % identifier)
692         write_indented_lines(ident + "   ", fw, doc, False)
693         fw("\n")
694     elif type(descr) == MemberDescriptorType:  # same as above but use 'data'
695         fw(ident + ".. data:: %s\n\n" % identifier)
696         write_indented_lines(ident + "   ", fw, doc, False)
697         fw("\n")
698     elif type(descr) in {MethodDescriptorType, ClassMethodDescriptorType}:
699         write_indented_lines(ident, fw, doc, False)
700         fw("\n")
701     else:
702         raise TypeError("type was not GetSetDescriptorType, MethodDescriptorType or ClassMethodDescriptorType")
703
704     write_example_ref(ident + "   ", fw, module_name + "." + type_name + "." + identifier)
705     fw("\n")
706
707
708 def py_c_func2sphinx(ident, fw, module_name, type_name, identifier, py_func, is_class=True):
709     '''
710     c defined function to sphinx.
711     '''
712
713     # dump the docstring, assume its formatted correctly
714     if py_func.__doc__:
715         write_indented_lines(ident, fw, py_func.__doc__, False)
716         fw("\n")
717     else:
718         fw(ident + ".. function:: %s()\n\n" % identifier)
719         fw(ident + "   " + undocumented_message(module_name, type_name, identifier))
720
721     if is_class:
722         write_example_ref(ident + "   ", fw, module_name + "." + type_name + "." + identifier)
723     else:
724         write_example_ref(ident + "   ", fw, module_name + "." + identifier)
725
726     fw("\n")
727
728
729 def pyprop2sphinx(ident, fw, identifier, py_prop):
730     '''
731     python property to sphinx
732     '''
733     # readonly properties use "data" directive, variables use "attribute" directive
734     if py_prop.fset is None:
735         fw(ident + ".. data:: %s\n\n" % identifier)
736     else:
737         fw(ident + ".. attribute:: %s\n\n" % identifier)
738     write_indented_lines(ident + "   ", fw, py_prop.__doc__)
739     if py_prop.fset is None:
740         fw(ident + "   (readonly)\n\n")
741     else:
742         fw("\n")
743
744
745 def pymodule2sphinx(basepath, module_name, module, title):
746     import types
747     attribute_set = set()
748     filepath = os.path.join(basepath, module_name + ".rst")
749
750     module_all = getattr(module, "__all__", None)
751     module_dir = sorted(dir(module))
752
753     if module_all:
754         module_dir = module_all
755
756     # TODO - currently only used for classes
757     # grouping support
758     module_grouping = MODULE_GROUPING.get(module_name)
759
760     def module_grouping_index(name):
761         if module_grouping is not None:
762             try:
763                 return module_grouping.index(name)
764             except ValueError:
765                 pass
766         return -1
767
768     def module_grouping_heading(name):
769         if module_grouping is not None:
770             i = module_grouping_index(name) - 1
771             if i >= 0 and type(module_grouping[i]) == tuple:
772                 return module_grouping[i]
773         return None, None
774
775     def module_grouping_sort_key(name):
776         return module_grouping_index(name)
777     # done grouping support
778
779     file = open(filepath, "w", encoding="utf-8")
780
781     fw = file.write
782
783     fw(title_string("%s (%s)" % (title, module_name), "="))
784
785     fw(".. module:: %s\n\n" % module_name)
786
787     if module.__doc__:
788         # Note, may contain sphinx syntax, dont mangle!
789         fw(module.__doc__.strip())
790         fw("\n\n")
791
792     write_example_ref("", fw, module_name)
793
794     # write submodules
795     # we could also scan files but this ensures __all__ is used correctly
796     if module_all is not None:
797         submod_name = None
798         submod = None
799         submod_ls = []
800         for submod_name in module_all:
801             ns = {}
802             exec_str = "from %s import %s as submod" % (module.__name__, submod_name)
803             exec(exec_str, ns, ns)
804             submod = ns["submod"]
805             if type(submod) == types.ModuleType:
806                 submod_ls.append((submod_name, submod))
807
808         del submod_name
809         del submod
810
811         if submod_ls:
812             fw(".. toctree::\n")
813             fw("   :maxdepth: 1\n\n")
814
815             for submod_name, submod in submod_ls:
816                 submod_name_full = "%s.%s" % (module_name, submod_name)
817                 fw("   %s.rst\n\n" % submod_name_full)
818
819                 pymodule2sphinx(basepath, submod_name_full, submod, "%s submodule" % module_name)
820         del submod_ls
821     # done writing submodules!
822
823     # write members of the module
824     # only tested with PyStructs which are not exactly modules
825     for key, descr in sorted(type(module).__dict__.items()):
826         if key.startswith("__"):
827             continue
828         # naughty, we also add getset's into PyStructs, this is not typical py but also not incorrect.
829
830         # type_name is only used for examples and messages
831         type_name = str(type(module)).strip("<>").split(" ", 1)[-1][1:-1]  # "<class 'bpy.app.handlers'>" --> bpy.app.handlers
832         if type(descr) == types.GetSetDescriptorType:
833             py_descr2sphinx("", fw, descr, module_name, type_name, key)
834             attribute_set.add(key)
835     descr_sorted = []
836     for key, descr in sorted(type(module).__dict__.items()):
837         if key.startswith("__"):
838             continue
839
840         if type(descr) == MemberDescriptorType:
841             if descr.__doc__:
842                 value = getattr(module, key, None)
843
844                 value_type = type(value)
845                 descr_sorted.append((key, descr, value, type(value)))
846     # sort by the valye type
847     descr_sorted.sort(key=lambda descr_data: str(descr_data[3]))
848     for key, descr, value, value_type in descr_sorted:
849
850         # must be documented as a submodule
851         if is_struct_seq(value):
852             continue
853
854         type_name = value_type.__name__
855         py_descr2sphinx("", fw, descr, module_name, type_name, key)
856
857         attribute_set.add(key)
858
859     del key, descr, descr_sorted
860
861     classes = []
862     submodules = []
863
864     # use this list so we can sort by type
865     module_dir_value_type = []
866
867     for attribute in module_dir:
868         if attribute.startswith("_"):
869             continue
870
871         if attribute in attribute_set:
872             continue
873
874         if attribute.startswith("n_"):  # annoying exception, needed for bpy.app
875             continue
876
877         # workaround for bpy.app documenting .index() and .count()
878         if isinstance(module, tuple) and hasattr(tuple, attribute):
879             continue
880
881         value = getattr(module, attribute)
882
883         module_dir_value_type.append((attribute, value, type(value)))
884
885     # sort by str of each type
886     # this way lists, functions etc are grouped.
887     module_dir_value_type.sort(key=lambda triple: str(triple[2]))
888
889     for attribute, value, value_type in module_dir_value_type:
890         if value_type == types.FunctionType:
891             pyfunc2sphinx("", fw, module_name, None, attribute, value, is_class=False)
892         elif value_type in {types.BuiltinMethodType, types.BuiltinFunctionType}:  # both the same at the moment but to be future proof
893             # note: can't get args from these, so dump the string as is
894             # this means any module used like this must have fully formatted docstrings.
895             py_c_func2sphinx("", fw, module_name, None, attribute, value, is_class=False)
896         elif value_type == type:
897             classes.append((attribute, value))
898         elif issubclass(value_type, types.ModuleType):
899             submodules.append((attribute, value))
900         elif issubclass(value_type, (bool, int, float, str, tuple)):
901             # constant, not much fun we can do here except to list it.
902             # TODO, figure out some way to document these!
903             fw(".. data:: %s\n\n" % attribute)
904             write_indented_lines("   ", fw, "constant value %s" % repr(value), False)
905             fw("\n")
906         else:
907             BPY_LOGGER.debug("\tnot documenting %s.%s of %r type" % (module_name, attribute, value_type.__name__))
908             continue
909
910         attribute_set.add(attribute)
911         # TODO, more types...
912     del module_dir_value_type
913
914     # TODO, bpy_extras does this already, mathutils not.
915     '''
916     if submodules:
917         fw("\n"
918            "**********\n"
919            "Submodules\n"
920            "**********\n"
921            "\n"
922            )
923         for attribute, submod in submodules:
924             fw("* :mod:`%s.%s`\n" % (module_name, attribute))
925         fw("\n")
926     '''
927
928     if module_grouping is not None:
929         classes.sort(key=lambda pair: module_grouping_sort_key(pair[0]))
930
931     # write collected classes now
932     for (type_name, value) in classes:
933
934         if module_grouping is not None:
935             heading, heading_char = module_grouping_heading(type_name)
936             if heading:
937                 fw(title_string(heading, heading_char))
938
939         # May need to be its own function
940         if value.__doc__:
941             if value.__doc__.startswith(".. class::"):
942                 fw(value.__doc__)
943             else:
944                 fw(".. class:: %s\n\n" % type_name)
945                 write_indented_lines("   ", fw, value.__doc__, False)
946         else:
947             fw(".. class:: %s\n\n" % type_name)
948         fw("\n")
949
950         write_example_ref("   ", fw, module_name + "." + type_name)
951
952         descr_items = [(key, descr) for key, descr in sorted(value.__dict__.items()) if not key.startswith("__")]
953
954         for key, descr in descr_items:
955             if type(descr) == ClassMethodDescriptorType:
956                 py_descr2sphinx("   ", fw, descr, module_name, type_name, key)
957
958         for key, descr in descr_items:
959             if type(descr) == MethodDescriptorType:
960                 py_descr2sphinx("   ", fw, descr, module_name, type_name, key)
961
962         for key, descr in descr_items:
963             if type(descr) == GetSetDescriptorType:
964                 py_descr2sphinx("   ", fw, descr, module_name, type_name, key)
965
966         for key, descr in descr_items:
967             if type(descr) == StaticMethodType:
968                 descr = getattr(value, key)
969                 write_indented_lines("   ", fw, descr.__doc__ or "Undocumented", False)
970                 fw("\n")
971
972         fw("\n\n")
973
974     file.close()
975
976 # Changes in blender will force errors here
977 context_type_map = {
978     "active_base": ("ObjectBase", False),
979     "active_bone": ("EditBone", False),
980     "active_gpencil_frame": ("GreasePencilLayer", True),
981     "active_gpencil_layer": ("GPencilLayer", True),
982     "active_node": ("Node", False),
983     "active_object": ("Object", False),
984     "active_operator": ("Operator", False),
985     "active_pose_bone": ("PoseBone", False),
986     "armature": ("Armature", False),
987     "bone": ("Bone", False),
988     "brush": ("Brush", False),
989     "camera": ("Camera", False),
990     "cloth": ("ClothModifier", False),
991     "collision": ("CollisionModifier", False),
992     "curve": ("Curve", False),
993     "dynamic_paint": ("DynamicPaintModifier", False),
994     "edit_bone": ("EditBone", False),
995     "edit_image": ("Image", False),
996     "edit_mask": ("Mask", False),
997     "edit_movieclip": ("MovieClip", False),
998     "edit_object": ("Object", False),
999     "edit_text": ("Text", False),
1000     "editable_bones": ("EditBone", True),
1001     "editable_gpencil_layers": ("GPencilLayer", True),
1002     "editable_gpencil_strokes": ("GPencilStroke", True),
1003     "fluid": ("FluidSimulationModifier", False),
1004     "gpencil_data": ("GreasePencel", False),
1005     "gpencil_data_owner": ("ID", False),
1006     "image_paint_object": ("Object", False),
1007     "lamp": ("Lamp", False),
1008     "lattice": ("Lattice", False),
1009     "line_style": ("FreestyleLineStyle", False),
1010     "material": ("Material", False),
1011     "material_slot": ("MaterialSlot", False),
1012     "mesh": ("Mesh", False),
1013     "meta_ball": ("MetaBall", False),
1014     "object": ("Object", False),
1015     "particle_edit_object": ("Object", False),
1016     "particle_settings": ("ParticleSettings", False),
1017     "particle_system": ("ParticleSystem", False),
1018     "particle_system_editable": ("ParticleSystem", False),
1019     "pose_bone": ("PoseBone", False),
1020     "scene": ("Scene", False),
1021     "sculpt_object": ("Object", False),
1022     "selectable_bases": ("ObjectBase", True),
1023     "selectable_objects": ("Object", True),
1024     "selected_bases": ("ObjectBase", True),
1025     "selected_bones": ("EditBone", True),
1026     "selected_editable_bases": ("ObjectBase", True),
1027     "selected_editable_bones": ("EditBone", True),
1028     "selected_editable_objects": ("Object", True),
1029     "selected_editable_sequences": ("Sequence", True),
1030     "selected_nodes": ("Node", True),
1031     "selected_objects": ("Object", True),
1032     "selected_pose_bones": ("PoseBone", True),
1033     "selected_sequences": ("Sequence", True),
1034     "sequences": ("Sequence", True),
1035     "smoke": ("SmokeModifier", False),
1036     "soft_body": ("SoftBodyModifier", False),
1037     "speaker": ("Speaker", False),
1038     "texture": ("Texture", False),
1039     "texture_slot": ("MaterialTextureSlot", False),
1040     "texture_user": ("ID", False),
1041     "texture_user_property": ("Property", False),
1042     "vertex_paint_object": ("Object", False),
1043     "visible_bases": ("ObjectBase", True),
1044     "visible_bones": ("EditBone", True),
1045     "visible_gpencil_layers": ("GPencilLayer", True),
1046     "visible_objects": ("Object", True),
1047     "visible_pose_bones": ("PoseBone", True),
1048     "weight_paint_object": ("Object", False),
1049     "world": ("World", False),
1050 }
1051
1052
1053 def pycontext2sphinx(basepath):
1054     # Only use once. very irregular
1055
1056     filepath = os.path.join(basepath, "bpy.context.rst")
1057     file = open(filepath, "w", encoding="utf-8")
1058     fw = file.write
1059     fw(title_string("Context Access (bpy.context)", "="))
1060     fw(".. module:: bpy.context\n")
1061     fw("\n")
1062     fw("The context members available depend on the area of blender which is currently being accessed.\n")
1063     fw("\n")
1064     fw("Note that all context values are readonly, but may be modified through the data api or by running operators\n\n")
1065
1066     def write_contex_cls():
1067
1068         fw(title_string("Global Context", "-"))
1069         fw("These properties are avilable in any contexts.\n\n")
1070
1071         # very silly. could make these global and only access once.
1072         # structs, funcs, ops, props = rna_info.BuildRNAInfo()
1073         structs, funcs, ops, props = rna_info_BuildRNAInfo_cache()
1074         struct = structs[("", "Context")]
1075         struct_blacklist = RNA_BLACKLIST.get(struct.identifier, ())
1076         del structs, funcs, ops, props
1077
1078         sorted_struct_properties = struct.properties[:]
1079         sorted_struct_properties.sort(key=lambda prop: prop.identifier)
1080
1081         # First write RNA
1082         for prop in sorted_struct_properties:
1083             # support blacklisting props
1084             if prop.identifier in struct_blacklist:
1085                 continue
1086
1087             type_descr = prop.get_type_description(class_fmt=":class:`bpy.types.%s`", collection_id=_BPY_PROP_COLLECTION_ID)
1088             fw(".. data:: %s\n\n" % prop.identifier)
1089             if prop.description:
1090                 fw("   %s\n\n" % prop.description)
1091
1092             # special exception, cant use genric code here for enums
1093             if prop.type == "enum":
1094                 enum_text = pyrna_enum2sphinx(prop)
1095                 if enum_text:
1096                     write_indented_lines("   ", fw, enum_text)
1097                     fw("\n")
1098                 del enum_text
1099             # end enum exception
1100
1101             fw("   :type: %s\n\n" % type_descr)
1102
1103     write_contex_cls()
1104     del write_contex_cls
1105     # end
1106
1107     # nasty, get strings directly from blender because there is no other way to get it
1108     import ctypes
1109
1110     context_strings = (
1111         "screen_context_dir",
1112         "view3d_context_dir",
1113         "buttons_context_dir",
1114         "image_context_dir",
1115         "node_context_dir",
1116         "text_context_dir",
1117         "clip_context_dir",
1118         "sequencer_context_dir",
1119     )
1120
1121     unique = set()
1122     blend_cdll = ctypes.CDLL("")
1123     for ctx_str in context_strings:
1124         subsection = "%s Context" % ctx_str.split("_")[0].title()
1125         fw("\n%s\n%s\n\n" % (subsection, (len(subsection) * '-')))
1126
1127         attr = ctypes.addressof(getattr(blend_cdll, ctx_str))
1128         c_char_p_p = ctypes.POINTER(ctypes.c_char_p)
1129         char_array = c_char_p_p.from_address(attr)
1130         i = 0
1131         while char_array[i] is not None:
1132             member = ctypes.string_at(char_array[i]).decode(encoding="ascii")
1133             fw(".. data:: %s\n\n" % member)
1134             member_type, is_seq = context_type_map[member]
1135             fw("   :type: %s :class:`bpy.types.%s`\n\n" % ("sequence of " if is_seq else "", member_type))
1136             unique.add(member)
1137             i += 1
1138
1139     # generate typemap...
1140     # for member in sorted(unique):
1141     #     print('        "%s": ("", False),' % member)
1142     if len(context_type_map) > len(unique):
1143         raise Exception("Some types are not used: %s" % str([member for member in context_type_map if member not in unique]))
1144     else:
1145         pass  # will have raised an error above
1146
1147     file.close()
1148
1149
1150 def pyrna_enum2sphinx(prop, use_empty_descriptions=False):
1151     """ write a bullet point list of enum + descrptons
1152     """
1153
1154     if use_empty_descriptions:
1155         ok = True
1156     else:
1157         ok = False
1158         for identifier, name, description in prop.enum_items:
1159             if description:
1160                 ok = True
1161                 break
1162
1163     if ok:
1164         return "".join(["* ``%s`` %s.\n" %
1165                         (identifier,
1166                          ", ".join(val for val in (name, description) if val),
1167                          )
1168                         for identifier, name, description in prop.enum_items
1169                         ])
1170     else:
1171         return ""
1172
1173
1174 def pyrna2sphinx(basepath):
1175     """ bpy.types and bpy.ops
1176     """
1177     # structs, funcs, ops, props = rna_info.BuildRNAInfo()
1178     structs, funcs, ops, props = rna_info_BuildRNAInfo_cache()
1179
1180     if FILTER_BPY_TYPES is not None:
1181         structs = {k: v for k, v in structs.items() if k[1] in FILTER_BPY_TYPES}
1182
1183     if FILTER_BPY_OPS is not None:
1184         ops = {k: v for k, v in ops.items() if v.module_name in FILTER_BPY_OPS}
1185
1186     def write_param(ident, fw, prop, is_return=False):
1187         if is_return:
1188             id_name = "return"
1189             id_type = "rtype"
1190             kwargs = {"as_ret": True}
1191             identifier = ""
1192         else:
1193             id_name = "arg"
1194             id_type = "type"
1195             kwargs = {"as_arg": True}
1196             identifier = " %s" % prop.identifier
1197
1198         kwargs["class_fmt"] = ":class:`%s`"
1199
1200         kwargs["collection_id"] = _BPY_PROP_COLLECTION_ID
1201
1202         type_descr = prop.get_type_description(**kwargs)
1203
1204         enum_text = pyrna_enum2sphinx(prop)
1205
1206         if prop.name or prop.description or enum_text:
1207             fw(ident + ":%s%s:\n\n" % (id_name, identifier))
1208
1209             if prop.name or prop.description:
1210                 fw(ident + "   " + ", ".join(val for val in (prop.name, prop.description) if val) + "\n\n")
1211
1212             # special exception, cant use genric code here for enums
1213             if enum_text:
1214                 write_indented_lines(ident + "   ", fw, enum_text)
1215                 fw("\n")
1216             del enum_text
1217             # end enum exception
1218
1219         fw(ident + ":%s%s: %s\n" % (id_type, identifier, type_descr))
1220
1221     def write_struct(struct):
1222         #if not struct.identifier.startswith("Sc") and not struct.identifier.startswith("I"):
1223         #    return
1224
1225         #if not struct.identifier == "Object":
1226         #    return
1227
1228         filepath = os.path.join(basepath, "bpy.types.%s.rst" % struct.identifier)
1229         file = open(filepath, "w", encoding="utf-8")
1230         fw = file.write
1231
1232         base_id = getattr(struct.base, "identifier", "")
1233         struct_id = struct.identifier
1234
1235         if _BPY_STRUCT_FAKE:
1236             if not base_id:
1237                 base_id = _BPY_STRUCT_FAKE
1238
1239         if base_id:
1240             title = "%s(%s)" % (struct_id, base_id)
1241         else:
1242             title = struct_id
1243
1244         fw(title_string(title, "="))
1245
1246         fw(".. module:: bpy.types\n\n")
1247
1248         # docs first?, ok
1249         write_example_ref("", fw, "bpy.types.%s" % struct_id)
1250
1251         base_ids = [base.identifier for base in struct.get_bases()]
1252
1253         if _BPY_STRUCT_FAKE:
1254             base_ids.append(_BPY_STRUCT_FAKE)
1255
1256         base_ids.reverse()
1257
1258         if base_ids:
1259             if len(base_ids) > 1:
1260                 fw("base classes --- ")
1261             else:
1262                 fw("base class --- ")
1263
1264             fw(", ".join((":class:`%s`" % base_id) for base_id in base_ids))
1265             fw("\n\n")
1266
1267         subclass_ids = [s.identifier for s in structs.values() if s.base is struct if not rna_info.rna_id_ignore(s.identifier)]
1268         if subclass_ids:
1269             fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in subclass_ids) + "\n\n")
1270
1271         base_id = getattr(struct.base, "identifier", "")
1272
1273         if _BPY_STRUCT_FAKE:
1274             if not base_id:
1275                 base_id = _BPY_STRUCT_FAKE
1276
1277         if base_id:
1278             fw(".. class:: %s(%s)\n\n" % (struct_id, base_id))
1279         else:
1280             fw(".. class:: %s\n\n" % struct_id)
1281
1282         fw("   %s\n\n" % struct.description)
1283
1284         # properties sorted in alphabetical order
1285         sorted_struct_properties = struct.properties[:]
1286         sorted_struct_properties.sort(key=lambda prop: prop.identifier)
1287
1288         # support blacklisting props
1289         struct_blacklist = RNA_BLACKLIST.get(struct_id, ())
1290
1291         for prop in sorted_struct_properties:
1292
1293             # support blacklisting props
1294             if prop.identifier in struct_blacklist:
1295                 continue
1296
1297             type_descr = prop.get_type_description(class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID)
1298             # readonly properties use "data" directive, variables properties use "attribute" directive
1299             if 'readonly' in type_descr:
1300                 fw("   .. data:: %s\n\n" % prop.identifier)
1301             else:
1302                 fw("   .. attribute:: %s\n\n" % prop.identifier)
1303             if prop.description:
1304                 fw("      %s\n\n" % prop.description)
1305
1306             # special exception, cant use genric code here for enums
1307             if prop.type == "enum":
1308                 enum_text = pyrna_enum2sphinx(prop)
1309                 if enum_text:
1310                     write_indented_lines("      ", fw, enum_text)
1311                     fw("\n")
1312                 del enum_text
1313             # end enum exception
1314
1315             fw("      :type: %s\n\n" % type_descr)
1316
1317         # python attributes
1318         py_properties = struct.get_py_properties()
1319         py_prop = None
1320         for identifier, py_prop in py_properties:
1321             pyprop2sphinx("   ", fw, identifier, py_prop)
1322         del py_properties, py_prop
1323
1324         for func in struct.functions:
1325             args_str = ", ".join(prop.get_arg_default(force=False) for prop in func.args)
1326
1327             fw("   .. %s:: %s(%s)\n\n" % ("classmethod" if func.is_classmethod else "method", func.identifier, args_str))
1328             fw("      %s\n\n" % func.description)
1329
1330             for prop in func.args:
1331                 write_param("      ", fw, prop)
1332
1333             if len(func.return_values) == 1:
1334                 write_param("      ", fw, func.return_values[0], is_return=True)
1335             elif func.return_values:  # multiple return values
1336                 fw("      :return (%s):\n" % ", ".join(prop.identifier for prop in func.return_values))
1337                 for prop in func.return_values:
1338                     # TODO, pyrna_enum2sphinx for multiple return values... actually dont think we even use this but still!!!
1339                     type_descr = prop.get_type_description(as_ret=True, class_fmt=":class:`%s`", collection_id=_BPY_PROP_COLLECTION_ID)
1340                     descr = prop.description
1341                     if not descr:
1342                         descr = prop.name
1343                     fw("         `%s`, %s, %s\n\n" % (prop.identifier, descr, type_descr))
1344
1345             write_example_ref("      ", fw, "bpy.types." + struct_id + "." + func.identifier)
1346
1347             fw("\n")
1348
1349         # python methods
1350         py_funcs = struct.get_py_functions()
1351         py_func = None
1352
1353         for identifier, py_func in py_funcs:
1354             pyfunc2sphinx("   ", fw, "bpy.types", struct_id, identifier, py_func, is_class=True)
1355         del py_funcs, py_func
1356
1357         py_funcs = struct.get_py_c_functions()
1358         py_func = None
1359
1360         for identifier, py_func in py_funcs:
1361             py_c_func2sphinx("   ", fw, "bpy.types", struct_id, identifier, py_func, is_class=True)
1362
1363         lines = []
1364
1365         if struct.base or _BPY_STRUCT_FAKE:
1366             bases = list(reversed(struct.get_bases()))
1367
1368             # props
1369             del lines[:]
1370
1371             if _BPY_STRUCT_FAKE:
1372                 descr_items = [(key, descr) for key, descr in sorted(bpy.types.Struct.__bases__[0].__dict__.items()) if not key.startswith("__")]
1373
1374             if _BPY_STRUCT_FAKE:
1375                 for key, descr in descr_items:
1376                     if type(descr) == GetSetDescriptorType:
1377                         lines.append("   * :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key))
1378
1379             for base in bases:
1380                 for prop in base.properties:
1381                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, prop.identifier))
1382
1383                 for identifier, py_prop in base.get_py_properties():
1384                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, identifier))
1385
1386                 for identifier, py_prop in base.get_py_properties():
1387                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, identifier))
1388
1389             if lines:
1390                 fw(".. rubric:: Inherited Properties\n\n")
1391
1392                 fw(".. hlist::\n")
1393                 fw("   :columns: 2\n\n")
1394
1395                 for line in lines:
1396                     fw(line)
1397                 fw("\n")
1398
1399             # funcs
1400             del lines[:]
1401
1402             if _BPY_STRUCT_FAKE:
1403                 for key, descr in descr_items:
1404                     if type(descr) == MethodDescriptorType:
1405                         lines.append("   * :class:`%s.%s`\n" % (_BPY_STRUCT_FAKE, key))
1406
1407             for base in bases:
1408                 for func in base.functions:
1409                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, func.identifier))
1410                 for identifier, py_func in base.get_py_functions():
1411                     lines.append("   * :class:`%s.%s`\n" % (base.identifier, identifier))
1412
1413             if lines:
1414                 fw(".. rubric:: Inherited Functions\n\n")
1415
1416                 fw(".. hlist::\n")
1417                 fw("   :columns: 2\n\n")
1418
1419                 for line in lines:
1420                     fw(line)
1421                 fw("\n")
1422
1423             del lines[:]
1424
1425         if struct.references:
1426             # use this otherwise it gets in the index for a normal heading.
1427             fw(".. rubric:: References\n\n")
1428
1429             fw(".. hlist::\n")
1430             fw("   :columns: 2\n\n")
1431
1432             # context does its own thing
1433             # "active_base": ("ObjectBase", False),
1434             for ref_attr, (ref_type, ref_is_seq) in sorted(context_type_map.items()):
1435                 if ref_type == struct_id:
1436                     fw("   * :mod:`bpy.context.%s`\n" % ref_attr)
1437             del ref_attr, ref_type, ref_is_seq
1438
1439             for ref in struct.references:
1440                 ref_split = ref.split(".")
1441                 if len(ref_split) > 2:
1442                     ref = ref_split[-2] + "." + ref_split[-1]
1443                 fw("   * :class:`%s`\n" % ref)
1444             fw("\n")
1445
1446         # docs last?, disable for now
1447         # write_example_ref("", fw, "bpy.types.%s" % struct_id)
1448         file.close()
1449
1450     if "bpy.types" not in EXCLUDE_MODULES:
1451         for struct in structs.values():
1452             # TODO, rna_info should filter these out!
1453             if "_OT_" in struct.identifier:
1454                 continue
1455             write_struct(struct)
1456
1457         def fake_bpy_type(class_value, class_name, descr_str, use_subclasses=True):
1458             filepath = os.path.join(basepath, "bpy.types.%s.rst" % class_name)
1459             file = open(filepath, "w", encoding="utf-8")
1460             fw = file.write
1461
1462             fw(title_string(class_name, "="))
1463
1464             fw(".. module:: bpy.types\n")
1465             fw("\n")
1466
1467             if use_subclasses:
1468                 subclass_ids = [s.identifier for s in structs.values() if s.base is None if not rna_info.rna_id_ignore(s.identifier)]
1469                 if subclass_ids:
1470                     fw("subclasses --- \n" + ", ".join((":class:`%s`" % s) for s in sorted(subclass_ids)) + "\n\n")
1471
1472             fw(".. class:: %s\n\n" % class_name)
1473             fw("   %s\n\n" % descr_str)
1474             fw("   .. note::\n\n")
1475             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)
1476
1477             descr_items = [(key, descr) for key, descr in sorted(class_value.__dict__.items()) if not key.startswith("__")]
1478
1479             for key, descr in descr_items:
1480                 if type(descr) == MethodDescriptorType:  # GetSetDescriptorType, GetSetDescriptorType's are not documented yet
1481                     py_descr2sphinx("   ", fw, descr, "bpy.types", class_name, key)
1482
1483             for key, descr in descr_items:
1484                 if type(descr) == GetSetDescriptorType:
1485                     py_descr2sphinx("   ", fw, descr, "bpy.types", class_name, key)
1486             file.close()
1487
1488         # write fake classes
1489         if _BPY_STRUCT_FAKE:
1490             class_value = bpy.types.Struct.__bases__[0]
1491             fake_bpy_type(class_value, _BPY_STRUCT_FAKE, "built-in base class for all classes in bpy.types.", use_subclasses=True)
1492
1493         if _BPY_PROP_COLLECTION_FAKE:
1494             class_value = bpy.data.objects.__class__
1495             fake_bpy_type(class_value, _BPY_PROP_COLLECTION_FAKE, "built-in class used for all collections.", use_subclasses=False)
1496
1497     # operators
1498     def write_ops():
1499         API_BASEURL = "http://svn.blender.org/svnroot/bf-blender/trunk/blender/release/scripts"
1500         API_BASEURL_ADDON = "http://svn.blender.org/svnroot/bf-extensions/trunk/py/scripts"
1501         API_BASEURL_ADDON_CONTRIB = "http://svn.blender.org/svnroot/bf-extensions/contrib/py/scripts"
1502
1503         op_modules = {}
1504         for op in ops.values():
1505             op_modules.setdefault(op.module_name, []).append(op)
1506         del op
1507
1508         for op_module_name, ops_mod in op_modules.items():
1509             filepath = os.path.join(basepath, "bpy.ops.%s.rst" % op_module_name)
1510             file = open(filepath, "w", encoding="utf-8")
1511             fw = file.write
1512
1513             title = "%s Operators" % op_module_name.replace("_", " ").title()
1514
1515             fw(title_string(title, "="))
1516
1517             fw(".. module:: bpy.ops.%s\n\n" % op_module_name)
1518
1519             ops_mod.sort(key=lambda op: op.func_name)
1520
1521             for op in ops_mod:
1522                 args_str = ", ".join(prop.get_arg_default(force=True) for prop in op.args)
1523                 fw(".. function:: %s(%s)\n\n" % (op.func_name, args_str))
1524
1525                 # if the description isn't valid, we output the standard warning
1526                 # with a link to the wiki so that people can help
1527                 if not op.description or op.description == "(undocumented operator)":
1528                     operator_description = undocumented_message('bpy.ops', op.module_name, op.func_name)
1529                 else:
1530                     operator_description = op.description
1531
1532                 fw("   %s\n\n" % operator_description)
1533                 for prop in op.args:
1534                     write_param("   ", fw, prop)
1535                 if op.args:
1536                     fw("\n")
1537
1538                 location = op.get_location()
1539                 if location != (None, None):
1540                     if location[0].startswith("addons_contrib" + os.sep):
1541                         url_base = API_BASEURL_ADDON_CONTRIB
1542                     elif location[0].startswith("addons" + os.sep):
1543                         url_base = API_BASEURL_ADDON
1544                     else:
1545                         url_base = API_BASEURL
1546
1547                     fw("   :file: `%s <%s/%s>`_:%d\n\n" % (location[0],
1548                                                            url_base,
1549                                                            location[0],
1550                                                            location[1]))
1551
1552             file.close()
1553
1554     if "bpy.ops" not in EXCLUDE_MODULES:
1555         write_ops()
1556
1557
1558 def write_sphinx_conf_py(basepath):
1559     '''
1560     Write sphinx's conf.py
1561     '''
1562     filepath = os.path.join(basepath, "conf.py")
1563     file = open(filepath, "w", encoding="utf-8")
1564     fw = file.write
1565
1566     fw("project = 'Blender'\n")
1567     # fw("master_doc = 'index'\n")
1568     fw("copyright = u'Blender Foundation'\n")
1569     fw("version = '%s - API'\n" % BLENDER_VERSION_DOTS)
1570     fw("release = '%s - API'\n" % BLENDER_VERSION_DOTS)
1571
1572     if ARGS.sphinx_theme != 'default':
1573         fw("html_theme = '%s'\n" % ARGS.sphinx_theme)
1574
1575     if ARGS.sphinx_theme in SPHINX_THEMES['bf']:
1576         fw("html_theme_path = ['../']\n")
1577         # copied with the theme, exclude else we get an error [#28873]
1578         fw("html_favicon = 'favicon.ico'\n")    # in <theme>/static/
1579
1580     # not helpful since the source is generated, adds to upload size.
1581     fw("html_copy_source = False\n")
1582     fw("\n")
1583
1584     # needed for latex, pdf gen
1585     fw("latex_documents = [ ('contents', 'contents.tex', 'Blender Index', 'Blender Foundation', 'manual'), ]\n")
1586     fw("latex_paper_size = 'a4paper'\n")
1587     file.close()
1588
1589
1590 def execfile(filepath):
1591     global_namespace = {"__file__": filepath, "__name__": "__main__"}
1592     file_handle = open(filepath)
1593     exec(compile(file_handle.read(), filepath, 'exec'), global_namespace)
1594     file_handle.close()
1595
1596
1597 def write_rst_contents(basepath):
1598     '''
1599     Write the rst file of the main page, needed for sphinx (index.html)
1600     '''
1601     filepath = os.path.join(basepath, "contents.rst")
1602     file = open(filepath, "w", encoding="utf-8")
1603     fw = file.write
1604
1605     fw(title_string("Blender Documentation Contents", "%", double=True))
1606     fw("\n")
1607     fw("Welcome, this document is an API reference for Blender %s, built %s.\n" % (BLENDER_VERSION_DOTS, BLENDER_DATE))
1608     fw("\n")
1609
1610     # fw("`A PDF version of this document is also available <%s>`_\n" % BLENDER_PDF_FILENAME)
1611     fw("`A compressed ZIP file of this site is available <%s>`_\n" % BLENDER_ZIP_FILENAME)
1612
1613     fw("\n")
1614
1615     if not EXCLUDE_INFO_DOCS:
1616         fw(title_string("Blender/Python Documentation", "=", double=True))
1617
1618         fw(".. toctree::\n")
1619         fw("   :maxdepth: 1\n\n")
1620         for info, info_desc in INFO_DOCS:
1621             fw("   %s <%s>\n\n" % (info_desc, info))
1622         fw("\n")
1623
1624     fw(title_string("Application Modules", "=", double=True))
1625     fw(".. toctree::\n")
1626     fw("   :maxdepth: 1\n\n")
1627
1628     app_modules = (
1629         "bpy.context",  # note: not actually a module
1630         "bpy.data",     # note: not actually a module
1631         "bpy.ops",
1632         "bpy.types",
1633
1634         # py modules
1635         "bpy.utils",
1636         "bpy.path",
1637         "bpy.app",
1638         "bpy.app.handlers",
1639         "bpy.app.translations",
1640
1641         # C modules
1642         "bpy.props",
1643         )
1644
1645     for mod in app_modules:
1646         if mod not in EXCLUDE_MODULES:
1647             fw("   %s\n\n" % mod)
1648
1649     fw(title_string("Standalone Modules", "=", double=True))
1650     fw(".. toctree::\n")
1651     fw("   :maxdepth: 1\n\n")
1652
1653     standalone_modules = (
1654         # mathutils
1655         "mathutils", "mathutils.geometry", "mathutils.kdtree", "mathutils.noise",
1656         # misc
1657         "freestyle", "bgl", "blf", "gpu", "aud", "bpy_extras",
1658         # bmesh, submodules are in own page
1659         "bmesh",
1660         )
1661
1662     for mod in standalone_modules:
1663         if mod not in EXCLUDE_MODULES:
1664             fw("   %s\n\n" % mod)
1665
1666     # special case, this 'bmesh.ops.rst' is extracted from C source
1667     if "bmesh.ops" not in EXCLUDE_MODULES:
1668         execfile(os.path.join(SCRIPT_DIR, "rst_from_bmesh_opdefines.py"))
1669
1670     # game engine
1671     if "bge" not in EXCLUDE_MODULES:
1672         fw(title_string("Game Engine Modules", "=", double=True))
1673         fw(".. toctree::\n")
1674         fw("   :maxdepth: 1\n\n")
1675         fw("   bge.types.rst\n\n")
1676         fw("   bge.logic.rst\n\n")
1677         fw("   bge.render.rst\n\n")
1678         fw("   bge.texture.rst\n\n")
1679         fw("   bge.events.rst\n\n")
1680         fw("   bge.constraints.rst\n\n")
1681
1682     # rna generated change log
1683     fw(title_string("API Info", "=", double=True))
1684     fw(".. toctree::\n")
1685     fw("   :maxdepth: 1\n\n")
1686     fw("   change_log.rst\n\n")
1687
1688     fw("\n")
1689     fw("\n")
1690     fw(".. note:: The Blender Python API has areas which are still in development.\n")
1691     fw("   \n")
1692     fw("   The following areas are subject to change.\n")
1693     fw("      * operator behavior, names and arguments\n")
1694     fw("      * mesh creation and editing functions\n")
1695     fw("   \n")
1696     fw("   These parts of the API are relatively stable and are unlikely to change significantly\n")
1697     fw("      * data API, access to attributes of blender data such as mesh verts, material color, timeline frames and scene objects\n")
1698     fw("      * user interface functions for defining buttons, creation of menus, headers, panels\n")
1699     fw("      * render engine integration\n")
1700     fw("      * modules: bgl, mathutils & game engine.\n")
1701     fw("\n")
1702
1703     file.close()
1704
1705
1706 def write_rst_bpy(basepath):
1707     '''
1708     Write rst file of bpy module (disabled by default)
1709     '''
1710     if ARGS.bpy:
1711         filepath = os.path.join(basepath, "bpy.rst")
1712         file = open(filepath, "w", encoding="utf-8")
1713         fw = file.write
1714
1715         fw("\n")
1716
1717         title = ":mod:`bpy` --- Blender Python Module"
1718
1719         fw(title_string(title, "="))
1720
1721         fw(".. module:: bpy.types\n\n")
1722         file.close()
1723
1724
1725 def write_rst_types_index(basepath):
1726     '''
1727     Write the rst file of bpy.types module (index)
1728     '''
1729     if "bpy.types" not in EXCLUDE_MODULES:
1730         filepath = os.path.join(basepath, "bpy.types.rst")
1731         file = open(filepath, "w", encoding="utf-8")
1732         fw = file.write
1733         fw(title_string("Types (bpy.types)", "="))
1734         fw(".. toctree::\n")
1735         fw("   :glob:\n\n")
1736         fw("   bpy.types.*\n\n")
1737         file.close()
1738
1739
1740 def write_rst_ops_index(basepath):
1741     '''
1742     Write the rst file of bpy.ops module (index)
1743     '''
1744     if "bpy.ops" not in EXCLUDE_MODULES:
1745         filepath = os.path.join(basepath, "bpy.ops.rst")
1746         file = open(filepath, "w", encoding="utf-8")
1747         fw = file.write
1748         fw(title_string("Operators (bpy.ops)", "="))
1749         write_example_ref("", fw, "bpy.ops")
1750         fw(".. toctree::\n")
1751         fw("   :glob:\n\n")
1752         fw("   bpy.ops.*\n\n")
1753         file.close()
1754
1755
1756 def write_rst_data(basepath):
1757     '''
1758     Write the rst file of bpy.data module
1759     '''
1760     if "bpy.data" not in EXCLUDE_MODULES:
1761         # not actually a module, only write this file so we
1762         # can reference in the TOC
1763         filepath = os.path.join(basepath, "bpy.data.rst")
1764         file = open(filepath, "w", encoding="utf-8")
1765         fw = file.write
1766         fw(title_string("Data Access (bpy.data)", "="))
1767         fw(".. module:: bpy\n")
1768         fw("\n")
1769         fw("This module is used for all blender/python access.\n")
1770         fw("\n")
1771         fw(".. data:: data\n")
1772         fw("\n")
1773         fw("   Access to blenders internal data\n")
1774         fw("\n")
1775         fw("   :type: :class:`bpy.types.BlendData`\n")
1776         fw("\n")
1777         fw(".. literalinclude:: ../examples/bpy.data.py\n")
1778         file.close()
1779
1780         EXAMPLE_SET_USED.add("bpy.data")
1781
1782
1783 def write_rst_importable_modules(basepath):
1784     '''
1785     Write the rst files of importable modules
1786     '''
1787     importable_modules = {
1788         # python_modules
1789         "bpy.path"             : "Path Utilities",
1790         "bpy.utils"            : "Utilities",
1791         "bpy_extras"           : "Extra Utilities",
1792
1793         # C_modules
1794         "aud"                  : "Audio System",
1795         "blf"                  : "Font Drawing",
1796         "bmesh"                : "BMesh Module",
1797         "bmesh.types"          : "BMesh Types",
1798         "bmesh.utils"          : "BMesh Utilities",
1799         "bmesh.geometry"       : "BMesh Geometry Utilities",
1800         "bpy.app"              : "Application Data",
1801         "bpy.app.handlers"     : "Application Handlers",
1802         "bpy.app.translations" : "Application Translations",
1803         "bpy.props"            : "Property Definitions",
1804         "mathutils"            : "Math Types & Utilities",
1805         "mathutils.geometry"   : "Geometry Utilities",
1806         "mathutils.kdtree"     : "KDTree Utilities",
1807         "mathutils.noise"      : "Noise Utilities",
1808         "freestyle"            : "Freestyle Module",
1809         "freestyle.types"      : "Freestyle Types",
1810         "freestyle.predicates" : "Freestyle Predicates",
1811         "freestyle.functions"  : "Freestyle Functions",
1812         "freestyle.chainingiterators" : "Freestyle Chaining Iterators",
1813         "freestyle.shaders"    : "Freestyle Shaders",
1814         "freestyle.utils"      : "Freestyle Utilities",
1815         }
1816     for mod_name, mod_descr in importable_modules.items():
1817         if mod_name not in EXCLUDE_MODULES:
1818             module = __import__(mod_name,
1819                                 fromlist=[mod_name.rsplit(".", 1)[-1]])
1820             pymodule2sphinx(basepath, mod_name, module, mod_descr)
1821
1822
1823 def copy_handwritten_rsts(basepath):
1824
1825     # info docs
1826     if not EXCLUDE_INFO_DOCS:
1827         for info, info_desc in INFO_DOCS:
1828             shutil.copy2(os.path.join(RST_DIR, info), basepath)
1829
1830     # TODO put this docs in blender's code and use import as per modules above
1831     handwritten_modules = [
1832         "bge.logic",
1833         "bge.render",
1834         "bge.texture",
1835         "bge.events",
1836         "bge.constraints",
1837         "bgl",  # "Blender OpenGl wrapper"
1838         "gpu",  # "GPU Shader Module"
1839
1840         "bmesh.ops",  # generated by rst_from_bmesh_opdefines.py
1841
1842         # includes...
1843         "include__bmesh",
1844     ]
1845     for mod_name in handwritten_modules:
1846         if mod_name not in EXCLUDE_MODULES:
1847             # copy2 keeps time/date stamps
1848             shutil.copy2(os.path.join(RST_DIR, "%s.rst" % mod_name), basepath)
1849
1850     if "bge.types" not in EXCLUDE_MODULES:
1851         shutil.copy2(os.path.join(RST_DIR, "bge.types.rst"), basepath)
1852
1853         bge_types_dir = os.path.join(RST_DIR, "bge_types")
1854
1855         for i in os.listdir(bge_types_dir):
1856             if i.startswith("."):
1857                 # Avoid things like .svn dir...
1858                 continue
1859             shutil.copy2(os.path.join(bge_types_dir, i), basepath)
1860
1861     # changelog
1862     shutil.copy2(os.path.join(RST_DIR, "change_log.rst"), basepath)
1863
1864     # copy images, could be smarter but just glob for now.
1865     for f in os.listdir(RST_DIR):
1866         if f.endswith(".png"):
1867             shutil.copy2(os.path.join(RST_DIR, f), basepath)
1868
1869
1870 def rna2sphinx(basepath):
1871
1872     try:
1873         os.mkdir(basepath)
1874     except:
1875         pass
1876
1877     # sphinx setup
1878     write_sphinx_conf_py(basepath)
1879
1880     # main page
1881     write_rst_contents(basepath)
1882
1883     # context
1884     if "bpy.context" not in EXCLUDE_MODULES:
1885         # one of a kind, context doc (uses ctypes to extract info!)
1886         # doesn't work on mac and windows
1887         if PLATFORM not in {"darwin", "windows"}:
1888             pycontext2sphinx(basepath)
1889
1890     # internal modules
1891     write_rst_bpy(basepath)                 # bpy, disabled by default
1892     write_rst_types_index(basepath)         # bpy.types
1893     write_rst_ops_index(basepath)           # bpy.ops
1894     pyrna2sphinx(basepath)                  # bpy.types.* and bpy.ops.*
1895     write_rst_data(basepath)                # bpy.data
1896     write_rst_importable_modules(basepath)
1897
1898     # copy the other rsts
1899     copy_handwritten_rsts(basepath)
1900
1901
1902 def align_sphinx_in_to_sphinx_in_tmp():
1903     '''
1904     Move changed files from SPHINX_IN_TMP to SPHINX_IN
1905     '''
1906     import filecmp
1907
1908     sphinx_in_files = set(os.listdir(SPHINX_IN))
1909     sphinx_in_tmp_files = set(os.listdir(SPHINX_IN_TMP))
1910
1911     # remove deprecated files that have been removed
1912     for f in sorted(sphinx_in_files):
1913         if f not in sphinx_in_tmp_files:
1914             BPY_LOGGER.debug("\tdeprecated: %s" % f)
1915             os.remove(os.path.join(SPHINX_IN, f))
1916
1917     # freshen with new files.
1918     for f in sorted(sphinx_in_tmp_files):
1919         f_from = os.path.join(SPHINX_IN_TMP, f)
1920         f_to = os.path.join(SPHINX_IN, f)
1921
1922         do_copy = True
1923         if f in sphinx_in_files:
1924             if filecmp.cmp(f_from, f_to):
1925                 do_copy = False
1926
1927         if do_copy:
1928             BPY_LOGGER.debug("\tupdating: %s" % f)
1929             shutil.copy(f_from, f_to)
1930
1931
1932 def refactor_sphinx_log(sphinx_logfile):
1933     refactored_log = []
1934     with open(sphinx_logfile, "r", encoding="utf-8") as original_logfile:
1935         lines = set(original_logfile.readlines())
1936         for line in lines:
1937             if 'warning' in line.lower() or 'error' in line.lower():
1938                 line = line.strip().split(None, 2)
1939                 if len(line) == 3:
1940                     location, kind, msg = line
1941                     location = os.path.relpath(location, start=SPHINX_IN)
1942                     refactored_log.append((kind, location, msg))
1943     with open(sphinx_logfile, "w", encoding="utf-8") as refactored_logfile:
1944         for log in sorted(refactored_log):
1945             refactored_logfile.write("%-12s %s\n             %s\n" % log)
1946
1947
1948 def monkey_patch():
1949     filepath = os.path.join(SCRIPT_DIR, "sphinx_doc_gen_monkeypatch.py")
1950     global_namespace = {"__file__": filepath, "__name__": "__main__"}
1951     file = open(filepath, 'rb')
1952     exec(compile(file.read(), filepath, 'exec'), global_namespace)
1953     file.close()
1954
1955
1956 def main():
1957
1958     # first monkey patch to load in fake members
1959     monkey_patch()
1960
1961     # eventually, create the dirs
1962     for dir_path in [ARGS.output_dir, SPHINX_IN]:
1963         if not os.path.exists(dir_path):
1964             os.mkdir(dir_path)
1965
1966     # eventually, log in files
1967     if ARGS.log:
1968         bpy_logfile = os.path.join(ARGS.output_dir, ".bpy.log")
1969         bpy_logfilehandler = logging.FileHandler(bpy_logfile, mode="w")
1970         bpy_logfilehandler.setLevel(logging.DEBUG)
1971         BPY_LOGGER.addHandler(bpy_logfilehandler)
1972
1973         # using a FileHandler seems to disable the stdout, so we add a StreamHandler
1974         bpy_log_stdout_handler = logging.StreamHandler(stream=sys.stdout)
1975         bpy_log_stdout_handler.setLevel(logging.DEBUG)
1976         BPY_LOGGER.addHandler(bpy_log_stdout_handler)
1977
1978     # in case of out-of-source build, copy the needed dirs
1979     if ARGS.output_dir != SCRIPT_DIR:
1980         # examples dir
1981         examples_dir_copy = os.path.join(ARGS.output_dir, "examples")
1982         if os.path.exists(examples_dir_copy):
1983             shutil.rmtree(examples_dir_copy, True)
1984         shutil.copytree(EXAMPLES_DIR,
1985                         examples_dir_copy,
1986                         ignore=shutil.ignore_patterns(*(".svn",)),
1987                         copy_function=shutil.copy)
1988
1989         # eventually, copy the theme dir
1990         if ARGS.sphinx_theme in SPHINX_THEMES['bf']:
1991             if os.path.exists(SPHINX_THEME_DIR):
1992                 shutil.rmtree(SPHINX_THEME_DIR, True)
1993             shutil.copytree(SPHINX_THEME_SVN_DIR,
1994                             SPHINX_THEME_DIR,
1995                             ignore=shutil.ignore_patterns(*(".svn",)),
1996                             copy_function=shutil.copy)
1997
1998     # dump the api in rst files
1999     if os.path.exists(SPHINX_IN_TMP):
2000         shutil.rmtree(SPHINX_IN_TMP, True)
2001
2002     rna2sphinx(SPHINX_IN_TMP)
2003
2004     if ARGS.full_rebuild:
2005         # only for full updates
2006         shutil.rmtree(SPHINX_IN, True)
2007         shutil.copytree(SPHINX_IN_TMP,
2008                         SPHINX_IN,
2009                         copy_function=shutil.copy)
2010         if ARGS.sphinx_build and os.path.exists(SPHINX_OUT):
2011             shutil.rmtree(SPHINX_OUT, True)
2012         if ARGS.sphinx_build_pdf and os.path.exists(SPHINX_OUT_PDF):
2013             shutil.rmtree(SPHINX_OUT_PDF, True)
2014     else:
2015         # move changed files in SPHINX_IN
2016         align_sphinx_in_to_sphinx_in_tmp()
2017
2018     # report which example files weren't used
2019     EXAMPLE_SET_UNUSED = EXAMPLE_SET - EXAMPLE_SET_USED
2020     if EXAMPLE_SET_UNUSED:
2021         BPY_LOGGER.debug("\nUnused examples found in '%s'..." % EXAMPLES_DIR)
2022         for f in sorted(EXAMPLE_SET_UNUSED):
2023             BPY_LOGGER.debug("    %s.py" % f)
2024         BPY_LOGGER.debug("  %d total\n" % len(EXAMPLE_SET_UNUSED))
2025
2026     # eventually, build the html docs
2027     if ARGS.sphinx_build:
2028         import subprocess
2029         subprocess.call(SPHINX_BUILD)
2030
2031         # sphinx-build log cleanup+sort
2032         if ARGS.log:
2033             if os.stat(SPHINX_BUILD_LOG).st_size:
2034                 refactor_sphinx_log(SPHINX_BUILD_LOG)
2035
2036     # eventually, build the pdf docs
2037     if ARGS.sphinx_build_pdf:
2038         import subprocess
2039         subprocess.call(SPHINX_BUILD_PDF)
2040         subprocess.call(SPHINX_MAKE_PDF, stdout=SPHINX_MAKE_PDF_STDOUT)
2041
2042         # sphinx-build log cleanup+sort
2043         if ARGS.log:
2044             if os.stat(SPHINX_BUILD_PDF_LOG).st_size:
2045                 refactor_sphinx_log(SPHINX_BUILD_PDF_LOG)
2046
2047     # eventually, prepare the dir to be deployed online (REFERENCE_PATH)
2048     if ARGS.pack_reference:
2049
2050         if ARGS.sphinx_build:
2051             # delete REFERENCE_PATH
2052             if os.path.exists(REFERENCE_PATH):
2053                 shutil.rmtree(REFERENCE_PATH, True)
2054
2055             # copy SPHINX_OUT to the REFERENCE_PATH
2056             ignores = ('.doctrees', 'objects.inv', '.buildinfo')
2057             shutil.copytree(SPHINX_OUT,
2058                             REFERENCE_PATH,
2059                             ignore=shutil.ignore_patterns(*ignores))
2060             shutil.copy(os.path.join(REFERENCE_PATH, "contents.html"),
2061                         os.path.join(REFERENCE_PATH, "index.html"))
2062
2063             # zip REFERENCE_PATH
2064             basename = os.path.join(ARGS.output_dir, REFERENCE_NAME)
2065             tmp_path = shutil.make_archive(basename, 'zip',
2066                                            root_dir=ARGS.output_dir,
2067                                            base_dir=REFERENCE_NAME)
2068             final_path = os.path.join(REFERENCE_PATH, BLENDER_ZIP_FILENAME)
2069             os.rename(tmp_path, final_path)
2070
2071         if ARGS.sphinx_build_pdf:
2072             # copy the pdf to REFERENCE_PATH
2073             shutil.copy(os.path.join(SPHINX_OUT_PDF, "contents.pdf"),
2074                         os.path.join(REFERENCE_PATH, BLENDER_PDF_FILENAME))
2075
2076     sys.exit()
2077
2078
2079 if __name__ == '__main__':
2080     main()