<NME> gen_reference.py
<BEF> #!/usr/bin/env python2

#
#  This file is part of Bakefile (http://bakefile.org)
#
#  Copyright (C) 2008-2013 Vaclav Slavik
#
#  Permission is hereby granted, free of charge, to any person obtaining a copy
#  of this software and associated documentation files (the "Software"), to
#  deal in the Software without restriction, including without limitation the
#  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
#  sell copies of the Software, and to permit persons to whom the Software is
#  furnished to do so, subject to the following conditions:
#
#  The above copyright notice and this permission notice shall be included in
#  all copies or substantial portions of the Software.
#
#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
#  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
#  IN THE SOFTWARE.
#

"""
This tool generates reference documentation for Bakefile plugins and puts
it in ref/ directory.
"""

import sys
import os
import os.path
import shutil
import sphinx.util.docstrings
from functools import partial

docs_path = os.path.dirname(sys.argv[0])
bkl_path = os.path.normpath(os.path.join(docs_path, "..", "src"))
sys.path = [bkl_path] + sys.path

import bkl
import bkl.api
from bkl.props import registry
from bkl.vartypes import ListType, EnumType

OUT_DIR = os.path.join(docs_path, "ref")
shutil.rmtree(OUT_DIR, ignore_errors=True)
os.makedirs(OUT_DIR)


def underline(s, how):
    u = "".join(how for i in xrange(0, len(s)))
    return "%s\n%s" % (s,u)


def prop_scope(symbolic):
    if symbolic == "properties":
        return ""
    if symbolic.startswith("properties_"):
        symbolic = symbolic[len("properties_"):]

    human = {
        bkl.api.Property.SCOPE_PROJECT : "Global to project",
        bkl.api.Property.SCOPE_MODULE : "Modules",
        bkl.api.Property.SCOPE_TARGET : "All targets",
        }
    if symbolic in human:
        return human[symbolic]

    assert False, "unrecognized scope"


def _get_allowed_values(vartype):
    if isinstance(vartype, ListType):
        return _get_allowed_values(vartype.item_type)
    elif isinstance(vartype, EnumType):
        return vartype.format_allowed_values()
    else:
        return None


def write_property(prop):
    desc = ""
    if prop.__doc__:
        desc += prop.__doc__
        desc += "\n"

    if prop.toolsets:
            default = prop.default
            if default == []:
                default = "empty"
            desc += "\n*Default:* %s\n" % default

    txt = "**%s** (type: %s)\n\n" % (prop.name, prop.type.name)
            if default == []:
                default = "empty"
            elif (hasattr(default, "__call__") and
                  hasattr(default, "__doc__")):
                default = default.__doc__
            desc += "\n*Default:* %s\n" % default

    allowed_values = _get_allowed_values(prop.type)
    if allowed_values:
        desc += "\n*Allowed values:* %s\n" % allowed_values

    desc += "\n*Inheritable from parent:* %s\n" % ("yes" if prop.inheritable else "no")

    txt = "**%s** (type: %s)\n\n" % (prop.name, prop.type.name)
    txt += "    " + "\n    ".join(desc.split("\n"))
    txt += "\n"
    return txt


def write_properties(props_func, scope=None):
    props = list(props_func())
    if not props:
        return ""

    txt = "\n\nProperties\n----------\n\n"

    for p in props:
        if p.internal:
            continue
        if scope and scope not in p.scopes:
            continue
        txt += write_property(p)

    return txt



DOC_TEMPLATE = """
.. This file was generated by gen_reference.py, don't edit manually!

.. _%(reflabel)s:

%(title)s

%(docstring)s
"""

def write_docs(filename, title, docstring):
    """
    Writes out documentation to a file.
    """
    reflabel = "ref_%s" % filename
    title = underline(title, "=")
    f = open("%s/%s.rst" % (OUT_DIR, filename), "wt")
    f.write(DOC_TEMPLATE % locals())


def write_extension_docs(kind, extension, props_func=None):
    """
    Writes out documentation for extension of given kind.
    """
    name = extension.name
    print "documenting %s %s..." % (kind, name)
    docstring = sphinx.util.docstrings.prepare_docstring(extension.__doc__)

    title = name
    if len(docstring) > 0:
        summary = docstring[0]
        if summary[-1] == ".":
            title = "%s *(%s)*" % (summary[:-1], name)

    docstring_text = "\n".join(docstring)

    if props_func is not None:
        docstring_text += write_properties(props_func)
    else:
        ptext = "\n\n%s\n\n" % underline("Properties", "-")
        for pkind in extension.all_properties_kinds():
            pall = list(p for p in extension.all_properties(pkind) if not p.internal)
            if pall:
                ptext += "\n\n%s\n\n" % underline(prop_scope(pkind), "^")
                for p in pall:
                    ptext += write_property(p)
        docstring_text += ptext

    write_docs("%s_%s" % (kind, name), title, docstring_text)




