|
1 # -*- coding: utf-8 -*- |
|
2 """ |
|
3 sphinx.directives.other |
|
4 ~~~~~~~~~~~~~~~~~~~~~~~ |
|
5 |
|
6 :copyright: 2007-2008 by Georg Brandl. |
|
7 :license: BSD. |
|
8 """ |
|
9 |
|
10 import re |
|
11 import posixpath |
|
12 |
|
13 from docutils import nodes |
|
14 from docutils.parsers.rst import directives |
|
15 |
|
16 from sphinx import addnodes |
|
17 from sphinx.locale import pairindextypes |
|
18 from sphinx.util import patfilter, ws_re, caption_ref_re |
|
19 from sphinx.util.compat import make_admonition |
|
20 |
|
21 |
|
22 # ------ the TOC tree --------------------------------------------------------------- |
|
23 |
|
24 def toctree_directive(name, arguments, options, content, lineno, |
|
25 content_offset, block_text, state, state_machine): |
|
26 env = state.document.settings.env |
|
27 suffix = env.config.source_suffix |
|
28 dirname = posixpath.dirname(env.docname) |
|
29 glob = 'glob' in options |
|
30 |
|
31 ret = [] |
|
32 subnode = addnodes.toctree() |
|
33 includefiles = [] |
|
34 includetitles = {} |
|
35 all_docnames = env.found_docs.copy() |
|
36 # don't add the currently visited file in catch-all patterns |
|
37 all_docnames.remove(env.docname) |
|
38 for entry in content: |
|
39 if not entry: |
|
40 continue |
|
41 if not glob: |
|
42 # look for explicit titles and documents ("Some Title <document>"). |
|
43 m = caption_ref_re.match(entry) |
|
44 if m: |
|
45 docname = m.group(2) |
|
46 includetitles[docname] = m.group(1) |
|
47 else: |
|
48 docname = entry |
|
49 # remove suffixes (backwards compatibility) |
|
50 if docname.endswith(suffix): |
|
51 docname = docname[:-len(suffix)] |
|
52 # absolutize filenames |
|
53 docname = posixpath.normpath(posixpath.join(dirname, docname)) |
|
54 if docname not in env.found_docs: |
|
55 ret.append(state.document.reporter.warning( |
|
56 'toctree references unknown document %r' % docname, line=lineno)) |
|
57 else: |
|
58 includefiles.append(docname) |
|
59 else: |
|
60 patname = posixpath.normpath(posixpath.join(dirname, entry)) |
|
61 docnames = sorted(patfilter(all_docnames, patname)) |
|
62 for docname in docnames: |
|
63 all_docnames.remove(docname) # don't include it again |
|
64 includefiles.append(docname) |
|
65 if not docnames: |
|
66 ret.append(state.document.reporter.warning( |
|
67 'toctree glob pattern %r didn\'t match any documents' % entry, |
|
68 line=lineno)) |
|
69 subnode['includefiles'] = includefiles |
|
70 subnode['includetitles'] = includetitles |
|
71 subnode['maxdepth'] = options.get('maxdepth', -1) |
|
72 subnode['glob'] = glob |
|
73 ret.append(subnode) |
|
74 return ret |
|
75 |
|
76 toctree_directive.content = 1 |
|
77 toctree_directive.options = {'maxdepth': int, 'glob': directives.flag} |
|
78 directives.register_directive('toctree', toctree_directive) |
|
79 |
|
80 |
|
81 # ------ section metadata ---------------------------------------------------------- |
|
82 |
|
83 def module_directive(name, arguments, options, content, lineno, |
|
84 content_offset, block_text, state, state_machine): |
|
85 env = state.document.settings.env |
|
86 modname = arguments[0].strip() |
|
87 noindex = 'noindex' in options |
|
88 env.currmodule = modname |
|
89 env.note_module(modname, options.get('synopsis', ''), |
|
90 options.get('platform', ''), |
|
91 'deprecated' in options) |
|
92 modulenode = addnodes.module() |
|
93 modulenode['modname'] = modname |
|
94 modulenode['synopsis'] = options.get('synopsis', '') |
|
95 targetnode = nodes.target('', '', ids=['module-' + modname]) |
|
96 state.document.note_explicit_target(targetnode) |
|
97 ret = [modulenode, targetnode] |
|
98 if 'platform' in options: |
|
99 modulenode['platform'] = options['platform'] |
|
100 node = nodes.paragraph() |
|
101 node += nodes.emphasis('', _('Platforms: ')) |
|
102 node += nodes.Text(options['platform'], options['platform']) |
|
103 ret.append(node) |
|
104 # the synopsis isn't printed; in fact, it is only used in the modindex currently |
|
105 if not noindex: |
|
106 indextext = _('%s (module)') % modname |
|
107 inode = addnodes.index(entries=[('single', indextext, |
|
108 'module-' + modname, modname)]) |
|
109 ret.insert(0, inode) |
|
110 return ret |
|
111 |
|
112 module_directive.arguments = (1, 0, 0) |
|
113 module_directive.options = {'platform': lambda x: x, |
|
114 'synopsis': lambda x: x, |
|
115 'noindex': directives.flag, |
|
116 'deprecated': directives.flag} |
|
117 directives.register_directive('module', module_directive) |
|
118 |
|
119 |
|
120 def currentmodule_directive(name, arguments, options, content, lineno, |
|
121 content_offset, block_text, state, state_machine): |
|
122 # This directive is just to tell people that we're documenting |
|
123 # stuff in module foo, but links to module foo won't lead here. |
|
124 env = state.document.settings.env |
|
125 modname = arguments[0].strip() |
|
126 if modname == 'None': |
|
127 env.currmodule = None |
|
128 else: |
|
129 env.currmodule = modname |
|
130 return [] |
|
131 |
|
132 currentmodule_directive.arguments = (1, 0, 0) |
|
133 directives.register_directive('currentmodule', currentmodule_directive) |
|
134 |
|
135 |
|
136 def author_directive(name, arguments, options, content, lineno, |
|
137 content_offset, block_text, state, state_machine): |
|
138 # Show authors only if the show_authors option is on |
|
139 env = state.document.settings.env |
|
140 if not env.config.show_authors: |
|
141 return [] |
|
142 para = nodes.paragraph() |
|
143 emph = nodes.emphasis() |
|
144 para += emph |
|
145 if name == 'sectionauthor': |
|
146 text = _('Section author: ') |
|
147 elif name == 'moduleauthor': |
|
148 text = _('Module author: ') |
|
149 else: |
|
150 text = _('Author: ') |
|
151 emph += nodes.Text(text, text) |
|
152 inodes, messages = state.inline_text(arguments[0], lineno) |
|
153 emph.extend(inodes) |
|
154 return [para] + messages |
|
155 |
|
156 author_directive.arguments = (1, 0, 1) |
|
157 directives.register_directive('sectionauthor', author_directive) |
|
158 directives.register_directive('moduleauthor', author_directive) |
|
159 |
|
160 |
|
161 def program_directive(name, arguments, options, content, lineno, |
|
162 content_offset, block_text, state, state_machine): |
|
163 env = state.document.settings.env |
|
164 program = ws_re.sub('-', arguments[0].strip()) |
|
165 if program == 'None': |
|
166 env.currprogram = None |
|
167 else: |
|
168 env.currprogram = program |
|
169 return [] |
|
170 |
|
171 program_directive.arguments = (1, 0, 1) |
|
172 directives.register_directive('program', program_directive) |
|
173 |
|
174 |
|
175 # ------ index markup -------------------------------------------------------------- |
|
176 |
|
177 indextypes = [ |
|
178 'single', 'pair', 'triple', |
|
179 ] |
|
180 |
|
181 def index_directive(name, arguments, options, content, lineno, |
|
182 content_offset, block_text, state, state_machine): |
|
183 arguments = arguments[0].split('\n') |
|
184 env = state.document.settings.env |
|
185 targetid = 'index-%s' % env.index_num |
|
186 env.index_num += 1 |
|
187 targetnode = nodes.target('', '', ids=[targetid]) |
|
188 state.document.note_explicit_target(targetnode) |
|
189 indexnode = addnodes.index() |
|
190 indexnode['entries'] = ne = [] |
|
191 for entry in arguments: |
|
192 entry = entry.strip() |
|
193 for type in pairindextypes: |
|
194 if entry.startswith(type+':'): |
|
195 value = entry[len(type)+1:].strip() |
|
196 value = pairindextypes[type] + '; ' + value |
|
197 ne.append(('pair', value, targetid, value)) |
|
198 break |
|
199 else: |
|
200 for type in indextypes: |
|
201 if entry.startswith(type+':'): |
|
202 value = entry[len(type)+1:].strip() |
|
203 ne.append((type, value, targetid, value)) |
|
204 break |
|
205 # shorthand notation for single entries |
|
206 else: |
|
207 for value in entry.split(','): |
|
208 value = value.strip() |
|
209 if not value: |
|
210 continue |
|
211 ne.append(('single', value, targetid, value)) |
|
212 return [indexnode, targetnode] |
|
213 |
|
214 index_directive.arguments = (1, 0, 1) |
|
215 directives.register_directive('index', index_directive) |
|
216 |
|
217 # ------ versionadded/versionchanged ----------------------------------------------- |
|
218 |
|
219 def version_directive(name, arguments, options, content, lineno, |
|
220 content_offset, block_text, state, state_machine): |
|
221 node = addnodes.versionmodified() |
|
222 node['type'] = name |
|
223 node['version'] = arguments[0] |
|
224 if len(arguments) == 2: |
|
225 inodes, messages = state.inline_text(arguments[1], lineno+1) |
|
226 node.extend(inodes) |
|
227 if content: |
|
228 state.nested_parse(content, content_offset, node) |
|
229 ret = [node] + messages |
|
230 else: |
|
231 ret = [node] |
|
232 env = state.document.settings.env |
|
233 env.note_versionchange(node['type'], node['version'], node, lineno) |
|
234 return ret |
|
235 |
|
236 version_directive.arguments = (1, 1, 1) |
|
237 version_directive.content = 1 |
|
238 |
|
239 directives.register_directive('deprecated', version_directive) |
|
240 directives.register_directive('versionadded', version_directive) |
|
241 directives.register_directive('versionchanged', version_directive) |
|
242 |
|
243 |
|
244 # ------ see also ------------------------------------------------------------------ |
|
245 |
|
246 def seealso_directive(name, arguments, options, content, lineno, |
|
247 content_offset, block_text, state, state_machine): |
|
248 ret = make_admonition( |
|
249 addnodes.seealso, name, [_('See also')], options, content, |
|
250 lineno, content_offset, block_text, state, state_machine) |
|
251 if arguments: |
|
252 argnodes, msgs = state.inline_text(arguments[0], lineno) |
|
253 para = nodes.paragraph() |
|
254 para += argnodes |
|
255 para += msgs |
|
256 ret[0].insert(1, para) |
|
257 return ret |
|
258 |
|
259 seealso_directive.content = 1 |
|
260 seealso_directive.arguments = (0, 1, 1) |
|
261 directives.register_directive('seealso', seealso_directive) |
|
262 |
|
263 |
|
264 # ------ production list (for the reference) --------------------------------------- |
|
265 |
|
266 token_re = re.compile('`([a-z_]+)`') |
|
267 |
|
268 def token_xrefs(text, env): |
|
269 retnodes = [] |
|
270 pos = 0 |
|
271 for m in token_re.finditer(text): |
|
272 if m.start() > pos: |
|
273 txt = text[pos:m.start()] |
|
274 retnodes.append(nodes.Text(txt, txt)) |
|
275 refnode = addnodes.pending_xref(m.group(1)) |
|
276 refnode['reftype'] = 'token' |
|
277 refnode['reftarget'] = m.group(1) |
|
278 refnode['modname'] = env.currmodule |
|
279 refnode['classname'] = env.currclass |
|
280 refnode += nodes.literal(m.group(1), m.group(1), classes=['xref']) |
|
281 retnodes.append(refnode) |
|
282 pos = m.end() |
|
283 if pos < len(text): |
|
284 retnodes.append(nodes.Text(text[pos:], text[pos:])) |
|
285 return retnodes |
|
286 |
|
287 def productionlist_directive(name, arguments, options, content, lineno, |
|
288 content_offset, block_text, state, state_machine): |
|
289 env = state.document.settings.env |
|
290 node = addnodes.productionlist() |
|
291 messages = [] |
|
292 i = 0 |
|
293 |
|
294 for rule in arguments[0].split('\n'): |
|
295 if i == 0 and ':' not in rule: |
|
296 # production group |
|
297 continue |
|
298 i += 1 |
|
299 try: |
|
300 name, tokens = rule.split(':', 1) |
|
301 except ValueError: |
|
302 break |
|
303 subnode = addnodes.production() |
|
304 subnode['tokenname'] = name.strip() |
|
305 if subnode['tokenname']: |
|
306 idname = 'grammar-token-%s' % subnode['tokenname'] |
|
307 if idname not in state.document.ids: |
|
308 subnode['ids'].append(idname) |
|
309 state.document.note_implicit_target(subnode, subnode) |
|
310 env.note_reftarget('token', subnode['tokenname'], idname) |
|
311 subnode.extend(token_xrefs(tokens, env)) |
|
312 node.append(subnode) |
|
313 return [node] + messages |
|
314 |
|
315 productionlist_directive.content = 0 |
|
316 productionlist_directive.arguments = (1, 0, 1) |
|
317 directives.register_directive('productionlist', productionlist_directive) |
|
318 |
|
319 |
|
320 # ------ glossary directive --------------------------------------------------------- |
|
321 |
|
322 def glossary_directive(name, arguments, options, content, lineno, |
|
323 content_offset, block_text, state, state_machine): |
|
324 """Glossary with cross-reference targets for :term: roles.""" |
|
325 env = state.document.settings.env |
|
326 node = addnodes.glossary() |
|
327 state.nested_parse(content, content_offset, node) |
|
328 |
|
329 # the content should be definition lists |
|
330 dls = [child for child in node if isinstance(child, nodes.definition_list)] |
|
331 # now, extract definition terms to enable cross-reference creation |
|
332 for dl in dls: |
|
333 dl['classes'].append('glossary') |
|
334 for li in dl.children: |
|
335 if not li.children or not isinstance(li[0], nodes.term): |
|
336 continue |
|
337 termtext = li.children[0].astext() |
|
338 new_id = 'term-' + nodes.make_id(termtext) |
|
339 if new_id in env.gloss_entries: |
|
340 new_id = 'term-' + str(len(env.gloss_entries)) |
|
341 env.gloss_entries.add(new_id) |
|
342 li[0]['names'].append(new_id) |
|
343 li[0]['ids'].append(new_id) |
|
344 state.document.settings.env.note_reftarget('term', termtext.lower(), |
|
345 new_id) |
|
346 # add an index entry too |
|
347 indexnode = addnodes.index() |
|
348 indexnode['entries'] = [('single', termtext, new_id, termtext)] |
|
349 li.insert(0, indexnode) |
|
350 return [node] |
|
351 |
|
352 glossary_directive.content = 1 |
|
353 glossary_directive.arguments = (0, 0, 0) |
|
354 directives.register_directive('glossary', glossary_directive) |
|
355 |
|
356 |
|
357 # ------ miscellaneous markup ------------------------------------------------------- |
|
358 |
|
359 def centered_directive(name, arguments, options, content, lineno, |
|
360 content_offset, block_text, state, state_machine): |
|
361 if not arguments: |
|
362 return [] |
|
363 subnode = addnodes.centered() |
|
364 inodes, messages = state.inline_text(arguments[0], lineno) |
|
365 subnode.extend(inodes) |
|
366 return [subnode] + messages |
|
367 |
|
368 centered_directive.arguments = (1, 0, 1) |
|
369 directives.register_directive('centered', centered_directive) |
|
370 |
|
371 |
|
372 def acks_directive(name, arguments, options, content, lineno, |
|
373 content_offset, block_text, state, state_machine): |
|
374 node = addnodes.acks() |
|
375 state.nested_parse(content, content_offset, node) |
|
376 if len(node.children) != 1 or not isinstance(node.children[0], nodes.bullet_list): |
|
377 return [state.document.reporter.warning('.. acks content is not a list', |
|
378 line=lineno)] |
|
379 return [node] |
|
380 |
|
381 acks_directive.content = 1 |
|
382 acks_directive.arguments = (0, 0, 0) |
|
383 directives.register_directive('acks', acks_directive) |
|
384 |
|
385 |
|
386 def tabularcolumns_directive(name, arguments, options, content, lineno, |
|
387 content_offset, block_text, state, state_machine): |
|
388 # support giving explicit tabulary column definition to latex |
|
389 node = addnodes.tabular_col_spec() |
|
390 node['spec'] = arguments[0] |
|
391 return [node] |
|
392 |
|
393 tabularcolumns_directive.content = 0 |
|
394 tabularcolumns_directive.arguments = (1, 0, 1) |
|
395 directives.register_directive('tabularcolumns', tabularcolumns_directive) |
|
396 |
|
397 |
|
398 # register the standard rst class directive under a different name |
|
399 |
|
400 try: |
|
401 # docutils 0.4 |
|
402 from docutils.parsers.rst.directives.misc import class_directive |
|
403 directives.register_directive('cssclass', class_directive) |
|
404 except ImportError: |
|
405 try: |
|
406 # docutils 0.5 |
|
407 from docutils.parsers.rst.directives.misc import Class |
|
408 directives.register_directive('cssclass', Class) |
|
409 except ImportError: |
|
410 # whatever :) |
|
411 pass |