-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request '[#311] Logic to automatically generate the docume…
…ntation for the dependencies of the integrations' (#330) from 311/auto-generate-deps-docs into master Reviewed-on: https://git.platypush.tech/platypush/platypush/pulls/330
- Loading branch information
Showing
171 changed files
with
6,727 additions
and
5,419 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
import inspect | ||
import os | ||
import re | ||
import sys | ||
import textwrap as tw | ||
from contextlib import contextmanager | ||
|
||
from sphinx.application import Sphinx | ||
|
||
base_path = os.path.abspath( | ||
os.path.join(os.path.dirname(os.path.relpath(__file__)), '..', '..', '..') | ||
) | ||
|
||
sys.path.insert(0, base_path) | ||
|
||
from platypush.utils import get_plugin_name_by_class # noqa | ||
from platypush.utils.mock import mock # noqa | ||
from platypush.utils.reflection import IntegrationMetadata, import_file # noqa | ||
|
||
|
||
class IntegrationEnricher: | ||
@staticmethod | ||
def add_events(source: list[str], manifest: IntegrationMetadata, idx: int) -> int: | ||
if not manifest.events: | ||
return idx | ||
|
||
source.insert( | ||
idx, | ||
'Triggered events\n----------------\n\n' | ||
+ '\n'.join( | ||
f'\t- :class:`{event.__module__}.{event.__qualname__}`' | ||
for event in manifest.events | ||
) | ||
+ '\n\n', | ||
) | ||
|
||
return idx + 1 | ||
|
||
@staticmethod | ||
def add_actions(source: list[str], manifest: IntegrationMetadata, idx: int) -> int: | ||
if not (manifest.actions and manifest.cls): | ||
return idx | ||
|
||
source.insert( | ||
idx, | ||
'Actions\n-------\n\n' | ||
+ '\n'.join( | ||
f'\t- `{get_plugin_name_by_class(manifest.cls)}.{action} ' | ||
+ f'<#{manifest.cls.__module__}.{manifest.cls.__qualname__}.{action}>`_' | ||
for action in sorted(manifest.actions.keys()) | ||
) | ||
+ '\n\n', | ||
) | ||
|
||
return idx + 1 | ||
|
||
@staticmethod | ||
def _shellify(title: str, cmd: str) -> str: | ||
return f'**{title}**\n\n' + '.. code-block:: bash\n\n\t' + cmd + '\n\n' | ||
|
||
@classmethod | ||
def add_install_deps( | ||
cls, source: list[str], manifest: IntegrationMetadata, idx: int | ||
) -> int: | ||
deps = manifest.deps | ||
parsed_deps = { | ||
'before': deps.before, | ||
'pip': deps.pip, | ||
'after': deps.after, | ||
} | ||
|
||
if not (any(parsed_deps.values()) or deps.by_pkg_manager): | ||
return idx | ||
|
||
source.insert(idx, 'Dependencies\n------------\n\n') | ||
idx += 1 | ||
|
||
if parsed_deps['before']: | ||
source.insert(idx, cls._shellify('Pre-install', '\n'.join(deps.before))) | ||
idx += 1 | ||
|
||
if parsed_deps['pip']: | ||
source.insert(idx, cls._shellify('pip', 'pip ' + ' '.join(deps.pip))) | ||
idx += 1 | ||
|
||
for pkg_manager, sys_deps in deps.by_pkg_manager.items(): | ||
if not sys_deps: | ||
continue | ||
|
||
source.insert( | ||
idx, | ||
cls._shellify( | ||
pkg_manager.value.default_os.value.description, | ||
pkg_manager.value.install_doc + ' ' + ' '.join(sys_deps), | ||
), | ||
) | ||
|
||
idx += 1 | ||
|
||
if parsed_deps['after']: | ||
source.insert(idx, cls._shellify('Post-install', '\n'.join(deps.after))) | ||
idx += 1 | ||
|
||
return idx | ||
|
||
@classmethod | ||
def add_description( | ||
cls, source: list[str], manifest: IntegrationMetadata, idx: int | ||
) -> int: | ||
docs = ( | ||
doc | ||
for doc in ( | ||
inspect.getdoc(manifest.cls) or '', | ||
manifest.constructor.doc if manifest.constructor else '', | ||
) | ||
if doc | ||
) | ||
|
||
if not docs: | ||
return idx | ||
|
||
docstring = '\n\n'.join(docs) | ||
source.insert(idx, f"Description\n-----------\n\n{docstring}\n\n") | ||
return idx + 1 | ||
|
||
@classmethod | ||
def add_conf_snippet( | ||
cls, source: list[str], manifest: IntegrationMetadata, idx: int | ||
) -> int: | ||
source.insert( | ||
idx, | ||
tw.dedent( | ||
f""" | ||
Configuration | ||
------------- | ||
.. code-block:: yaml | ||
{tw.indent(manifest.config_snippet, ' ')} | ||
""" | ||
), | ||
) | ||
|
||
return idx + 1 | ||
|
||
def __call__(self, _: Sphinx, doc: str, source: list[str]): | ||
if not (source and re.match(r'^platypush/(backend|plugins)/.*', doc)): | ||
return | ||
|
||
src = [src.split('\n') for src in source][0] | ||
if len(src) < 3: | ||
return | ||
|
||
manifest_file = os.path.join( | ||
base_path, | ||
*doc.split(os.sep)[:-1], | ||
*doc.split(os.sep)[-1].split('.'), | ||
'manifest.yaml', | ||
) | ||
|
||
if not os.path.isfile(manifest_file): | ||
return | ||
|
||
with mock_imports(): | ||
manifest = IntegrationMetadata.from_manifest(manifest_file) | ||
idx = self.add_description(src, manifest, idx=3) | ||
idx = self.add_conf_snippet(src, manifest, idx=idx) | ||
idx = self.add_install_deps(src, manifest, idx=idx) | ||
idx = self.add_events(src, manifest, idx=idx) | ||
idx = self.add_actions(src, manifest, idx=idx) | ||
|
||
src.insert(idx, '\n\nModule reference\n----------------\n\n') | ||
source[0] = '\n'.join(src) | ||
|
||
|
||
@contextmanager | ||
def mock_imports(): | ||
conf_mod = import_file(os.path.join(base_path, 'docs', 'source', 'conf.py')) | ||
mock_mods = getattr(conf_mod, 'autodoc_mock_imports', []) | ||
with mock(*mock_mods): | ||
yield | ||
|
||
|
||
def setup(app: Sphinx): | ||
app.connect('source-read', IntegrationEnricher()) | ||
return { | ||
'version': '0.1', | ||
'parallel_read_safe': True, | ||
'parallel_write_safe': True, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.