1 from __future__ import print_function, absolute_import, division
5 from functools import reduce
6 from itertools import count, groupby
8 from docutils import nodes
9 from docutils.statemachine import ViewList
10 from sphinx import addnodes
11 from sphinx.ext.autosummary import Autosummary, autosummary_table
13 from autodoxy import get_doxygen_root
14 from autodoxy.autodoxy import DoxygenMethodDocumenter, DoxygenClassDocumenter
15 from autodoxy.xmlutils import format_xml_paragraph
18 def import_by_name(name, env=None, prefixes=None, i=0):
19 """Get xml documentation for a class/method with a given name.
20 If there are multiple classes or methods with that name, you
21 can use the `i` kwarg to pick which one.
27 parent = env.ref_context.get('cpp:parent_symbol')
29 while parent is not None and parent.identifier is not None:
30 parent_symbols.insert(0, str(parent.identifier))
31 parent = parent.parent
32 prefixes.append('::'.join(parent_symbols))
35 for prefix in prefixes:
38 prefixed_name = '::'.join([prefix, name])
41 return _import_by_name(prefixed_name, i=i)
43 tried.append(prefixed_name)
44 raise ImportError('no module named %s' % ' or '.join(tried))
47 def _import_by_name(name, i=0):
48 root = get_doxygen_root()
49 name = name.replace('.', '::')
53 './/compoundname[text()="%s"]/../'
54 'sectiondef[@kind="public-func"]/memberdef[@kind="function"]/'
55 'name[text()="%s"]/..') % tuple(name.rsplit('::', 1))
56 m = root.xpath(xpath_query)
59 full_name = '.'.join(name.rsplit('::', 1))
60 return full_name, obj, full_name, ''
63 './/compoundname[text()="%s"]/../'
64 'sectiondef[@kind="public-type"]/memberdef[@kind="enum"]/'
65 'name[text()="%s"]/..') % tuple(name.rsplit('::', 1))
66 m = root.xpath(xpath_query)
69 full_name = '.'.join(name.rsplit('::', 1))
70 return full_name, obj, full_name, ''
72 xpath_query = ('.//compoundname[text()="%s"]/..' % name)
73 m = root.xpath(xpath_query)
76 return (name, obj, name, '')
81 def get_documenter(obj, full_name):
82 if obj.tag == 'memberdef' and obj.get('kind') == 'function':
83 return DoxygenMethodDocumenter
84 elif obj.tag == 'compounddef':
85 return DoxygenClassDocumenter
87 raise NotImplementedError(obj.tag)
90 class DoxygenAutosummary(Autosummary):
91 def get_items(self, names):
92 """Try to import the given names, and return a list of
93 ``[(name, signature, summary_string, real_name), ...]``.
95 env = self.state.document.settings.env
98 names_and_counts = reduce(operator.add,
99 [tuple(zip(g, count())) for _, g in groupby(names)]) # type: List[(Str, Int)]
101 for name, i in names_and_counts:
103 if name.startswith('~'):
105 display_name = name.split('::')[-1]
108 real_name, obj, parent, modname = import_by_name(name, env=env, i=i)
110 self.warn('failed to import %s' % name)
111 items.append((name, '', '', name))
114 self.result = ViewList() # initialize for each documenter
115 documenter = get_documenter(obj, parent)(self, real_name, id=obj.get('id'))
116 if not documenter.parse_name():
117 self.warn('failed to parse name %s' % real_name)
118 items.append((display_name, '', '', real_name))
120 if not documenter.import_object():
121 self.warn('failed to import object %s' % real_name)
122 items.append((display_name, '', '', real_name))
124 if documenter.options.members and not documenter.check_module():
126 # -- Grab the signature
127 sig = documenter.format_signature()
129 # -- Grab the summary
130 documenter.add_content(None)
131 doc = list(documenter.process_doc([self.result.data]))
133 while doc and not doc[0].strip():
136 # If there's a blank line, then we can assume the first sentence /
137 # paragraph has ended, so anything after shouldn't be part of the
139 for i, piece in enumerate(doc):
140 if not piece.strip():
144 # Try to find the "first sentence", which may span multiple lines
145 m = re.search(r"^([A-Z].*?\.)(?:\s|$)", " ".join(doc).strip())
147 summary = m.group(1).strip()
149 summary = doc[0].strip()
153 items.append((display_name, sig, summary, real_name))
157 def get_tablespec(self):
158 table_spec = addnodes.tabular_col_spec()
159 table_spec['spec'] = 'll'
161 table = autosummary_table('')
162 real_table = nodes.table('', classes=['longtable'])
163 table.append(real_table)
164 group = nodes.tgroup('', cols=2)
165 real_table.append(group)
166 group.append(nodes.colspec('', colwidth=10))
167 group.append(nodes.colspec('', colwidth=90))
168 body = nodes.tbody('')
171 def append_row(*column_texts):
173 for text in column_texts:
174 node = nodes.paragraph('')
176 vl.append(text, '<autosummary>')
177 self.state.nested_parse(vl, 0, node)
179 if isinstance(node[0], nodes.paragraph):
183 row.append(nodes.entry('', node))
185 return table, table_spec, append_row
187 def get_table(self, items):
188 """Generate a proper list of table nodes for autosummary:: directive.
190 *items* is a list produced by :meth:`get_items`.
192 table, table_spec, append_row = self.get_tablespec()
193 for name, sig, summary, real_name in items:
194 qualifier = 'cpp:any'
195 # required for cpp autolink
196 full_name = real_name.replace('.', '::')
197 col1 = ':%s:`%s <%s>`' % (qualifier, name, full_name)
199 append_row(col1, col2)
201 self.result.append(' .. rubric: sdsf', 0)
202 return [table_spec, table]
205 class DoxygenAutoEnum(DoxygenAutosummary):
207 def get_items(self, names):
208 env = self.state.document.settings.env
211 real_name, obj, parent, modname = import_by_name(self.name, env=env)
212 names = [n.text for n in obj.findall('./enumvalue/name')]
213 descriptions = [format_xml_paragraph(d) for d in obj.findall('./enumvalue/detaileddescription')]
214 return zip(names, descriptions)
216 def get_table(self, items):
217 table, table_spec, append_row = self.get_tablespec()
218 for name, description in items:
219 col1 = ':strong:`' + name + '`'
220 while description and not description[0].strip():
222 col2 = ' '.join(description)
223 append_row(col1, col2)
224 return [nodes.rubric('', 'Enum: %s' % self.name), table]