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