898da626d8c07a7b637ceab5c2a199e6eb473c9b
[osm/N2VC.git] / modules / libjuju / docs / _extensions / automembersummary.py
1 # Copyright 2014-2015 Canonical Limited.
2 #
3 # This file is part of charm-helpers.
4 #
5 # charm-helpers is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Lesser General Public License version 3 as
7 # published by the Free Software Foundation.
8 #
9 # charm-helpers is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU Lesser General Public License for more details.
13 #
14 # You should have received a copy of the GNU Lesser General Public License
15 # along with charm-helpers. If not, see <http://www.gnu.org/licenses/>.
16
17
18 import importlib
19 import inspect
20 import textwrap
21
22 from docutils import nodes
23 from docutils.statemachine import ViewList
24 from sphinx.errors import SphinxError
25 from sphinx.util.compat import Directive
26 from sphinx.util.nodes import nested_parse_with_titles
27
28
29 class AutoMemberSummary(Directive):
30 required_arguments = 1
31
32 def run(self):
33 module_name = self.arguments[0]
34
35 try:
36 module = importlib.import_module(module_name)
37 except ImportError:
38 raise SphinxError("Unable to generate reference docs for %s, "
39 "could not import" % (module_name))
40
41 divider = '+{:-<80}+'.format('')
42 row = '| {:<78} |'.format
43 lines = []
44 for member_name, member in inspect.getmembers(module):
45 if not self._filter(module_name, member_name, member):
46 continue
47 summary = textwrap.wrap(self._get_summary(member), 78) or ['']
48 link = '`{} <#{}>`_'.format(member_name,
49 '.'.join([module_name,
50 member_name]))
51 methods = ['* `{} <#{}>`_'.format(n,
52 '.'.join([module_name,
53 member_name,
54 n]))
55 for n, m in inspect.getmembers(member)
56 if not n.startswith('_') and inspect.isfunction(m)]
57
58 lines.append(divider)
59 lines.append(row(link))
60 lines.append(divider)
61 for line in summary:
62 lines.append(row(line))
63 if methods:
64 lines.append(row(''))
65 lines.append(row('Methods:'))
66 lines.append(row(''))
67 for i, method in enumerate(methods):
68 lines.append(row(method))
69 lines.append(divider)
70 content = '\n'.join(lines)
71
72 result = self._parse(content, '<automembersummary>')
73 return result
74
75 def _get_summary(self, member):
76 doc = (member.__doc__ or '').splitlines()
77
78 # strip any leading blank lines
79 while doc and not doc[0].strip():
80 doc.pop(0)
81
82 # strip anything after the first blank line
83 for i, piece in enumerate(doc):
84 if not piece.strip():
85 doc = doc[:i]
86 break
87
88 return " ".join(doc).strip()
89
90 def _filter(self, module_name, member_name, member):
91 if member_name.startswith('_'):
92 return False
93 if hasattr(member, '__module__'):
94 # skip imported classes & functions
95 return member.__module__.startswith(module_name)
96 elif hasattr(member, '__name__'):
97 # skip imported modules
98 return member.__name__.startswith(module_name)
99 else:
100 return False # skip instances
101 return True
102
103 def _parse(self, rst_text, annotation):
104 result = ViewList()
105 for line in rst_text.split("\n"):
106 result.append(line, annotation)
107 node = nodes.paragraph()
108 node.document = self.state.document
109 nested_parse_with_titles(self.state, result, node)
110 return node.children
111
112
113 def setup(app):
114 app.add_directive('automembersummary', AutoMemberSummary)