blob: cfe0b84b857c51ce323f846f6e6dc70075667524 [file] [log] [blame]
Adam Israeldcdf82b2017-08-15 15:26:43 -04001# Copyright 2014-2015 Canonical Limited.
2#
Adam Israel1a15d1c2017-10-23 12:00:49 -04003# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
Adam Israeldcdf82b2017-08-15 15:26:43 -04006#
Adam Israel1a15d1c2017-10-23 12:00:49 -04007# http://www.apache.org/licenses/LICENSE-2.0
Adam Israeldcdf82b2017-08-15 15:26:43 -04008#
Adam Israel1a15d1c2017-10-23 12:00:49 -04009# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
Adam Israeldcdf82b2017-08-15 15:26:43 -040014
15
16import importlib
17import inspect
18import textwrap
19
20from docutils import nodes
21from docutils.statemachine import ViewList
22from sphinx.errors import SphinxError
23from sphinx.util.compat import Directive
24from sphinx.util.nodes import nested_parse_with_titles
25
26
27class AutoMemberSummary(Directive):
28 required_arguments = 1
29
30 def run(self):
31 module_name = self.arguments[0]
32
33 try:
34 module = importlib.import_module(module_name)
35 except ImportError:
36 raise SphinxError("Unable to generate reference docs for %s, "
37 "could not import" % (module_name))
38
39 divider = '+{:-<80}+'.format('')
40 row = '| {:<78} |'.format
41 lines = []
42 for member_name, member in inspect.getmembers(module):
43 if not self._filter(module_name, member_name, member):
44 continue
45 summary = textwrap.wrap(self._get_summary(member), 78) or ['']
46 link = '`{} <#{}>`_'.format(member_name,
47 '.'.join([module_name,
48 member_name]))
49 methods = ['* `{} <#{}>`_'.format(n,
50 '.'.join([module_name,
51 member_name,
52 n]))
53 for n, m in inspect.getmembers(member)
54 if not n.startswith('_') and inspect.isfunction(m)]
55
56 lines.append(divider)
57 lines.append(row(link))
58 lines.append(divider)
59 for line in summary:
60 lines.append(row(line))
61 if methods:
62 lines.append(row(''))
63 lines.append(row('Methods:'))
64 lines.append(row(''))
65 for i, method in enumerate(methods):
66 lines.append(row(method))
67 lines.append(divider)
68 content = '\n'.join(lines)
69
70 result = self._parse(content, '<automembersummary>')
71 return result
72
73 def _get_summary(self, member):
74 doc = (member.__doc__ or '').splitlines()
75
76 # strip any leading blank lines
77 while doc and not doc[0].strip():
78 doc.pop(0)
79
80 # strip anything after the first blank line
81 for i, piece in enumerate(doc):
82 if not piece.strip():
83 doc = doc[:i]
84 break
85
86 return " ".join(doc).strip()
87
88 def _filter(self, module_name, member_name, member):
89 if member_name.startswith('_'):
90 return False
91 if hasattr(member, '__module__'):
92 # skip imported classes & functions
93 return member.__module__.startswith(module_name)
94 elif hasattr(member, '__name__'):
95 # skip imported modules
96 return member.__name__.startswith(module_name)
97 else:
98 return False # skip instances
99 return True
100
101 def _parse(self, rst_text, annotation):
102 result = ViewList()
103 for line in rst_text.split("\n"):
104 result.append(line, annotation)
105 node = nodes.paragraph()
106 node.document = self.state.document
107 nested_parse_with_titles(self.state, result, node)
108 return node.children
109
110
111def setup(app):
112 app.add_directive('automembersummary', AutoMemberSummary)