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