Merge lp:~mitya57/ubuntu/raring/sphinx/1.1.3+dfsg-5ubuntu1 into lp:ubuntu/raring/sphinx
- Raring (13.04)
- 1.1.3+dfsg-5ubuntu1
- Merge into raring
Proposed by
Dmitry Shachnev
Status: | Merged |
---|---|
Merged at revision: | 39 |
Proposed branch: | lp:~mitya57/ubuntu/raring/sphinx/1.1.3+dfsg-5ubuntu1 |
Merge into: | lp:ubuntu/raring/sphinx |
Diff against target: |
5032 lines (+2599/-2162) 22 files modified
.pc/applied-patches (+3/-0) .pc/fix_manpages_generation_with_new_docutils.diff/sphinx/writers/manpage.py (+0/-345) .pc/l10n_fixes.diff/sphinx/environment.py (+1762/-0) .pc/sort_stopwords.diff/sphinx/search/__init__.py (+287/-0) .pc/support_python_3.3.diff/sphinx/environment.py (+0/-1762) .pc/test_build_html_rb.diff/tests/test_build_html.py (+339/-0) debian/changelog (+58/-0) debian/control (+3/-3) debian/dh-sphinxdoc/dh_sphinxdoc (+4/-2) debian/patches/l10n_fixes.diff (+58/-0) debian/patches/series (+3/-0) debian/patches/sort_stopwords.diff (+16/-0) debian/patches/support_python_3.3.diff (+11/-35) debian/patches/test_build_html_rb.diff (+17/-0) debian/rules (+3/-2) debian/sphinx-autogen.1 (+1/-1) debian/tests/control (+0/-2) debian/tests/python-sphinx (+1/-1) debian/tests/python3-sphinx (+1/-1) sphinx/environment.py (+30/-6) sphinx/search/__init__.py (+1/-1) tests/test_build_html.py (+1/-1) |
To merge this branch: | bzr merge lp:~mitya57/ubuntu/raring/sphinx/1.1.3+dfsg-5ubuntu1 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Daniel Holbach (community) | Approve | ||
Ubuntu branches | Pending | ||
Review via email: mp+136582@code.launchpad.net |
Commit message
Description of the change
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file '.pc/applied-patches' |
2 | --- .pc/applied-patches 2012-11-01 21:39:16 +0000 |
3 | +++ .pc/applied-patches 2012-11-28 07:12:20 +0000 |
4 | @@ -9,4 +9,7 @@ |
5 | pygments_byte_strings.diff |
6 | fix_shorthandoff.diff |
7 | fix_manpages_generation_with_new_docutils.diff |
8 | +test_build_html_rb.diff |
9 | +sort_stopwords.diff |
10 | support_python_3.3.diff |
11 | +l10n_fixes.diff |
12 | |
13 | === removed directory '.pc/fix_manpages_generation_with_new_docutils.diff' |
14 | === removed directory '.pc/fix_manpages_generation_with_new_docutils.diff/sphinx' |
15 | === removed directory '.pc/fix_manpages_generation_with_new_docutils.diff/sphinx/writers' |
16 | === removed file '.pc/fix_manpages_generation_with_new_docutils.diff/sphinx/writers/manpage.py' |
17 | --- .pc/fix_manpages_generation_with_new_docutils.diff/sphinx/writers/manpage.py 2012-10-22 20:20:35 +0000 |
18 | +++ .pc/fix_manpages_generation_with_new_docutils.diff/sphinx/writers/manpage.py 1970-01-01 00:00:00 +0000 |
19 | @@ -1,345 +0,0 @@ |
20 | -# -*- coding: utf-8 -*- |
21 | -""" |
22 | - sphinx.writers.manpage |
23 | - ~~~~~~~~~~~~~~~~~~~~~~ |
24 | - |
25 | - Manual page writer, extended for Sphinx custom nodes. |
26 | - |
27 | - :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. |
28 | - :license: BSD, see LICENSE for details. |
29 | -""" |
30 | - |
31 | -from docutils import nodes |
32 | -try: |
33 | - from docutils.writers.manpage import MACRO_DEF, Writer, \ |
34 | - Translator as BaseTranslator |
35 | - has_manpage_writer = True |
36 | -except ImportError: |
37 | - # define the classes in any case, sphinx.application needs it |
38 | - Writer = BaseTranslator = object |
39 | - has_manpage_writer = False |
40 | - |
41 | -from sphinx import addnodes |
42 | -from sphinx.locale import admonitionlabels, versionlabels, _ |
43 | -from sphinx.util.osutil import ustrftime |
44 | - |
45 | - |
46 | -class ManualPageWriter(Writer): |
47 | - def __init__(self, builder): |
48 | - Writer.__init__(self) |
49 | - self.builder = builder |
50 | - |
51 | - def translate(self): |
52 | - visitor = ManualPageTranslator(self.builder, self.document) |
53 | - self.visitor = visitor |
54 | - self.document.walkabout(visitor) |
55 | - self.output = visitor.astext() |
56 | - |
57 | - |
58 | -class ManualPageTranslator(BaseTranslator): |
59 | - """ |
60 | - Custom translator. |
61 | - """ |
62 | - |
63 | - def __init__(self, builder, *args, **kwds): |
64 | - BaseTranslator.__init__(self, *args, **kwds) |
65 | - self.builder = builder |
66 | - |
67 | - self.in_productionlist = 0 |
68 | - |
69 | - # first title is the manpage title |
70 | - self.section_level = -1 |
71 | - |
72 | - # docinfo set by man_pages config value |
73 | - self._docinfo['title'] = self.document.settings.title |
74 | - self._docinfo['subtitle'] = self.document.settings.subtitle |
75 | - if self.document.settings.authors: |
76 | - # don't set it if no author given |
77 | - self._docinfo['author'] = self.document.settings.authors |
78 | - self._docinfo['manual_section'] = self.document.settings.section |
79 | - |
80 | - # docinfo set by other config values |
81 | - self._docinfo['title_upper'] = self._docinfo['title'].upper() |
82 | - if builder.config.today: |
83 | - self._docinfo['date'] = builder.config.today |
84 | - else: |
85 | - self._docinfo['date'] = ustrftime(builder.config.today_fmt |
86 | - or _('%B %d, %Y')) |
87 | - self._docinfo['copyright'] = builder.config.copyright |
88 | - self._docinfo['version'] = builder.config.version |
89 | - self._docinfo['manual_group'] = builder.config.project |
90 | - |
91 | - # since self.append_header() is never called, need to do this here |
92 | - self.body.append(MACRO_DEF) |
93 | - |
94 | - # overwritten -- added quotes around all .TH arguments |
95 | - def header(self): |
96 | - tmpl = (".TH \"%(title_upper)s\" \"%(manual_section)s\"" |
97 | - " \"%(date)s\" \"%(version)s\" \"%(manual_group)s\"\n" |
98 | - ".SH NAME\n" |
99 | - "%(title)s \- %(subtitle)s\n") |
100 | - return tmpl % self._docinfo |
101 | - |
102 | - def visit_start_of_file(self, node): |
103 | - pass |
104 | - def depart_start_of_file(self, node): |
105 | - pass |
106 | - |
107 | - def visit_desc(self, node): |
108 | - self.visit_definition_list(node) |
109 | - def depart_desc(self, node): |
110 | - self.depart_definition_list(node) |
111 | - |
112 | - def visit_desc_signature(self, node): |
113 | - self.visit_definition_list_item(node) |
114 | - self.visit_term(node) |
115 | - def depart_desc_signature(self, node): |
116 | - self.depart_term(node) |
117 | - |
118 | - def visit_desc_addname(self, node): |
119 | - pass |
120 | - def depart_desc_addname(self, node): |
121 | - pass |
122 | - |
123 | - def visit_desc_type(self, node): |
124 | - pass |
125 | - def depart_desc_type(self, node): |
126 | - pass |
127 | - |
128 | - def visit_desc_returns(self, node): |
129 | - self.body.append(' -> ') |
130 | - def depart_desc_returns(self, node): |
131 | - pass |
132 | - |
133 | - def visit_desc_name(self, node): |
134 | - pass |
135 | - def depart_desc_name(self, node): |
136 | - pass |
137 | - |
138 | - def visit_desc_parameterlist(self, node): |
139 | - self.body.append('(') |
140 | - self.first_param = 1 |
141 | - def depart_desc_parameterlist(self, node): |
142 | - self.body.append(')') |
143 | - |
144 | - def visit_desc_parameter(self, node): |
145 | - if not self.first_param: |
146 | - self.body.append(', ') |
147 | - else: |
148 | - self.first_param = 0 |
149 | - def depart_desc_parameter(self, node): |
150 | - pass |
151 | - |
152 | - def visit_desc_optional(self, node): |
153 | - self.body.append('[') |
154 | - def depart_desc_optional(self, node): |
155 | - self.body.append(']') |
156 | - |
157 | - def visit_desc_annotation(self, node): |
158 | - pass |
159 | - def depart_desc_annotation(self, node): |
160 | - pass |
161 | - |
162 | - def visit_desc_content(self, node): |
163 | - self.visit_definition(node) |
164 | - def depart_desc_content(self, node): |
165 | - self.depart_definition(node) |
166 | - |
167 | - def visit_refcount(self, node): |
168 | - self.body.append(self.defs['emphasis'][0]) |
169 | - def depart_refcount(self, node): |
170 | - self.body.append(self.defs['emphasis'][1]) |
171 | - |
172 | - def visit_versionmodified(self, node): |
173 | - self.visit_paragraph(node) |
174 | - text = versionlabels[node['type']] % node['version'] |
175 | - if len(node): |
176 | - text += ': ' |
177 | - else: |
178 | - text += '.' |
179 | - self.body.append(text) |
180 | - def depart_versionmodified(self, node): |
181 | - self.depart_paragraph(node) |
182 | - |
183 | - def visit_termsep(self, node): |
184 | - self.body.append(', ') |
185 | - raise nodes.SkipNode |
186 | - |
187 | - # overwritten -- we don't want source comments to show up |
188 | - def visit_comment(self, node): |
189 | - raise nodes.SkipNode |
190 | - |
191 | - # overwritten -- added ensure_eol() |
192 | - def visit_footnote(self, node): |
193 | - self.ensure_eol() |
194 | - BaseTranslator.visit_footnote(self, node) |
195 | - |
196 | - # overwritten -- handle footnotes rubric |
197 | - def visit_rubric(self, node): |
198 | - self.ensure_eol() |
199 | - if len(node.children) == 1: |
200 | - rubtitle = node.children[0].astext() |
201 | - if rubtitle in ('Footnotes', _('Footnotes')): |
202 | - self.body.append('.SH ' + self.deunicode(rubtitle).upper() + |
203 | - '\n') |
204 | - raise nodes.SkipNode |
205 | - else: |
206 | - self.body.append('.sp\n') |
207 | - def depart_rubric(self, node): |
208 | - pass |
209 | - |
210 | - def visit_seealso(self, node): |
211 | - self.visit_admonition(node) |
212 | - def depart_seealso(self, node): |
213 | - self.depart_admonition(node) |
214 | - |
215 | - # overwritten -- use our own label translations |
216 | - def visit_admonition(self, node, name=None): |
217 | - if name: |
218 | - self.body.append('.IP %s\n' % |
219 | - self.deunicode(admonitionlabels.get(name, name))) |
220 | - |
221 | - def visit_productionlist(self, node): |
222 | - self.ensure_eol() |
223 | - names = [] |
224 | - self.in_productionlist += 1 |
225 | - self.body.append('.sp\n.nf\n') |
226 | - for production in node: |
227 | - names.append(production['tokenname']) |
228 | - maxlen = max(len(name) for name in names) |
229 | - for production in node: |
230 | - if production['tokenname']: |
231 | - lastname = production['tokenname'].ljust(maxlen) |
232 | - self.body.append(self.defs['strong'][0]) |
233 | - self.body.append(self.deunicode(lastname)) |
234 | - self.body.append(self.defs['strong'][1]) |
235 | - self.body.append(' ::= ') |
236 | - else: |
237 | - self.body.append('%s ' % (' '*len(lastname))) |
238 | - production.walkabout(self) |
239 | - self.body.append('\n') |
240 | - self.body.append('\n.fi\n') |
241 | - self.in_productionlist -= 1 |
242 | - raise nodes.SkipNode |
243 | - |
244 | - def visit_production(self, node): |
245 | - pass |
246 | - def depart_production(self, node): |
247 | - pass |
248 | - |
249 | - # overwritten -- don't emit a warning for images |
250 | - def visit_image(self, node): |
251 | - if 'alt' in node.attributes: |
252 | - self.body.append(_('[image: %s]') % node['alt'] + '\n') |
253 | - self.body.append(_('[image]') + '\n') |
254 | - raise nodes.SkipNode |
255 | - |
256 | - # overwritten -- don't visit inner marked up nodes |
257 | - def visit_reference(self, node): |
258 | - self.body.append(self.defs['reference'][0]) |
259 | - self.body.append(node.astext()) |
260 | - self.body.append(self.defs['reference'][1]) |
261 | - |
262 | - uri = node.get('refuri', '') |
263 | - if uri.startswith('mailto:') or uri.startswith('http:') or \ |
264 | - uri.startswith('https:') or uri.startswith('ftp:'): |
265 | - # if configured, put the URL after the link |
266 | - if self.builder.config.man_show_urls and \ |
267 | - node.astext() != uri: |
268 | - if uri.startswith('mailto:'): |
269 | - uri = uri[7:] |
270 | - self.body.extend([ |
271 | - ' <', |
272 | - self.defs['strong'][0], uri, self.defs['strong'][1], |
273 | - '>']) |
274 | - raise nodes.SkipNode |
275 | - |
276 | - def visit_centered(self, node): |
277 | - self.ensure_eol() |
278 | - self.body.append('.sp\n.ce\n') |
279 | - def depart_centered(self, node): |
280 | - self.body.append('\n.ce 0\n') |
281 | - |
282 | - def visit_compact_paragraph(self, node): |
283 | - pass |
284 | - def depart_compact_paragraph(self, node): |
285 | - pass |
286 | - |
287 | - def visit_highlightlang(self, node): |
288 | - pass |
289 | - def depart_highlightlang(self, node): |
290 | - pass |
291 | - |
292 | - def visit_download_reference(self, node): |
293 | - pass |
294 | - def depart_download_reference(self, node): |
295 | - pass |
296 | - |
297 | - def visit_toctree(self, node): |
298 | - raise nodes.SkipNode |
299 | - |
300 | - def visit_index(self, node): |
301 | - raise nodes.SkipNode |
302 | - |
303 | - def visit_tabular_col_spec(self, node): |
304 | - raise nodes.SkipNode |
305 | - |
306 | - def visit_glossary(self, node): |
307 | - pass |
308 | - def depart_glossary(self, node): |
309 | - pass |
310 | - |
311 | - def visit_acks(self, node): |
312 | - self.ensure_eol() |
313 | - self.body.append(', '.join(n.astext() |
314 | - for n in node.children[0].children) + '.') |
315 | - self.body.append('\n') |
316 | - raise nodes.SkipNode |
317 | - |
318 | - def visit_hlist(self, node): |
319 | - self.visit_bullet_list(node) |
320 | - def depart_hlist(self, node): |
321 | - self.depart_bullet_list(node) |
322 | - |
323 | - def visit_hlistcol(self, node): |
324 | - pass |
325 | - def depart_hlistcol(self, node): |
326 | - pass |
327 | - |
328 | - def visit_literal_emphasis(self, node): |
329 | - return self.visit_emphasis(node) |
330 | - def depart_literal_emphasis(self, node): |
331 | - return self.depart_emphasis(node) |
332 | - |
333 | - def visit_abbreviation(self, node): |
334 | - pass |
335 | - def depart_abbreviation(self, node): |
336 | - pass |
337 | - |
338 | - # overwritten: handle section titles better than in 0.6 release |
339 | - def visit_title(self, node): |
340 | - if isinstance(node.parent, addnodes.seealso): |
341 | - self.body.append('.IP "') |
342 | - return |
343 | - elif isinstance(node.parent, nodes.section): |
344 | - if self.section_level == 0: |
345 | - # skip the document title |
346 | - raise nodes.SkipNode |
347 | - elif self.section_level == 1: |
348 | - self.body.append('.SH %s\n' % |
349 | - self.deunicode(node.astext().upper())) |
350 | - raise nodes.SkipNode |
351 | - return BaseTranslator.visit_title(self, node) |
352 | - def depart_title(self, node): |
353 | - if isinstance(node.parent, addnodes.seealso): |
354 | - self.body.append('"\n') |
355 | - return |
356 | - return BaseTranslator.depart_title(self, node) |
357 | - |
358 | - def visit_raw(self, node): |
359 | - if 'manpage' in node.get('format', '').split(): |
360 | - self.body.append(node.astext()) |
361 | - raise nodes.SkipNode |
362 | - |
363 | - def unknown_visit(self, node): |
364 | - raise NotImplementedError('Unknown node: ' + node.__class__.__name__) |
365 | |
366 | === added directory '.pc/l10n_fixes.diff' |
367 | === added directory '.pc/l10n_fixes.diff/sphinx' |
368 | === added file '.pc/l10n_fixes.diff/sphinx/environment.py' |
369 | --- .pc/l10n_fixes.diff/sphinx/environment.py 1970-01-01 00:00:00 +0000 |
370 | +++ .pc/l10n_fixes.diff/sphinx/environment.py 2012-11-28 07:12:20 +0000 |
371 | @@ -0,0 +1,1762 @@ |
372 | +# -*- coding: utf-8 -*- |
373 | +""" |
374 | + sphinx.environment |
375 | + ~~~~~~~~~~~~~~~~~~ |
376 | + |
377 | + Global creation environment. |
378 | + |
379 | + :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. |
380 | + :license: BSD, see LICENSE for details. |
381 | +""" |
382 | + |
383 | +import re |
384 | +import os |
385 | +import sys |
386 | +import time |
387 | +import types |
388 | +import codecs |
389 | +import imghdr |
390 | +import string |
391 | +import unicodedata |
392 | +import cPickle as pickle |
393 | +from os import path |
394 | +from glob import glob |
395 | +from itertools import izip, groupby |
396 | + |
397 | +from docutils import nodes |
398 | +from docutils.io import FileInput, NullOutput |
399 | +from docutils.core import Publisher |
400 | +from docutils.utils import Reporter, relative_path, new_document, \ |
401 | + get_source_line |
402 | +from docutils.readers import standalone |
403 | +from docutils.parsers.rst import roles, directives, Parser as RSTParser |
404 | +from docutils.parsers.rst.languages import en as english |
405 | +from docutils.parsers.rst.directives.html import MetaBody |
406 | +from docutils.writers import UnfilteredWriter |
407 | +from docutils.transforms import Transform |
408 | +from docutils.transforms.parts import ContentsFilter |
409 | + |
410 | +from sphinx import addnodes |
411 | +from sphinx.util import url_re, get_matching_docs, docname_join, split_into, \ |
412 | + FilenameUniqDict |
413 | +from sphinx.util.nodes import clean_astext, make_refnode, extract_messages, \ |
414 | + WarningStream |
415 | +from sphinx.util.osutil import movefile, SEP, ustrftime, find_catalog |
416 | +from sphinx.util.matching import compile_matchers |
417 | +from sphinx.util.pycompat import all, class_types |
418 | +from sphinx.util.websupport import is_commentable |
419 | +from sphinx.errors import SphinxError, ExtensionError |
420 | +from sphinx.locale import _, init as init_locale |
421 | +from sphinx.versioning import add_uids, merge_doctrees |
422 | + |
423 | +fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() |
424 | + |
425 | +orig_role_function = roles.role |
426 | +orig_directive_function = directives.directive |
427 | + |
428 | +class ElementLookupError(Exception): pass |
429 | + |
430 | + |
431 | +default_settings = { |
432 | + 'embed_stylesheet': False, |
433 | + 'cloak_email_addresses': True, |
434 | + 'pep_base_url': 'http://www.python.org/dev/peps/', |
435 | + 'rfc_base_url': 'http://tools.ietf.org/html/', |
436 | + 'input_encoding': 'utf-8-sig', |
437 | + 'doctitle_xform': False, |
438 | + 'sectsubtitle_xform': False, |
439 | + 'halt_level': 5, |
440 | +} |
441 | + |
442 | +# This is increased every time an environment attribute is added |
443 | +# or changed to properly invalidate pickle files. |
444 | +ENV_VERSION = 41 |
445 | + |
446 | + |
447 | +default_substitutions = set([ |
448 | + 'version', |
449 | + 'release', |
450 | + 'today', |
451 | +]) |
452 | + |
453 | +dummy_reporter = Reporter('', 4, 4) |
454 | + |
455 | +versioning_conditions = { |
456 | + 'none': False, |
457 | + 'text': nodes.TextElement, |
458 | + 'commentable': is_commentable, |
459 | +} |
460 | + |
461 | + |
462 | +class NoUri(Exception): |
463 | + """Raised by get_relative_uri if there is no URI available.""" |
464 | + pass |
465 | + |
466 | + |
467 | +class DefaultSubstitutions(Transform): |
468 | + """ |
469 | + Replace some substitutions if they aren't defined in the document. |
470 | + """ |
471 | + # run before the default Substitutions |
472 | + default_priority = 210 |
473 | + |
474 | + def apply(self): |
475 | + config = self.document.settings.env.config |
476 | + # only handle those not otherwise defined in the document |
477 | + to_handle = default_substitutions - set(self.document.substitution_defs) |
478 | + for ref in self.document.traverse(nodes.substitution_reference): |
479 | + refname = ref['refname'] |
480 | + if refname in to_handle: |
481 | + text = config[refname] |
482 | + if refname == 'today' and not text: |
483 | + # special handling: can also specify a strftime format |
484 | + text = ustrftime(config.today_fmt or _('%B %d, %Y')) |
485 | + ref.replace_self(nodes.Text(text, text)) |
486 | + |
487 | + |
488 | +class MoveModuleTargets(Transform): |
489 | + """ |
490 | + Move module targets that are the first thing in a section to the section |
491 | + title. |
492 | + |
493 | + XXX Python specific |
494 | + """ |
495 | + default_priority = 210 |
496 | + |
497 | + def apply(self): |
498 | + for node in self.document.traverse(nodes.target): |
499 | + if not node['ids']: |
500 | + continue |
501 | + if (node.has_key('ismod') and |
502 | + node.parent.__class__ is nodes.section and |
503 | + # index 0 is the section title node |
504 | + node.parent.index(node) == 1): |
505 | + node.parent['ids'][0:0] = node['ids'] |
506 | + node.parent.remove(node) |
507 | + |
508 | + |
509 | +class HandleCodeBlocks(Transform): |
510 | + """ |
511 | + Several code block related transformations. |
512 | + """ |
513 | + default_priority = 210 |
514 | + |
515 | + def apply(self): |
516 | + # move doctest blocks out of blockquotes |
517 | + for node in self.document.traverse(nodes.block_quote): |
518 | + if all(isinstance(child, nodes.doctest_block) for child |
519 | + in node.children): |
520 | + node.replace_self(node.children) |
521 | + # combine successive doctest blocks |
522 | + #for node in self.document.traverse(nodes.doctest_block): |
523 | + # if node not in node.parent.children: |
524 | + # continue |
525 | + # parindex = node.parent.index(node) |
526 | + # while len(node.parent) > parindex+1 and \ |
527 | + # isinstance(node.parent[parindex+1], nodes.doctest_block): |
528 | + # node[0] = nodes.Text(node[0] + '\n\n' + |
529 | + # node.parent[parindex+1][0]) |
530 | + # del node.parent[parindex+1] |
531 | + |
532 | + |
533 | +class SortIds(Transform): |
534 | + """ |
535 | + Sort secion IDs so that the "id[0-9]+" one comes last. |
536 | + """ |
537 | + default_priority = 261 |
538 | + |
539 | + def apply(self): |
540 | + for node in self.document.traverse(nodes.section): |
541 | + if len(node['ids']) > 1 and node['ids'][0].startswith('id'): |
542 | + node['ids'] = node['ids'][1:] + [node['ids'][0]] |
543 | + |
544 | + |
545 | +class CitationReferences(Transform): |
546 | + """ |
547 | + Replace citation references by pending_xref nodes before the default |
548 | + docutils transform tries to resolve them. |
549 | + """ |
550 | + default_priority = 619 |
551 | + |
552 | + def apply(self): |
553 | + for citnode in self.document.traverse(nodes.citation_reference): |
554 | + cittext = citnode.astext() |
555 | + refnode = addnodes.pending_xref(cittext, reftype='citation', |
556 | + reftarget=cittext, refwarn=True) |
557 | + refnode.line = citnode.line or citnode.parent.line |
558 | + refnode += nodes.Text('[' + cittext + ']') |
559 | + citnode.parent.replace(citnode, refnode) |
560 | + |
561 | + |
562 | +class Locale(Transform): |
563 | + """ |
564 | + Replace translatable nodes with their translated doctree. |
565 | + """ |
566 | + default_priority = 0 |
567 | + def apply(self): |
568 | + env = self.document.settings.env |
569 | + settings, source = self.document.settings, self.document['source'] |
570 | + # XXX check if this is reliable |
571 | + assert source.startswith(env.srcdir) |
572 | + docname = path.splitext(relative_path(env.srcdir, source))[0] |
573 | + textdomain = find_catalog(docname, |
574 | + self.document.settings.gettext_compact) |
575 | + |
576 | + # fetch translations |
577 | + dirs = [path.join(env.srcdir, directory) |
578 | + for directory in env.config.locale_dirs] |
579 | + catalog, has_catalog = init_locale(dirs, env.config.language, |
580 | + textdomain) |
581 | + if not has_catalog: |
582 | + return |
583 | + |
584 | + parser = RSTParser() |
585 | + |
586 | + for node, msg in extract_messages(self.document): |
587 | + patch = new_document(source, settings) |
588 | + msgstr = catalog.gettext(msg) |
589 | + # XXX add marker to untranslated parts |
590 | + if not msgstr or msgstr == msg: # as-of-yet untranslated |
591 | + continue |
592 | + parser.parse(msgstr, patch) |
593 | + patch = patch[0] |
594 | + # XXX doctest and other block markup |
595 | + if not isinstance(patch, nodes.paragraph): |
596 | + continue # skip for now |
597 | + for child in patch.children: # update leaves |
598 | + child.parent = node |
599 | + node.children = patch.children |
600 | + |
601 | + |
602 | +class SphinxStandaloneReader(standalone.Reader): |
603 | + """ |
604 | + Add our own transforms. |
605 | + """ |
606 | + transforms = [Locale, CitationReferences, DefaultSubstitutions, |
607 | + MoveModuleTargets, HandleCodeBlocks, SortIds] |
608 | + |
609 | + def get_transforms(self): |
610 | + return standalone.Reader.get_transforms(self) + self.transforms |
611 | + |
612 | + |
613 | +class SphinxDummyWriter(UnfilteredWriter): |
614 | + supported = ('html',) # needed to keep "meta" nodes |
615 | + |
616 | + def translate(self): |
617 | + pass |
618 | + |
619 | + |
620 | +class SphinxContentsFilter(ContentsFilter): |
621 | + """ |
622 | + Used with BuildEnvironment.add_toc_from() to discard cross-file links |
623 | + within table-of-contents link nodes. |
624 | + """ |
625 | + def visit_pending_xref(self, node): |
626 | + text = node.astext() |
627 | + self.parent.append(nodes.literal(text, text)) |
628 | + raise nodes.SkipNode |
629 | + |
630 | + def visit_image(self, node): |
631 | + raise nodes.SkipNode |
632 | + |
633 | + |
634 | +class BuildEnvironment: |
635 | + """ |
636 | + The environment in which the ReST files are translated. |
637 | + Stores an inventory of cross-file targets and provides doctree |
638 | + transformations to resolve links to them. |
639 | + """ |
640 | + |
641 | + # --------- ENVIRONMENT PERSISTENCE ---------------------------------------- |
642 | + |
643 | + @staticmethod |
644 | + def frompickle(config, filename): |
645 | + picklefile = open(filename, 'rb') |
646 | + try: |
647 | + env = pickle.load(picklefile) |
648 | + finally: |
649 | + picklefile.close() |
650 | + if env.version != ENV_VERSION: |
651 | + raise IOError('env version not current') |
652 | + env.config.values = config.values |
653 | + return env |
654 | + |
655 | + def topickle(self, filename): |
656 | + # remove unpicklable attributes |
657 | + warnfunc = self._warnfunc |
658 | + self.set_warnfunc(None) |
659 | + values = self.config.values |
660 | + del self.config.values |
661 | + domains = self.domains |
662 | + del self.domains |
663 | + # first write to a temporary file, so that if dumping fails, |
664 | + # the existing environment won't be overwritten |
665 | + picklefile = open(filename + '.tmp', 'wb') |
666 | + # remove potentially pickling-problematic values from config |
667 | + for key, val in vars(self.config).items(): |
668 | + if key.startswith('_') or \ |
669 | + isinstance(val, types.ModuleType) or \ |
670 | + isinstance(val, types.FunctionType) or \ |
671 | + isinstance(val, class_types): |
672 | + del self.config[key] |
673 | + try: |
674 | + pickle.dump(self, picklefile, pickle.HIGHEST_PROTOCOL) |
675 | + finally: |
676 | + picklefile.close() |
677 | + movefile(filename + '.tmp', filename) |
678 | + # reset attributes |
679 | + self.domains = domains |
680 | + self.config.values = values |
681 | + self.set_warnfunc(warnfunc) |
682 | + |
683 | + # --------- ENVIRONMENT INITIALIZATION ------------------------------------- |
684 | + |
685 | + def __init__(self, srcdir, doctreedir, config): |
686 | + self.doctreedir = doctreedir |
687 | + self.srcdir = srcdir |
688 | + self.config = config |
689 | + |
690 | + # the method of doctree versioning; see set_versioning_method |
691 | + self.versioning_condition = None |
692 | + |
693 | + # the application object; only set while update() runs |
694 | + self.app = None |
695 | + |
696 | + # all the registered domains, set by the application |
697 | + self.domains = {} |
698 | + |
699 | + # the docutils settings for building |
700 | + self.settings = default_settings.copy() |
701 | + self.settings['env'] = self |
702 | + |
703 | + # the function to write warning messages with |
704 | + self._warnfunc = None |
705 | + |
706 | + # this is to invalidate old pickles |
707 | + self.version = ENV_VERSION |
708 | + |
709 | + # make this a set for faster testing |
710 | + self._nitpick_ignore = set(self.config.nitpick_ignore) |
711 | + |
712 | + # All "docnames" here are /-separated and relative and exclude |
713 | + # the source suffix. |
714 | + |
715 | + self.found_docs = set() # contains all existing docnames |
716 | + self.all_docs = {} # docname -> mtime at the time of build |
717 | + # contains all built docnames |
718 | + self.dependencies = {} # docname -> set of dependent file |
719 | + # names, relative to documentation root |
720 | + self.reread_always = set() # docnames to re-read unconditionally on |
721 | + # next build |
722 | + |
723 | + # File metadata |
724 | + self.metadata = {} # docname -> dict of metadata items |
725 | + |
726 | + # TOC inventory |
727 | + self.titles = {} # docname -> title node |
728 | + self.longtitles = {} # docname -> title node; only different if |
729 | + # set differently with title directive |
730 | + self.tocs = {} # docname -> table of contents nodetree |
731 | + self.toc_num_entries = {} # docname -> number of real entries |
732 | + # used to determine when to show the TOC |
733 | + # in a sidebar (don't show if it's only one item) |
734 | + self.toc_secnumbers = {} # docname -> dict of sectionid -> number |
735 | + |
736 | + self.toctree_includes = {} # docname -> list of toctree includefiles |
737 | + self.files_to_rebuild = {} # docname -> set of files |
738 | + # (containing its TOCs) to rebuild too |
739 | + self.glob_toctrees = set() # docnames that have :glob: toctrees |
740 | + self.numbered_toctrees = set() # docnames that have :numbered: toctrees |
741 | + |
742 | + # domain-specific inventories, here to be pickled |
743 | + self.domaindata = {} # domainname -> domain-specific dict |
744 | + |
745 | + # Other inventories |
746 | + self.citations = {} # citation name -> docname, labelid |
747 | + self.indexentries = {} # docname -> list of |
748 | + # (type, string, target, aliasname) |
749 | + self.versionchanges = {} # version -> list of (type, docname, |
750 | + # lineno, module, descname, content) |
751 | + |
752 | + # these map absolute path -> (docnames, unique filename) |
753 | + self.images = FilenameUniqDict() |
754 | + self.dlfiles = FilenameUniqDict() |
755 | + |
756 | + # temporary data storage while reading a document |
757 | + self.temp_data = {} |
758 | + |
759 | + def set_warnfunc(self, func): |
760 | + self._warnfunc = func |
761 | + self.settings['warning_stream'] = WarningStream(func) |
762 | + |
763 | + def set_versioning_method(self, method): |
764 | + """This sets the doctree versioning method for this environment. |
765 | + |
766 | + Versioning methods are a builder property; only builders with the same |
767 | + versioning method can share the same doctree directory. Therefore, we |
768 | + raise an exception if the user tries to use an environment with an |
769 | + incompatible versioning method. |
770 | + """ |
771 | + if method not in versioning_conditions: |
772 | + raise ValueError('invalid versioning method: %r' % method) |
773 | + condition = versioning_conditions[method] |
774 | + if self.versioning_condition not in (None, condition): |
775 | + raise SphinxError('This environment is incompatible with the ' |
776 | + 'selected builder, please choose another ' |
777 | + 'doctree directory.') |
778 | + self.versioning_condition = condition |
779 | + |
780 | + def warn(self, docname, msg, lineno=None): |
781 | + # strange argument order is due to backwards compatibility |
782 | + self._warnfunc(msg, (docname, lineno)) |
783 | + |
784 | + def warn_node(self, msg, node): |
785 | + self._warnfunc(msg, '%s:%s' % get_source_line(node)) |
786 | + |
787 | + def clear_doc(self, docname): |
788 | + """Remove all traces of a source file in the inventory.""" |
789 | + if docname in self.all_docs: |
790 | + self.all_docs.pop(docname, None) |
791 | + self.reread_always.discard(docname) |
792 | + self.metadata.pop(docname, None) |
793 | + self.dependencies.pop(docname, None) |
794 | + self.titles.pop(docname, None) |
795 | + self.longtitles.pop(docname, None) |
796 | + self.tocs.pop(docname, None) |
797 | + self.toc_secnumbers.pop(docname, None) |
798 | + self.toc_num_entries.pop(docname, None) |
799 | + self.toctree_includes.pop(docname, None) |
800 | + self.indexentries.pop(docname, None) |
801 | + self.glob_toctrees.discard(docname) |
802 | + self.numbered_toctrees.discard(docname) |
803 | + self.images.purge_doc(docname) |
804 | + self.dlfiles.purge_doc(docname) |
805 | + |
806 | + for subfn, fnset in self.files_to_rebuild.items(): |
807 | + fnset.discard(docname) |
808 | + if not fnset: |
809 | + del self.files_to_rebuild[subfn] |
810 | + for key, (fn, _) in self.citations.items(): |
811 | + if fn == docname: |
812 | + del self.citations[key] |
813 | + for version, changes in self.versionchanges.items(): |
814 | + new = [change for change in changes if change[1] != docname] |
815 | + changes[:] = new |
816 | + |
817 | + for domain in self.domains.values(): |
818 | + domain.clear_doc(docname) |
819 | + |
820 | + def doc2path(self, docname, base=True, suffix=None): |
821 | + """Return the filename for the document name. |
822 | + |
823 | + If *base* is True, return absolute path under self.srcdir. |
824 | + If *base* is None, return relative path to self.srcdir. |
825 | + If *base* is a path string, return absolute path under that. |
826 | + If *suffix* is not None, add it instead of config.source_suffix. |
827 | + """ |
828 | + docname = docname.replace(SEP, path.sep) |
829 | + suffix = suffix or self.config.source_suffix |
830 | + if base is True: |
831 | + return path.join(self.srcdir, docname) + suffix |
832 | + elif base is None: |
833 | + return docname + suffix |
834 | + else: |
835 | + return path.join(base, docname) + suffix |
836 | + |
837 | + def relfn2path(self, filename, docname=None): |
838 | + """Return paths to a file referenced from a document, relative to |
839 | + documentation root and absolute. |
840 | + |
841 | + Absolute filenames are relative to the source dir, while relative |
842 | + filenames are relative to the dir of the containing document. |
843 | + """ |
844 | + if filename.startswith('/') or filename.startswith(os.sep): |
845 | + rel_fn = filename[1:] |
846 | + else: |
847 | + docdir = path.dirname(self.doc2path(docname or self.docname, |
848 | + base=None)) |
849 | + rel_fn = path.join(docdir, filename) |
850 | + try: |
851 | + return rel_fn, path.join(self.srcdir, rel_fn) |
852 | + except UnicodeDecodeError: |
853 | + # the source directory is a bytestring with non-ASCII characters; |
854 | + # let's try to encode the rel_fn in the file system encoding |
855 | + enc_rel_fn = rel_fn.encode(sys.getfilesystemencoding()) |
856 | + return rel_fn, path.join(self.srcdir, enc_rel_fn) |
857 | + |
858 | + def find_files(self, config): |
859 | + """Find all source files in the source dir and put them in |
860 | + self.found_docs. |
861 | + """ |
862 | + matchers = compile_matchers( |
863 | + config.exclude_patterns[:] + |
864 | + config.exclude_trees + |
865 | + [d + config.source_suffix for d in config.unused_docs] + |
866 | + ['**/' + d for d in config.exclude_dirnames] + |
867 | + ['**/_sources', '.#*'] |
868 | + ) |
869 | + self.found_docs = set(get_matching_docs( |
870 | + self.srcdir, config.source_suffix, exclude_matchers=matchers)) |
871 | + |
872 | + def get_outdated_files(self, config_changed): |
873 | + """Return (added, changed, removed) sets.""" |
874 | + # clear all files no longer present |
875 | + removed = set(self.all_docs) - self.found_docs |
876 | + |
877 | + added = set() |
878 | + changed = set() |
879 | + |
880 | + if config_changed: |
881 | + # config values affect e.g. substitutions |
882 | + added = self.found_docs |
883 | + else: |
884 | + for docname in self.found_docs: |
885 | + if docname not in self.all_docs: |
886 | + added.add(docname) |
887 | + continue |
888 | + # if the doctree file is not there, rebuild |
889 | + if not path.isfile(self.doc2path(docname, self.doctreedir, |
890 | + '.doctree')): |
891 | + changed.add(docname) |
892 | + continue |
893 | + # check the "reread always" list |
894 | + if docname in self.reread_always: |
895 | + changed.add(docname) |
896 | + continue |
897 | + # check the mtime of the document |
898 | + mtime = self.all_docs[docname] |
899 | + newmtime = path.getmtime(self.doc2path(docname)) |
900 | + if newmtime > mtime: |
901 | + changed.add(docname) |
902 | + continue |
903 | + # finally, check the mtime of dependencies |
904 | + for dep in self.dependencies.get(docname, ()): |
905 | + try: |
906 | + # this will do the right thing when dep is absolute too |
907 | + deppath = path.join(self.srcdir, dep) |
908 | + if not path.isfile(deppath): |
909 | + changed.add(docname) |
910 | + break |
911 | + depmtime = path.getmtime(deppath) |
912 | + if depmtime > mtime: |
913 | + changed.add(docname) |
914 | + break |
915 | + except EnvironmentError: |
916 | + # give it another chance |
917 | + changed.add(docname) |
918 | + break |
919 | + |
920 | + return added, changed, removed |
921 | + |
922 | + def update(self, config, srcdir, doctreedir, app=None): |
923 | + """(Re-)read all files new or changed since last update. |
924 | + |
925 | + Returns a summary, the total count of documents to reread and an |
926 | + iterator that yields docnames as it processes them. Store all |
927 | + environment docnames in the canonical format (ie using SEP as a |
928 | + separator in place of os.path.sep). |
929 | + """ |
930 | + config_changed = False |
931 | + if self.config is None: |
932 | + msg = '[new config] ' |
933 | + config_changed = True |
934 | + else: |
935 | + # check if a config value was changed that affects how |
936 | + # doctrees are read |
937 | + for key, descr in config.values.iteritems(): |
938 | + if descr[1] != 'env': |
939 | + continue |
940 | + if self.config[key] != config[key]: |
941 | + msg = '[config changed] ' |
942 | + config_changed = True |
943 | + break |
944 | + else: |
945 | + msg = '' |
946 | + # this value is not covered by the above loop because it is handled |
947 | + # specially by the config class |
948 | + if self.config.extensions != config.extensions: |
949 | + msg = '[extensions changed] ' |
950 | + config_changed = True |
951 | + # the source and doctree directories may have been relocated |
952 | + self.srcdir = srcdir |
953 | + self.doctreedir = doctreedir |
954 | + self.find_files(config) |
955 | + self.config = config |
956 | + |
957 | + added, changed, removed = self.get_outdated_files(config_changed) |
958 | + |
959 | + # allow user intervention as well |
960 | + for docs in app.emit('env-get-outdated', self, added, changed, removed): |
961 | + changed.update(set(docs) & self.found_docs) |
962 | + |
963 | + # if files were added or removed, all documents with globbed toctrees |
964 | + # must be reread |
965 | + if added or removed: |
966 | + # ... but not those that already were removed |
967 | + changed.update(self.glob_toctrees & self.found_docs) |
968 | + |
969 | + msg += '%s added, %s changed, %s removed' % (len(added), len(changed), |
970 | + len(removed)) |
971 | + |
972 | + def update_generator(): |
973 | + self.app = app |
974 | + |
975 | + # clear all files no longer present |
976 | + for docname in removed: |
977 | + if app: |
978 | + app.emit('env-purge-doc', self, docname) |
979 | + self.clear_doc(docname) |
980 | + |
981 | + # read all new and changed files |
982 | + for docname in sorted(added | changed): |
983 | + yield docname |
984 | + self.read_doc(docname, app=app) |
985 | + |
986 | + if config.master_doc not in self.all_docs: |
987 | + self.warn(None, 'master file %s not found' % |
988 | + self.doc2path(config.master_doc)) |
989 | + |
990 | + self.app = None |
991 | + if app: |
992 | + app.emit('env-updated', self) |
993 | + |
994 | + return msg, len(added | changed), update_generator() |
995 | + |
996 | + def check_dependents(self, already): |
997 | + to_rewrite = self.assign_section_numbers() |
998 | + for docname in to_rewrite: |
999 | + if docname not in already: |
1000 | + yield docname |
1001 | + |
1002 | + # --------- SINGLE FILE READING -------------------------------------------- |
1003 | + |
1004 | + def warn_and_replace(self, error): |
1005 | + """Custom decoding error handler that warns and replaces.""" |
1006 | + linestart = error.object.rfind('\n', 0, error.start) |
1007 | + lineend = error.object.find('\n', error.start) |
1008 | + if lineend == -1: lineend = len(error.object) |
1009 | + lineno = error.object.count('\n', 0, error.start) + 1 |
1010 | + self.warn(self.docname, 'undecodable source characters, ' |
1011 | + 'replacing with "?": %r' % |
1012 | + (error.object[linestart+1:error.start] + '>>>' + |
1013 | + error.object[error.start:error.end] + '<<<' + |
1014 | + error.object[error.end:lineend]), lineno) |
1015 | + return (u'?', error.end) |
1016 | + |
1017 | + def lookup_domain_element(self, type, name): |
1018 | + """Lookup a markup element (directive or role), given its name which can |
1019 | + be a full name (with domain). |
1020 | + """ |
1021 | + name = name.lower() |
1022 | + # explicit domain given? |
1023 | + if ':' in name: |
1024 | + domain_name, name = name.split(':', 1) |
1025 | + if domain_name in self.domains: |
1026 | + domain = self.domains[domain_name] |
1027 | + element = getattr(domain, type)(name) |
1028 | + if element is not None: |
1029 | + return element, [] |
1030 | + # else look in the default domain |
1031 | + else: |
1032 | + def_domain = self.temp_data.get('default_domain') |
1033 | + if def_domain is not None: |
1034 | + element = getattr(def_domain, type)(name) |
1035 | + if element is not None: |
1036 | + return element, [] |
1037 | + # always look in the std domain |
1038 | + element = getattr(self.domains['std'], type)(name) |
1039 | + if element is not None: |
1040 | + return element, [] |
1041 | + raise ElementLookupError |
1042 | + |
1043 | + def patch_lookup_functions(self): |
1044 | + """Monkey-patch directive and role dispatch, so that domain-specific |
1045 | + markup takes precedence. |
1046 | + """ |
1047 | + def directive(name, lang_module, document): |
1048 | + try: |
1049 | + return self.lookup_domain_element('directive', name) |
1050 | + except ElementLookupError: |
1051 | + return orig_directive_function(name, lang_module, document) |
1052 | + |
1053 | + def role(name, lang_module, lineno, reporter): |
1054 | + try: |
1055 | + return self.lookup_domain_element('role', name) |
1056 | + except ElementLookupError: |
1057 | + return orig_role_function(name, lang_module, lineno, reporter) |
1058 | + |
1059 | + directives.directive = directive |
1060 | + roles.role = role |
1061 | + |
1062 | + def read_doc(self, docname, src_path=None, save_parsed=True, app=None): |
1063 | + """Parse a file and add/update inventory entries for the doctree. |
1064 | + |
1065 | + If srcpath is given, read from a different source file. |
1066 | + """ |
1067 | + # remove all inventory entries for that file |
1068 | + if app: |
1069 | + app.emit('env-purge-doc', self, docname) |
1070 | + |
1071 | + self.clear_doc(docname) |
1072 | + |
1073 | + if src_path is None: |
1074 | + src_path = self.doc2path(docname) |
1075 | + |
1076 | + self.temp_data['docname'] = docname |
1077 | + # defaults to the global default, but can be re-set in a document |
1078 | + self.temp_data['default_domain'] = \ |
1079 | + self.domains.get(self.config.primary_domain) |
1080 | + |
1081 | + self.settings['input_encoding'] = self.config.source_encoding |
1082 | + self.settings['trim_footnote_reference_space'] = \ |
1083 | + self.config.trim_footnote_reference_space |
1084 | + self.settings['gettext_compact'] = self.config.gettext_compact |
1085 | + |
1086 | + self.patch_lookup_functions() |
1087 | + |
1088 | + if self.config.default_role: |
1089 | + role_fn, messages = roles.role(self.config.default_role, english, |
1090 | + 0, dummy_reporter) |
1091 | + if role_fn: |
1092 | + roles._roles[''] = role_fn |
1093 | + else: |
1094 | + self.warn(docname, 'default role %s not found' % |
1095 | + self.config.default_role) |
1096 | + |
1097 | + codecs.register_error('sphinx', self.warn_and_replace) |
1098 | + |
1099 | + class SphinxSourceClass(FileInput): |
1100 | + def __init__(self_, *args, **kwds): |
1101 | + # don't call sys.exit() on IOErrors |
1102 | + kwds['handle_io_errors'] = False |
1103 | + FileInput.__init__(self_, *args, **kwds) |
1104 | + |
1105 | + def decode(self_, data): |
1106 | + if isinstance(data, unicode): |
1107 | + return data |
1108 | + return data.decode(self_.encoding, 'sphinx') |
1109 | + |
1110 | + def read(self_): |
1111 | + data = FileInput.read(self_) |
1112 | + if app: |
1113 | + arg = [data] |
1114 | + app.emit('source-read', docname, arg) |
1115 | + data = arg[0] |
1116 | + if self.config.rst_epilog: |
1117 | + data = data + '\n' + self.config.rst_epilog + '\n' |
1118 | + if self.config.rst_prolog: |
1119 | + data = self.config.rst_prolog + '\n' + data |
1120 | + return data |
1121 | + |
1122 | + # publish manually |
1123 | + pub = Publisher(reader=SphinxStandaloneReader(), |
1124 | + writer=SphinxDummyWriter(), |
1125 | + source_class=SphinxSourceClass, |
1126 | + destination_class=NullOutput) |
1127 | + pub.set_components(None, 'restructuredtext', None) |
1128 | + pub.process_programmatic_settings(None, self.settings, None) |
1129 | + pub.set_source(None, src_path.encode(fs_encoding)) |
1130 | + pub.set_destination(None, None) |
1131 | + try: |
1132 | + pub.publish() |
1133 | + doctree = pub.document |
1134 | + except UnicodeError, err: |
1135 | + raise SphinxError(str(err)) |
1136 | + |
1137 | + # post-processing |
1138 | + self.filter_messages(doctree) |
1139 | + self.process_dependencies(docname, doctree) |
1140 | + self.process_images(docname, doctree) |
1141 | + self.process_downloads(docname, doctree) |
1142 | + self.process_metadata(docname, doctree) |
1143 | + self.process_refonly_bullet_lists(docname, doctree) |
1144 | + self.create_title_from(docname, doctree) |
1145 | + self.note_indexentries_from(docname, doctree) |
1146 | + self.note_citations_from(docname, doctree) |
1147 | + self.build_toc_from(docname, doctree) |
1148 | + for domain in self.domains.itervalues(): |
1149 | + domain.process_doc(self, docname, doctree) |
1150 | + |
1151 | + # allow extension-specific post-processing |
1152 | + if app: |
1153 | + app.emit('doctree-read', doctree) |
1154 | + |
1155 | + # store time of build, for outdated files detection |
1156 | + self.all_docs[docname] = time.time() |
1157 | + |
1158 | + if self.versioning_condition: |
1159 | + # get old doctree |
1160 | + try: |
1161 | + f = open(self.doc2path(docname, |
1162 | + self.doctreedir, '.doctree'), 'rb') |
1163 | + try: |
1164 | + old_doctree = pickle.load(f) |
1165 | + finally: |
1166 | + f.close() |
1167 | + except EnvironmentError: |
1168 | + old_doctree = None |
1169 | + |
1170 | + # add uids for versioning |
1171 | + if old_doctree is None: |
1172 | + list(add_uids(doctree, self.versioning_condition)) |
1173 | + else: |
1174 | + list(merge_doctrees( |
1175 | + old_doctree, doctree, self.versioning_condition)) |
1176 | + |
1177 | + # make it picklable |
1178 | + doctree.reporter = None |
1179 | + doctree.transformer = None |
1180 | + doctree.settings.warning_stream = None |
1181 | + doctree.settings.env = None |
1182 | + doctree.settings.record_dependencies = None |
1183 | + for metanode in doctree.traverse(MetaBody.meta): |
1184 | + # docutils' meta nodes aren't picklable because the class is nested |
1185 | + metanode.__class__ = addnodes.meta |
1186 | + |
1187 | + # cleanup |
1188 | + self.temp_data.clear() |
1189 | + |
1190 | + if save_parsed: |
1191 | + # save the parsed doctree |
1192 | + doctree_filename = self.doc2path(docname, self.doctreedir, |
1193 | + '.doctree') |
1194 | + dirname = path.dirname(doctree_filename) |
1195 | + if not path.isdir(dirname): |
1196 | + os.makedirs(dirname) |
1197 | + f = open(doctree_filename, 'wb') |
1198 | + try: |
1199 | + pickle.dump(doctree, f, pickle.HIGHEST_PROTOCOL) |
1200 | + finally: |
1201 | + f.close() |
1202 | + else: |
1203 | + return doctree |
1204 | + |
1205 | + # utilities to use while reading a document |
1206 | + |
1207 | + @property |
1208 | + def docname(self): |
1209 | + """Backwards compatible alias.""" |
1210 | + return self.temp_data['docname'] |
1211 | + |
1212 | + @property |
1213 | + def currmodule(self): |
1214 | + """Backwards compatible alias.""" |
1215 | + return self.temp_data.get('py:module') |
1216 | + |
1217 | + @property |
1218 | + def currclass(self): |
1219 | + """Backwards compatible alias.""" |
1220 | + return self.temp_data.get('py:class') |
1221 | + |
1222 | + def new_serialno(self, category=''): |
1223 | + """Return a serial number, e.g. for index entry targets.""" |
1224 | + key = category + 'serialno' |
1225 | + cur = self.temp_data.get(key, 0) |
1226 | + self.temp_data[key] = cur + 1 |
1227 | + return cur |
1228 | + |
1229 | + def note_dependency(self, filename): |
1230 | + self.dependencies.setdefault(self.docname, set()).add(filename) |
1231 | + |
1232 | + def note_reread(self): |
1233 | + self.reread_always.add(self.docname) |
1234 | + |
1235 | + def note_versionchange(self, type, version, node, lineno): |
1236 | + self.versionchanges.setdefault(version, []).append( |
1237 | + (type, self.temp_data['docname'], lineno, |
1238 | + self.temp_data.get('py:module'), |
1239 | + self.temp_data.get('object'), node.astext())) |
1240 | + |
1241 | + # post-processing of read doctrees |
1242 | + |
1243 | + def filter_messages(self, doctree): |
1244 | + """Filter system messages from a doctree.""" |
1245 | + filterlevel = self.config.keep_warnings and 2 or 5 |
1246 | + for node in doctree.traverse(nodes.system_message): |
1247 | + if node['level'] < filterlevel: |
1248 | + node.parent.remove(node) |
1249 | + |
1250 | + |
1251 | + def process_dependencies(self, docname, doctree): |
1252 | + """Process docutils-generated dependency info.""" |
1253 | + cwd = os.getcwd() |
1254 | + frompath = path.join(path.normpath(self.srcdir), 'dummy') |
1255 | + deps = doctree.settings.record_dependencies |
1256 | + if not deps: |
1257 | + return |
1258 | + for dep in deps.list: |
1259 | + # the dependency path is relative to the working dir, so get |
1260 | + # one relative to the srcdir |
1261 | + relpath = relative_path(frompath, |
1262 | + path.normpath(path.join(cwd, dep))) |
1263 | + self.dependencies.setdefault(docname, set()).add(relpath) |
1264 | + |
1265 | + def process_downloads(self, docname, doctree): |
1266 | + """Process downloadable file paths. """ |
1267 | + for node in doctree.traverse(addnodes.download_reference): |
1268 | + targetname = node['reftarget'] |
1269 | + rel_filename, filename = self.relfn2path(targetname, docname) |
1270 | + self.dependencies.setdefault(docname, set()).add(rel_filename) |
1271 | + if not os.access(filename, os.R_OK): |
1272 | + self.warn_node('download file not readable: %s' % filename, |
1273 | + node) |
1274 | + continue |
1275 | + uniquename = self.dlfiles.add_file(docname, filename) |
1276 | + node['filename'] = uniquename |
1277 | + |
1278 | + def process_images(self, docname, doctree): |
1279 | + """Process and rewrite image URIs.""" |
1280 | + for node in doctree.traverse(nodes.image): |
1281 | + # Map the mimetype to the corresponding image. The writer may |
1282 | + # choose the best image from these candidates. The special key * is |
1283 | + # set if there is only single candidate to be used by a writer. |
1284 | + # The special key ? is set for nonlocal URIs. |
1285 | + node['candidates'] = candidates = {} |
1286 | + imguri = node['uri'] |
1287 | + if imguri.find('://') != -1: |
1288 | + self.warn_node('nonlocal image URI found: %s' % imguri, node) |
1289 | + candidates['?'] = imguri |
1290 | + continue |
1291 | + rel_imgpath, full_imgpath = self.relfn2path(imguri, docname) |
1292 | + # set imgpath as default URI |
1293 | + node['uri'] = rel_imgpath |
1294 | + if rel_imgpath.endswith(os.extsep + '*'): |
1295 | + for filename in glob(full_imgpath): |
1296 | + new_imgpath = relative_path(self.srcdir, filename) |
1297 | + if filename.lower().endswith('.pdf'): |
1298 | + candidates['application/pdf'] = new_imgpath |
1299 | + elif filename.lower().endswith('.svg'): |
1300 | + candidates['image/svg+xml'] = new_imgpath |
1301 | + else: |
1302 | + try: |
1303 | + f = open(filename, 'rb') |
1304 | + try: |
1305 | + imgtype = imghdr.what(f) |
1306 | + finally: |
1307 | + f.close() |
1308 | + except (OSError, IOError), err: |
1309 | + self.warn_node('image file %s not readable: %s' % |
1310 | + (filename, err), node) |
1311 | + if imgtype: |
1312 | + candidates['image/' + imgtype] = new_imgpath |
1313 | + else: |
1314 | + candidates['*'] = rel_imgpath |
1315 | + # map image paths to unique image names (so that they can be put |
1316 | + # into a single directory) |
1317 | + for imgpath in candidates.itervalues(): |
1318 | + self.dependencies.setdefault(docname, set()).add(imgpath) |
1319 | + if not os.access(path.join(self.srcdir, imgpath), os.R_OK): |
1320 | + self.warn_node('image file not readable: %s' % imgpath, |
1321 | + node) |
1322 | + continue |
1323 | + self.images.add_file(docname, imgpath) |
1324 | + |
1325 | + def process_metadata(self, docname, doctree): |
1326 | + """Process the docinfo part of the doctree as metadata. |
1327 | + |
1328 | + Keep processing minimal -- just return what docutils says. |
1329 | + """ |
1330 | + self.metadata[docname] = md = {} |
1331 | + try: |
1332 | + docinfo = doctree[0] |
1333 | + except IndexError: |
1334 | + # probably an empty document |
1335 | + return |
1336 | + if docinfo.__class__ is not nodes.docinfo: |
1337 | + # nothing to see here |
1338 | + return |
1339 | + for node in docinfo: |
1340 | + # nodes are multiply inherited... |
1341 | + if isinstance(node, nodes.authors): |
1342 | + md['authors'] = [author.astext() for author in node] |
1343 | + elif isinstance(node, nodes.TextElement): # e.g. author |
1344 | + md[node.__class__.__name__] = node.astext() |
1345 | + else: |
1346 | + name, body = node |
1347 | + md[name.astext()] = body.astext() |
1348 | + del doctree[0] |
1349 | + |
1350 | + def process_refonly_bullet_lists(self, docname, doctree): |
1351 | + """Change refonly bullet lists to use compact_paragraphs. |
1352 | + |
1353 | + Specifically implemented for 'Indices and Tables' section, which looks |
1354 | + odd when html_compact_lists is false. |
1355 | + """ |
1356 | + if self.config.html_compact_lists: |
1357 | + return |
1358 | + |
1359 | + class RefOnlyListChecker(nodes.GenericNodeVisitor): |
1360 | + """Raise `nodes.NodeFound` if non-simple list item is encountered. |
1361 | + |
1362 | + Here 'simple' means a list item containing only a paragraph with a |
1363 | + single reference in it. |
1364 | + """ |
1365 | + |
1366 | + def default_visit(self, node): |
1367 | + raise nodes.NodeFound |
1368 | + |
1369 | + def visit_bullet_list(self, node): |
1370 | + pass |
1371 | + |
1372 | + def visit_list_item(self, node): |
1373 | + children = [] |
1374 | + for child in node.children: |
1375 | + if not isinstance(child, nodes.Invisible): |
1376 | + children.append(child) |
1377 | + if len(children) != 1: |
1378 | + raise nodes.NodeFound |
1379 | + if not isinstance(children[0], nodes.paragraph): |
1380 | + raise nodes.NodeFound |
1381 | + para = children[0] |
1382 | + if len(para) != 1: |
1383 | + raise nodes.NodeFound |
1384 | + if not isinstance(para[0], addnodes.pending_xref): |
1385 | + raise nodes.NodeFound |
1386 | + raise nodes.SkipChildren |
1387 | + |
1388 | + def invisible_visit(self, node): |
1389 | + """Invisible nodes should be ignored.""" |
1390 | + pass |
1391 | + |
1392 | + def check_refonly_list(node): |
1393 | + """Check for list with only references in it.""" |
1394 | + visitor = RefOnlyListChecker(doctree) |
1395 | + try: |
1396 | + node.walk(visitor) |
1397 | + except nodes.NodeFound: |
1398 | + return False |
1399 | + else: |
1400 | + return True |
1401 | + |
1402 | + for node in doctree.traverse(nodes.bullet_list): |
1403 | + if check_refonly_list(node): |
1404 | + for item in node.traverse(nodes.list_item): |
1405 | + para = item[0] |
1406 | + ref = para[0] |
1407 | + compact_para = addnodes.compact_paragraph() |
1408 | + compact_para += ref |
1409 | + item.replace(para, compact_para) |
1410 | + |
1411 | + def create_title_from(self, docname, document): |
1412 | + """Add a title node to the document (just copy the first section title), |
1413 | + and store that title in the environment. |
1414 | + """ |
1415 | + titlenode = nodes.title() |
1416 | + longtitlenode = titlenode |
1417 | + # explicit title set with title directive; use this only for |
1418 | + # the <title> tag in HTML output |
1419 | + if document.has_key('title'): |
1420 | + longtitlenode = nodes.title() |
1421 | + longtitlenode += nodes.Text(document['title']) |
1422 | + # look for first section title and use that as the title |
1423 | + for node in document.traverse(nodes.section): |
1424 | + visitor = SphinxContentsFilter(document) |
1425 | + node[0].walkabout(visitor) |
1426 | + titlenode += visitor.get_entry_text() |
1427 | + break |
1428 | + else: |
1429 | + # document has no title |
1430 | + titlenode += nodes.Text('<no title>') |
1431 | + self.titles[docname] = titlenode |
1432 | + self.longtitles[docname] = longtitlenode |
1433 | + |
1434 | + def note_indexentries_from(self, docname, document): |
1435 | + entries = self.indexentries[docname] = [] |
1436 | + for node in document.traverse(addnodes.index): |
1437 | + entries.extend(node['entries']) |
1438 | + |
1439 | + def note_citations_from(self, docname, document): |
1440 | + for node in document.traverse(nodes.citation): |
1441 | + label = node[0].astext() |
1442 | + if label in self.citations: |
1443 | + self.warn_node('duplicate citation %s, ' % label + |
1444 | + 'other instance in %s' % self.doc2path( |
1445 | + self.citations[label][0]), node) |
1446 | + self.citations[label] = (docname, node['ids'][0]) |
1447 | + |
1448 | + def note_toctree(self, docname, toctreenode): |
1449 | + """Note a TOC tree directive in a document and gather information about |
1450 | + file relations from it. |
1451 | + """ |
1452 | + if toctreenode['glob']: |
1453 | + self.glob_toctrees.add(docname) |
1454 | + if toctreenode.get('numbered'): |
1455 | + self.numbered_toctrees.add(docname) |
1456 | + includefiles = toctreenode['includefiles'] |
1457 | + for includefile in includefiles: |
1458 | + # note that if the included file is rebuilt, this one must be |
1459 | + # too (since the TOC of the included file could have changed) |
1460 | + self.files_to_rebuild.setdefault(includefile, set()).add(docname) |
1461 | + self.toctree_includes.setdefault(docname, []).extend(includefiles) |
1462 | + |
1463 | + def build_toc_from(self, docname, document): |
1464 | + """Build a TOC from the doctree and store it in the inventory.""" |
1465 | + numentries = [0] # nonlocal again... |
1466 | + |
1467 | + try: |
1468 | + maxdepth = int(self.metadata[docname].get('tocdepth', 0)) |
1469 | + except ValueError: |
1470 | + maxdepth = 0 |
1471 | + |
1472 | + def traverse_in_section(node, cls): |
1473 | + """Like traverse(), but stay within the same section.""" |
1474 | + result = [] |
1475 | + if isinstance(node, cls): |
1476 | + result.append(node) |
1477 | + for child in node.children: |
1478 | + if isinstance(child, nodes.section): |
1479 | + continue |
1480 | + result.extend(traverse_in_section(child, cls)) |
1481 | + return result |
1482 | + |
1483 | + def build_toc(node, depth=1): |
1484 | + entries = [] |
1485 | + for sectionnode in node: |
1486 | + # find all toctree nodes in this section and add them |
1487 | + # to the toc (just copying the toctree node which is then |
1488 | + # resolved in self.get_and_resolve_doctree) |
1489 | + if isinstance(sectionnode, addnodes.only): |
1490 | + onlynode = addnodes.only(expr=sectionnode['expr']) |
1491 | + blist = build_toc(sectionnode, depth) |
1492 | + if blist: |
1493 | + onlynode += blist.children |
1494 | + entries.append(onlynode) |
1495 | + if not isinstance(sectionnode, nodes.section): |
1496 | + for toctreenode in traverse_in_section(sectionnode, |
1497 | + addnodes.toctree): |
1498 | + item = toctreenode.copy() |
1499 | + entries.append(item) |
1500 | + # important: do the inventory stuff |
1501 | + self.note_toctree(docname, toctreenode) |
1502 | + continue |
1503 | + title = sectionnode[0] |
1504 | + # copy the contents of the section title, but without references |
1505 | + # and unnecessary stuff |
1506 | + visitor = SphinxContentsFilter(document) |
1507 | + title.walkabout(visitor) |
1508 | + nodetext = visitor.get_entry_text() |
1509 | + if not numentries[0]: |
1510 | + # for the very first toc entry, don't add an anchor |
1511 | + # as it is the file's title anyway |
1512 | + anchorname = '' |
1513 | + else: |
1514 | + anchorname = '#' + sectionnode['ids'][0] |
1515 | + numentries[0] += 1 |
1516 | + # make these nodes: |
1517 | + # list_item -> compact_paragraph -> reference |
1518 | + reference = nodes.reference( |
1519 | + '', '', internal=True, refuri=docname, |
1520 | + anchorname=anchorname, *nodetext) |
1521 | + para = addnodes.compact_paragraph('', '', reference) |
1522 | + item = nodes.list_item('', para) |
1523 | + if maxdepth == 0 or depth < maxdepth: |
1524 | + item += build_toc(sectionnode, depth+1) |
1525 | + entries.append(item) |
1526 | + if entries: |
1527 | + return nodes.bullet_list('', *entries) |
1528 | + return [] |
1529 | + toc = build_toc(document) |
1530 | + if toc: |
1531 | + self.tocs[docname] = toc |
1532 | + else: |
1533 | + self.tocs[docname] = nodes.bullet_list('') |
1534 | + self.toc_num_entries[docname] = numentries[0] |
1535 | + |
1536 | + def get_toc_for(self, docname, builder): |
1537 | + """Return a TOC nodetree -- for use on the same page only!""" |
1538 | + try: |
1539 | + toc = self.tocs[docname].deepcopy() |
1540 | + except KeyError: |
1541 | + # the document does not exist anymore: return a dummy node that |
1542 | + # renders to nothing |
1543 | + return nodes.paragraph() |
1544 | + self.process_only_nodes(toc, builder, docname) |
1545 | + for node in toc.traverse(nodes.reference): |
1546 | + node['refuri'] = node['anchorname'] or '#' |
1547 | + return toc |
1548 | + |
1549 | + def get_toctree_for(self, docname, builder, collapse, **kwds): |
1550 | + """Return the global TOC nodetree.""" |
1551 | + doctree = self.get_doctree(self.config.master_doc) |
1552 | + toctrees = [] |
1553 | + if 'includehidden' not in kwds: |
1554 | + kwds['includehidden'] = True |
1555 | + if 'maxdepth' not in kwds: |
1556 | + kwds['maxdepth'] = 0 |
1557 | + kwds['collapse'] = collapse |
1558 | + for toctreenode in doctree.traverse(addnodes.toctree): |
1559 | + toctree = self.resolve_toctree(docname, builder, toctreenode, |
1560 | + prune=True, **kwds) |
1561 | + toctrees.append(toctree) |
1562 | + if not toctrees: |
1563 | + return None |
1564 | + result = toctrees[0] |
1565 | + for toctree in toctrees[1:]: |
1566 | + result.extend(toctree.children) |
1567 | + return result |
1568 | + |
1569 | + def get_domain(self, domainname): |
1570 | + """Return the domain instance with the specified name. |
1571 | + |
1572 | + Raises an ExtensionError if the domain is not registered. |
1573 | + """ |
1574 | + try: |
1575 | + return self.domains[domainname] |
1576 | + except KeyError: |
1577 | + raise ExtensionError('Domain %r is not registered' % domainname) |
1578 | + |
1579 | + # --------- RESOLVING REFERENCES AND TOCTREES ------------------------------ |
1580 | + |
1581 | + def get_doctree(self, docname): |
1582 | + """Read the doctree for a file from the pickle and return it.""" |
1583 | + doctree_filename = self.doc2path(docname, self.doctreedir, '.doctree') |
1584 | + f = open(doctree_filename, 'rb') |
1585 | + try: |
1586 | + doctree = pickle.load(f) |
1587 | + finally: |
1588 | + f.close() |
1589 | + doctree.settings.env = self |
1590 | + doctree.reporter = Reporter(self.doc2path(docname), 2, 5, |
1591 | + stream=WarningStream(self._warnfunc)) |
1592 | + return doctree |
1593 | + |
1594 | + |
1595 | + def get_and_resolve_doctree(self, docname, builder, doctree=None, |
1596 | + prune_toctrees=True): |
1597 | + """Read the doctree from the pickle, resolve cross-references and |
1598 | + toctrees and return it. |
1599 | + """ |
1600 | + if doctree is None: |
1601 | + doctree = self.get_doctree(docname) |
1602 | + |
1603 | + # resolve all pending cross-references |
1604 | + self.resolve_references(doctree, docname, builder) |
1605 | + |
1606 | + # now, resolve all toctree nodes |
1607 | + for toctreenode in doctree.traverse(addnodes.toctree): |
1608 | + result = self.resolve_toctree(docname, builder, toctreenode, |
1609 | + prune=prune_toctrees) |
1610 | + if result is None: |
1611 | + toctreenode.replace_self([]) |
1612 | + else: |
1613 | + toctreenode.replace_self(result) |
1614 | + |
1615 | + return doctree |
1616 | + |
1617 | + def resolve_toctree(self, docname, builder, toctree, prune=True, maxdepth=0, |
1618 | + titles_only=False, collapse=False, includehidden=False): |
1619 | + """Resolve a *toctree* node into individual bullet lists with titles |
1620 | + as items, returning None (if no containing titles are found) or |
1621 | + a new node. |
1622 | + |
1623 | + If *prune* is True, the tree is pruned to *maxdepth*, or if that is 0, |
1624 | + to the value of the *maxdepth* option on the *toctree* node. |
1625 | + If *titles_only* is True, only toplevel document titles will be in the |
1626 | + resulting tree. |
1627 | + If *collapse* is True, all branches not containing docname will |
1628 | + be collapsed. |
1629 | + """ |
1630 | + if toctree.get('hidden', False) and not includehidden: |
1631 | + return None |
1632 | + |
1633 | + def _walk_depth(node, depth, maxdepth): |
1634 | + """Utility: Cut a TOC at a specified depth.""" |
1635 | + |
1636 | + # For reading this function, it is useful to keep in mind the node |
1637 | + # structure of a toctree (using HTML-like node names for brevity): |
1638 | + # |
1639 | + # <ul> |
1640 | + # <li> |
1641 | + # <p><a></p> |
1642 | + # <p><a></p> |
1643 | + # ... |
1644 | + # <ul> |
1645 | + # ... |
1646 | + # </ul> |
1647 | + # </li> |
1648 | + # </ul> |
1649 | + |
1650 | + for subnode in node.children[:]: |
1651 | + if isinstance(subnode, (addnodes.compact_paragraph, |
1652 | + nodes.list_item)): |
1653 | + # for <p> and <li>, just indicate the depth level and |
1654 | + # recurse to children |
1655 | + subnode['classes'].append('toctree-l%d' % (depth-1)) |
1656 | + _walk_depth(subnode, depth, maxdepth) |
1657 | + |
1658 | + elif isinstance(subnode, nodes.bullet_list): |
1659 | + # for <ul>, determine if the depth is too large or if the |
1660 | + # entry is to be collapsed |
1661 | + if maxdepth > 0 and depth > maxdepth: |
1662 | + subnode.parent.replace(subnode, []) |
1663 | + else: |
1664 | + # to find out what to collapse, *first* walk subitems, |
1665 | + # since that determines which children point to the |
1666 | + # current page |
1667 | + _walk_depth(subnode, depth+1, maxdepth) |
1668 | + # cull sub-entries whose parents aren't 'current' |
1669 | + if (collapse and depth > 1 and |
1670 | + 'iscurrent' not in subnode.parent): |
1671 | + subnode.parent.remove(subnode) |
1672 | + |
1673 | + elif isinstance(subnode, nodes.reference): |
1674 | + # for <a>, identify which entries point to the current |
1675 | + # document and therefore may not be collapsed |
1676 | + if subnode['refuri'] == docname: |
1677 | + if not subnode['anchorname']: |
1678 | + # give the whole branch a 'current' class |
1679 | + # (useful for styling it differently) |
1680 | + branchnode = subnode |
1681 | + while branchnode: |
1682 | + branchnode['classes'].append('current') |
1683 | + branchnode = branchnode.parent |
1684 | + # mark the list_item as "on current page" |
1685 | + if subnode.parent.parent.get('iscurrent'): |
1686 | + # but only if it's not already done |
1687 | + return |
1688 | + while subnode: |
1689 | + subnode['iscurrent'] = True |
1690 | + subnode = subnode.parent |
1691 | + |
1692 | + def _entries_from_toctree(toctreenode, parents, |
1693 | + separate=False, subtree=False): |
1694 | + """Return TOC entries for a toctree node.""" |
1695 | + refs = [(e[0], str(e[1])) for e in toctreenode['entries']] |
1696 | + entries = [] |
1697 | + for (title, ref) in refs: |
1698 | + try: |
1699 | + refdoc = None |
1700 | + if url_re.match(ref): |
1701 | + reference = nodes.reference('', '', internal=False, |
1702 | + refuri=ref, anchorname='', |
1703 | + *[nodes.Text(title)]) |
1704 | + para = addnodes.compact_paragraph('', '', reference) |
1705 | + item = nodes.list_item('', para) |
1706 | + toc = nodes.bullet_list('', item) |
1707 | + elif ref == 'self': |
1708 | + # 'self' refers to the document from which this |
1709 | + # toctree originates |
1710 | + ref = toctreenode['parent'] |
1711 | + if not title: |
1712 | + title = clean_astext(self.titles[ref]) |
1713 | + reference = nodes.reference('', '', internal=True, |
1714 | + refuri=ref, |
1715 | + anchorname='', |
1716 | + *[nodes.Text(title)]) |
1717 | + para = addnodes.compact_paragraph('', '', reference) |
1718 | + item = nodes.list_item('', para) |
1719 | + # don't show subitems |
1720 | + toc = nodes.bullet_list('', item) |
1721 | + else: |
1722 | + if ref in parents: |
1723 | + self.warn(ref, 'circular toctree references ' |
1724 | + 'detected, ignoring: %s <- %s' % |
1725 | + (ref, ' <- '.join(parents))) |
1726 | + continue |
1727 | + refdoc = ref |
1728 | + toc = self.tocs[ref].deepcopy() |
1729 | + self.process_only_nodes(toc, builder, ref) |
1730 | + if title and toc.children and len(toc.children) == 1: |
1731 | + child = toc.children[0] |
1732 | + for refnode in child.traverse(nodes.reference): |
1733 | + if refnode['refuri'] == ref and \ |
1734 | + not refnode['anchorname']: |
1735 | + refnode.children = [nodes.Text(title)] |
1736 | + if not toc.children: |
1737 | + # empty toc means: no titles will show up in the toctree |
1738 | + self.warn_node( |
1739 | + 'toctree contains reference to document %r that ' |
1740 | + 'doesn\'t have a title: no link will be generated' |
1741 | + % ref, toctreenode) |
1742 | + except KeyError: |
1743 | + # this is raised if the included file does not exist |
1744 | + self.warn_node( |
1745 | + 'toctree contains reference to nonexisting document %r' |
1746 | + % ref, toctreenode) |
1747 | + else: |
1748 | + # if titles_only is given, only keep the main title and |
1749 | + # sub-toctrees |
1750 | + if titles_only: |
1751 | + # delete everything but the toplevel title(s) |
1752 | + # and toctrees |
1753 | + for toplevel in toc: |
1754 | + # nodes with length 1 don't have any children anyway |
1755 | + if len(toplevel) > 1: |
1756 | + subtrees = toplevel.traverse(addnodes.toctree) |
1757 | + toplevel[1][:] = subtrees |
1758 | + # resolve all sub-toctrees |
1759 | + for toctreenode in toc.traverse(addnodes.toctree): |
1760 | + if not (toctreenode.get('hidden', False) |
1761 | + and not includehidden): |
1762 | + i = toctreenode.parent.index(toctreenode) + 1 |
1763 | + for item in _entries_from_toctree( |
1764 | + toctreenode, [refdoc] + parents, |
1765 | + subtree=True): |
1766 | + toctreenode.parent.insert(i, item) |
1767 | + i += 1 |
1768 | + toctreenode.parent.remove(toctreenode) |
1769 | + if separate: |
1770 | + entries.append(toc) |
1771 | + else: |
1772 | + entries.extend(toc.children) |
1773 | + if not subtree and not separate: |
1774 | + ret = nodes.bullet_list() |
1775 | + ret += entries |
1776 | + return [ret] |
1777 | + return entries |
1778 | + |
1779 | + maxdepth = maxdepth or toctree.get('maxdepth', -1) |
1780 | + if not titles_only and toctree.get('titlesonly', False): |
1781 | + titles_only = True |
1782 | + |
1783 | + # NOTE: previously, this was separate=True, but that leads to artificial |
1784 | + # separation when two or more toctree entries form a logical unit, so |
1785 | + # separating mode is no longer used -- it's kept here for history's sake |
1786 | + tocentries = _entries_from_toctree(toctree, [], separate=False) |
1787 | + if not tocentries: |
1788 | + return None |
1789 | + |
1790 | + newnode = addnodes.compact_paragraph('', '', *tocentries) |
1791 | + newnode['toctree'] = True |
1792 | + |
1793 | + # prune the tree to maxdepth and replace titles, also set level classes |
1794 | + _walk_depth(newnode, 1, prune and maxdepth or 0) |
1795 | + |
1796 | + # set the target paths in the toctrees (they are not known at TOC |
1797 | + # generation time) |
1798 | + for refnode in newnode.traverse(nodes.reference): |
1799 | + if not url_re.match(refnode['refuri']): |
1800 | + refnode['refuri'] = builder.get_relative_uri( |
1801 | + docname, refnode['refuri']) + refnode['anchorname'] |
1802 | + return newnode |
1803 | + |
1804 | + def resolve_references(self, doctree, fromdocname, builder): |
1805 | + for node in doctree.traverse(addnodes.pending_xref): |
1806 | + contnode = node[0].deepcopy() |
1807 | + newnode = None |
1808 | + |
1809 | + typ = node['reftype'] |
1810 | + target = node['reftarget'] |
1811 | + refdoc = node.get('refdoc', fromdocname) |
1812 | + domain = None |
1813 | + |
1814 | + try: |
1815 | + if 'refdomain' in node and node['refdomain']: |
1816 | + # let the domain try to resolve the reference |
1817 | + try: |
1818 | + domain = self.domains[node['refdomain']] |
1819 | + except KeyError: |
1820 | + raise NoUri |
1821 | + newnode = domain.resolve_xref(self, fromdocname, builder, |
1822 | + typ, target, node, contnode) |
1823 | + # really hardwired reference types |
1824 | + elif typ == 'doc': |
1825 | + # directly reference to document by source name; |
1826 | + # can be absolute or relative |
1827 | + docname = docname_join(refdoc, target) |
1828 | + if docname in self.all_docs: |
1829 | + if node['refexplicit']: |
1830 | + # reference with explicit title |
1831 | + caption = node.astext() |
1832 | + else: |
1833 | + caption = clean_astext(self.titles[docname]) |
1834 | + innernode = nodes.emphasis(caption, caption) |
1835 | + newnode = nodes.reference('', '', internal=True) |
1836 | + newnode['refuri'] = builder.get_relative_uri( |
1837 | + fromdocname, docname) |
1838 | + newnode.append(innernode) |
1839 | + elif typ == 'citation': |
1840 | + docname, labelid = self.citations.get(target, ('', '')) |
1841 | + if docname: |
1842 | + newnode = make_refnode(builder, fromdocname, docname, |
1843 | + labelid, contnode) |
1844 | + # no new node found? try the missing-reference event |
1845 | + if newnode is None: |
1846 | + newnode = builder.app.emit_firstresult( |
1847 | + 'missing-reference', self, node, contnode) |
1848 | + # still not found? warn if in nit-picky mode |
1849 | + if newnode is None: |
1850 | + self._warn_missing_reference( |
1851 | + fromdocname, typ, target, node, domain) |
1852 | + except NoUri: |
1853 | + newnode = contnode |
1854 | + node.replace_self(newnode or contnode) |
1855 | + |
1856 | + # remove only-nodes that do not belong to our builder |
1857 | + self.process_only_nodes(doctree, builder, fromdocname) |
1858 | + |
1859 | + # allow custom references to be resolved |
1860 | + builder.app.emit('doctree-resolved', doctree, fromdocname) |
1861 | + |
1862 | + def _warn_missing_reference(self, fromdoc, typ, target, node, domain): |
1863 | + warn = node.get('refwarn') |
1864 | + if self.config.nitpicky: |
1865 | + warn = True |
1866 | + if self._nitpick_ignore: |
1867 | + dtype = domain and '%s:%s' % (domain.name, typ) or typ |
1868 | + if (dtype, target) in self._nitpick_ignore: |
1869 | + warn = False |
1870 | + if not warn: |
1871 | + return |
1872 | + if domain and typ in domain.dangling_warnings: |
1873 | + msg = domain.dangling_warnings[typ] |
1874 | + elif typ == 'doc': |
1875 | + msg = 'unknown document: %(target)s' |
1876 | + elif typ == 'citation': |
1877 | + msg = 'citation not found: %(target)s' |
1878 | + elif node.get('refdomain', 'std') != 'std': |
1879 | + msg = '%s:%s reference target not found: %%(target)s' % \ |
1880 | + (node['refdomain'], typ) |
1881 | + else: |
1882 | + msg = '%s reference target not found: %%(target)s' % typ |
1883 | + self.warn_node(msg % {'target': target}, node) |
1884 | + |
1885 | + def process_only_nodes(self, doctree, builder, fromdocname=None): |
1886 | + # A comment on the comment() nodes being inserted: replacing by [] would |
1887 | + # result in a "Losing ids" exception if there is a target node before |
1888 | + # the only node, so we make sure docutils can transfer the id to |
1889 | + # something, even if it's just a comment and will lose the id anyway... |
1890 | + for node in doctree.traverse(addnodes.only): |
1891 | + try: |
1892 | + ret = builder.tags.eval_condition(node['expr']) |
1893 | + except Exception, err: |
1894 | + self.warn_node('exception while evaluating only ' |
1895 | + 'directive expression: %s' % err, node) |
1896 | + node.replace_self(node.children or nodes.comment()) |
1897 | + else: |
1898 | + if ret: |
1899 | + node.replace_self(node.children or nodes.comment()) |
1900 | + else: |
1901 | + node.replace_self(nodes.comment()) |
1902 | + |
1903 | + def assign_section_numbers(self): |
1904 | + """Assign a section number to each heading under a numbered toctree.""" |
1905 | + # a list of all docnames whose section numbers changed |
1906 | + rewrite_needed = [] |
1907 | + |
1908 | + old_secnumbers = self.toc_secnumbers |
1909 | + self.toc_secnumbers = {} |
1910 | + |
1911 | + def _walk_toc(node, secnums, depth, titlenode=None): |
1912 | + # titlenode is the title of the document, it will get assigned a |
1913 | + # secnumber too, so that it shows up in next/prev/parent rellinks |
1914 | + for subnode in node.children: |
1915 | + if isinstance(subnode, nodes.bullet_list): |
1916 | + numstack.append(0) |
1917 | + _walk_toc(subnode, secnums, depth-1, titlenode) |
1918 | + numstack.pop() |
1919 | + titlenode = None |
1920 | + elif isinstance(subnode, nodes.list_item): |
1921 | + _walk_toc(subnode, secnums, depth, titlenode) |
1922 | + titlenode = None |
1923 | + elif isinstance(subnode, addnodes.only): |
1924 | + # at this stage we don't know yet which sections are going |
1925 | + # to be included; just include all of them, even if it leads |
1926 | + # to gaps in the numbering |
1927 | + _walk_toc(subnode, secnums, depth, titlenode) |
1928 | + titlenode = None |
1929 | + elif isinstance(subnode, addnodes.compact_paragraph): |
1930 | + numstack[-1] += 1 |
1931 | + if depth > 0: |
1932 | + number = tuple(numstack) |
1933 | + else: |
1934 | + number = None |
1935 | + secnums[subnode[0]['anchorname']] = \ |
1936 | + subnode[0]['secnumber'] = number |
1937 | + if titlenode: |
1938 | + titlenode['secnumber'] = number |
1939 | + titlenode = None |
1940 | + elif isinstance(subnode, addnodes.toctree): |
1941 | + _walk_toctree(subnode, depth) |
1942 | + |
1943 | + def _walk_toctree(toctreenode, depth): |
1944 | + if depth == 0: |
1945 | + return |
1946 | + for (title, ref) in toctreenode['entries']: |
1947 | + if url_re.match(ref) or ref == 'self': |
1948 | + # don't mess with those |
1949 | + continue |
1950 | + if ref in self.tocs: |
1951 | + secnums = self.toc_secnumbers[ref] = {} |
1952 | + _walk_toc(self.tocs[ref], secnums, depth, |
1953 | + self.titles.get(ref)) |
1954 | + if secnums != old_secnumbers.get(ref): |
1955 | + rewrite_needed.append(ref) |
1956 | + |
1957 | + for docname in self.numbered_toctrees: |
1958 | + doctree = self.get_doctree(docname) |
1959 | + for toctreenode in doctree.traverse(addnodes.toctree): |
1960 | + depth = toctreenode.get('numbered', 0) |
1961 | + if depth: |
1962 | + # every numbered toctree gets new numbering |
1963 | + numstack = [0] |
1964 | + _walk_toctree(toctreenode, depth) |
1965 | + |
1966 | + return rewrite_needed |
1967 | + |
1968 | + def create_index(self, builder, group_entries=True, |
1969 | + _fixre=re.compile(r'(.*) ([(][^()]*[)])')): |
1970 | + """Create the real index from the collected index entries.""" |
1971 | + new = {} |
1972 | + |
1973 | + def add_entry(word, subword, link=True, dic=new): |
1974 | + entry = dic.get(word) |
1975 | + if not entry: |
1976 | + dic[word] = entry = [[], {}] |
1977 | + if subword: |
1978 | + add_entry(subword, '', link=link, dic=entry[1]) |
1979 | + elif link: |
1980 | + try: |
1981 | + uri = builder.get_relative_uri('genindex', fn) + '#' + tid |
1982 | + except NoUri: |
1983 | + pass |
1984 | + else: |
1985 | + entry[0].append((main, uri)) |
1986 | + |
1987 | + for fn, entries in self.indexentries.iteritems(): |
1988 | + # new entry types must be listed in directives/other.py! |
1989 | + for type, value, tid, main in entries: |
1990 | + try: |
1991 | + if type == 'single': |
1992 | + try: |
1993 | + entry, subentry = split_into(2, 'single', value) |
1994 | + except ValueError: |
1995 | + entry, = split_into(1, 'single', value) |
1996 | + subentry = '' |
1997 | + add_entry(entry, subentry) |
1998 | + elif type == 'pair': |
1999 | + first, second = split_into(2, 'pair', value) |
2000 | + add_entry(first, second) |
2001 | + add_entry(second, first) |
2002 | + elif type == 'triple': |
2003 | + first, second, third = split_into(3, 'triple', value) |
2004 | + add_entry(first, second+' '+third) |
2005 | + add_entry(second, third+', '+first) |
2006 | + add_entry(third, first+' '+second) |
2007 | + elif type == 'see': |
2008 | + first, second = split_into(2, 'see', value) |
2009 | + add_entry(first, _('see %s') % second, link=False) |
2010 | + elif type == 'seealso': |
2011 | + first, second = split_into(2, 'see', value) |
2012 | + add_entry(first, _('see also %s') % second, link=False) |
2013 | + else: |
2014 | + self.warn(fn, 'unknown index entry type %r' % type) |
2015 | + except ValueError, err: |
2016 | + self.warn(fn, str(err)) |
2017 | + |
2018 | + # sort the index entries; put all symbols at the front, even those |
2019 | + # following the letters in ASCII, this is where the chr(127) comes from |
2020 | + def keyfunc(entry, lcletters=string.ascii_lowercase + '_'): |
2021 | + lckey = unicodedata.normalize('NFD', entry[0].lower()) |
2022 | + if lckey[0:1] in lcletters: |
2023 | + return chr(127) + lckey |
2024 | + return lckey |
2025 | + newlist = new.items() |
2026 | + newlist.sort(key=keyfunc) |
2027 | + |
2028 | + if group_entries: |
2029 | + # fixup entries: transform |
2030 | + # func() (in module foo) |
2031 | + # func() (in module bar) |
2032 | + # into |
2033 | + # func() |
2034 | + # (in module foo) |
2035 | + # (in module bar) |
2036 | + oldkey = '' |
2037 | + oldsubitems = None |
2038 | + i = 0 |
2039 | + while i < len(newlist): |
2040 | + key, (targets, subitems) = newlist[i] |
2041 | + # cannot move if it has subitems; structure gets too complex |
2042 | + if not subitems: |
2043 | + m = _fixre.match(key) |
2044 | + if m: |
2045 | + if oldkey == m.group(1): |
2046 | + # prefixes match: add entry as subitem of the |
2047 | + # previous entry |
2048 | + oldsubitems.setdefault(m.group(2), [[], {}])[0].\ |
2049 | + extend(targets) |
2050 | + del newlist[i] |
2051 | + continue |
2052 | + oldkey = m.group(1) |
2053 | + else: |
2054 | + oldkey = key |
2055 | + oldsubitems = subitems |
2056 | + i += 1 |
2057 | + |
2058 | + # group the entries by letter |
2059 | + def keyfunc2(item, letters=string.ascii_uppercase + '_'): |
2060 | + # hack: mutating the subitems dicts to a list in the keyfunc |
2061 | + k, v = item |
2062 | + v[1] = sorted((si, se) for (si, (se, void)) in v[1].iteritems()) |
2063 | + # now calculate the key |
2064 | + letter = unicodedata.normalize('NFD', k[0])[0].upper() |
2065 | + if letter in letters: |
2066 | + return letter |
2067 | + else: |
2068 | + # get all other symbols under one heading |
2069 | + return 'Symbols' |
2070 | + return [(key, list(group)) |
2071 | + for (key, group) in groupby(newlist, keyfunc2)] |
2072 | + |
2073 | + def collect_relations(self): |
2074 | + relations = {} |
2075 | + getinc = self.toctree_includes.get |
2076 | + def collect(parents, parents_set, docname, previous, next): |
2077 | + # circular relationship? |
2078 | + if docname in parents_set: |
2079 | + # we will warn about this in resolve_toctree() |
2080 | + return |
2081 | + includes = getinc(docname) |
2082 | + # previous |
2083 | + if not previous: |
2084 | + # if no previous sibling, go to parent |
2085 | + previous = parents[0][0] |
2086 | + else: |
2087 | + # else, go to previous sibling, or if it has children, to |
2088 | + # the last of its children, or if that has children, to the |
2089 | + # last of those, and so forth |
2090 | + while 1: |
2091 | + previncs = getinc(previous) |
2092 | + if previncs: |
2093 | + previous = previncs[-1] |
2094 | + else: |
2095 | + break |
2096 | + # next |
2097 | + if includes: |
2098 | + # if it has children, go to first of them |
2099 | + next = includes[0] |
2100 | + elif next: |
2101 | + # else, if next sibling, go to it |
2102 | + pass |
2103 | + else: |
2104 | + # else, go to the next sibling of the parent, if present, |
2105 | + # else the grandparent's sibling, if present, and so forth |
2106 | + for parname, parindex in parents: |
2107 | + parincs = getinc(parname) |
2108 | + if parincs and parindex + 1 < len(parincs): |
2109 | + next = parincs[parindex+1] |
2110 | + break |
2111 | + # else it will stay None |
2112 | + # same for children |
2113 | + if includes: |
2114 | + for subindex, args in enumerate(izip(includes, |
2115 | + [None] + includes, |
2116 | + includes[1:] + [None])): |
2117 | + collect([(docname, subindex)] + parents, |
2118 | + parents_set.union([docname]), *args) |
2119 | + relations[docname] = [parents[0][0], previous, next] |
2120 | + collect([(None, 0)], set(), self.config.master_doc, None, None) |
2121 | + return relations |
2122 | + |
2123 | + def check_consistency(self): |
2124 | + """Do consistency checks.""" |
2125 | + for docname in sorted(self.all_docs): |
2126 | + if docname not in self.files_to_rebuild: |
2127 | + if docname == self.config.master_doc: |
2128 | + # the master file is not included anywhere ;) |
2129 | + continue |
2130 | + if 'orphan' in self.metadata[docname]: |
2131 | + continue |
2132 | + self.warn(docname, 'document isn\'t included in any toctree') |
2133 | + |
2134 | |
2135 | === added directory '.pc/sort_stopwords.diff' |
2136 | === added directory '.pc/sort_stopwords.diff/sphinx' |
2137 | === added directory '.pc/sort_stopwords.diff/sphinx/search' |
2138 | === added file '.pc/sort_stopwords.diff/sphinx/search/__init__.py' |
2139 | --- .pc/sort_stopwords.diff/sphinx/search/__init__.py 1970-01-01 00:00:00 +0000 |
2140 | +++ .pc/sort_stopwords.diff/sphinx/search/__init__.py 2012-11-28 07:12:20 +0000 |
2141 | @@ -0,0 +1,287 @@ |
2142 | +# -*- coding: utf-8 -*- |
2143 | +""" |
2144 | + sphinx.search |
2145 | + ~~~~~~~~~~~~~ |
2146 | + |
2147 | + Create a full-text search index for offline search. |
2148 | + |
2149 | + :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. |
2150 | + :license: BSD, see LICENSE for details. |
2151 | +""" |
2152 | +import re |
2153 | +import cPickle as pickle |
2154 | + |
2155 | +from docutils.nodes import comment, Text, NodeVisitor, SkipNode |
2156 | + |
2157 | +from sphinx.util import jsdump, rpartition |
2158 | + |
2159 | + |
2160 | +class SearchLanguage(object): |
2161 | + """ |
2162 | + This class is the base class for search natural language preprocessors. If |
2163 | + you want to add support for a new language, you should override the methods |
2164 | + of this class. |
2165 | + |
2166 | + You should override `lang` class property too (e.g. 'en', 'fr' and so on). |
2167 | + |
2168 | + .. attribute:: stopwords |
2169 | + |
2170 | + This is a set of stop words of the target language. Default `stopwords` |
2171 | + is empty. This word is used for building index and embedded in JS. |
2172 | + |
2173 | + .. attribute:: js_stemmer_code |
2174 | + |
2175 | + Return stemmer class of JavaScript version. This class' name should be |
2176 | + ``Stemmer`` and this class must have ``stemWord`` method. This string is |
2177 | + embedded as-is in searchtools.js. |
2178 | + |
2179 | + This class is used to preprocess search word which Sphinx HTML readers |
2180 | + type, before searching index. Default implementation does nothing. |
2181 | + """ |
2182 | + lang = None |
2183 | + stopwords = set() |
2184 | + js_stemmer_code = """ |
2185 | +/** |
2186 | + * Dummy stemmer for languages without stemming rules. |
2187 | + */ |
2188 | +var Stemmer = function() { |
2189 | + this.stemWord = function(w) { |
2190 | + return w; |
2191 | + } |
2192 | +} |
2193 | +""" |
2194 | + |
2195 | + _word_re = re.compile(r'\w+(?u)') |
2196 | + |
2197 | + def __init__(self, options): |
2198 | + self.options = options |
2199 | + self.init(options) |
2200 | + |
2201 | + def init(self, options): |
2202 | + """ |
2203 | + Initialize the class with the options the user has given. |
2204 | + """ |
2205 | + |
2206 | + def split(self, input): |
2207 | + """ |
2208 | + This method splits a sentence into words. Default splitter splits input |
2209 | + at white spaces, which should be enough for most languages except CJK |
2210 | + languages. |
2211 | + """ |
2212 | + return self._word_re.findall(input) |
2213 | + |
2214 | + def stem(self, word): |
2215 | + """ |
2216 | + This method implements stemming algorithm of the Python version. |
2217 | + |
2218 | + Default implementation does nothing. You should implement this if the |
2219 | + language has any stemming rules. |
2220 | + |
2221 | + This class is used to preprocess search words before registering them in |
2222 | + the search index. The stemming of the Python version and the JS version |
2223 | + (given in the js_stemmer_code attribute) must be compatible. |
2224 | + """ |
2225 | + return word |
2226 | + |
2227 | + def word_filter(self, word): |
2228 | + """ |
2229 | + Return true if the target word should be registered in the search index. |
2230 | + This method is called after stemming. |
2231 | + """ |
2232 | + return not (((len(word) < 3) and (12353 < ord(word[0]) < 12436)) or |
2233 | + (ord(word[0]) < 256 and (len(word) < 3 or word in self.stopwords or |
2234 | + word.isdigit()))) |
2235 | + |
2236 | +from sphinx.search import en, ja |
2237 | + |
2238 | +languages = { |
2239 | + 'en': en.SearchEnglish, |
2240 | + 'ja': ja.SearchJapanese, |
2241 | +} |
2242 | + |
2243 | + |
2244 | +class _JavaScriptIndex(object): |
2245 | + """ |
2246 | + The search index as javascript file that calls a function |
2247 | + on the documentation search object to register the index. |
2248 | + """ |
2249 | + |
2250 | + PREFIX = 'Search.setIndex(' |
2251 | + SUFFIX = ')' |
2252 | + |
2253 | + def dumps(self, data): |
2254 | + return self.PREFIX + jsdump.dumps(data) + self.SUFFIX |
2255 | + |
2256 | + def loads(self, s): |
2257 | + data = s[len(self.PREFIX):-len(self.SUFFIX)] |
2258 | + if not data or not s.startswith(self.PREFIX) or not \ |
2259 | + s.endswith(self.SUFFIX): |
2260 | + raise ValueError('invalid data') |
2261 | + return jsdump.loads(data) |
2262 | + |
2263 | + def dump(self, data, f): |
2264 | + f.write(self.dumps(data)) |
2265 | + |
2266 | + def load(self, f): |
2267 | + return self.loads(f.read()) |
2268 | + |
2269 | + |
2270 | +js_index = _JavaScriptIndex() |
2271 | + |
2272 | + |
2273 | +class WordCollector(NodeVisitor): |
2274 | + """ |
2275 | + A special visitor that collects words for the `IndexBuilder`. |
2276 | + """ |
2277 | + |
2278 | + def __init__(self, document, lang): |
2279 | + NodeVisitor.__init__(self, document) |
2280 | + self.found_words = [] |
2281 | + self.lang = lang |
2282 | + |
2283 | + def dispatch_visit(self, node): |
2284 | + if node.__class__ is comment: |
2285 | + raise SkipNode |
2286 | + if node.__class__ is Text: |
2287 | + self.found_words.extend(self.lang.split(node.astext())) |
2288 | + |
2289 | + |
2290 | +class IndexBuilder(object): |
2291 | + """ |
2292 | + Helper class that creates a searchindex based on the doctrees |
2293 | + passed to the `feed` method. |
2294 | + """ |
2295 | + formats = { |
2296 | + 'jsdump': jsdump, |
2297 | + 'pickle': pickle |
2298 | + } |
2299 | + |
2300 | + def __init__(self, env, lang, options): |
2301 | + self.env = env |
2302 | + # filename -> title |
2303 | + self._titles = {} |
2304 | + # stemmed word -> set(filenames) |
2305 | + self._mapping = {} |
2306 | + # objtype -> index |
2307 | + self._objtypes = {} |
2308 | + # objtype index -> (domain, type, objname (localized)) |
2309 | + self._objnames = {} |
2310 | + # add language-specific SearchLanguage instance |
2311 | + self.lang = languages[lang](options) |
2312 | + |
2313 | + def load(self, stream, format): |
2314 | + """Reconstruct from frozen data.""" |
2315 | + if isinstance(format, basestring): |
2316 | + format = self.formats[format] |
2317 | + frozen = format.load(stream) |
2318 | + # if an old index is present, we treat it as not existing. |
2319 | + if not isinstance(frozen, dict): |
2320 | + raise ValueError('old format') |
2321 | + index2fn = frozen['filenames'] |
2322 | + self._titles = dict(zip(index2fn, frozen['titles'])) |
2323 | + self._mapping = {} |
2324 | + for k, v in frozen['terms'].iteritems(): |
2325 | + if isinstance(v, int): |
2326 | + self._mapping[k] = set([index2fn[v]]) |
2327 | + else: |
2328 | + self._mapping[k] = set(index2fn[i] for i in v) |
2329 | + # no need to load keywords/objtypes |
2330 | + |
2331 | + def dump(self, stream, format): |
2332 | + """Dump the frozen index to a stream.""" |
2333 | + if isinstance(format, basestring): |
2334 | + format = self.formats[format] |
2335 | + format.dump(self.freeze(), stream) |
2336 | + |
2337 | + def get_objects(self, fn2index): |
2338 | + rv = {} |
2339 | + otypes = self._objtypes |
2340 | + onames = self._objnames |
2341 | + for domainname, domain in self.env.domains.iteritems(): |
2342 | + for fullname, dispname, type, docname, anchor, prio in \ |
2343 | + domain.get_objects(): |
2344 | + # XXX use dispname? |
2345 | + if docname not in fn2index: |
2346 | + continue |
2347 | + if prio < 0: |
2348 | + continue |
2349 | + prefix, name = rpartition(fullname, '.') |
2350 | + pdict = rv.setdefault(prefix, {}) |
2351 | + try: |
2352 | + typeindex = otypes[domainname, type] |
2353 | + except KeyError: |
2354 | + typeindex = len(otypes) |
2355 | + otypes[domainname, type] = typeindex |
2356 | + otype = domain.object_types.get(type) |
2357 | + if otype: |
2358 | + # use unicode() to fire translation proxies |
2359 | + onames[typeindex] = (domainname, type, |
2360 | + unicode(domain.get_type_name(otype))) |
2361 | + else: |
2362 | + onames[typeindex] = (domainname, type, type) |
2363 | + if anchor == fullname: |
2364 | + shortanchor = '' |
2365 | + elif anchor == type + '-' + fullname: |
2366 | + shortanchor = '-' |
2367 | + else: |
2368 | + shortanchor = anchor |
2369 | + pdict[name] = (fn2index[docname], typeindex, prio, shortanchor) |
2370 | + return rv |
2371 | + |
2372 | + def get_terms(self, fn2index): |
2373 | + rv = {} |
2374 | + for k, v in self._mapping.iteritems(): |
2375 | + if len(v) == 1: |
2376 | + fn, = v |
2377 | + if fn in fn2index: |
2378 | + rv[k] = fn2index[fn] |
2379 | + else: |
2380 | + rv[k] = [fn2index[fn] for fn in v if fn in fn2index] |
2381 | + return rv |
2382 | + |
2383 | + def freeze(self): |
2384 | + """Create a usable data structure for serializing.""" |
2385 | + filenames = self._titles.keys() |
2386 | + titles = self._titles.values() |
2387 | + fn2index = dict((f, i) for (i, f) in enumerate(filenames)) |
2388 | + terms = self.get_terms(fn2index) |
2389 | + objects = self.get_objects(fn2index) # populates _objtypes |
2390 | + objtypes = dict((v, k[0] + ':' + k[1]) |
2391 | + for (k, v) in self._objtypes.iteritems()) |
2392 | + objnames = self._objnames |
2393 | + return dict(filenames=filenames, titles=titles, terms=terms, |
2394 | + objects=objects, objtypes=objtypes, objnames=objnames) |
2395 | + |
2396 | + def prune(self, filenames): |
2397 | + """Remove data for all filenames not in the list.""" |
2398 | + new_titles = {} |
2399 | + for filename in filenames: |
2400 | + if filename in self._titles: |
2401 | + new_titles[filename] = self._titles[filename] |
2402 | + self._titles = new_titles |
2403 | + for wordnames in self._mapping.itervalues(): |
2404 | + wordnames.intersection_update(filenames) |
2405 | + |
2406 | + def feed(self, filename, title, doctree): |
2407 | + """Feed a doctree to the index.""" |
2408 | + self._titles[filename] = title |
2409 | + |
2410 | + visitor = WordCollector(doctree, self.lang) |
2411 | + doctree.walk(visitor) |
2412 | + |
2413 | + def add_term(word, stem=self.lang.stem): |
2414 | + word = stem(word) |
2415 | + if self.lang.word_filter(word): |
2416 | + self._mapping.setdefault(word, set()).add(filename) |
2417 | + |
2418 | + for word in self.lang.split(title): |
2419 | + add_term(word) |
2420 | + |
2421 | + for word in visitor.found_words: |
2422 | + add_term(word) |
2423 | + |
2424 | + def context_for_searchtool(self): |
2425 | + return dict( |
2426 | + search_language_stemming_code = self.lang.js_stemmer_code, |
2427 | + search_language_stop_words = jsdump.dumps(self.lang.stopwords), |
2428 | + ) |
2429 | |
2430 | === removed file '.pc/support_python_3.3.diff/sphinx/environment.py' |
2431 | --- .pc/support_python_3.3.diff/sphinx/environment.py 2012-11-01 21:39:16 +0000 |
2432 | +++ .pc/support_python_3.3.diff/sphinx/environment.py 1970-01-01 00:00:00 +0000 |
2433 | @@ -1,1762 +0,0 @@ |
2434 | -# -*- coding: utf-8 -*- |
2435 | -""" |
2436 | - sphinx.environment |
2437 | - ~~~~~~~~~~~~~~~~~~ |
2438 | - |
2439 | - Global creation environment. |
2440 | - |
2441 | - :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. |
2442 | - :license: BSD, see LICENSE for details. |
2443 | -""" |
2444 | - |
2445 | -import re |
2446 | -import os |
2447 | -import sys |
2448 | -import time |
2449 | -import types |
2450 | -import codecs |
2451 | -import imghdr |
2452 | -import string |
2453 | -import unicodedata |
2454 | -import cPickle as pickle |
2455 | -from os import path |
2456 | -from glob import glob |
2457 | -from itertools import izip, groupby |
2458 | - |
2459 | -from docutils import nodes |
2460 | -from docutils.io import FileInput, NullOutput |
2461 | -from docutils.core import Publisher |
2462 | -from docutils.utils import Reporter, relative_path, new_document, \ |
2463 | - get_source_line |
2464 | -from docutils.readers import standalone |
2465 | -from docutils.parsers.rst import roles, directives, Parser as RSTParser |
2466 | -from docutils.parsers.rst.languages import en as english |
2467 | -from docutils.parsers.rst.directives.html import MetaBody |
2468 | -from docutils.writers import UnfilteredWriter |
2469 | -from docutils.transforms import Transform |
2470 | -from docutils.transforms.parts import ContentsFilter |
2471 | - |
2472 | -from sphinx import addnodes |
2473 | -from sphinx.util import url_re, get_matching_docs, docname_join, split_into, \ |
2474 | - FilenameUniqDict |
2475 | -from sphinx.util.nodes import clean_astext, make_refnode, extract_messages, \ |
2476 | - WarningStream |
2477 | -from sphinx.util.osutil import movefile, SEP, ustrftime, find_catalog |
2478 | -from sphinx.util.matching import compile_matchers |
2479 | -from sphinx.util.pycompat import all, class_types |
2480 | -from sphinx.util.websupport import is_commentable |
2481 | -from sphinx.errors import SphinxError, ExtensionError |
2482 | -from sphinx.locale import _, init as init_locale |
2483 | -from sphinx.versioning import add_uids, merge_doctrees |
2484 | - |
2485 | -fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() |
2486 | - |
2487 | -orig_role_function = roles.role |
2488 | -orig_directive_function = directives.directive |
2489 | - |
2490 | -class ElementLookupError(Exception): pass |
2491 | - |
2492 | - |
2493 | -default_settings = { |
2494 | - 'embed_stylesheet': False, |
2495 | - 'cloak_email_addresses': True, |
2496 | - 'pep_base_url': 'http://www.python.org/dev/peps/', |
2497 | - 'rfc_base_url': 'http://tools.ietf.org/html/', |
2498 | - 'input_encoding': 'utf-8-sig', |
2499 | - 'doctitle_xform': False, |
2500 | - 'sectsubtitle_xform': False, |
2501 | - 'halt_level': 5, |
2502 | -} |
2503 | - |
2504 | -# This is increased every time an environment attribute is added |
2505 | -# or changed to properly invalidate pickle files. |
2506 | -ENV_VERSION = 41 |
2507 | - |
2508 | - |
2509 | -default_substitutions = set([ |
2510 | - 'version', |
2511 | - 'release', |
2512 | - 'today', |
2513 | -]) |
2514 | - |
2515 | -dummy_reporter = Reporter('', 4, 4) |
2516 | - |
2517 | -versioning_conditions = { |
2518 | - 'none': False, |
2519 | - 'text': nodes.TextElement, |
2520 | - 'commentable': is_commentable, |
2521 | -} |
2522 | - |
2523 | - |
2524 | -class NoUri(Exception): |
2525 | - """Raised by get_relative_uri if there is no URI available.""" |
2526 | - pass |
2527 | - |
2528 | - |
2529 | -class DefaultSubstitutions(Transform): |
2530 | - """ |
2531 | - Replace some substitutions if they aren't defined in the document. |
2532 | - """ |
2533 | - # run before the default Substitutions |
2534 | - default_priority = 210 |
2535 | - |
2536 | - def apply(self): |
2537 | - config = self.document.settings.env.config |
2538 | - # only handle those not otherwise defined in the document |
2539 | - to_handle = default_substitutions - set(self.document.substitution_defs) |
2540 | - for ref in self.document.traverse(nodes.substitution_reference): |
2541 | - refname = ref['refname'] |
2542 | - if refname in to_handle: |
2543 | - text = config[refname] |
2544 | - if refname == 'today' and not text: |
2545 | - # special handling: can also specify a strftime format |
2546 | - text = ustrftime(config.today_fmt or _('%B %d, %Y')) |
2547 | - ref.replace_self(nodes.Text(text, text)) |
2548 | - |
2549 | - |
2550 | -class MoveModuleTargets(Transform): |
2551 | - """ |
2552 | - Move module targets that are the first thing in a section to the section |
2553 | - title. |
2554 | - |
2555 | - XXX Python specific |
2556 | - """ |
2557 | - default_priority = 210 |
2558 | - |
2559 | - def apply(self): |
2560 | - for node in self.document.traverse(nodes.target): |
2561 | - if not node['ids']: |
2562 | - continue |
2563 | - if (node.has_key('ismod') and |
2564 | - node.parent.__class__ is nodes.section and |
2565 | - # index 0 is the section title node |
2566 | - node.parent.index(node) == 1): |
2567 | - node.parent['ids'][0:0] = node['ids'] |
2568 | - node.parent.remove(node) |
2569 | - |
2570 | - |
2571 | -class HandleCodeBlocks(Transform): |
2572 | - """ |
2573 | - Several code block related transformations. |
2574 | - """ |
2575 | - default_priority = 210 |
2576 | - |
2577 | - def apply(self): |
2578 | - # move doctest blocks out of blockquotes |
2579 | - for node in self.document.traverse(nodes.block_quote): |
2580 | - if all(isinstance(child, nodes.doctest_block) for child |
2581 | - in node.children): |
2582 | - node.replace_self(node.children) |
2583 | - # combine successive doctest blocks |
2584 | - #for node in self.document.traverse(nodes.doctest_block): |
2585 | - # if node not in node.parent.children: |
2586 | - # continue |
2587 | - # parindex = node.parent.index(node) |
2588 | - # while len(node.parent) > parindex+1 and \ |
2589 | - # isinstance(node.parent[parindex+1], nodes.doctest_block): |
2590 | - # node[0] = nodes.Text(node[0] + '\n\n' + |
2591 | - # node.parent[parindex+1][0]) |
2592 | - # del node.parent[parindex+1] |
2593 | - |
2594 | - |
2595 | -class SortIds(Transform): |
2596 | - """ |
2597 | - Sort secion IDs so that the "id[0-9]+" one comes last. |
2598 | - """ |
2599 | - default_priority = 261 |
2600 | - |
2601 | - def apply(self): |
2602 | - for node in self.document.traverse(nodes.section): |
2603 | - if len(node['ids']) > 1 and node['ids'][0].startswith('id'): |
2604 | - node['ids'] = node['ids'][1:] + [node['ids'][0]] |
2605 | - |
2606 | - |
2607 | -class CitationReferences(Transform): |
2608 | - """ |
2609 | - Replace citation references by pending_xref nodes before the default |
2610 | - docutils transform tries to resolve them. |
2611 | - """ |
2612 | - default_priority = 619 |
2613 | - |
2614 | - def apply(self): |
2615 | - for citnode in self.document.traverse(nodes.citation_reference): |
2616 | - cittext = citnode.astext() |
2617 | - refnode = addnodes.pending_xref(cittext, reftype='citation', |
2618 | - reftarget=cittext, refwarn=True) |
2619 | - refnode.line = citnode.line or citnode.parent.line |
2620 | - refnode += nodes.Text('[' + cittext + ']') |
2621 | - citnode.parent.replace(citnode, refnode) |
2622 | - |
2623 | - |
2624 | -class Locale(Transform): |
2625 | - """ |
2626 | - Replace translatable nodes with their translated doctree. |
2627 | - """ |
2628 | - default_priority = 0 |
2629 | - def apply(self): |
2630 | - env = self.document.settings.env |
2631 | - settings, source = self.document.settings, self.document['source'] |
2632 | - # XXX check if this is reliable |
2633 | - assert source.startswith(env.srcdir) |
2634 | - docname = path.splitext(relative_path(env.srcdir, source))[0] |
2635 | - textdomain = find_catalog(docname, |
2636 | - self.document.settings.gettext_compact) |
2637 | - |
2638 | - # fetch translations |
2639 | - dirs = [path.join(env.srcdir, directory) |
2640 | - for directory in env.config.locale_dirs] |
2641 | - catalog, has_catalog = init_locale(dirs, env.config.language, |
2642 | - textdomain) |
2643 | - if not has_catalog: |
2644 | - return |
2645 | - |
2646 | - parser = RSTParser() |
2647 | - |
2648 | - for node, msg in extract_messages(self.document): |
2649 | - patch = new_document(source, settings) |
2650 | - msgstr = catalog.gettext(msg) |
2651 | - # XXX add marker to untranslated parts |
2652 | - if not msgstr or msgstr == msg: # as-of-yet untranslated |
2653 | - continue |
2654 | - parser.parse(msgstr, patch) |
2655 | - patch = patch[0] |
2656 | - # XXX doctest and other block markup |
2657 | - if not isinstance(patch, nodes.paragraph): |
2658 | - continue # skip for now |
2659 | - for child in patch.children: # update leaves |
2660 | - child.parent = node |
2661 | - node.children = patch.children |
2662 | - |
2663 | - |
2664 | -class SphinxStandaloneReader(standalone.Reader): |
2665 | - """ |
2666 | - Add our own transforms. |
2667 | - """ |
2668 | - transforms = [Locale, CitationReferences, DefaultSubstitutions, |
2669 | - MoveModuleTargets, HandleCodeBlocks, SortIds] |
2670 | - |
2671 | - def get_transforms(self): |
2672 | - return standalone.Reader.get_transforms(self) + self.transforms |
2673 | - |
2674 | - |
2675 | -class SphinxDummyWriter(UnfilteredWriter): |
2676 | - supported = ('html',) # needed to keep "meta" nodes |
2677 | - |
2678 | - def translate(self): |
2679 | - pass |
2680 | - |
2681 | - |
2682 | -class SphinxContentsFilter(ContentsFilter): |
2683 | - """ |
2684 | - Used with BuildEnvironment.add_toc_from() to discard cross-file links |
2685 | - within table-of-contents link nodes. |
2686 | - """ |
2687 | - def visit_pending_xref(self, node): |
2688 | - text = node.astext() |
2689 | - self.parent.append(nodes.literal(text, text)) |
2690 | - raise nodes.SkipNode |
2691 | - |
2692 | - def visit_image(self, node): |
2693 | - raise nodes.SkipNode |
2694 | - |
2695 | - |
2696 | -class BuildEnvironment: |
2697 | - """ |
2698 | - The environment in which the ReST files are translated. |
2699 | - Stores an inventory of cross-file targets and provides doctree |
2700 | - transformations to resolve links to them. |
2701 | - """ |
2702 | - |
2703 | - # --------- ENVIRONMENT PERSISTENCE ---------------------------------------- |
2704 | - |
2705 | - @staticmethod |
2706 | - def frompickle(config, filename): |
2707 | - picklefile = open(filename, 'rb') |
2708 | - try: |
2709 | - env = pickle.load(picklefile) |
2710 | - finally: |
2711 | - picklefile.close() |
2712 | - if env.version != ENV_VERSION: |
2713 | - raise IOError('env version not current') |
2714 | - env.config.values = config.values |
2715 | - return env |
2716 | - |
2717 | - def topickle(self, filename): |
2718 | - # remove unpicklable attributes |
2719 | - warnfunc = self._warnfunc |
2720 | - self.set_warnfunc(None) |
2721 | - values = self.config.values |
2722 | - del self.config.values |
2723 | - domains = self.domains |
2724 | - del self.domains |
2725 | - # first write to a temporary file, so that if dumping fails, |
2726 | - # the existing environment won't be overwritten |
2727 | - picklefile = open(filename + '.tmp', 'wb') |
2728 | - # remove potentially pickling-problematic values from config |
2729 | - for key, val in vars(self.config).items(): |
2730 | - if key.startswith('_') or \ |
2731 | - isinstance(val, types.ModuleType) or \ |
2732 | - isinstance(val, types.FunctionType) or \ |
2733 | - isinstance(val, class_types): |
2734 | - del self.config[key] |
2735 | - try: |
2736 | - pickle.dump(self, picklefile, pickle.HIGHEST_PROTOCOL) |
2737 | - finally: |
2738 | - picklefile.close() |
2739 | - movefile(filename + '.tmp', filename) |
2740 | - # reset attributes |
2741 | - self.domains = domains |
2742 | - self.config.values = values |
2743 | - self.set_warnfunc(warnfunc) |
2744 | - |
2745 | - # --------- ENVIRONMENT INITIALIZATION ------------------------------------- |
2746 | - |
2747 | - def __init__(self, srcdir, doctreedir, config): |
2748 | - self.doctreedir = doctreedir |
2749 | - self.srcdir = srcdir |
2750 | - self.config = config |
2751 | - |
2752 | - # the method of doctree versioning; see set_versioning_method |
2753 | - self.versioning_condition = None |
2754 | - |
2755 | - # the application object; only set while update() runs |
2756 | - self.app = None |
2757 | - |
2758 | - # all the registered domains, set by the application |
2759 | - self.domains = {} |
2760 | - |
2761 | - # the docutils settings for building |
2762 | - self.settings = default_settings.copy() |
2763 | - self.settings['env'] = self |
2764 | - |
2765 | - # the function to write warning messages with |
2766 | - self._warnfunc = None |
2767 | - |
2768 | - # this is to invalidate old pickles |
2769 | - self.version = ENV_VERSION |
2770 | - |
2771 | - # make this a set for faster testing |
2772 | - self._nitpick_ignore = set(self.config.nitpick_ignore) |
2773 | - |
2774 | - # All "docnames" here are /-separated and relative and exclude |
2775 | - # the source suffix. |
2776 | - |
2777 | - self.found_docs = set() # contains all existing docnames |
2778 | - self.all_docs = {} # docname -> mtime at the time of build |
2779 | - # contains all built docnames |
2780 | - self.dependencies = {} # docname -> set of dependent file |
2781 | - # names, relative to documentation root |
2782 | - self.reread_always = set() # docnames to re-read unconditionally on |
2783 | - # next build |
2784 | - |
2785 | - # File metadata |
2786 | - self.metadata = {} # docname -> dict of metadata items |
2787 | - |
2788 | - # TOC inventory |
2789 | - self.titles = {} # docname -> title node |
2790 | - self.longtitles = {} # docname -> title node; only different if |
2791 | - # set differently with title directive |
2792 | - self.tocs = {} # docname -> table of contents nodetree |
2793 | - self.toc_num_entries = {} # docname -> number of real entries |
2794 | - # used to determine when to show the TOC |
2795 | - # in a sidebar (don't show if it's only one item) |
2796 | - self.toc_secnumbers = {} # docname -> dict of sectionid -> number |
2797 | - |
2798 | - self.toctree_includes = {} # docname -> list of toctree includefiles |
2799 | - self.files_to_rebuild = {} # docname -> set of files |
2800 | - # (containing its TOCs) to rebuild too |
2801 | - self.glob_toctrees = set() # docnames that have :glob: toctrees |
2802 | - self.numbered_toctrees = set() # docnames that have :numbered: toctrees |
2803 | - |
2804 | - # domain-specific inventories, here to be pickled |
2805 | - self.domaindata = {} # domainname -> domain-specific dict |
2806 | - |
2807 | - # Other inventories |
2808 | - self.citations = {} # citation name -> docname, labelid |
2809 | - self.indexentries = {} # docname -> list of |
2810 | - # (type, string, target, aliasname) |
2811 | - self.versionchanges = {} # version -> list of (type, docname, |
2812 | - # lineno, module, descname, content) |
2813 | - |
2814 | - # these map absolute path -> (docnames, unique filename) |
2815 | - self.images = FilenameUniqDict() |
2816 | - self.dlfiles = FilenameUniqDict() |
2817 | - |
2818 | - # temporary data storage while reading a document |
2819 | - self.temp_data = {} |
2820 | - |
2821 | - def set_warnfunc(self, func): |
2822 | - self._warnfunc = func |
2823 | - self.settings['warning_stream'] = WarningStream(func) |
2824 | - |
2825 | - def set_versioning_method(self, method): |
2826 | - """This sets the doctree versioning method for this environment. |
2827 | - |
2828 | - Versioning methods are a builder property; only builders with the same |
2829 | - versioning method can share the same doctree directory. Therefore, we |
2830 | - raise an exception if the user tries to use an environment with an |
2831 | - incompatible versioning method. |
2832 | - """ |
2833 | - if method not in versioning_conditions: |
2834 | - raise ValueError('invalid versioning method: %r' % method) |
2835 | - condition = versioning_conditions[method] |
2836 | - if self.versioning_condition not in (None, condition): |
2837 | - raise SphinxError('This environment is incompatible with the ' |
2838 | - 'selected builder, please choose another ' |
2839 | - 'doctree directory.') |
2840 | - self.versioning_condition = condition |
2841 | - |
2842 | - def warn(self, docname, msg, lineno=None): |
2843 | - # strange argument order is due to backwards compatibility |
2844 | - self._warnfunc(msg, (docname, lineno)) |
2845 | - |
2846 | - def warn_node(self, msg, node): |
2847 | - self._warnfunc(msg, '%s:%s' % get_source_line(node)) |
2848 | - |
2849 | - def clear_doc(self, docname): |
2850 | - """Remove all traces of a source file in the inventory.""" |
2851 | - if docname in self.all_docs: |
2852 | - self.all_docs.pop(docname, None) |
2853 | - self.reread_always.discard(docname) |
2854 | - self.metadata.pop(docname, None) |
2855 | - self.dependencies.pop(docname, None) |
2856 | - self.titles.pop(docname, None) |
2857 | - self.longtitles.pop(docname, None) |
2858 | - self.tocs.pop(docname, None) |
2859 | - self.toc_secnumbers.pop(docname, None) |
2860 | - self.toc_num_entries.pop(docname, None) |
2861 | - self.toctree_includes.pop(docname, None) |
2862 | - self.indexentries.pop(docname, None) |
2863 | - self.glob_toctrees.discard(docname) |
2864 | - self.numbered_toctrees.discard(docname) |
2865 | - self.images.purge_doc(docname) |
2866 | - self.dlfiles.purge_doc(docname) |
2867 | - |
2868 | - for subfn, fnset in self.files_to_rebuild.items(): |
2869 | - fnset.discard(docname) |
2870 | - if not fnset: |
2871 | - del self.files_to_rebuild[subfn] |
2872 | - for key, (fn, _) in self.citations.items(): |
2873 | - if fn == docname: |
2874 | - del self.citations[key] |
2875 | - for version, changes in self.versionchanges.items(): |
2876 | - new = [change for change in changes if change[1] != docname] |
2877 | - changes[:] = new |
2878 | - |
2879 | - for domain in self.domains.values(): |
2880 | - domain.clear_doc(docname) |
2881 | - |
2882 | - def doc2path(self, docname, base=True, suffix=None): |
2883 | - """Return the filename for the document name. |
2884 | - |
2885 | - If *base* is True, return absolute path under self.srcdir. |
2886 | - If *base* is None, return relative path to self.srcdir. |
2887 | - If *base* is a path string, return absolute path under that. |
2888 | - If *suffix* is not None, add it instead of config.source_suffix. |
2889 | - """ |
2890 | - docname = docname.replace(SEP, path.sep) |
2891 | - suffix = suffix or self.config.source_suffix |
2892 | - if base is True: |
2893 | - return path.join(self.srcdir, docname) + suffix |
2894 | - elif base is None: |
2895 | - return docname + suffix |
2896 | - else: |
2897 | - return path.join(base, docname) + suffix |
2898 | - |
2899 | - def relfn2path(self, filename, docname=None): |
2900 | - """Return paths to a file referenced from a document, relative to |
2901 | - documentation root and absolute. |
2902 | - |
2903 | - Absolute filenames are relative to the source dir, while relative |
2904 | - filenames are relative to the dir of the containing document. |
2905 | - """ |
2906 | - if filename.startswith('/') or filename.startswith(os.sep): |
2907 | - rel_fn = filename[1:] |
2908 | - else: |
2909 | - docdir = path.dirname(self.doc2path(docname or self.docname, |
2910 | - base=None)) |
2911 | - rel_fn = path.join(docdir, filename) |
2912 | - try: |
2913 | - return rel_fn, path.join(self.srcdir, rel_fn) |
2914 | - except UnicodeDecodeError: |
2915 | - # the source directory is a bytestring with non-ASCII characters; |
2916 | - # let's try to encode the rel_fn in the file system encoding |
2917 | - enc_rel_fn = rel_fn.encode(sys.getfilesystemencoding()) |
2918 | - return rel_fn, path.join(self.srcdir, enc_rel_fn) |
2919 | - |
2920 | - def find_files(self, config): |
2921 | - """Find all source files in the source dir and put them in |
2922 | - self.found_docs. |
2923 | - """ |
2924 | - matchers = compile_matchers( |
2925 | - config.exclude_patterns[:] + |
2926 | - config.exclude_trees + |
2927 | - [d + config.source_suffix for d in config.unused_docs] + |
2928 | - ['**/' + d for d in config.exclude_dirnames] + |
2929 | - ['**/_sources', '.#*'] |
2930 | - ) |
2931 | - self.found_docs = set(get_matching_docs( |
2932 | - self.srcdir, config.source_suffix, exclude_matchers=matchers)) |
2933 | - |
2934 | - def get_outdated_files(self, config_changed): |
2935 | - """Return (added, changed, removed) sets.""" |
2936 | - # clear all files no longer present |
2937 | - removed = set(self.all_docs) - self.found_docs |
2938 | - |
2939 | - added = set() |
2940 | - changed = set() |
2941 | - |
2942 | - if config_changed: |
2943 | - # config values affect e.g. substitutions |
2944 | - added = self.found_docs |
2945 | - else: |
2946 | - for docname in self.found_docs: |
2947 | - if docname not in self.all_docs: |
2948 | - added.add(docname) |
2949 | - continue |
2950 | - # if the doctree file is not there, rebuild |
2951 | - if not path.isfile(self.doc2path(docname, self.doctreedir, |
2952 | - '.doctree')): |
2953 | - changed.add(docname) |
2954 | - continue |
2955 | - # check the "reread always" list |
2956 | - if docname in self.reread_always: |
2957 | - changed.add(docname) |
2958 | - continue |
2959 | - # check the mtime of the document |
2960 | - mtime = self.all_docs[docname] |
2961 | - newmtime = path.getmtime(self.doc2path(docname)) |
2962 | - if newmtime > mtime: |
2963 | - changed.add(docname) |
2964 | - continue |
2965 | - # finally, check the mtime of dependencies |
2966 | - for dep in self.dependencies.get(docname, ()): |
2967 | - try: |
2968 | - # this will do the right thing when dep is absolute too |
2969 | - deppath = path.join(self.srcdir, dep) |
2970 | - if not path.isfile(deppath): |
2971 | - changed.add(docname) |
2972 | - break |
2973 | - depmtime = path.getmtime(deppath) |
2974 | - if depmtime > mtime: |
2975 | - changed.add(docname) |
2976 | - break |
2977 | - except EnvironmentError: |
2978 | - # give it another chance |
2979 | - changed.add(docname) |
2980 | - break |
2981 | - |
2982 | - return added, changed, removed |
2983 | - |
2984 | - def update(self, config, srcdir, doctreedir, app=None): |
2985 | - """(Re-)read all files new or changed since last update. |
2986 | - |
2987 | - Returns a summary, the total count of documents to reread and an |
2988 | - iterator that yields docnames as it processes them. Store all |
2989 | - environment docnames in the canonical format (ie using SEP as a |
2990 | - separator in place of os.path.sep). |
2991 | - """ |
2992 | - config_changed = False |
2993 | - if self.config is None: |
2994 | - msg = '[new config] ' |
2995 | - config_changed = True |
2996 | - else: |
2997 | - # check if a config value was changed that affects how |
2998 | - # doctrees are read |
2999 | - for key, descr in config.values.iteritems(): |
3000 | - if descr[1] != 'env': |
3001 | - continue |
3002 | - if self.config[key] != config[key]: |
3003 | - msg = '[config changed] ' |
3004 | - config_changed = True |
3005 | - break |
3006 | - else: |
3007 | - msg = '' |
3008 | - # this value is not covered by the above loop because it is handled |
3009 | - # specially by the config class |
3010 | - if self.config.extensions != config.extensions: |
3011 | - msg = '[extensions changed] ' |
3012 | - config_changed = True |
3013 | - # the source and doctree directories may have been relocated |
3014 | - self.srcdir = srcdir |
3015 | - self.doctreedir = doctreedir |
3016 | - self.find_files(config) |
3017 | - self.config = config |
3018 | - |
3019 | - added, changed, removed = self.get_outdated_files(config_changed) |
3020 | - |
3021 | - # allow user intervention as well |
3022 | - for docs in app.emit('env-get-outdated', self, added, changed, removed): |
3023 | - changed.update(set(docs) & self.found_docs) |
3024 | - |
3025 | - # if files were added or removed, all documents with globbed toctrees |
3026 | - # must be reread |
3027 | - if added or removed: |
3028 | - # ... but not those that already were removed |
3029 | - changed.update(self.glob_toctrees & self.found_docs) |
3030 | - |
3031 | - msg += '%s added, %s changed, %s removed' % (len(added), len(changed), |
3032 | - len(removed)) |
3033 | - |
3034 | - def update_generator(): |
3035 | - self.app = app |
3036 | - |
3037 | - # clear all files no longer present |
3038 | - for docname in removed: |
3039 | - if app: |
3040 | - app.emit('env-purge-doc', self, docname) |
3041 | - self.clear_doc(docname) |
3042 | - |
3043 | - # read all new and changed files |
3044 | - for docname in sorted(added | changed): |
3045 | - yield docname |
3046 | - self.read_doc(docname, app=app) |
3047 | - |
3048 | - if config.master_doc not in self.all_docs: |
3049 | - self.warn(None, 'master file %s not found' % |
3050 | - self.doc2path(config.master_doc)) |
3051 | - |
3052 | - self.app = None |
3053 | - if app: |
3054 | - app.emit('env-updated', self) |
3055 | - |
3056 | - return msg, len(added | changed), update_generator() |
3057 | - |
3058 | - def check_dependents(self, already): |
3059 | - to_rewrite = self.assign_section_numbers() |
3060 | - for docname in to_rewrite: |
3061 | - if docname not in already: |
3062 | - yield docname |
3063 | - |
3064 | - # --------- SINGLE FILE READING -------------------------------------------- |
3065 | - |
3066 | - def warn_and_replace(self, error): |
3067 | - """Custom decoding error handler that warns and replaces.""" |
3068 | - linestart = error.object.rfind('\n', 0, error.start) |
3069 | - lineend = error.object.find('\n', error.start) |
3070 | - if lineend == -1: lineend = len(error.object) |
3071 | - lineno = error.object.count('\n', 0, error.start) + 1 |
3072 | - self.warn(self.docname, 'undecodable source characters, ' |
3073 | - 'replacing with "?": %r' % |
3074 | - (error.object[linestart+1:error.start] + '>>>' + |
3075 | - error.object[error.start:error.end] + '<<<' + |
3076 | - error.object[error.end:lineend]), lineno) |
3077 | - return (u'?', error.end) |
3078 | - |
3079 | - def lookup_domain_element(self, type, name): |
3080 | - """Lookup a markup element (directive or role), given its name which can |
3081 | - be a full name (with domain). |
3082 | - """ |
3083 | - name = name.lower() |
3084 | - # explicit domain given? |
3085 | - if ':' in name: |
3086 | - domain_name, name = name.split(':', 1) |
3087 | - if domain_name in self.domains: |
3088 | - domain = self.domains[domain_name] |
3089 | - element = getattr(domain, type)(name) |
3090 | - if element is not None: |
3091 | - return element, [] |
3092 | - # else look in the default domain |
3093 | - else: |
3094 | - def_domain = self.temp_data.get('default_domain') |
3095 | - if def_domain is not None: |
3096 | - element = getattr(def_domain, type)(name) |
3097 | - if element is not None: |
3098 | - return element, [] |
3099 | - # always look in the std domain |
3100 | - element = getattr(self.domains['std'], type)(name) |
3101 | - if element is not None: |
3102 | - return element, [] |
3103 | - raise ElementLookupError |
3104 | - |
3105 | - def patch_lookup_functions(self): |
3106 | - """Monkey-patch directive and role dispatch, so that domain-specific |
3107 | - markup takes precedence. |
3108 | - """ |
3109 | - def directive(name, lang_module, document): |
3110 | - try: |
3111 | - return self.lookup_domain_element('directive', name) |
3112 | - except ElementLookupError: |
3113 | - return orig_directive_function(name, lang_module, document) |
3114 | - |
3115 | - def role(name, lang_module, lineno, reporter): |
3116 | - try: |
3117 | - return self.lookup_domain_element('role', name) |
3118 | - except ElementLookupError: |
3119 | - return orig_role_function(name, lang_module, lineno, reporter) |
3120 | - |
3121 | - directives.directive = directive |
3122 | - roles.role = role |
3123 | - |
3124 | - def read_doc(self, docname, src_path=None, save_parsed=True, app=None): |
3125 | - """Parse a file and add/update inventory entries for the doctree. |
3126 | - |
3127 | - If srcpath is given, read from a different source file. |
3128 | - """ |
3129 | - # remove all inventory entries for that file |
3130 | - if app: |
3131 | - app.emit('env-purge-doc', self, docname) |
3132 | - |
3133 | - self.clear_doc(docname) |
3134 | - |
3135 | - if src_path is None: |
3136 | - src_path = self.doc2path(docname) |
3137 | - |
3138 | - self.temp_data['docname'] = docname |
3139 | - # defaults to the global default, but can be re-set in a document |
3140 | - self.temp_data['default_domain'] = \ |
3141 | - self.domains.get(self.config.primary_domain) |
3142 | - |
3143 | - self.settings['input_encoding'] = self.config.source_encoding |
3144 | - self.settings['trim_footnote_reference_space'] = \ |
3145 | - self.config.trim_footnote_reference_space |
3146 | - self.settings['gettext_compact'] = self.config.gettext_compact |
3147 | - |
3148 | - self.patch_lookup_functions() |
3149 | - |
3150 | - if self.config.default_role: |
3151 | - role_fn, messages = roles.role(self.config.default_role, english, |
3152 | - 0, dummy_reporter) |
3153 | - if role_fn: |
3154 | - roles._roles[''] = role_fn |
3155 | - else: |
3156 | - self.warn(docname, 'default role %s not found' % |
3157 | - self.config.default_role) |
3158 | - |
3159 | - codecs.register_error('sphinx', self.warn_and_replace) |
3160 | - |
3161 | - class SphinxSourceClass(FileInput): |
3162 | - def __init__(self_, *args, **kwds): |
3163 | - # don't call sys.exit() on IOErrors |
3164 | - kwds['handle_io_errors'] = False |
3165 | - FileInput.__init__(self_, *args, **kwds) |
3166 | - |
3167 | - def decode(self_, data): |
3168 | - if isinstance(data, unicode): |
3169 | - return data |
3170 | - return data.decode(self_.encoding, 'sphinx') |
3171 | - |
3172 | - def read(self_): |
3173 | - data = FileInput.read(self_) |
3174 | - if app: |
3175 | - arg = [data] |
3176 | - app.emit('source-read', docname, arg) |
3177 | - data = arg[0] |
3178 | - if self.config.rst_epilog: |
3179 | - data = data + '\n' + self.config.rst_epilog + '\n' |
3180 | - if self.config.rst_prolog: |
3181 | - data = self.config.rst_prolog + '\n' + data |
3182 | - return data |
3183 | - |
3184 | - # publish manually |
3185 | - pub = Publisher(reader=SphinxStandaloneReader(), |
3186 | - writer=SphinxDummyWriter(), |
3187 | - source_class=SphinxSourceClass, |
3188 | - destination_class=NullOutput) |
3189 | - pub.set_components(None, 'restructuredtext', None) |
3190 | - pub.process_programmatic_settings(None, self.settings, None) |
3191 | - pub.set_source(None, src_path.encode(fs_encoding)) |
3192 | - pub.set_destination(None, None) |
3193 | - try: |
3194 | - pub.publish() |
3195 | - doctree = pub.document |
3196 | - except UnicodeError, err: |
3197 | - raise SphinxError(str(err)) |
3198 | - |
3199 | - # post-processing |
3200 | - self.filter_messages(doctree) |
3201 | - self.process_dependencies(docname, doctree) |
3202 | - self.process_images(docname, doctree) |
3203 | - self.process_downloads(docname, doctree) |
3204 | - self.process_metadata(docname, doctree) |
3205 | - self.process_refonly_bullet_lists(docname, doctree) |
3206 | - self.create_title_from(docname, doctree) |
3207 | - self.note_indexentries_from(docname, doctree) |
3208 | - self.note_citations_from(docname, doctree) |
3209 | - self.build_toc_from(docname, doctree) |
3210 | - for domain in self.domains.itervalues(): |
3211 | - domain.process_doc(self, docname, doctree) |
3212 | - |
3213 | - # allow extension-specific post-processing |
3214 | - if app: |
3215 | - app.emit('doctree-read', doctree) |
3216 | - |
3217 | - # store time of build, for outdated files detection |
3218 | - self.all_docs[docname] = time.time() |
3219 | - |
3220 | - if self.versioning_condition: |
3221 | - # get old doctree |
3222 | - try: |
3223 | - f = open(self.doc2path(docname, |
3224 | - self.doctreedir, '.doctree'), 'rb') |
3225 | - try: |
3226 | - old_doctree = pickle.load(f) |
3227 | - finally: |
3228 | - f.close() |
3229 | - except EnvironmentError: |
3230 | - old_doctree = None |
3231 | - |
3232 | - # add uids for versioning |
3233 | - if old_doctree is None: |
3234 | - list(add_uids(doctree, self.versioning_condition)) |
3235 | - else: |
3236 | - list(merge_doctrees( |
3237 | - old_doctree, doctree, self.versioning_condition)) |
3238 | - |
3239 | - # make it picklable |
3240 | - doctree.reporter = None |
3241 | - doctree.transformer = None |
3242 | - doctree.settings.warning_stream = None |
3243 | - doctree.settings.env = None |
3244 | - doctree.settings.record_dependencies = None |
3245 | - for metanode in doctree.traverse(MetaBody.meta): |
3246 | - # docutils' meta nodes aren't picklable because the class is nested |
3247 | - metanode.__class__ = addnodes.meta |
3248 | - |
3249 | - # cleanup |
3250 | - self.temp_data.clear() |
3251 | - |
3252 | - if save_parsed: |
3253 | - # save the parsed doctree |
3254 | - doctree_filename = self.doc2path(docname, self.doctreedir, |
3255 | - '.doctree') |
3256 | - dirname = path.dirname(doctree_filename) |
3257 | - if not path.isdir(dirname): |
3258 | - os.makedirs(dirname) |
3259 | - f = open(doctree_filename, 'wb') |
3260 | - try: |
3261 | - pickle.dump(doctree, f, pickle.HIGHEST_PROTOCOL) |
3262 | - finally: |
3263 | - f.close() |
3264 | - else: |
3265 | - return doctree |
3266 | - |
3267 | - # utilities to use while reading a document |
3268 | - |
3269 | - @property |
3270 | - def docname(self): |
3271 | - """Backwards compatible alias.""" |
3272 | - return self.temp_data['docname'] |
3273 | - |
3274 | - @property |
3275 | - def currmodule(self): |
3276 | - """Backwards compatible alias.""" |
3277 | - return self.temp_data.get('py:module') |
3278 | - |
3279 | - @property |
3280 | - def currclass(self): |
3281 | - """Backwards compatible alias.""" |
3282 | - return self.temp_data.get('py:class') |
3283 | - |
3284 | - def new_serialno(self, category=''): |
3285 | - """Return a serial number, e.g. for index entry targets.""" |
3286 | - key = category + 'serialno' |
3287 | - cur = self.temp_data.get(key, 0) |
3288 | - self.temp_data[key] = cur + 1 |
3289 | - return cur |
3290 | - |
3291 | - def note_dependency(self, filename): |
3292 | - self.dependencies.setdefault(self.docname, set()).add(filename) |
3293 | - |
3294 | - def note_reread(self): |
3295 | - self.reread_always.add(self.docname) |
3296 | - |
3297 | - def note_versionchange(self, type, version, node, lineno): |
3298 | - self.versionchanges.setdefault(version, []).append( |
3299 | - (type, self.temp_data['docname'], lineno, |
3300 | - self.temp_data.get('py:module'), |
3301 | - self.temp_data.get('object'), node.astext())) |
3302 | - |
3303 | - # post-processing of read doctrees |
3304 | - |
3305 | - def filter_messages(self, doctree): |
3306 | - """Filter system messages from a doctree.""" |
3307 | - filterlevel = self.config.keep_warnings and 2 or 5 |
3308 | - for node in doctree.traverse(nodes.system_message): |
3309 | - if node['level'] < filterlevel: |
3310 | - node.parent.remove(node) |
3311 | - |
3312 | - |
3313 | - def process_dependencies(self, docname, doctree): |
3314 | - """Process docutils-generated dependency info.""" |
3315 | - cwd = os.getcwd() |
3316 | - frompath = path.join(path.normpath(self.srcdir), 'dummy') |
3317 | - deps = doctree.settings.record_dependencies |
3318 | - if not deps: |
3319 | - return |
3320 | - for dep in deps.list: |
3321 | - # the dependency path is relative to the working dir, so get |
3322 | - # one relative to the srcdir |
3323 | - relpath = relative_path(frompath, |
3324 | - path.normpath(path.join(cwd, dep))) |
3325 | - self.dependencies.setdefault(docname, set()).add(relpath) |
3326 | - |
3327 | - def process_downloads(self, docname, doctree): |
3328 | - """Process downloadable file paths. """ |
3329 | - for node in doctree.traverse(addnodes.download_reference): |
3330 | - targetname = node['reftarget'] |
3331 | - rel_filename, filename = self.relfn2path(targetname, docname) |
3332 | - self.dependencies.setdefault(docname, set()).add(rel_filename) |
3333 | - if not os.access(filename, os.R_OK): |
3334 | - self.warn_node('download file not readable: %s' % filename, |
3335 | - node) |
3336 | - continue |
3337 | - uniquename = self.dlfiles.add_file(docname, filename) |
3338 | - node['filename'] = uniquename |
3339 | - |
3340 | - def process_images(self, docname, doctree): |
3341 | - """Process and rewrite image URIs.""" |
3342 | - for node in doctree.traverse(nodes.image): |
3343 | - # Map the mimetype to the corresponding image. The writer may |
3344 | - # choose the best image from these candidates. The special key * is |
3345 | - # set if there is only single candidate to be used by a writer. |
3346 | - # The special key ? is set for nonlocal URIs. |
3347 | - node['candidates'] = candidates = {} |
3348 | - imguri = node['uri'] |
3349 | - if imguri.find('://') != -1: |
3350 | - self.warn_node('nonlocal image URI found: %s' % imguri, node) |
3351 | - candidates['?'] = imguri |
3352 | - continue |
3353 | - rel_imgpath, full_imgpath = self.relfn2path(imguri, docname) |
3354 | - # set imgpath as default URI |
3355 | - node['uri'] = rel_imgpath |
3356 | - if rel_imgpath.endswith(os.extsep + '*'): |
3357 | - for filename in glob(full_imgpath): |
3358 | - new_imgpath = relative_path(self.srcdir, filename) |
3359 | - if filename.lower().endswith('.pdf'): |
3360 | - candidates['application/pdf'] = new_imgpath |
3361 | - elif filename.lower().endswith('.svg'): |
3362 | - candidates['image/svg+xml'] = new_imgpath |
3363 | - else: |
3364 | - try: |
3365 | - f = open(filename, 'rb') |
3366 | - try: |
3367 | - imgtype = imghdr.what(f) |
3368 | - finally: |
3369 | - f.close() |
3370 | - except (OSError, IOError), err: |
3371 | - self.warn_node('image file %s not readable: %s' % |
3372 | - (filename, err), node) |
3373 | - if imgtype: |
3374 | - candidates['image/' + imgtype] = new_imgpath |
3375 | - else: |
3376 | - candidates['*'] = rel_imgpath |
3377 | - # map image paths to unique image names (so that they can be put |
3378 | - # into a single directory) |
3379 | - for imgpath in candidates.itervalues(): |
3380 | - self.dependencies.setdefault(docname, set()).add(imgpath) |
3381 | - if not os.access(path.join(self.srcdir, imgpath), os.R_OK): |
3382 | - self.warn_node('image file not readable: %s' % imgpath, |
3383 | - node) |
3384 | - continue |
3385 | - self.images.add_file(docname, imgpath) |
3386 | - |
3387 | - def process_metadata(self, docname, doctree): |
3388 | - """Process the docinfo part of the doctree as metadata. |
3389 | - |
3390 | - Keep processing minimal -- just return what docutils says. |
3391 | - """ |
3392 | - self.metadata[docname] = md = {} |
3393 | - try: |
3394 | - docinfo = doctree[0] |
3395 | - except IndexError: |
3396 | - # probably an empty document |
3397 | - return |
3398 | - if docinfo.__class__ is not nodes.docinfo: |
3399 | - # nothing to see here |
3400 | - return |
3401 | - for node in docinfo: |
3402 | - # nodes are multiply inherited... |
3403 | - if isinstance(node, nodes.authors): |
3404 | - md['authors'] = [author.astext() for author in node] |
3405 | - elif isinstance(node, nodes.TextElement): # e.g. author |
3406 | - md[node.__class__.__name__] = node.astext() |
3407 | - else: |
3408 | - name, body = node |
3409 | - md[name.astext()] = body.astext() |
3410 | - del doctree[0] |
3411 | - |
3412 | - def process_refonly_bullet_lists(self, docname, doctree): |
3413 | - """Change refonly bullet lists to use compact_paragraphs. |
3414 | - |
3415 | - Specifically implemented for 'Indices and Tables' section, which looks |
3416 | - odd when html_compact_lists is false. |
3417 | - """ |
3418 | - if self.config.html_compact_lists: |
3419 | - return |
3420 | - |
3421 | - class RefOnlyListChecker(nodes.GenericNodeVisitor): |
3422 | - """Raise `nodes.NodeFound` if non-simple list item is encountered. |
3423 | - |
3424 | - Here 'simple' means a list item containing only a paragraph with a |
3425 | - single reference in it. |
3426 | - """ |
3427 | - |
3428 | - def default_visit(self, node): |
3429 | - raise nodes.NodeFound |
3430 | - |
3431 | - def visit_bullet_list(self, node): |
3432 | - pass |
3433 | - |
3434 | - def visit_list_item(self, node): |
3435 | - children = [] |
3436 | - for child in node.children: |
3437 | - if not isinstance(child, nodes.Invisible): |
3438 | - children.append(child) |
3439 | - if len(children) != 1: |
3440 | - raise nodes.NodeFound |
3441 | - if not isinstance(children[0], nodes.paragraph): |
3442 | - raise nodes.NodeFound |
3443 | - para = children[0] |
3444 | - if len(para) != 1: |
3445 | - raise nodes.NodeFound |
3446 | - if not isinstance(para[0], addnodes.pending_xref): |
3447 | - raise nodes.NodeFound |
3448 | - raise nodes.SkipChildren |
3449 | - |
3450 | - def invisible_visit(self, node): |
3451 | - """Invisible nodes should be ignored.""" |
3452 | - pass |
3453 | - |
3454 | - def check_refonly_list(node): |
3455 | - """Check for list with only references in it.""" |
3456 | - visitor = RefOnlyListChecker(doctree) |
3457 | - try: |
3458 | - node.walk(visitor) |
3459 | - except nodes.NodeFound: |
3460 | - return False |
3461 | - else: |
3462 | - return True |
3463 | - |
3464 | - for node in doctree.traverse(nodes.bullet_list): |
3465 | - if check_refonly_list(node): |
3466 | - for item in node.traverse(nodes.list_item): |
3467 | - para = item[0] |
3468 | - ref = para[0] |
3469 | - compact_para = addnodes.compact_paragraph() |
3470 | - compact_para += ref |
3471 | - item.replace(para, compact_para) |
3472 | - |
3473 | - def create_title_from(self, docname, document): |
3474 | - """Add a title node to the document (just copy the first section title), |
3475 | - and store that title in the environment. |
3476 | - """ |
3477 | - titlenode = nodes.title() |
3478 | - longtitlenode = titlenode |
3479 | - # explicit title set with title directive; use this only for |
3480 | - # the <title> tag in HTML output |
3481 | - if document.has_key('title'): |
3482 | - longtitlenode = nodes.title() |
3483 | - longtitlenode += nodes.Text(document['title']) |
3484 | - # look for first section title and use that as the title |
3485 | - for node in document.traverse(nodes.section): |
3486 | - visitor = SphinxContentsFilter(document) |
3487 | - node[0].walkabout(visitor) |
3488 | - titlenode += visitor.get_entry_text() |
3489 | - break |
3490 | - else: |
3491 | - # document has no title |
3492 | - titlenode += nodes.Text('<no title>') |
3493 | - self.titles[docname] = titlenode |
3494 | - self.longtitles[docname] = longtitlenode |
3495 | - |
3496 | - def note_indexentries_from(self, docname, document): |
3497 | - entries = self.indexentries[docname] = [] |
3498 | - for node in document.traverse(addnodes.index): |
3499 | - entries.extend(node['entries']) |
3500 | - |
3501 | - def note_citations_from(self, docname, document): |
3502 | - for node in document.traverse(nodes.citation): |
3503 | - label = node[0].astext() |
3504 | - if label in self.citations: |
3505 | - self.warn_node('duplicate citation %s, ' % label + |
3506 | - 'other instance in %s' % self.doc2path( |
3507 | - self.citations[label][0]), node) |
3508 | - self.citations[label] = (docname, node['ids'][0]) |
3509 | - |
3510 | - def note_toctree(self, docname, toctreenode): |
3511 | - """Note a TOC tree directive in a document and gather information about |
3512 | - file relations from it. |
3513 | - """ |
3514 | - if toctreenode['glob']: |
3515 | - self.glob_toctrees.add(docname) |
3516 | - if toctreenode.get('numbered'): |
3517 | - self.numbered_toctrees.add(docname) |
3518 | - includefiles = toctreenode['includefiles'] |
3519 | - for includefile in includefiles: |
3520 | - # note that if the included file is rebuilt, this one must be |
3521 | - # too (since the TOC of the included file could have changed) |
3522 | - self.files_to_rebuild.setdefault(includefile, set()).add(docname) |
3523 | - self.toctree_includes.setdefault(docname, []).extend(includefiles) |
3524 | - |
3525 | - def build_toc_from(self, docname, document): |
3526 | - """Build a TOC from the doctree and store it in the inventory.""" |
3527 | - numentries = [0] # nonlocal again... |
3528 | - |
3529 | - try: |
3530 | - maxdepth = int(self.metadata[docname].get('tocdepth', 0)) |
3531 | - except ValueError: |
3532 | - maxdepth = 0 |
3533 | - |
3534 | - def traverse_in_section(node, cls): |
3535 | - """Like traverse(), but stay within the same section.""" |
3536 | - result = [] |
3537 | - if isinstance(node, cls): |
3538 | - result.append(node) |
3539 | - for child in node.children: |
3540 | - if isinstance(child, nodes.section): |
3541 | - continue |
3542 | - result.extend(traverse_in_section(child, cls)) |
3543 | - return result |
3544 | - |
3545 | - def build_toc(node, depth=1): |
3546 | - entries = [] |
3547 | - for sectionnode in node: |
3548 | - # find all toctree nodes in this section and add them |
3549 | - # to the toc (just copying the toctree node which is then |
3550 | - # resolved in self.get_and_resolve_doctree) |
3551 | - if isinstance(sectionnode, addnodes.only): |
3552 | - onlynode = addnodes.only(expr=sectionnode['expr']) |
3553 | - blist = build_toc(sectionnode, depth) |
3554 | - if blist: |
3555 | - onlynode += blist.children |
3556 | - entries.append(onlynode) |
3557 | - if not isinstance(sectionnode, nodes.section): |
3558 | - for toctreenode in traverse_in_section(sectionnode, |
3559 | - addnodes.toctree): |
3560 | - item = toctreenode.copy() |
3561 | - entries.append(item) |
3562 | - # important: do the inventory stuff |
3563 | - self.note_toctree(docname, toctreenode) |
3564 | - continue |
3565 | - title = sectionnode[0] |
3566 | - # copy the contents of the section title, but without references |
3567 | - # and unnecessary stuff |
3568 | - visitor = SphinxContentsFilter(document) |
3569 | - title.walkabout(visitor) |
3570 | - nodetext = visitor.get_entry_text() |
3571 | - if not numentries[0]: |
3572 | - # for the very first toc entry, don't add an anchor |
3573 | - # as it is the file's title anyway |
3574 | - anchorname = '' |
3575 | - else: |
3576 | - anchorname = '#' + sectionnode['ids'][0] |
3577 | - numentries[0] += 1 |
3578 | - # make these nodes: |
3579 | - # list_item -> compact_paragraph -> reference |
3580 | - reference = nodes.reference( |
3581 | - '', '', internal=True, refuri=docname, |
3582 | - anchorname=anchorname, *nodetext) |
3583 | - para = addnodes.compact_paragraph('', '', reference) |
3584 | - item = nodes.list_item('', para) |
3585 | - if maxdepth == 0 or depth < maxdepth: |
3586 | - item += build_toc(sectionnode, depth+1) |
3587 | - entries.append(item) |
3588 | - if entries: |
3589 | - return nodes.bullet_list('', *entries) |
3590 | - return [] |
3591 | - toc = build_toc(document) |
3592 | - if toc: |
3593 | - self.tocs[docname] = toc |
3594 | - else: |
3595 | - self.tocs[docname] = nodes.bullet_list('') |
3596 | - self.toc_num_entries[docname] = numentries[0] |
3597 | - |
3598 | - def get_toc_for(self, docname, builder): |
3599 | - """Return a TOC nodetree -- for use on the same page only!""" |
3600 | - try: |
3601 | - toc = self.tocs[docname].deepcopy() |
3602 | - except KeyError: |
3603 | - # the document does not exist anymore: return a dummy node that |
3604 | - # renders to nothing |
3605 | - return nodes.paragraph() |
3606 | - self.process_only_nodes(toc, builder, docname) |
3607 | - for node in toc.traverse(nodes.reference): |
3608 | - node['refuri'] = node['anchorname'] or '#' |
3609 | - return toc |
3610 | - |
3611 | - def get_toctree_for(self, docname, builder, collapse, **kwds): |
3612 | - """Return the global TOC nodetree.""" |
3613 | - doctree = self.get_doctree(self.config.master_doc) |
3614 | - toctrees = [] |
3615 | - if 'includehidden' not in kwds: |
3616 | - kwds['includehidden'] = True |
3617 | - if 'maxdepth' not in kwds: |
3618 | - kwds['maxdepth'] = 0 |
3619 | - kwds['collapse'] = collapse |
3620 | - for toctreenode in doctree.traverse(addnodes.toctree): |
3621 | - toctree = self.resolve_toctree(docname, builder, toctreenode, |
3622 | - prune=True, **kwds) |
3623 | - toctrees.append(toctree) |
3624 | - if not toctrees: |
3625 | - return None |
3626 | - result = toctrees[0] |
3627 | - for toctree in toctrees[1:]: |
3628 | - result.extend(toctree.children) |
3629 | - return result |
3630 | - |
3631 | - def get_domain(self, domainname): |
3632 | - """Return the domain instance with the specified name. |
3633 | - |
3634 | - Raises an ExtensionError if the domain is not registered. |
3635 | - """ |
3636 | - try: |
3637 | - return self.domains[domainname] |
3638 | - except KeyError: |
3639 | - raise ExtensionError('Domain %r is not registered' % domainname) |
3640 | - |
3641 | - # --------- RESOLVING REFERENCES AND TOCTREES ------------------------------ |
3642 | - |
3643 | - def get_doctree(self, docname): |
3644 | - """Read the doctree for a file from the pickle and return it.""" |
3645 | - doctree_filename = self.doc2path(docname, self.doctreedir, '.doctree') |
3646 | - f = open(doctree_filename, 'rb') |
3647 | - try: |
3648 | - doctree = pickle.load(f) |
3649 | - finally: |
3650 | - f.close() |
3651 | - doctree.settings.env = self |
3652 | - doctree.reporter = Reporter(self.doc2path(docname), 2, 5, |
3653 | - stream=WarningStream(self._warnfunc)) |
3654 | - return doctree |
3655 | - |
3656 | - |
3657 | - def get_and_resolve_doctree(self, docname, builder, doctree=None, |
3658 | - prune_toctrees=True): |
3659 | - """Read the doctree from the pickle, resolve cross-references and |
3660 | - toctrees and return it. |
3661 | - """ |
3662 | - if doctree is None: |
3663 | - doctree = self.get_doctree(docname) |
3664 | - |
3665 | - # resolve all pending cross-references |
3666 | - self.resolve_references(doctree, docname, builder) |
3667 | - |
3668 | - # now, resolve all toctree nodes |
3669 | - for toctreenode in doctree.traverse(addnodes.toctree): |
3670 | - result = self.resolve_toctree(docname, builder, toctreenode, |
3671 | - prune=prune_toctrees) |
3672 | - if result is None: |
3673 | - toctreenode.replace_self([]) |
3674 | - else: |
3675 | - toctreenode.replace_self(result) |
3676 | - |
3677 | - return doctree |
3678 | - |
3679 | - def resolve_toctree(self, docname, builder, toctree, prune=True, maxdepth=0, |
3680 | - titles_only=False, collapse=False, includehidden=False): |
3681 | - """Resolve a *toctree* node into individual bullet lists with titles |
3682 | - as items, returning None (if no containing titles are found) or |
3683 | - a new node. |
3684 | - |
3685 | - If *prune* is True, the tree is pruned to *maxdepth*, or if that is 0, |
3686 | - to the value of the *maxdepth* option on the *toctree* node. |
3687 | - If *titles_only* is True, only toplevel document titles will be in the |
3688 | - resulting tree. |
3689 | - If *collapse* is True, all branches not containing docname will |
3690 | - be collapsed. |
3691 | - """ |
3692 | - if toctree.get('hidden', False) and not includehidden: |
3693 | - return None |
3694 | - |
3695 | - def _walk_depth(node, depth, maxdepth): |
3696 | - """Utility: Cut a TOC at a specified depth.""" |
3697 | - |
3698 | - # For reading this function, it is useful to keep in mind the node |
3699 | - # structure of a toctree (using HTML-like node names for brevity): |
3700 | - # |
3701 | - # <ul> |
3702 | - # <li> |
3703 | - # <p><a></p> |
3704 | - # <p><a></p> |
3705 | - # ... |
3706 | - # <ul> |
3707 | - # ... |
3708 | - # </ul> |
3709 | - # </li> |
3710 | - # </ul> |
3711 | - |
3712 | - for subnode in node.children[:]: |
3713 | - if isinstance(subnode, (addnodes.compact_paragraph, |
3714 | - nodes.list_item)): |
3715 | - # for <p> and <li>, just indicate the depth level and |
3716 | - # recurse to children |
3717 | - subnode['classes'].append('toctree-l%d' % (depth-1)) |
3718 | - _walk_depth(subnode, depth, maxdepth) |
3719 | - |
3720 | - elif isinstance(subnode, nodes.bullet_list): |
3721 | - # for <ul>, determine if the depth is too large or if the |
3722 | - # entry is to be collapsed |
3723 | - if maxdepth > 0 and depth > maxdepth: |
3724 | - subnode.parent.replace(subnode, []) |
3725 | - else: |
3726 | - # to find out what to collapse, *first* walk subitems, |
3727 | - # since that determines which children point to the |
3728 | - # current page |
3729 | - _walk_depth(subnode, depth+1, maxdepth) |
3730 | - # cull sub-entries whose parents aren't 'current' |
3731 | - if (collapse and depth > 1 and |
3732 | - 'iscurrent' not in subnode.parent): |
3733 | - subnode.parent.remove(subnode) |
3734 | - |
3735 | - elif isinstance(subnode, nodes.reference): |
3736 | - # for <a>, identify which entries point to the current |
3737 | - # document and therefore may not be collapsed |
3738 | - if subnode['refuri'] == docname: |
3739 | - if not subnode['anchorname']: |
3740 | - # give the whole branch a 'current' class |
3741 | - # (useful for styling it differently) |
3742 | - branchnode = subnode |
3743 | - while branchnode: |
3744 | - branchnode['classes'].append('current') |
3745 | - branchnode = branchnode.parent |
3746 | - # mark the list_item as "on current page" |
3747 | - if subnode.parent.parent.get('iscurrent'): |
3748 | - # but only if it's not already done |
3749 | - return |
3750 | - while subnode: |
3751 | - subnode['iscurrent'] = True |
3752 | - subnode = subnode.parent |
3753 | - |
3754 | - def _entries_from_toctree(toctreenode, parents, |
3755 | - separate=False, subtree=False): |
3756 | - """Return TOC entries for a toctree node.""" |
3757 | - refs = [(e[0], str(e[1])) for e in toctreenode['entries']] |
3758 | - entries = [] |
3759 | - for (title, ref) in refs: |
3760 | - try: |
3761 | - refdoc = None |
3762 | - if url_re.match(ref): |
3763 | - reference = nodes.reference('', '', internal=False, |
3764 | - refuri=ref, anchorname='', |
3765 | - *[nodes.Text(title)]) |
3766 | - para = addnodes.compact_paragraph('', '', reference) |
3767 | - item = nodes.list_item('', para) |
3768 | - toc = nodes.bullet_list('', item) |
3769 | - elif ref == 'self': |
3770 | - # 'self' refers to the document from which this |
3771 | - # toctree originates |
3772 | - ref = toctreenode['parent'] |
3773 | - if not title: |
3774 | - title = clean_astext(self.titles[ref]) |
3775 | - reference = nodes.reference('', '', internal=True, |
3776 | - refuri=ref, |
3777 | - anchorname='', |
3778 | - *[nodes.Text(title)]) |
3779 | - para = addnodes.compact_paragraph('', '', reference) |
3780 | - item = nodes.list_item('', para) |
3781 | - # don't show subitems |
3782 | - toc = nodes.bullet_list('', item) |
3783 | - else: |
3784 | - if ref in parents: |
3785 | - self.warn(ref, 'circular toctree references ' |
3786 | - 'detected, ignoring: %s <- %s' % |
3787 | - (ref, ' <- '.join(parents))) |
3788 | - continue |
3789 | - refdoc = ref |
3790 | - toc = self.tocs[ref].deepcopy() |
3791 | - self.process_only_nodes(toc, builder, ref) |
3792 | - if title and toc.children and len(toc.children) == 1: |
3793 | - child = toc.children[0] |
3794 | - for refnode in child.traverse(nodes.reference): |
3795 | - if refnode['refuri'] == ref and \ |
3796 | - not refnode['anchorname']: |
3797 | - refnode.children = [nodes.Text(title)] |
3798 | - if not toc.children: |
3799 | - # empty toc means: no titles will show up in the toctree |
3800 | - self.warn_node( |
3801 | - 'toctree contains reference to document %r that ' |
3802 | - 'doesn\'t have a title: no link will be generated' |
3803 | - % ref, toctreenode) |
3804 | - except KeyError: |
3805 | - # this is raised if the included file does not exist |
3806 | - self.warn_node( |
3807 | - 'toctree contains reference to nonexisting document %r' |
3808 | - % ref, toctreenode) |
3809 | - else: |
3810 | - # if titles_only is given, only keep the main title and |
3811 | - # sub-toctrees |
3812 | - if titles_only: |
3813 | - # delete everything but the toplevel title(s) |
3814 | - # and toctrees |
3815 | - for toplevel in toc: |
3816 | - # nodes with length 1 don't have any children anyway |
3817 | - if len(toplevel) > 1: |
3818 | - subtrees = toplevel.traverse(addnodes.toctree) |
3819 | - toplevel[1][:] = subtrees |
3820 | - # resolve all sub-toctrees |
3821 | - for toctreenode in toc.traverse(addnodes.toctree): |
3822 | - if not (toctreenode.get('hidden', False) |
3823 | - and not includehidden): |
3824 | - i = toctreenode.parent.index(toctreenode) + 1 |
3825 | - for item in _entries_from_toctree( |
3826 | - toctreenode, [refdoc] + parents, |
3827 | - subtree=True): |
3828 | - toctreenode.parent.insert(i, item) |
3829 | - i += 1 |
3830 | - toctreenode.parent.remove(toctreenode) |
3831 | - if separate: |
3832 | - entries.append(toc) |
3833 | - else: |
3834 | - entries.extend(toc.children) |
3835 | - if not subtree and not separate: |
3836 | - ret = nodes.bullet_list() |
3837 | - ret += entries |
3838 | - return [ret] |
3839 | - return entries |
3840 | - |
3841 | - maxdepth = maxdepth or toctree.get('maxdepth', -1) |
3842 | - if not titles_only and toctree.get('titlesonly', False): |
3843 | - titles_only = True |
3844 | - |
3845 | - # NOTE: previously, this was separate=True, but that leads to artificial |
3846 | - # separation when two or more toctree entries form a logical unit, so |
3847 | - # separating mode is no longer used -- it's kept here for history's sake |
3848 | - tocentries = _entries_from_toctree(toctree, [], separate=False) |
3849 | - if not tocentries: |
3850 | - return None |
3851 | - |
3852 | - newnode = addnodes.compact_paragraph('', '', *tocentries) |
3853 | - newnode['toctree'] = True |
3854 | - |
3855 | - # prune the tree to maxdepth and replace titles, also set level classes |
3856 | - _walk_depth(newnode, 1, prune and maxdepth or 0) |
3857 | - |
3858 | - # set the target paths in the toctrees (they are not known at TOC |
3859 | - # generation time) |
3860 | - for refnode in newnode.traverse(nodes.reference): |
3861 | - if not url_re.match(refnode['refuri']): |
3862 | - refnode['refuri'] = builder.get_relative_uri( |
3863 | - docname, refnode['refuri']) + refnode['anchorname'] |
3864 | - return newnode |
3865 | - |
3866 | - def resolve_references(self, doctree, fromdocname, builder): |
3867 | - for node in doctree.traverse(addnodes.pending_xref): |
3868 | - contnode = node[0].deepcopy() |
3869 | - newnode = None |
3870 | - |
3871 | - typ = node['reftype'] |
3872 | - target = node['reftarget'] |
3873 | - refdoc = node.get('refdoc', fromdocname) |
3874 | - domain = None |
3875 | - |
3876 | - try: |
3877 | - if 'refdomain' in node and node['refdomain']: |
3878 | - # let the domain try to resolve the reference |
3879 | - try: |
3880 | - domain = self.domains[node['refdomain']] |
3881 | - except KeyError: |
3882 | - raise NoUri |
3883 | - newnode = domain.resolve_xref(self, fromdocname, builder, |
3884 | - typ, target, node, contnode) |
3885 | - # really hardwired reference types |
3886 | - elif typ == 'doc': |
3887 | - # directly reference to document by source name; |
3888 | - # can be absolute or relative |
3889 | - docname = docname_join(refdoc, target) |
3890 | - if docname in self.all_docs: |
3891 | - if node['refexplicit']: |
3892 | - # reference with explicit title |
3893 | - caption = node.astext() |
3894 | - else: |
3895 | - caption = clean_astext(self.titles[docname]) |
3896 | - innernode = nodes.emphasis(caption, caption) |
3897 | - newnode = nodes.reference('', '', internal=True) |
3898 | - newnode['refuri'] = builder.get_relative_uri( |
3899 | - fromdocname, docname) |
3900 | - newnode.append(innernode) |
3901 | - elif typ == 'citation': |
3902 | - docname, labelid = self.citations.get(target, ('', '')) |
3903 | - if docname: |
3904 | - newnode = make_refnode(builder, fromdocname, docname, |
3905 | - labelid, contnode) |
3906 | - # no new node found? try the missing-reference event |
3907 | - if newnode is None: |
3908 | - newnode = builder.app.emit_firstresult( |
3909 | - 'missing-reference', self, node, contnode) |
3910 | - # still not found? warn if in nit-picky mode |
3911 | - if newnode is None: |
3912 | - self._warn_missing_reference( |
3913 | - fromdocname, typ, target, node, domain) |
3914 | - except NoUri: |
3915 | - newnode = contnode |
3916 | - node.replace_self(newnode or contnode) |
3917 | - |
3918 | - # remove only-nodes that do not belong to our builder |
3919 | - self.process_only_nodes(doctree, builder, fromdocname) |
3920 | - |
3921 | - # allow custom references to be resolved |
3922 | - builder.app.emit('doctree-resolved', doctree, fromdocname) |
3923 | - |
3924 | - def _warn_missing_reference(self, fromdoc, typ, target, node, domain): |
3925 | - warn = node.get('refwarn') |
3926 | - if self.config.nitpicky: |
3927 | - warn = True |
3928 | - if self._nitpick_ignore: |
3929 | - dtype = domain and '%s:%s' % (domain.name, typ) or typ |
3930 | - if (dtype, target) in self._nitpick_ignore: |
3931 | - warn = False |
3932 | - if not warn: |
3933 | - return |
3934 | - if domain and typ in domain.dangling_warnings: |
3935 | - msg = domain.dangling_warnings[typ] |
3936 | - elif typ == 'doc': |
3937 | - msg = 'unknown document: %(target)s' |
3938 | - elif typ == 'citation': |
3939 | - msg = 'citation not found: %(target)s' |
3940 | - elif node.get('refdomain', 'std') != 'std': |
3941 | - msg = '%s:%s reference target not found: %%(target)s' % \ |
3942 | - (node['refdomain'], typ) |
3943 | - else: |
3944 | - msg = '%s reference target not found: %%(target)s' % typ |
3945 | - self.warn_node(msg % {'target': target}, node) |
3946 | - |
3947 | - def process_only_nodes(self, doctree, builder, fromdocname=None): |
3948 | - # A comment on the comment() nodes being inserted: replacing by [] would |
3949 | - # result in a "Losing ids" exception if there is a target node before |
3950 | - # the only node, so we make sure docutils can transfer the id to |
3951 | - # something, even if it's just a comment and will lose the id anyway... |
3952 | - for node in doctree.traverse(addnodes.only): |
3953 | - try: |
3954 | - ret = builder.tags.eval_condition(node['expr']) |
3955 | - except Exception, err: |
3956 | - self.warn_node('exception while evaluating only ' |
3957 | - 'directive expression: %s' % err, node) |
3958 | - node.replace_self(node.children or nodes.comment()) |
3959 | - else: |
3960 | - if ret: |
3961 | - node.replace_self(node.children or nodes.comment()) |
3962 | - else: |
3963 | - node.replace_self(nodes.comment()) |
3964 | - |
3965 | - def assign_section_numbers(self): |
3966 | - """Assign a section number to each heading under a numbered toctree.""" |
3967 | - # a list of all docnames whose section numbers changed |
3968 | - rewrite_needed = [] |
3969 | - |
3970 | - old_secnumbers = self.toc_secnumbers |
3971 | - self.toc_secnumbers = {} |
3972 | - |
3973 | - def _walk_toc(node, secnums, depth, titlenode=None): |
3974 | - # titlenode is the title of the document, it will get assigned a |
3975 | - # secnumber too, so that it shows up in next/prev/parent rellinks |
3976 | - for subnode in node.children: |
3977 | - if isinstance(subnode, nodes.bullet_list): |
3978 | - numstack.append(0) |
3979 | - _walk_toc(subnode, secnums, depth-1, titlenode) |
3980 | - numstack.pop() |
3981 | - titlenode = None |
3982 | - elif isinstance(subnode, nodes.list_item): |
3983 | - _walk_toc(subnode, secnums, depth, titlenode) |
3984 | - titlenode = None |
3985 | - elif isinstance(subnode, addnodes.only): |
3986 | - # at this stage we don't know yet which sections are going |
3987 | - # to be included; just include all of them, even if it leads |
3988 | - # to gaps in the numbering |
3989 | - _walk_toc(subnode, secnums, depth, titlenode) |
3990 | - titlenode = None |
3991 | - elif isinstance(subnode, addnodes.compact_paragraph): |
3992 | - numstack[-1] += 1 |
3993 | - if depth > 0: |
3994 | - number = tuple(numstack) |
3995 | - else: |
3996 | - number = None |
3997 | - secnums[subnode[0]['anchorname']] = \ |
3998 | - subnode[0]['secnumber'] = number |
3999 | - if titlenode: |
4000 | - titlenode['secnumber'] = number |
4001 | - titlenode = None |
4002 | - elif isinstance(subnode, addnodes.toctree): |
4003 | - _walk_toctree(subnode, depth) |
4004 | - |
4005 | - def _walk_toctree(toctreenode, depth): |
4006 | - if depth == 0: |
4007 | - return |
4008 | - for (title, ref) in toctreenode['entries']: |
4009 | - if url_re.match(ref) or ref == 'self': |
4010 | - # don't mess with those |
4011 | - continue |
4012 | - if ref in self.tocs: |
4013 | - secnums = self.toc_secnumbers[ref] = {} |
4014 | - _walk_toc(self.tocs[ref], secnums, depth, |
4015 | - self.titles.get(ref)) |
4016 | - if secnums != old_secnumbers.get(ref): |
4017 | - rewrite_needed.append(ref) |
4018 | - |
4019 | - for docname in self.numbered_toctrees: |
4020 | - doctree = self.get_doctree(docname) |
4021 | - for toctreenode in doctree.traverse(addnodes.toctree): |
4022 | - depth = toctreenode.get('numbered', 0) |
4023 | - if depth: |
4024 | - # every numbered toctree gets new numbering |
4025 | - numstack = [0] |
4026 | - _walk_toctree(toctreenode, depth) |
4027 | - |
4028 | - return rewrite_needed |
4029 | - |
4030 | - def create_index(self, builder, group_entries=True, |
4031 | - _fixre=re.compile(r'(.*) ([(][^()]*[)])')): |
4032 | - """Create the real index from the collected index entries.""" |
4033 | - new = {} |
4034 | - |
4035 | - def add_entry(word, subword, link=True, dic=new): |
4036 | - entry = dic.get(word) |
4037 | - if not entry: |
4038 | - dic[word] = entry = [[], {}] |
4039 | - if subword: |
4040 | - add_entry(subword, '', link=link, dic=entry[1]) |
4041 | - elif link: |
4042 | - try: |
4043 | - uri = builder.get_relative_uri('genindex', fn) + '#' + tid |
4044 | - except NoUri: |
4045 | - pass |
4046 | - else: |
4047 | - entry[0].append((main, uri)) |
4048 | - |
4049 | - for fn, entries in self.indexentries.iteritems(): |
4050 | - # new entry types must be listed in directives/other.py! |
4051 | - for type, value, tid, main in entries: |
4052 | - try: |
4053 | - if type == 'single': |
4054 | - try: |
4055 | - entry, subentry = split_into(2, 'single', value) |
4056 | - except ValueError: |
4057 | - entry, = split_into(1, 'single', value) |
4058 | - subentry = '' |
4059 | - add_entry(entry, subentry) |
4060 | - elif type == 'pair': |
4061 | - first, second = split_into(2, 'pair', value) |
4062 | - add_entry(first, second) |
4063 | - add_entry(second, first) |
4064 | - elif type == 'triple': |
4065 | - first, second, third = split_into(3, 'triple', value) |
4066 | - add_entry(first, second+' '+third) |
4067 | - add_entry(second, third+', '+first) |
4068 | - add_entry(third, first+' '+second) |
4069 | - elif type == 'see': |
4070 | - first, second = split_into(2, 'see', value) |
4071 | - add_entry(first, _('see %s') % second, link=False) |
4072 | - elif type == 'seealso': |
4073 | - first, second = split_into(2, 'see', value) |
4074 | - add_entry(first, _('see also %s') % second, link=False) |
4075 | - else: |
4076 | - self.warn(fn, 'unknown index entry type %r' % type) |
4077 | - except ValueError, err: |
4078 | - self.warn(fn, str(err)) |
4079 | - |
4080 | - # sort the index entries; put all symbols at the front, even those |
4081 | - # following the letters in ASCII, this is where the chr(127) comes from |
4082 | - def keyfunc(entry, lcletters=string.ascii_lowercase + '_'): |
4083 | - lckey = unicodedata.normalize('NFD', entry[0].lower()) |
4084 | - if lckey[0:1] in lcletters: |
4085 | - return chr(127) + lckey |
4086 | - return lckey |
4087 | - newlist = new.items() |
4088 | - newlist.sort(key=keyfunc) |
4089 | - |
4090 | - if group_entries: |
4091 | - # fixup entries: transform |
4092 | - # func() (in module foo) |
4093 | - # func() (in module bar) |
4094 | - # into |
4095 | - # func() |
4096 | - # (in module foo) |
4097 | - # (in module bar) |
4098 | - oldkey = '' |
4099 | - oldsubitems = None |
4100 | - i = 0 |
4101 | - while i < len(newlist): |
4102 | - key, (targets, subitems) = newlist[i] |
4103 | - # cannot move if it has subitems; structure gets too complex |
4104 | - if not subitems: |
4105 | - m = _fixre.match(key) |
4106 | - if m: |
4107 | - if oldkey == m.group(1): |
4108 | - # prefixes match: add entry as subitem of the |
4109 | - # previous entry |
4110 | - oldsubitems.setdefault(m.group(2), [[], {}])[0].\ |
4111 | - extend(targets) |
4112 | - del newlist[i] |
4113 | - continue |
4114 | - oldkey = m.group(1) |
4115 | - else: |
4116 | - oldkey = key |
4117 | - oldsubitems = subitems |
4118 | - i += 1 |
4119 | - |
4120 | - # group the entries by letter |
4121 | - def keyfunc2(item, letters=string.ascii_uppercase + '_'): |
4122 | - # hack: mutating the subitems dicts to a list in the keyfunc |
4123 | - k, v = item |
4124 | - v[1] = sorted((si, se) for (si, (se, void)) in v[1].iteritems()) |
4125 | - # now calculate the key |
4126 | - letter = unicodedata.normalize('NFD', k[0])[0].upper() |
4127 | - if letter in letters: |
4128 | - return letter |
4129 | - else: |
4130 | - # get all other symbols under one heading |
4131 | - return 'Symbols' |
4132 | - return [(key, list(group)) |
4133 | - for (key, group) in groupby(newlist, keyfunc2)] |
4134 | - |
4135 | - def collect_relations(self): |
4136 | - relations = {} |
4137 | - getinc = self.toctree_includes.get |
4138 | - def collect(parents, parents_set, docname, previous, next): |
4139 | - # circular relationship? |
4140 | - if docname in parents_set: |
4141 | - # we will warn about this in resolve_toctree() |
4142 | - return |
4143 | - includes = getinc(docname) |
4144 | - # previous |
4145 | - if not previous: |
4146 | - # if no previous sibling, go to parent |
4147 | - previous = parents[0][0] |
4148 | - else: |
4149 | - # else, go to previous sibling, or if it has children, to |
4150 | - # the last of its children, or if that has children, to the |
4151 | - # last of those, and so forth |
4152 | - while 1: |
4153 | - previncs = getinc(previous) |
4154 | - if previncs: |
4155 | - previous = previncs[-1] |
4156 | - else: |
4157 | - break |
4158 | - # next |
4159 | - if includes: |
4160 | - # if it has children, go to first of them |
4161 | - next = includes[0] |
4162 | - elif next: |
4163 | - # else, if next sibling, go to it |
4164 | - pass |
4165 | - else: |
4166 | - # else, go to the next sibling of the parent, if present, |
4167 | - # else the grandparent's sibling, if present, and so forth |
4168 | - for parname, parindex in parents: |
4169 | - parincs = getinc(parname) |
4170 | - if parincs and parindex + 1 < len(parincs): |
4171 | - next = parincs[parindex+1] |
4172 | - break |
4173 | - # else it will stay None |
4174 | - # same for children |
4175 | - if includes: |
4176 | - for subindex, args in enumerate(izip(includes, |
4177 | - [None] + includes, |
4178 | - includes[1:] + [None])): |
4179 | - collect([(docname, subindex)] + parents, |
4180 | - parents_set.union([docname]), *args) |
4181 | - relations[docname] = [parents[0][0], previous, next] |
4182 | - collect([(None, 0)], set(), self.config.master_doc, None, None) |
4183 | - return relations |
4184 | - |
4185 | - def check_consistency(self): |
4186 | - """Do consistency checks.""" |
4187 | - for docname in sorted(self.all_docs): |
4188 | - if docname not in self.files_to_rebuild: |
4189 | - if docname == self.config.master_doc: |
4190 | - # the master file is not included anywhere ;) |
4191 | - continue |
4192 | - if 'orphan' in self.metadata[docname]: |
4193 | - continue |
4194 | - self.warn(docname, 'document isn\'t included in any toctree') |
4195 | - |
4196 | |
4197 | === added directory '.pc/test_build_html_rb.diff' |
4198 | === added directory '.pc/test_build_html_rb.diff/tests' |
4199 | === added file '.pc/test_build_html_rb.diff/tests/test_build_html.py' |
4200 | --- .pc/test_build_html_rb.diff/tests/test_build_html.py 1970-01-01 00:00:00 +0000 |
4201 | +++ .pc/test_build_html_rb.diff/tests/test_build_html.py 2012-11-28 07:12:20 +0000 |
4202 | @@ -0,0 +1,339 @@ |
4203 | +# -*- coding: utf-8 -*- |
4204 | +""" |
4205 | + test_build_html |
4206 | + ~~~~~~~~~~~~~~~ |
4207 | + |
4208 | + Test the HTML builder and check output against XPath. |
4209 | + |
4210 | + :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. |
4211 | + :license: BSD, see LICENSE for details. |
4212 | +""" |
4213 | + |
4214 | +import os |
4215 | +import re |
4216 | +import sys |
4217 | +import htmlentitydefs |
4218 | +from StringIO import StringIO |
4219 | + |
4220 | +try: |
4221 | + import pygments |
4222 | +except ImportError: |
4223 | + pygments = None |
4224 | + |
4225 | +from sphinx import __version__ |
4226 | +from util import * |
4227 | +from etree13 import ElementTree as ET |
4228 | + |
4229 | + |
4230 | +def teardown_module(): |
4231 | + (test_root / '_build').rmtree(True) |
4232 | + |
4233 | + |
4234 | +html_warnfile = StringIO() |
4235 | + |
4236 | +ENV_WARNINGS = """\ |
4237 | +%(root)s/autodoc_fodder.py:docstring of autodoc_fodder\\.MarkupError:2: \ |
4238 | +WARNING: Explicit markup ends without a blank line; unexpected \ |
4239 | +unindent\\.\\n? |
4240 | +%(root)s/images.txt:9: WARNING: image file not readable: foo.png |
4241 | +%(root)s/images.txt:23: WARNING: nonlocal image URI found: \ |
4242 | +http://www.python.org/logo.png |
4243 | +%(root)s/includes.txt:\\d*: WARNING: Encoding 'utf-8-sig' used for \ |
4244 | +reading included file u'.*?wrongenc.inc' seems to be wrong, try giving an \ |
4245 | +:encoding: option\\n? |
4246 | +%(root)s/includes.txt:4: WARNING: download file not readable: .*?nonexisting.png |
4247 | +%(root)s/objects.txt:\\d*: WARNING: using old C markup; please migrate to \ |
4248 | +new-style markup \(e.g. c:function instead of cfunction\), see \ |
4249 | +http://sphinx.pocoo.org/domains.html |
4250 | +""" |
4251 | + |
4252 | +HTML_WARNINGS = ENV_WARNINGS + """\ |
4253 | +%(root)s/images.txt:20: WARNING: no matching candidate for image URI u'foo.\\*' |
4254 | +%(root)s/markup.txt:: WARNING: invalid single index entry u'' |
4255 | +%(root)s/markup.txt:: WARNING: invalid pair index entry u'' |
4256 | +%(root)s/markup.txt:: WARNING: invalid pair index entry u'keyword; ' |
4257 | +""" |
4258 | + |
4259 | +if sys.version_info >= (3, 0): |
4260 | + ENV_WARNINGS = remove_unicode_literals(ENV_WARNINGS) |
4261 | + HTML_WARNINGS = remove_unicode_literals(HTML_WARNINGS) |
4262 | + |
4263 | + |
4264 | +def tail_check(check): |
4265 | + rex = re.compile(check) |
4266 | + def checker(nodes): |
4267 | + for node in nodes: |
4268 | + if node.tail and rex.search(node.tail): |
4269 | + return True |
4270 | + assert False, '%r not found in tail of any nodes %s' % (check, nodes) |
4271 | + return checker |
4272 | + |
4273 | + |
4274 | +HTML_XPATH = { |
4275 | + 'images.html': [ |
4276 | + (".//img[@src='_images/img.png']", ''), |
4277 | + (".//img[@src='_images/img1.png']", ''), |
4278 | + (".//img[@src='_images/simg.png']", ''), |
4279 | + (".//img[@src='_images/svgimg.svg']", ''), |
4280 | + ], |
4281 | + 'subdir/images.html': [ |
4282 | + (".//img[@src='../_images/img1.png']", ''), |
4283 | + (".//img[@src='../_images/rimg.png']", ''), |
4284 | + ], |
4285 | + 'subdir/includes.html': [ |
4286 | + (".//a[@href='../_downloads/img.png']", ''), |
4287 | + (".//img[@src='../_images/img.png']", ''), |
4288 | + (".//p", 'This is an include file.'), |
4289 | + ], |
4290 | + 'includes.html': [ |
4291 | + (".//pre", u'Max Strauß'), |
4292 | + (".//a[@href='_downloads/img.png']", ''), |
4293 | + (".//a[@href='_downloads/img1.png']", ''), |
4294 | + (".//pre", u'"quotes"'), |
4295 | + (".//pre", u"'included'"), |
4296 | + ], |
4297 | + 'autodoc.html': [ |
4298 | + (".//dt[@id='test_autodoc.Class']", ''), |
4299 | + (".//dt[@id='test_autodoc.function']/em", r'\*\*kwds'), |
4300 | + (".//dd/p", r'Return spam\.'), |
4301 | + ], |
4302 | + 'extapi.html': [ |
4303 | + (".//strong", 'from function: Foo'), |
4304 | + (".//strong", 'from class: Bar'), |
4305 | + ], |
4306 | + 'markup.html': [ |
4307 | + (".//title", 'set by title directive'), |
4308 | + (".//p/em", 'Section author: Georg Brandl'), |
4309 | + (".//p/em", 'Module author: Georg Brandl'), |
4310 | + # created by the meta directive |
4311 | + (".//meta[@name='author'][@content='Me']", ''), |
4312 | + (".//meta[@name='keywords'][@content='docs, sphinx']", ''), |
4313 | + # a label created by ``.. _label:`` |
4314 | + (".//div[@id='label']", ''), |
4315 | + # code with standard code blocks |
4316 | + (".//pre", '^some code$'), |
4317 | + # an option list |
4318 | + (".//span[@class='option']", '--help'), |
4319 | + # admonitions |
4320 | + (".//p[@class='first admonition-title']", 'My Admonition'), |
4321 | + (".//p[@class='last']", 'Note text.'), |
4322 | + (".//p[@class='last']", 'Warning text.'), |
4323 | + # inline markup |
4324 | + (".//li/strong", r'^command\\n$'), |
4325 | + (".//li/strong", r'^program\\n$'), |
4326 | + (".//li/em", r'^dfn\\n$'), |
4327 | + (".//li/tt/span[@class='pre']", r'^kbd\\n$'), |
4328 | + (".//li/em", u'File \N{TRIANGULAR BULLET} Close'), |
4329 | + (".//li/tt/span[@class='pre']", '^a/$'), |
4330 | + (".//li/tt/em/span[@class='pre']", '^varpart$'), |
4331 | + (".//li/tt/em/span[@class='pre']", '^i$'), |
4332 | + (".//a[@href='http://www.python.org/dev/peps/pep-0008']" |
4333 | + "[@class='pep reference external']/strong", 'PEP 8'), |
4334 | + (".//a[@href='http://tools.ietf.org/html/rfc1.html']" |
4335 | + "[@class='rfc reference external']/strong", 'RFC 1'), |
4336 | + (".//a[@href='objects.html#envvar-HOME']" |
4337 | + "[@class='reference internal']/tt/span[@class='pre']", 'HOME'), |
4338 | + (".//a[@href='#with']" |
4339 | + "[@class='reference internal']/tt/span[@class='pre']", '^with$'), |
4340 | + (".//a[@href='#grammar-token-try_stmt']" |
4341 | + "[@class='reference internal']/tt/span", '^statement$'), |
4342 | + (".//a[@href='subdir/includes.html']" |
4343 | + "[@class='reference internal']/em", 'Including in subdir'), |
4344 | + (".//a[@href='objects.html#cmdoption-python-c']" |
4345 | + "[@class='reference internal']/em", 'Python -c option'), |
4346 | + # abbreviations |
4347 | + (".//abbr[@title='abbreviation']", '^abbr$'), |
4348 | + # version stuff |
4349 | + (".//span[@class='versionmodified']", 'New in version 0.6'), |
4350 | + # footnote reference |
4351 | + (".//a[@class='footnote-reference']", r'\[1\]'), |
4352 | + # created by reference lookup |
4353 | + (".//a[@href='contents.html#ref1']", ''), |
4354 | + # ``seealso`` directive |
4355 | + (".//div/p[@class='first admonition-title']", 'See also'), |
4356 | + # a ``hlist`` directive |
4357 | + (".//table[@class='hlist']/tr/td/ul/li", '^This$'), |
4358 | + # a ``centered`` directive |
4359 | + (".//p[@class='centered']/strong", 'LICENSE'), |
4360 | + # a glossary |
4361 | + (".//dl/dt[@id='term-boson']", 'boson'), |
4362 | + # a production list |
4363 | + (".//pre/strong", 'try_stmt'), |
4364 | + (".//pre/a[@href='#grammar-token-try1_stmt']/tt/span", 'try1_stmt'), |
4365 | + # tests for ``only`` directive |
4366 | + (".//p", 'A global substitution.'), |
4367 | + (".//p", 'In HTML.'), |
4368 | + (".//p", 'In both.'), |
4369 | + (".//p", 'Always present'), |
4370 | + ], |
4371 | + 'objects.html': [ |
4372 | + (".//dt[@id='mod.Cls.meth1']", ''), |
4373 | + (".//dt[@id='errmod.Error']", ''), |
4374 | + (".//dt/tt", r'long\(parameter,\s* list\)'), |
4375 | + (".//dt/tt", 'another one'), |
4376 | + (".//a[@href='#mod.Cls'][@class='reference internal']", ''), |
4377 | + (".//dl[@class='userdesc']", ''), |
4378 | + (".//dt[@id='userdesc-myobj']", ''), |
4379 | + (".//a[@href='#userdesc-myobj'][@class='reference internal']", ''), |
4380 | + # C references |
4381 | + (".//span[@class='pre']", 'CFunction()'), |
4382 | + (".//a[@href='#Sphinx_DoSomething']", ''), |
4383 | + (".//a[@href='#SphinxStruct.member']", ''), |
4384 | + (".//a[@href='#SPHINX_USE_PYTHON']", ''), |
4385 | + (".//a[@href='#SphinxType']", ''), |
4386 | + (".//a[@href='#sphinx_global']", ''), |
4387 | + # reference from old C markup extension |
4388 | + (".//a[@href='#Sphinx_Func']", ''), |
4389 | + # test global TOC created by toctree() |
4390 | + (".//ul[@class='current']/li[@class='toctree-l1 current']/a[@href='']", |
4391 | + 'Testing object descriptions'), |
4392 | + (".//li[@class='toctree-l1']/a[@href='markup.html']", |
4393 | + 'Testing various markup'), |
4394 | + # custom sidebar |
4395 | + (".//h4", 'Custom sidebar'), |
4396 | + # docfields |
4397 | + (".//td[@class='field-body']/strong", '^moo$'), |
4398 | + (".//td[@class='field-body']/strong", |
4399 | + tail_check(r'\(Moo\) .* Moo')), |
4400 | + (".//td[@class='field-body']/ul/li/strong", '^hour$'), |
4401 | + (".//td[@class='field-body']/ul/li/em", '^DuplicateType$'), |
4402 | + (".//td[@class='field-body']/ul/li/em", |
4403 | + tail_check(r'.* Some parameter')), |
4404 | + ], |
4405 | + 'contents.html': [ |
4406 | + (".//meta[@name='hc'][@content='hcval']", ''), |
4407 | + (".//meta[@name='hc_co'][@content='hcval_co']", ''), |
4408 | + (".//meta[@name='testopt'][@content='testoverride']", ''), |
4409 | + (".//td[@class='label']", r'\[Ref1\]'), |
4410 | + (".//td[@class='label']", ''), |
4411 | + (".//li[@class='toctree-l1']/a", 'Testing various markup'), |
4412 | + (".//li[@class='toctree-l2']/a", 'Inline markup'), |
4413 | + (".//title", 'Sphinx <Tests>'), |
4414 | + (".//div[@class='footer']", 'Georg Brandl & Team'), |
4415 | + (".//a[@href='http://python.org/']" |
4416 | + "[@class='reference external']", ''), |
4417 | + (".//li/a[@href='genindex.html']/em", 'Index'), |
4418 | + (".//li/a[@href='py-modindex.html']/em", 'Module Index'), |
4419 | + (".//li/a[@href='search.html']/em", 'Search Page'), |
4420 | + # custom sidebar only for contents |
4421 | + (".//h4", 'Contents sidebar'), |
4422 | + # custom JavaScript |
4423 | + (".//script[@src='file://moo.js']", ''), |
4424 | + ], |
4425 | + 'bom.html': [ |
4426 | + (".//title", " File with UTF-8 BOM"), |
4427 | + ], |
4428 | + 'extensions.html': [ |
4429 | + (".//a[@href='http://python.org/dev/']", "http://python.org/dev/"), |
4430 | + (".//a[@href='http://bugs.python.org/issue1000']", "issue 1000"), |
4431 | + (".//a[@href='http://bugs.python.org/issue1042']", "explicit caption"), |
4432 | + ], |
4433 | + '_static/statictmpl.html': [ |
4434 | + (".//project", 'Sphinx <Tests>'), |
4435 | + ], |
4436 | + 'genindex.html': [ |
4437 | + # index entries |
4438 | + (".//a/strong", "Main"), |
4439 | + (".//a/strong", "[1]"), |
4440 | + (".//a/strong", "Other"), |
4441 | + (".//a", "entry"), |
4442 | + (".//dt/a", "double"), |
4443 | + ] |
4444 | +} |
4445 | + |
4446 | +if pygments: |
4447 | + HTML_XPATH['includes.html'].extend([ |
4448 | + (".//pre/span[@class='s']", u'üöä'), |
4449 | + (".//div[@class='inc-pyobj1 highlight-text']//pre", |
4450 | + r'^class Foo:\n pass\n\s*$'), |
4451 | + (".//div[@class='inc-pyobj2 highlight-text']//pre", |
4452 | + r'^ def baz\(\):\n pass\n\s*$'), |
4453 | + (".//div[@class='inc-lines highlight-text']//pre", |
4454 | + r'^class Foo:\n pass\nclass Bar:\n$'), |
4455 | + (".//div[@class='inc-startend highlight-text']//pre", |
4456 | + ur'^foo = "Including Unicode characters: üöä"\n$'), |
4457 | + (".//div[@class='inc-preappend highlight-text']//pre", |
4458 | + r'(?m)^START CODE$'), |
4459 | + (".//div[@class='inc-pyobj-dedent highlight-python']//span", |
4460 | + r'def'), |
4461 | + (".//div[@class='inc-tab3 highlight-text']//pre", |
4462 | + r'-| |-'), |
4463 | + (".//div[@class='inc-tab8 highlight-python']//pre/span", |
4464 | + r'-| |-'), |
4465 | + ]) |
4466 | + HTML_XPATH['subdir/includes.html'].extend([ |
4467 | + (".//pre/span", 'line 1'), |
4468 | + (".//pre/span", 'line 2'), |
4469 | + ]) |
4470 | + |
4471 | +class NslessParser(ET.XMLParser): |
4472 | + """XMLParser that throws away namespaces in tag names.""" |
4473 | + |
4474 | + def _fixname(self, key): |
4475 | + try: |
4476 | + return self._names[key] |
4477 | + except KeyError: |
4478 | + name = key |
4479 | + br = name.find('}') |
4480 | + if br > 0: |
4481 | + name = name[br+1:] |
4482 | + self._names[key] = name = self._fixtext(name) |
4483 | + return name |
4484 | + |
4485 | + |
4486 | +def check_xpath(etree, fname, path, check): |
4487 | + nodes = list(etree.findall(path)) |
4488 | + assert nodes != [], ('did not find any node matching xpath ' |
4489 | + '%r in file %s' % (path, fname)) |
4490 | + if hasattr(check, '__call__'): |
4491 | + check(nodes) |
4492 | + elif not check: |
4493 | + # only check for node presence |
4494 | + pass |
4495 | + else: |
4496 | + rex = re.compile(check) |
4497 | + for node in nodes: |
4498 | + if node.text and rex.search(node.text): |
4499 | + break |
4500 | + else: |
4501 | + assert False, ('%r not found in any node matching ' |
4502 | + 'path %s in %s: %r' % (check, path, fname, |
4503 | + [node.text for node in nodes])) |
4504 | + |
4505 | +def check_static_entries(outdir): |
4506 | + staticdir = outdir / '_static' |
4507 | + assert staticdir.isdir() |
4508 | + # a file from a directory entry in html_static_path |
4509 | + assert (staticdir / 'README').isfile() |
4510 | + # a directory from a directory entry in html_static_path |
4511 | + assert (staticdir / 'subdir' / 'foo.css').isfile() |
4512 | + # a file from a file entry in html_static_path |
4513 | + assert (staticdir / 'templated.css').isfile() |
4514 | + assert (staticdir / 'templated.css').text().splitlines()[1] == __version__ |
4515 | + # a file from _static, but matches exclude_patterns |
4516 | + assert not (staticdir / 'excluded.css').exists() |
4517 | + |
4518 | +@gen_with_app(buildername='html', warning=html_warnfile, cleanenv=True, |
4519 | + confoverrides={'html_context.hckey_co': 'hcval_co'}, |
4520 | + tags=['testtag']) |
4521 | +def test_html(app): |
4522 | + app.builder.build_all() |
4523 | + html_warnings = html_warnfile.getvalue().replace(os.sep, '/') |
4524 | + html_warnings_exp = HTML_WARNINGS % {'root': re.escape(app.srcdir)} |
4525 | + assert re.match(html_warnings_exp + '$', html_warnings), \ |
4526 | + 'Warnings don\'t match:\n' + \ |
4527 | + '--- Expected (regex):\n' + html_warnings_exp + \ |
4528 | + '--- Got:\n' + html_warnings |
4529 | + |
4530 | + for fname, paths in HTML_XPATH.iteritems(): |
4531 | + parser = NslessParser() |
4532 | + parser.entity.update(htmlentitydefs.entitydefs) |
4533 | + fp = open(os.path.join(app.outdir, fname)) |
4534 | + try: |
4535 | + etree = ET.parse(fp, parser) |
4536 | + finally: |
4537 | + fp.close() |
4538 | + for path, check in paths: |
4539 | + yield check_xpath, etree, fname, path, check |
4540 | + |
4541 | + check_static_entries(app.builder.outdir) |
4542 | |
4543 | === modified file 'debian/changelog' |
4544 | --- debian/changelog 2012-11-01 21:39:16 +0000 |
4545 | +++ debian/changelog 2012-11-28 07:12:20 +0000 |
4546 | @@ -1,3 +1,61 @@ |
4547 | +sphinx (1.1.3+dfsg-5ubuntu1) UNRELEASED; urgency=low |
4548 | + |
4549 | + * Merge with Debian packaging SVN. |
4550 | + - This brings in fix for LP: #1068493. |
4551 | + * Remaining Ubuntu changes: |
4552 | + - Switch to dh_python2. |
4553 | + - debian/rules: export NO_PKG_MANGLE=1 in order to not have translations |
4554 | + stripped. |
4555 | + - debian/rules: Modify xvfb-run to use auto-servernum flag (fixes FTBFS). |
4556 | + - debian/control: Drop the dependency on python-whoosh. |
4557 | + - debian/control: Add "XS-Testsuite: autopkgtest" header. |
4558 | + - debian/patches/fix_manpages_generation_with_new_docutils.diff: |
4559 | + Fix FTBFS with the new python-docutils package. |
4560 | + |
4561 | + -- Dmitry Shachnev <mitya57@ubuntu.com> Tue, 27 Nov 2012 19:20:44 +0400 |
4562 | + |
4563 | +sphinx (1.1.3+dfsg-6) UNRELEASED; urgency=low |
4564 | + |
4565 | + [ Jakub Wilk ] |
4566 | + * DEP-8 tests: remove “Features: no-build-needed”; it's the default now. |
4567 | + * Bump standards version to 3.9.4; no changes needed. |
4568 | + |
4569 | + [ Dmitry Shachnev ] |
4570 | + * debian/patches/l10n_fixes.diff: fix crashes and not working external |
4571 | + links in l10n mode (closes: #691719). |
4572 | + |
4573 | + -- Jakub Wilk <jwilk@debian.org> Tue, 13 Nov 2012 22:36:10 +0100 |
4574 | + |
4575 | +sphinx (1.1.3+dfsg-5) experimental; urgency=low |
4576 | + |
4577 | + [ Jakub Wilk ] |
4578 | + * DEP-8 tests: use $ADTTMP. |
4579 | + * dh_sphinxdoc: ignore comments when analysing HTML files (closes: #682850). |
4580 | + Thanks to Dmitry Shachnev for the bug report. |
4581 | + * Add dvipng to Suggests (closes: #687273). Thanks to Matthias Klose for the |
4582 | + bug report. |
4583 | + * Set PYTHONHASHSEED=random in debian/rules and in DEP-8 tests. |
4584 | + * Backport upstream patch to fix encoding issues in test_build_html. Now |
4585 | + that this is fixed, stop running Python 3 tests under LC_ALL=C. |
4586 | + * Make “debian/rules binary-arch” no-op. |
4587 | + * Update version number in the sphinx-autogen manpage. |
4588 | + * Improve dh_sphinxdoc: |
4589 | + + Fix the --tmpdir option. Thanks to Andriy Senkovych for the bug report. |
4590 | + + Ignore references to JavaScript code that start with an URI scheme. |
4591 | + Thanks to Dmitry Shachnev for the bug report. |
4592 | + + Strip query (?...) and fragment (#...) components from JavaScript |
4593 | + references. Thanks to Dmitry Shachnev for the bug report. |
4594 | + * Sort stopwords in searchtools.js. Thanks to Dmitry Shachnev for the bug |
4595 | + report. |
4596 | + * Fix compatibility with Python 3.3. Thanks to Dmitry Shachnev for the bug |
4597 | + report and hunting down the upstream patch. |
4598 | + |
4599 | + [ Dmitry Shachnev ] |
4600 | + * Update Homepage field to point to http://sphinx-doc.org/. |
4601 | + * Build-depend of python3-all instead of python3. |
4602 | + |
4603 | + -- Jakub Wilk <jwilk@debian.org> Thu, 08 Nov 2012 16:28:23 +0100 |
4604 | + |
4605 | sphinx (1.1.3+dfsg-4ubuntu5) raring; urgency=low |
4606 | |
4607 | * Build-depend on python3-all instead of python3. |
4608 | |
4609 | === modified file 'debian/control' |
4610 | --- debian/control 2012-11-01 21:39:16 +0000 |
4611 | +++ debian/control 2012-11-28 07:12:20 +0000 |
4612 | @@ -22,7 +22,7 @@ |
4613 | XS-Python-Version: >= 2.5 |
4614 | X-Python3-Version: >= 3.1 |
4615 | XS-Testsuite: autopkgtest |
4616 | -Standards-Version: 3.9.3 |
4617 | +Standards-Version: 3.9.4 |
4618 | Vcs-Svn: svn://svn.debian.org/python-modules/packages/sphinx/trunk/ |
4619 | Vcs-Browser: http://svn.debian.org/viewsvn/python-modules/packages/sphinx/trunk/ |
4620 | |
4621 | @@ -36,7 +36,7 @@ |
4622 | Recommends: python (>= 2.6) | python-simplejson, python-imaging, |
4623 | sphinx-doc |
4624 | Suggests: |
4625 | - jsmath, libjs-mathjax, |
4626 | + jsmath, libjs-mathjax, dvipng, |
4627 | texlive-latex-recommended, texlive-latex-extra, texlive-fonts-recommended |
4628 | Description: documentation generator for Python projects (implemented in Python 2) |
4629 | Sphinx is a tool for producing documentation for Python projects, using |
4630 | @@ -62,7 +62,7 @@ |
4631 | sphinx-common (= ${source:Version}) |
4632 | Recommends: python3-imaging |
4633 | Suggests: |
4634 | - jsmath, libjs-mathjax, |
4635 | + jsmath, libjs-mathjax, dvipng, |
4636 | texlive-latex-recommended, texlive-latex-extra, texlive-fonts-recommended, |
4637 | sphinx-doc |
4638 | Description: documentation generator for Python projects (implemented in Python 3) |
4639 | |
4640 | === modified file 'debian/dh-sphinxdoc/dh_sphinxdoc' |
4641 | --- debian/dh-sphinxdoc/dh_sphinxdoc 2012-08-21 17:45:58 +0000 |
4642 | +++ debian/dh-sphinxdoc/dh_sphinxdoc 2012-11-28 07:12:20 +0000 |
4643 | @@ -109,7 +109,9 @@ |
4644 | sub load_packaged_js() |
4645 | { |
4646 | my %versions = (); |
4647 | - my $root = tmpdir('libjs-sphinxdoc'); |
4648 | + my $root = 'debian/libjs-sphinxdoc'; # It's tempting to use |
4649 | + # tmpdir('libjs-sphinxdoc') here, but it would break if the user passed |
4650 | + # --tmpdir to the command. |
4651 | $root = '' unless -d $root; |
4652 | my $path = "$root/usr/share/javascript/sphinxdoc"; |
4653 | open(F, '<', "$path/index") or error("cannot open $path/index"); |
4654 | @@ -185,7 +187,7 @@ |
4655 | close F; |
4656 | $search =~ s/<!--.*?-->//g; # strip comments |
4657 | my %js = (); |
4658 | - grep { $js{$_} = 1 unless excludefile("$path/$_"); } $search =~ m{<script type="text/javascript" src="([^"]++)"></script>}g; |
4659 | + grep { s/[?#].*//; $js{$_} = 1 unless m/^[a-z][a-z0-9.+-]*:/i or excludefile("$path/$_"); } $search =~ m{<script type="text/javascript" src="([^"]++)"></script>}g; |
4660 | my $loads_searchindex = $search =~ m/\QjQuery(function() { Search.loadIndex("searchindex.js"); });\E/; |
4661 | my ($has_source) = $search =~ m{HAS_SOURCE:\s*(true|false)}; |
4662 | my ($url_root) = $search =~ m{URL_ROOT:\s*'([^']*)'}; |
4663 | |
4664 | === added file 'debian/patches/l10n_fixes.diff' |
4665 | --- debian/patches/l10n_fixes.diff 1970-01-01 00:00:00 +0000 |
4666 | +++ debian/patches/l10n_fixes.diff 2012-11-28 07:12:20 +0000 |
4667 | @@ -0,0 +1,58 @@ |
4668 | +Description: Fix l10n build of text containing footnotes |
4669 | + Based on initial patch by Cristophe Simonis and modifications by Takayuki Shimizukawa |
4670 | + (upstream pull request #86). |
4671 | +Bug: https://bitbucket.org/birkenfeld/sphinx/issue/955/cant-build-html-with-footnotes-when-using |
4672 | +Bug-Debian: http://bugs.debian.org/691719 |
4673 | +Author: Takayuki Shimizukawa <shimizukawa@gmail.com> |
4674 | +Last-Update: 2012-11-27 |
4675 | + |
4676 | +=== modified file 'sphinx/environment.py' |
4677 | +--- a/sphinx/environment.py 2012-03-12 12:18:37 +0000 |
4678 | ++++ b/sphinx/environment.py 2012-11-27 14:05:36 +0000 |
4679 | +@@ -213,16 +213,44 @@ |
4680 | + parser = RSTParser() |
4681 | + |
4682 | + for node, msg in extract_messages(self.document): |
4683 | +- patch = new_document(source, settings) |
4684 | + msgstr = catalog.gettext(msg) |
4685 | + # XXX add marker to untranslated parts |
4686 | + if not msgstr or msgstr == msg: # as-of-yet untranslated |
4687 | + continue |
4688 | ++ |
4689 | ++ patch = new_document(source, settings) |
4690 | + parser.parse(msgstr, patch) |
4691 | + patch = patch[0] |
4692 | + # XXX doctest and other block markup |
4693 | + if not isinstance(patch, nodes.paragraph): |
4694 | + continue # skip for now |
4695 | ++ |
4696 | ++ footnote_refs = [r for r in node.children |
4697 | ++ if isinstance(r, nodes.footnote_reference) |
4698 | ++ and r.get('auto') == 1] |
4699 | ++ refs = [r for r in node.children if isinstance(r, nodes.reference)] |
4700 | ++ |
4701 | ++ for i, child in enumerate(patch.children): # update leaves |
4702 | ++ if isinstance(child, nodes.footnote_reference) \ |
4703 | ++ and child.get('auto') == 1: |
4704 | ++ # use original 'footnote_reference' object. |
4705 | ++ # this object is already registered in self.document.autofootnote_refs |
4706 | ++ patch.children[i] = footnote_refs.pop(0) |
4707 | ++ # Some duplicated footnote_reference in msgstr causes |
4708 | ++ # IndexError in .pop(0). That is invalid msgstr. |
4709 | ++ |
4710 | ++ elif isinstance(child, nodes.reference): |
4711 | ++ # reference should use original 'refname'. |
4712 | ++ # * reference target ".. _Python: ..." is not translatable. |
4713 | ++ # * section refname is not translatable. |
4714 | ++ # * inline reference "`Python <...>`_" has no 'refname'. |
4715 | ++ if refs and 'refname' in refs[0]: |
4716 | ++ refname = child['refname'] = refs.pop(0)['refname'] |
4717 | ++ self.document.refnames.setdefault( |
4718 | ++ refname, []).append(child) |
4719 | ++ # if number of reference nodes had been changed, that |
4720 | ++ # would often generate unknown link target warning. |
4721 | ++ |
4722 | + for child in patch.children: # update leaves |
4723 | + child.parent = node |
4724 | + node.children = patch.children |
4725 | + |
4726 | |
4727 | === modified file 'debian/patches/series' |
4728 | --- debian/patches/series 2012-11-01 21:39:16 +0000 |
4729 | +++ debian/patches/series 2012-11-28 07:12:20 +0000 |
4730 | @@ -9,4 +9,7 @@ |
4731 | pygments_byte_strings.diff |
4732 | fix_shorthandoff.diff |
4733 | fix_manpages_generation_with_new_docutils.diff |
4734 | +test_build_html_rb.diff |
4735 | +sort_stopwords.diff |
4736 | support_python_3.3.diff |
4737 | +l10n_fixes.diff |
4738 | |
4739 | === added file 'debian/patches/sort_stopwords.diff' |
4740 | --- debian/patches/sort_stopwords.diff 1970-01-01 00:00:00 +0000 |
4741 | +++ debian/patches/sort_stopwords.diff 2012-11-28 07:12:20 +0000 |
4742 | @@ -0,0 +1,16 @@ |
4743 | +Description: sort stopwords in searchtools.js |
4744 | + The order of stopwords in searchtools.js would be random if hash randomization |
4745 | + was enabled, breaking dh_sphinxdoc. This patch makes the order deterministic. |
4746 | +Author: Jakub Wilk <jwilk@debian.org> |
4747 | +Applied-Upstream: https://bitbucket.org/birkenfeld/sphinx/changeset/6cf5320e65 |
4748 | +Last-Update: 2012-11-10 |
4749 | + |
4750 | +--- a/sphinx/search/__init__.py |
4751 | ++++ b/sphinx/search/__init__.py |
4752 | +@@ -283,5 +283,5 @@ |
4753 | + def context_for_searchtool(self): |
4754 | + return dict( |
4755 | + search_language_stemming_code = self.lang.js_stemmer_code, |
4756 | +- search_language_stop_words = jsdump.dumps(self.lang.stopwords), |
4757 | ++ search_language_stop_words = jsdump.dumps(sorted(self.lang.stopwords)), |
4758 | + ) |
4759 | |
4760 | === modified file 'debian/patches/support_python_3.3.diff' |
4761 | --- debian/patches/support_python_3.3.diff 2012-11-01 21:39:16 +0000 |
4762 | +++ debian/patches/support_python_3.3.diff 2012-11-28 07:12:20 +0000 |
4763 | @@ -1,29 +1,11 @@ |
4764 | -Description: Fix various testsuite failures with Python 3.3 |
4765 | +Description: fix compatibility with Python 3.3 |
4766 | Author: Takayuki Shimizukawa <shimizukawa@gmail.com> |
4767 | Bug: https://bitbucket.org/birkenfeld/sphinx/issue/1008/test-failures-with-python-33 |
4768 | Bug-Ubuntu: https://bugs.launchpad.net/bugs/1070336 |
4769 | -Last-Update: 2012-11-01 |
4770 | - |
4771 | -=== modified file 'sphinx/environment.py' |
4772 | ---- a/sphinx/environment.py 2012-03-30 23:32:16 +0000 |
4773 | -+++ b/sphinx/environment.py 2012-11-01 17:33:08 +0000 |
4774 | -@@ -782,7 +782,11 @@ |
4775 | - app.emit('doctree-read', doctree) |
4776 | - |
4777 | - # store time of build, for outdated files detection |
4778 | -- self.all_docs[docname] = time.time() |
4779 | -+ # (Some filesystems have coarse timestamp resolution; |
4780 | -+ # therefore time.time() is older than filesystem's timestamp. |
4781 | -+ # For example, FAT32 has 2sec timestamp resolution.) |
4782 | -+ self.all_docs[docname] = max( |
4783 | -+ time.time(), path.getmtime(self.doc2path(docname))) |
4784 | - |
4785 | - if self.versioning_condition: |
4786 | - # get old doctree |
4787 | - |
4788 | -=== modified file 'sphinx/ext/autodoc.py' |
4789 | ---- a/sphinx/ext/autodoc.py 2012-03-30 23:32:16 +0000 |
4790 | -+++ b/sphinx/ext/autodoc.py 2012-11-01 17:33:08 +0000 |
4791 | +Last-Update: 2012-11-08 |
4792 | + |
4793 | +--- a/sphinx/ext/autodoc.py |
4794 | ++++ b/sphinx/ext/autodoc.py |
4795 | @@ -1098,7 +1098,7 @@ |
4796 | """ |
4797 | objtype = 'method' |
4798 | @@ -33,10 +15,8 @@ |
4799 | |
4800 | @classmethod |
4801 | def can_document_member(cls, member, membername, isattr, parent): |
4802 | - |
4803 | -=== modified file 'sphinx/ext/intersphinx.py' |
4804 | ---- a/sphinx/ext/intersphinx.py 2012-03-30 23:32:16 +0000 |
4805 | -+++ b/sphinx/ext/intersphinx.py 2012-11-01 17:33:13 +0000 |
4806 | +--- a/sphinx/ext/intersphinx.py |
4807 | ++++ b/sphinx/ext/intersphinx.py |
4808 | @@ -188,7 +188,17 @@ |
4809 | if update: |
4810 | env.intersphinx_inventory = {} |
4811 | @@ -56,17 +36,13 @@ |
4812 | if name: |
4813 | env.intersphinx_named_inventory[name] = invdata |
4814 | for type, objects in invdata.iteritems(): |
4815 | - |
4816 | -=== modified file 'sphinx/util/__init__.py' |
4817 | ---- a/sphinx/util/__init__.py 2012-03-30 23:32:16 +0000 |
4818 | -+++ b/sphinx/util/__init__.py 2012-11-01 17:37:41 +0000 |
4819 | +--- a/sphinx/util/__init__.py |
4820 | ++++ b/sphinx/util/__init__.py |
4821 | @@ -197,13 +197,18 @@ |
4822 | except Exception, err: |
4823 | raise PycodeError('error importing %r' % modname, err) |
4824 | mod = sys.modules[modname] |
4825 | - if hasattr(mod, '__loader__'): |
4826 | -- try: |
4827 | -- source = mod.__loader__.get_source(modname) |
4828 | + filename = getattr(mod, '__file__', None) |
4829 | + loader = getattr(mod, '__loader__', None) |
4830 | + if loader and getattr(loader, 'get_filename', None): |
4831 | @@ -75,7 +51,8 @@ |
4832 | + except Exception, err: |
4833 | + raise PycodeError('error getting filename for %r' % filename, err) |
4834 | + if filename is None and loader: |
4835 | -+ try: |
4836 | + try: |
4837 | +- source = mod.__loader__.get_source(modname) |
4838 | + return 'string', loader.get_source(modname) |
4839 | except Exception, err: |
4840 | raise PycodeError('error getting source for %r' % modname, err) |
4841 | @@ -84,4 +61,3 @@ |
4842 | if filename is None: |
4843 | raise PycodeError('no source found for module %r' % modname) |
4844 | filename = path.normpath(path.abspath(filename)) |
4845 | - |
4846 | |
4847 | === added file 'debian/patches/test_build_html_rb.diff' |
4848 | --- debian/patches/test_build_html_rb.diff 1970-01-01 00:00:00 +0000 |
4849 | +++ debian/patches/test_build_html_rb.diff 2012-11-28 07:12:20 +0000 |
4850 | @@ -0,0 +1,17 @@ |
4851 | +Description: fix encoding issues in test_build_html |
4852 | + test_build_html: open files that are fed to ElementTree parser in "rb" |
4853 | + mode, fixing encoding issues. |
4854 | +Origin: upstream, https://bitbucket.org/birkenfeld/sphinx/changeset/15c9d212bbf4 |
4855 | +Bug: https://bitbucket.org/birkenfeld/sphinx/issue/895 |
4856 | + |
4857 | +--- a/tests/test_build_html.py |
4858 | ++++ b/tests/test_build_html.py |
4859 | +@@ -328,7 +328,7 @@ |
4860 | + for fname, paths in HTML_XPATH.iteritems(): |
4861 | + parser = NslessParser() |
4862 | + parser.entity.update(htmlentitydefs.entitydefs) |
4863 | +- fp = open(os.path.join(app.outdir, fname)) |
4864 | ++ fp = open(os.path.join(app.outdir, fname), 'rb') |
4865 | + try: |
4866 | + etree = ET.parse(fp, parser) |
4867 | + finally: |
4868 | |
4869 | === modified file 'debian/rules' |
4870 | --- debian/rules 2012-06-19 09:06:35 +0000 |
4871 | +++ debian/rules 2012-11-28 07:12:20 +0000 |
4872 | @@ -5,6 +5,7 @@ |
4873 | |
4874 | export NO_PKG_MANGLE=1 |
4875 | export PYTHONWARNINGS=d |
4876 | +export PYTHONHASHSEED=random |
4877 | |
4878 | here = $(dir $(firstword $(MAKEFILE_LIST)))/.. |
4879 | debian_version = $(word 2,$(shell cd $(here) && dpkg-parsechangelog | grep ^Version:)) |
4880 | @@ -39,7 +40,7 @@ |
4881 | ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS))) |
4882 | find sphinx/locale/ -name '*.po' | xargs -t -I {} msgfmt -o /dev/null -c {} |
4883 | $(python_all) tests/run.py --verbose --no-skip |
4884 | - export LC_ALL=C.UTF-8 && $(python3_all) tests/run.py --verbose |
4885 | + $(python3_all) tests/run.py --verbose |
4886 | cd build/py3/ && rm -rf tests/ sphinx/pycode/Grammar.pickle |
4887 | xvfb-run --auto-servernum ./debian/jstest/run-tests |
4888 | endif |
4889 | @@ -146,7 +147,7 @@ |
4890 | dh_md5sums |
4891 | dh_builddeb |
4892 | |
4893 | -binary-arch: build install |
4894 | +binary-arch: |
4895 | |
4896 | binary: binary-indep binary-arch |
4897 | |
4898 | |
4899 | === modified file 'debian/sphinx-autogen.1' |
4900 | --- debian/sphinx-autogen.1 2011-11-20 15:56:50 +0000 |
4901 | +++ debian/sphinx-autogen.1 2012-11-28 07:12:20 +0000 |
4902 | @@ -1,4 +1,4 @@ |
4903 | -.TH sphinx\-autogen 1 "Aug 2010" "Sphinx 1.1" "User Commands" |
4904 | +.TH sphinx\-autogen 1 "Nov 2012" "Sphinx 1.1.3" "User Commands" |
4905 | |
4906 | .SH NAME |
4907 | sphinx\-autogen \- generate ReStructuredText using \fBautosummary\fR |
4908 | |
4909 | === modified file 'debian/tests/control' |
4910 | --- debian/tests/control 2012-05-24 18:53:29 +0000 |
4911 | +++ debian/tests/control 2012-11-28 07:12:20 +0000 |
4912 | @@ -1,7 +1,5 @@ |
4913 | Tests: python-sphinx |
4914 | -Features: no-build-needed |
4915 | Depends: python-sphinx, python-nose |
4916 | |
4917 | Tests: python3-sphinx |
4918 | -Features: no-build-needed |
4919 | Depends: python3-sphinx, python3-nose |
4920 | |
4921 | === modified file 'debian/tests/python-sphinx' |
4922 | --- debian/tests/python-sphinx 2012-08-21 17:45:58 +0000 |
4923 | +++ debian/tests/python-sphinx 2012-11-28 07:12:20 +0000 |
4924 | @@ -4,7 +4,7 @@ |
4925 | cd "$ADTTMP" |
4926 | pyversions -i \ |
4927 | | tr ' ' '\n' \ |
4928 | -| xargs -I {} env PYTHONWARNINGS=d {} \ |
4929 | +| xargs -I {} env PYTHONWARNINGS=d PYTHONHASHSEED=random {} \ |
4930 | /usr/bin/nosetests --verbose 2>&1 |
4931 | |
4932 | # vim:ts=4 sw=4 et |
4933 | |
4934 | === modified file 'debian/tests/python3-sphinx' |
4935 | --- debian/tests/python3-sphinx 2012-08-21 17:45:58 +0000 |
4936 | +++ debian/tests/python3-sphinx 2012-11-28 07:12:20 +0000 |
4937 | @@ -5,7 +5,7 @@ |
4938 | cd "$ADTTMP" |
4939 | py3versions -i \ |
4940 | | tr ' ' '\n' \ |
4941 | -| xargs -I {} env PYTHONWARNINGS=d {} \ |
4942 | +| xargs -I {} env PYTHONWARNINGS=d PYTHONHASHSEED=random {} \ |
4943 | /usr/bin/nosetests3 --verbose 2>&1 |
4944 | |
4945 | # vim:ts=4 sw=4 et |
4946 | |
4947 | === modified file 'sphinx/environment.py' |
4948 | --- sphinx/environment.py 2012-11-01 21:39:16 +0000 |
4949 | +++ sphinx/environment.py 2012-11-28 07:12:20 +0000 |
4950 | @@ -213,16 +213,44 @@ |
4951 | parser = RSTParser() |
4952 | |
4953 | for node, msg in extract_messages(self.document): |
4954 | - patch = new_document(source, settings) |
4955 | msgstr = catalog.gettext(msg) |
4956 | # XXX add marker to untranslated parts |
4957 | if not msgstr or msgstr == msg: # as-of-yet untranslated |
4958 | continue |
4959 | + |
4960 | + patch = new_document(source, settings) |
4961 | parser.parse(msgstr, patch) |
4962 | patch = patch[0] |
4963 | # XXX doctest and other block markup |
4964 | if not isinstance(patch, nodes.paragraph): |
4965 | continue # skip for now |
4966 | + |
4967 | + footnote_refs = [r for r in node.children |
4968 | + if isinstance(r, nodes.footnote_reference) |
4969 | + and r.get('auto') == 1] |
4970 | + refs = [r for r in node.children if isinstance(r, nodes.reference)] |
4971 | + |
4972 | + for i, child in enumerate(patch.children): # update leaves |
4973 | + if isinstance(child, nodes.footnote_reference) \ |
4974 | + and child.get('auto') == 1: |
4975 | + # use original 'footnote_reference' object. |
4976 | + # this object is already registered in self.document.autofootnote_refs |
4977 | + patch.children[i] = footnote_refs.pop(0) |
4978 | + # Some duplicated footnote_reference in msgstr causes |
4979 | + # IndexError in .pop(0). That is invalid msgstr. |
4980 | + |
4981 | + elif isinstance(child, nodes.reference): |
4982 | + # reference should use original 'refname'. |
4983 | + # * reference target ".. _Python: ..." is not translatable. |
4984 | + # * section refname is not translatable. |
4985 | + # * inline reference "`Python <...>`_" has no 'refname'. |
4986 | + if refs and 'refname' in refs[0]: |
4987 | + refname = child['refname'] = refs.pop(0)['refname'] |
4988 | + self.document.refnames.setdefault( |
4989 | + refname, []).append(child) |
4990 | + # if number of reference nodes had been changed, that |
4991 | + # would often generate unknown link target warning. |
4992 | + |
4993 | for child in patch.children: # update leaves |
4994 | child.parent = node |
4995 | node.children = patch.children |
4996 | @@ -782,11 +810,7 @@ |
4997 | app.emit('doctree-read', doctree) |
4998 | |
4999 | # store time of build, for outdated files detection |
5000 | - # (Some filesystems have coarse timestamp resolution; |
The diff has been truncated for viewing.
Uploaded. Thanks.