| # Copyright 2014-2015 Canonical Limited. |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| |
| |
| 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) |