# write docs for all targets:
for t in bkl.api.TargetType.all():
    write_extension_docs("target", t, partial(registry.enum_target_props, t))

# write docs for all toolsets:
for t in bkl.api.Toolset.all():
    write_extension_docs("toolset", t)

# write docs for projects/modules:
write_docs("project", "Global project properties",
           write_properties(registry.enum_project_props, bkl.api.Property.SCOPE_PROJECT))
write_docs("module", "Module properties",
           write_properties(registry.enum_module_props, bkl.api.Property.SCOPE_MODULE))
write_docs("setting", "Setting properties",
           write_properties(registry.enum_setting_props, bkl.api.Property.SCOPE_SETTING))

<MSG> If property default value is a function, use its __doc__ for  documentation.

<DFF> @@ -91,6 +91,10 @@ def write_property(prop):
             default = prop.default
             if default == []:
                 default = "empty"
+            elif ((isinstance(default, types.FunctionType) or
+                 isinstance(default, types.MethodType)) and
+                "__doc__" in dir(default)):
+                default = default.__doc__
             desc += "\n*Default:* %s\n" % default
 
     txt = "**%s** (type: %s)\n\n" % (prop.name, prop.type.name)

{'commit': 'diff --git a/docs/gen_reference.py b/docs/gen_reference.py\nindex dbded8c..4dffa69 100755\n--- a/docs/gen_reference.py\n+++ b/docs/gen_reference.py\n@@ -91,6 +91,10 @@ def write_property(prop):\n             default = prop.default\n             if default == []:\n                 default = "empty"\n+            elif ((isinstance(default, types.FunctionType) or\n+                 isinstance(default, types.MethodType)) and\n+                "__doc__" in dir(default)):\n+                default = default.__doc__\n             desc += "\\n*Default:* %s\\n" % default\n \n     txt = "**%s** (type: %s)\\n\\n" % (prop.name, prop.type.name)\n', 'commit_subject': 'If property default value is a function, use its __doc__ for  documentation.', 'repo_name': 'vslavik/bakefile', 'committer': {'name': 'Vaclav Slavik', 'email': '7887d68a6924d5869f71ebb811bc4b6164606a6a@gmail.com', 'time_sec': '1317295761', 'tz_offset': '120', 'date': {'seconds': '1317295761'}}, 'commit_tree': '1b528a305d83b9f10ac0eeb0018a83f4c4276aff', 'commit_message': 'If property default value is a function, use its __doc__ for  documentation.\n', 'language_name': 'C++', 'license': 'mit', 'hunks': '@@ -91,6 +91,10 @@ def write_property(prop):\n             default = prop.default\n             if default == []:\n                 default = "empty"\n+            elif ((isinstance(default, types.FunctionType) or\n+                 isinstance(default, types.MethodType)) and\n+                "__doc__" in dir(default)):\n+                default = default.__doc__\n             desc += "\\n*Default:* %s\\n" % default\n \n     txt = "**%s** (type: %s)\\n\\n" % (prop.name, prop.type.name)\n', 'addition_count': 4, 'deletion_count': 0, 'src_file': 'a/docs/gen_reference.py', 'tgt_file': 'b/docs/gen_reference.py', 'file_extension': '.py', 'before_file': '#!/usr/bin/env python2\n\n#\n#  This file is part of Bakefile (http://bakefile.org)\n#\n#  Copyright (C) 2008-2013 Vaclav Slavik\n#\n#  Permission is hereby granted, free of charge, to any person obtaining a copy\n#  of this software and associated documentation files (the "Software"), to\n#  deal in the Software without restriction, including without limitation the\n#  rights to use, copy, modify, merge, publish, distribute, sublicense, and/or\n#  sell copies of the Software, and to permit persons to whom the Software is\n#  furnished to do so, subject to the following conditions:\n#\n#  The above copyright notice and this permission notice shall be included in\n#  all copies or substantial portions of the Software.\n#\n#  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n#  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n#  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n#  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n#  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING\n#  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS\n#  IN THE SOFTWARE.\n#\n\n"""\nThis tool generates reference documentation for Bakefile plugins and puts\nit in ref/ directory.\n"""\n\nimport sys\nimport os\nimport os.path\nimport shutil\nimport sphinx.util.docstrings\nfrom functools import partial\n\ndocs_path = os.path.dirname(sys.argv[0])\nbkl_path = os.path.normpath(os.path.join(docs_path, "..", "src"))\nsys.path = [bkl_path] + sys.path\n\nimport bkl\nimport bkl.api\nfrom bkl.props import registry\nfrom bkl.vartypes import ListType, EnumType\n\nOUT_DIR = os.path.join(docs_path, "ref")\nshutil.rmtree(OUT_DIR, ignore_errors=True)\nos.makedirs(OUT_DIR)\n\n\ndef underline(s, how):\n    u = "".join(how for i in xrange(0, len(s)))\n    return "%s\\n%s" % (s,u)\n\n\ndef prop_scope(symbolic):\n    if symbolic == "properties":\n        return ""\n    if symbolic.startswith("properties_"):\n        symbolic = symbolic[len("properties_"):]\n\n    human = {\n        bkl.api.Property.SCOPE_PROJECT : "Global to project",\n        bkl.api.Property.SCOPE_MODULE : "Modules",\n        bkl.api.Property.SCOPE_TARGET : "All targets",\n        }\n    if symbolic in human:\n        return human[symbolic]\n\n    assert False, "unrecognized scope"\n\n\ndef _get_allowed_values(vartype):\n    if isinstance(vartype, ListType):\n        return _get_allowed_values(vartype.item_type)\n    elif isinstance(vartype, EnumType):\n        return vartype.format_allowed_values()\n    else:\n        return None\n\n\ndef write_property(prop):\n    desc = ""\n    if prop.__doc__:\n        desc += prop.__doc__\n        desc += "\\n"\n\n    if prop.toolsets:\n            default = prop.default\n            if default == []:\n                default = "empty"\n            desc += "\\n*Default:* %s\\n" % default\n\n    txt = "**%s** (type: %s)\\n\\n" % (prop.name, prop.type.name)\n            if default == []:\n                default = "empty"\n            elif (hasattr(default, "__call__") and\n                  hasattr(default, "__doc__")):\n                default = default.__doc__\n            desc += "\\n*Default:* %s\\n" % default\n\n    allowed_values = _get_allowed_values(prop.type)\n    if allowed_values:\n        desc += "\\n*Allowed values:* %s\\n" % allowed_values\n\n    desc += "\\n*Inheritable from parent:* %s\\n" % ("yes" if prop.inheritable else "no")\n\n    txt = "**%s** (type: %s)\\n\\n" % (prop.name, prop.type.name)\n    txt += "    " + "\\n    ".join(desc.split("\\n"))\n    txt += "\\n"\n    return txt\n\n\ndef write_properties(props_func, scope=None):\n    props = list(props_func())\n    if not props:\n        return ""\n\n    txt = "\\n\\nProperties\\n----------\\n\\n"\n\n    for p in props:\n        if p.internal:\n            continue\n        if scope and scope not in p.scopes:\n            continue\n        txt += write_property(p)\n\n    return txt\n\n\n\nDOC_TEMPLATE = """\n.. This file was generated by gen_reference.py, don\'t edit manually!\n\n.. _%(reflabel)s:\n\n%(title)s\n\n%(docstring)s\n"""\n\ndef write_docs(filename, title, docstring):\n    """\n    Writes out documentation to a file.\n    """\n    reflabel = "ref_%s" % filename\n    title = underline(title, "=")\n    f = open("%s/%s.rst" % (OUT_DIR, filename), "wt")\n    f.write(DOC_TEMPLATE % locals())\n\n\ndef write_extension_docs(kind, extension, props_func=None):\n    """\n    Writes out documentation for extension of given kind.\n    """\n    name = extension.name\n    print "documenting %s %s..." % (kind, name)\n    docstring = sphinx.util.docstrings.prepare_docstring(extension.__doc__)\n\n    title = name\n    if len(docstring) > 0:\n        summary = docstring[0]\n        if summary[-1] == ".":\n            title = "%s *(%s)*" % (summary[:-1], name)\n\n    docstring_text = "\\n".join(docstring)\n\n    if props_func is not None:\n        docstring_text += write_properties(props_func)\n    else:\n        ptext = "\\n\\n%s\\n\\n" % underline("Properties", "-")\n        for pkind in extension.all_properties_kinds():\n            pall = list(p for p in extension.all_properties(pkind) if not p.internal)\n            if pall:\n                ptext += "\\n\\n%s\\n\\n" % underline(prop_scope(pkind), "^")\n                for p in pall:\n                    ptext += write_property(p)\n        docstring_text += ptext\n\n    write_docs("%s_%s" % (kind, name), title, docstring_text)\n\n\n\n\n# write docs for all targets:\nfor t in bkl.api.TargetType.all():\n    write_extension_docs("target", t, partial(registry.enum_target_props, t))\n\n# write docs for all toolsets:\nfor t in bkl.api.Toolset.all():\n    write_extension_docs("toolset", t)\n\n# write docs for projects/modules:\nwrite_docs("project", "Global project properties",\n           write_properties(registry.enum_project_props, bkl.api.Property.SCOPE_PROJECT))\nwrite_docs("module", "Module properties",\n           write_properties(registry.enum_module_props, bkl.api.Property.SCOPE_MODULE))\nwrite_docs("setting", "Setting properties",\n           write_properties(registry.enum_setting_props, bkl.api.Property.SCOPE_SETTING))\n', 'message': 'If property default value is a function, use its __doc__ for  documentation.\n', 'author_name': 'Vaclav Slavik', 'lang': 'py'}