--- /dev/null
+# 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)