| # Copyright 2014-2015 Canonical Limited. |
| # |
| # This file is part of charm-helpers. |
| # |
| # charm-helpers is free software: you can redistribute it and/or modify |
| # it under the terms of the GNU Lesser General Public License version 3 as |
| # published by the Free Software Foundation. |
| # |
| # charm-helpers is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| # GNU Lesser General Public License for more details. |
| # |
| # You should have received a copy of the GNU Lesser General Public License |
| # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>. |
| |
| |
| import importlib |
| import inspect |
| import textwrap |
| |
| from docutils import nodes |
| from docutils.statemachine import ViewList |
| from sphinx.errors import SphinxError |
| from sphinx.util.compat import Directive |
| from sphinx.util.nodes import nested_parse_with_titles |
| |
| |
| class AutoMemberSummary(Directive): |
| required_arguments = 1 |
| |
| def run(self): |
| module_name = self.arguments[0] |
| |
| try: |
| module = importlib.import_module(module_name) |
| except ImportError: |
| raise SphinxError("Unable to generate reference docs for %s, " |
| "could not import" % (module_name)) |
| |
| divider = '+{:-<80}+'.format('') |
| row = '| {:<78} |'.format |
| lines = [] |
| for member_name, member in inspect.getmembers(module): |
| if not self._filter(module_name, member_name, member): |
| continue |
| summary = textwrap.wrap(self._get_summary(member), 78) or [''] |
| link = '`{} <#{}>`_'.format(member_name, |
| '.'.join([module_name, |
| member_name])) |
| methods = ['* `{} <#{}>`_'.format(n, |
| '.'.join([module_name, |
| member_name, |
| n])) |
| for n, m in inspect.getmembers(member) |
| if not n.startswith('_') and inspect.isfunction(m)] |
| |
| lines.append(divider) |
| lines.append(row(link)) |
| lines.append(divider) |
| for line in summary: |
| lines.append(row(line)) |
| if methods: |
| lines.append(row('')) |
| lines.append(row('Methods:')) |
| lines.append(row('')) |
| for i, method in enumerate(methods): |
| lines.append(row(method)) |
| lines.append(divider) |
| content = '\n'.join(lines) |
| |
| result = self._parse(content, '<automembersummary>') |
| return result |
| |
| def _get_summary(self, member): |
| doc = (member.__doc__ or '').splitlines() |
| |
| # strip any leading blank lines |
| while doc and not doc[0].strip(): |
| doc.pop(0) |
| |
| # strip anything after the first blank line |
| for i, piece in enumerate(doc): |
| if not piece.strip(): |
| doc = doc[:i] |
| break |
| |
| return " ".join(doc).strip() |
| |
| def _filter(self, module_name, member_name, member): |
| if member_name.startswith('_'): |
| return False |
| if hasattr(member, '__module__'): |
| # skip imported classes & functions |
| return member.__module__.startswith(module_name) |
| elif hasattr(member, '__name__'): |
| # skip imported modules |
| return member.__name__.startswith(module_name) |
| else: |
| return False # skip instances |
| return True |
| |
| def _parse(self, rst_text, annotation): |
| result = ViewList() |
| for line in rst_text.split("\n"): |
| result.append(line, annotation) |
| node = nodes.paragraph() |
| node.document = self.state.document |
| nested_parse_with_titles(self.state, result, node) |
| return node.children |
| |
| |
| def setup(app): |
| app.add_directive('automembersummary', AutoMemberSummary) |