From 8ab93bc5605cdb9d328829f591671d50cfd4e580 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9rome=20Perrin?= Date: Thu, 18 Oct 2018 15:36:00 +0900 Subject: [PATCH 1/2] erp5_code_documentation_generator: initial submission a quick and dirty external method to compare existing python documentation generators on erp5 modules. --- ...tension.erp5.CodeDocumentationGenerator.py | 178 ++++++++++++++++++ ...ension.erp5.CodeDocumentationGenerator.xml | 123 ++++++++++++ .../erp5_code_documentation_generator.xml | 26 +++ .../ERP5Site_generateCodeDocumentation.xml | 28 +++ .../bt/template_extension_id_list | 1 + .../bt/template_format_version | 1 + .../bt/template_skin_id_list | 1 + .../bt/title | 1 + 8 files changed, 359 insertions(+) create mode 100644 bt5/erp5_code_documentation_generator/ExtensionTemplateItem/portal_components/extension.erp5.CodeDocumentationGenerator.py create mode 100644 bt5/erp5_code_documentation_generator/ExtensionTemplateItem/portal_components/extension.erp5.CodeDocumentationGenerator.xml create mode 100644 bt5/erp5_code_documentation_generator/SkinTemplateItem/portal_skins/erp5_code_documentation_generator.xml create mode 100644 bt5/erp5_code_documentation_generator/SkinTemplateItem/portal_skins/erp5_code_documentation_generator/ERP5Site_generateCodeDocumentation.xml create mode 100644 bt5/erp5_code_documentation_generator/bt/template_extension_id_list create mode 100644 bt5/erp5_code_documentation_generator/bt/template_format_version create mode 100644 bt5/erp5_code_documentation_generator/bt/template_skin_id_list create mode 100644 bt5/erp5_code_documentation_generator/bt/title diff --git a/bt5/erp5_code_documentation_generator/ExtensionTemplateItem/portal_components/extension.erp5.CodeDocumentationGenerator.py b/bt5/erp5_code_documentation_generator/ExtensionTemplateItem/portal_components/extension.erp5.CodeDocumentationGenerator.py new file mode 100644 index 00000000000..68b7657ca6f --- /dev/null +++ b/bt5/erp5_code_documentation_generator/ExtensionTemplateItem/portal_components/extension.erp5.CodeDocumentationGenerator.py @@ -0,0 +1,178 @@ +import tempfile +import os +import shutil +import sys +import types + +def importModule(module_name): + __import__(module_name) + return sys.modules[module_name] + + +def ERP5Site_generateCodeDocumentation(self, + backend, + module_name_list=("erp5.component.test.erp5_version.testSupportRequest", "erp5.component.test.erp5_version.testERP5Administration",), + REQUEST=None, + RESPONSE=None): + """Generate documentation for a component module. + + `module_name` must be the "importable" dotted name of the component, ie not + test.erp5.testSupportRequest but erp5.component.test.${version}.testSupportRequest + """ + if backend == 'pdoc': + import pdoc # 0.32 + + pdoc.import_path = sys.path # when reloading this seems needed. + def docfilter(x): + """pdoc documents by default inherited classes and attributes. + + This is too verbose when documenting a test component (which is my use case now). + """ + if isinstance(x, pdoc.Function) or isinstance(x, pdoc.Variable): + if not x.cls: + return True + return x.name in x.cls.cls.__dict__ + return True + + def doc_module(module_name): + return pdoc.html(module_name, docfilter=docfilter, allsubmodules=True) \ + + '''''' # workaround style problem with long names. + + # first generate an index page, by creating a fake module aggregating all the modules + # we are documenting. + fake_module_name = 'index' # https://pypi.org/project/index/ ... looks we won't have this installed. + sys.modules[fake_module_name] = module = types.ModuleType(fake_module_name) + module_all = [] + for module_name in module_name_list: + setattr(module, module_name, importModule(module_name)) + module_all.append(module_name) + module.__all__ = module_all + + tmpdir = tempfile.mkdtemp() + try: + doc_dir = os.path.join(tmpdir, 'html') + os.mkdir(doc_dir) + open(os.path.join(doc_dir, 'index.html'), 'w').write(doc_module(fake_module_name)) + for module_name in module_name_list: + # create parent directory structure + parts = module_name.split('.') + for i in range(len(parts)): + dir_ = os.path.join(doc_dir, *parts[:i+1]) + if not os.path.exists(dir_): + os.mkdir(dir_) + open(os.path.join(doc_dir, *parts + ['index.html']), 'w').write( + doc_module(module_name)) + + data = open(shutil.make_archive(os.path.join(tmpdir, 'out.zip'), 'zip', doc_dir), 'rb').read() + if RESPONSE is not None: + RESPONSE.setHeader('content-disposition', 'attachment;filename=doc.zip') + return data + finally: + shutil.rmtree(tmpdir) + + return doc_module(fake_module_name) + + if backend == 'epydoc': + from epydoc.docbuilder import build_doc_index + from epydoc.docwriter.html import HTMLWriter + tmpdir = tempfile.mkdtemp() + try: + doc_dir = os.path.join(tmpdir, 'html') + os.mkdir(doc_dir) + for module_name in module_name_list: + module = importModule(module_name) + if hasattr(module, '__loader__'): + # XXX epydoc wants __file__, we'll give him files... + with tempfile.NamedTemporaryFile(suffix='.py', dir=tmpdir, delete=False) as f: + f.write(module.__loader__.get_source(module_name)) + module.__file__ = f.name + + html_writer = HTMLWriter(build_doc_index(module_name_list)) + html_writer.write(doc_dir) + + data = open(shutil.make_archive(os.path.join(tmpdir, 'out.zip'), 'zip', doc_dir), 'rb').read() + if RESPONSE is not None: + RESPONSE.setHeader('content-disposition', 'attachment;filename=doc.zip') + return data + finally: + shutil.rmtree(tmpdir) + + if backend == 'sphinx': + tmpdir = tempfile.mkdtemp() + try: + with open(os.path.join(tmpdir, 'conf.py'), 'w') as conf: + conf.write(""" +project = u'ERP5 Generated Documentation' +html_theme = 'default' +master_doc = 'index' +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.autosummary', + 'sphinx.ext.doctest', + 'sphinx.ext.viewcode', + 'sphinxcontrib.plantuml'] + +plantuml = '/srv/slapgrid/slappart8/bin/plantuml' +htmlhelp_basename = 'ERP5Doc' +html_show_sourcelink = True +pygments_style = 'sphinx' +autodoc_default_flags = ['members', 'undoc-members'] +""") + + with open(os.path.join(tmpdir, 'index.rst'), 'w') as index: + index.write(""" +ERP5 Auto-Generated Documentation +================================= + +.. toctree:: + :maxdepth: 4 + :caption: Contents: + +""") + for module_name in module_name_list: + module = importModule(module_name) + # XXX workaround https://nexedi.erp5.net/bug_module/20181018-12F285A + # XXX if modules have a __file__, sphinx will use this, even if the module have a loader providing get_source + # https://github.com/sphinx-doc/sphinx/blob/c89edd82eb76b175be919774ce7d4047bfe446ed/sphinx/util/__init__.py#L301 + # as a workaround, just remove the file on the module. + # XXX not sure this is safe. + module.__file__ = None + + title = '{} module'.format(module_name) + index.write(title + '\n') + index.write('=' * len(title) + '\n') + index.write(""" +.. automodule:: {} + +""".format(module_name)) + + index.write(""" +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` +""") + + from sphinx.cmd.build import main # Sphinx-1.8.1 + # https://github.com/sphinx-doc/sphinx/blob/master/sphinx/cmd/build.py#L299 + main(argv=[tmpdir, os.path.join(tmpdir, 'html')]) + data = open( + shutil.make_archive( + os.path.join(tmpdir, 'doc.zip'), + 'zip', + os.path.join(tmpdir, 'html') + ), 'rb').read() + if RESPONSE is not None: + RESPONSE.setHeader('content-disposition', 'attachement;filename=doc.zip') + return data + finally: + shutil.rmtree(tmpdir) + + if backend == 'pydoc': + import pydoc + # XXX only supports 1 module (pydoc is anyway a bit ugly compared to others) + return pydoc.html.docmodule(importModule(module_name_list[0])) + + raise TypeError("Unsupported backend {}".format(backend)) \ No newline at end of file diff --git a/bt5/erp5_code_documentation_generator/ExtensionTemplateItem/portal_components/extension.erp5.CodeDocumentationGenerator.xml b/bt5/erp5_code_documentation_generator/ExtensionTemplateItem/portal_components/extension.erp5.CodeDocumentationGenerator.xml new file mode 100644 index 00000000000..9360c26d1f4 --- /dev/null +++ b/bt5/erp5_code_documentation_generator/ExtensionTemplateItem/portal_components/extension.erp5.CodeDocumentationGenerator.xml @@ -0,0 +1,123 @@ + + + + + + + + + + _recorded_property_dict + + AAAAAAAAAAI= + + + + default_reference + CodeDocumentationGenerator + + + description + + + + + + id + extension.erp5.CodeDocumentationGenerator + + + portal_type + Extension Component + + + sid + + + + + + text_content_error_message + + + + + + text_content_warning_message + + + + + + version + erp5 + + + workflow_history + + AAAAAAAAAAM= + + + + + + + + + + + + + data + + + + + + + + + + + + + + + data + + + + component_validation_workflow + + AAAAAAAAAAQ= + + + + + + + + + + + + + + + + + + + action + validate + + + validation_state + validated + + + + + + + diff --git a/bt5/erp5_code_documentation_generator/SkinTemplateItem/portal_skins/erp5_code_documentation_generator.xml b/bt5/erp5_code_documentation_generator/SkinTemplateItem/portal_skins/erp5_code_documentation_generator.xml new file mode 100644 index 00000000000..3cd1a8b9fbb --- /dev/null +++ b/bt5/erp5_code_documentation_generator/SkinTemplateItem/portal_skins/erp5_code_documentation_generator.xml @@ -0,0 +1,26 @@ + + + + + + + + + + _objects + + + + + + id + erp5_code_documentation_generator + + + title + + + + + + diff --git a/bt5/erp5_code_documentation_generator/SkinTemplateItem/portal_skins/erp5_code_documentation_generator/ERP5Site_generateCodeDocumentation.xml b/bt5/erp5_code_documentation_generator/SkinTemplateItem/portal_skins/erp5_code_documentation_generator/ERP5Site_generateCodeDocumentation.xml new file mode 100644 index 00000000000..da3861edb61 --- /dev/null +++ b/bt5/erp5_code_documentation_generator/SkinTemplateItem/portal_skins/erp5_code_documentation_generator/ERP5Site_generateCodeDocumentation.xml @@ -0,0 +1,28 @@ + + + + + + + + + + _function + ERP5Site_generateCodeDocumentation + + + _module + CodeDocumentationGenerator + + + id + ERP5Site_generateCodeDocumentation + + + title + + + + + + diff --git a/bt5/erp5_code_documentation_generator/bt/template_extension_id_list b/bt5/erp5_code_documentation_generator/bt/template_extension_id_list new file mode 100644 index 00000000000..db674136735 --- /dev/null +++ b/bt5/erp5_code_documentation_generator/bt/template_extension_id_list @@ -0,0 +1 @@ +extension.erp5.CodeDocumentationGenerator \ No newline at end of file diff --git a/bt5/erp5_code_documentation_generator/bt/template_format_version b/bt5/erp5_code_documentation_generator/bt/template_format_version new file mode 100644 index 00000000000..56a6051ca2b --- /dev/null +++ b/bt5/erp5_code_documentation_generator/bt/template_format_version @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/bt5/erp5_code_documentation_generator/bt/template_skin_id_list b/bt5/erp5_code_documentation_generator/bt/template_skin_id_list new file mode 100644 index 00000000000..241cff5ecef --- /dev/null +++ b/bt5/erp5_code_documentation_generator/bt/template_skin_id_list @@ -0,0 +1 @@ +erp5_code_documentation_generator \ No newline at end of file diff --git a/bt5/erp5_code_documentation_generator/bt/title b/bt5/erp5_code_documentation_generator/bt/title new file mode 100644 index 00000000000..241cff5ecef --- /dev/null +++ b/bt5/erp5_code_documentation_generator/bt/title @@ -0,0 +1 @@ +erp5_code_documentation_generator \ No newline at end of file -- 2.30.9 From 5f95ad5628fd76ca52d35930ba0d56ed55342ee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9rome=20Perrin?= Date: Thu, 18 Oct 2018 08:37:23 +0200 Subject: [PATCH 2/2] patches: a "more compatible" docutils.utils.relative_path Previous monkey patch was not compatible with sphinx and sphinx was not compying the theme files in _static directory. --- product/ERP5Type/patches/python.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/product/ERP5Type/patches/python.py b/product/ERP5Type/patches/python.py index cb165841cb6..84ed0bc93fb 100644 --- a/product/ERP5Type/patches/python.py +++ b/product/ERP5Type/patches/python.py @@ -89,7 +89,11 @@ if 1: # Workaround bad use of getcwd() in docutils. # Required by PortalTransforms.transforms.rest from docutils import utils -utils.relative_path = lambda source, target: os.path.abspath(target) +def relative_path(source, target): + if not source: + return os.path.abspath(target) + return os.path.relpath(source, target) +utils.relative_path = relative_path def patch_linecache(): import linecache -- 2.30.9