PageRenderTime 28ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/notify_user/pymodules/python2.7/lib/python/Sphinx-1.2.3-py2.7.egg/sphinx/builders/html.py

https://gitlab.com/pooja043/Globus_Docker_4
Python | 1107 lines | 1048 code | 24 blank | 35 comment | 60 complexity | 31194f5350cab4023b1c3554628f1112 MD5 | raw file
  1. # -*- coding: utf-8 -*-
  2. """
  3. sphinx.builders.html
  4. ~~~~~~~~~~~~~~~~~~~~
  5. Several HTML builders.
  6. :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS.
  7. :license: BSD, see LICENSE for details.
  8. """
  9. import os
  10. import sys
  11. import zlib
  12. import codecs
  13. import posixpath
  14. import cPickle as pickle
  15. from os import path
  16. try:
  17. from hashlib import md5
  18. except ImportError:
  19. # 2.4 compatibility
  20. from md5 import md5
  21. from docutils import nodes
  22. from docutils.io import DocTreeInput, StringOutput
  23. from docutils.core import Publisher
  24. from docutils.utils import new_document
  25. from docutils.frontend import OptionParser
  26. from docutils.readers.doctree import Reader as DoctreeReader
  27. from sphinx import package_dir, __version__
  28. from sphinx.util import jsonimpl, copy_static_entry
  29. from sphinx.util.osutil import SEP, os_path, relative_uri, ensuredir, \
  30. movefile, ustrftime, copyfile
  31. from sphinx.util.nodes import inline_all_toctrees
  32. from sphinx.util.matching import patmatch, compile_matchers
  33. from sphinx.util.pycompat import any, b
  34. from sphinx.errors import SphinxError
  35. from sphinx.locale import _
  36. from sphinx.search import js_index
  37. from sphinx.theming import Theme
  38. from sphinx.builders import Builder
  39. from sphinx.application import ENV_PICKLE_FILENAME
  40. from sphinx.highlighting import PygmentsBridge
  41. from sphinx.util.console import bold, darkgreen, brown
  42. from sphinx.writers.html import HTMLWriter, HTMLTranslator, \
  43. SmartyPantsHTMLTranslator
  44. #: the filename for the inventory of objects
  45. INVENTORY_FILENAME = 'objects.inv'
  46. #: the filename for the "last build" file (for serializing builders)
  47. LAST_BUILD_FILENAME = 'last_build'
  48. def get_stable_hash(obj):
  49. """
  50. Return a stable hash for a Python data structure. We can't just use
  51. the md5 of str(obj) since for example dictionary items are enumerated
  52. in unpredictable order due to hash randomization in newer Pythons.
  53. """
  54. if isinstance(obj, dict):
  55. return get_stable_hash(list(obj.items()))
  56. elif isinstance(obj, (list, tuple)):
  57. obj = sorted(get_stable_hash(o) for o in obj)
  58. return md5(unicode(obj).encode('utf8')).hexdigest()
  59. class StandaloneHTMLBuilder(Builder):
  60. """
  61. Builds standalone HTML docs.
  62. """
  63. name = 'html'
  64. format = 'html'
  65. copysource = True
  66. allow_parallel = True
  67. out_suffix = '.html'
  68. link_suffix = '.html' # defaults to matching out_suffix
  69. indexer_format = js_index
  70. indexer_dumps_unicode = True
  71. supported_image_types = ['image/svg+xml', 'image/png',
  72. 'image/gif', 'image/jpeg']
  73. searchindex_filename = 'searchindex.js'
  74. add_permalinks = True
  75. embedded = False # for things like HTML help or Qt help: suppresses sidebar
  76. # This is a class attribute because it is mutated by Sphinx.add_javascript.
  77. script_files = ['_static/jquery.js', '_static/underscore.js',
  78. '_static/doctools.js']
  79. # Dito for this one.
  80. css_files = []
  81. default_sidebars = ['localtoc.html', 'relations.html',
  82. 'sourcelink.html', 'searchbox.html']
  83. # cached publisher object for snippets
  84. _publisher = None
  85. def init(self):
  86. # a hash of all config values that, if changed, cause a full rebuild
  87. self.config_hash = ''
  88. self.tags_hash = ''
  89. # section numbers for headings in the currently visited document
  90. self.secnumbers = {}
  91. # currently written docname
  92. self.current_docname = None
  93. self.init_templates()
  94. self.init_highlighter()
  95. self.init_translator_class()
  96. if self.config.html_file_suffix is not None:
  97. self.out_suffix = self.config.html_file_suffix
  98. if self.config.html_link_suffix is not None:
  99. self.link_suffix = self.config.html_link_suffix
  100. else:
  101. self.link_suffix = self.out_suffix
  102. if self.config.language is not None:
  103. if self._get_translations_js():
  104. self.script_files.append('_static/translations.js')
  105. def _get_translations_js(self):
  106. candidates = [path.join(package_dir, 'locale', self.config.language,
  107. 'LC_MESSAGES', 'sphinx.js'),
  108. path.join(sys.prefix, 'share/sphinx/locale',
  109. self.config.language, 'sphinx.js')] + \
  110. [path.join(dir, self.config.language,
  111. 'LC_MESSAGES', 'sphinx.js')
  112. for dir in self.config.locale_dirs]
  113. for jsfile in candidates:
  114. if path.isfile(jsfile):
  115. return jsfile
  116. return None
  117. def get_theme_config(self):
  118. return self.config.html_theme, self.config.html_theme_options
  119. def init_templates(self):
  120. Theme.init_themes(self.confdir, self.config.html_theme_path,
  121. warn=self.warn)
  122. themename, themeoptions = self.get_theme_config()
  123. self.theme = Theme(themename)
  124. self.theme_options = themeoptions.copy()
  125. self.create_template_bridge()
  126. self.templates.init(self, self.theme)
  127. def init_highlighter(self):
  128. # determine Pygments style and create the highlighter
  129. if self.config.pygments_style is not None:
  130. style = self.config.pygments_style
  131. elif self.theme:
  132. style = self.theme.get_confstr('theme', 'pygments_style', 'none')
  133. else:
  134. style = 'sphinx'
  135. self.highlighter = PygmentsBridge('html', style,
  136. self.config.trim_doctest_flags)
  137. def init_translator_class(self):
  138. if self.config.html_translator_class:
  139. self.translator_class = self.app.import_object(
  140. self.config.html_translator_class,
  141. 'html_translator_class setting')
  142. elif self.config.html_use_smartypants:
  143. self.translator_class = SmartyPantsHTMLTranslator
  144. else:
  145. self.translator_class = HTMLTranslator
  146. def get_outdated_docs(self):
  147. cfgdict = dict((name, self.config[name])
  148. for (name, desc) in self.config.values.iteritems()
  149. if desc[1] == 'html')
  150. self.config_hash = get_stable_hash(cfgdict)
  151. self.tags_hash = get_stable_hash(sorted(self.tags))
  152. old_config_hash = old_tags_hash = ''
  153. try:
  154. fp = open(path.join(self.outdir, '.buildinfo'))
  155. try:
  156. version = fp.readline()
  157. if version.rstrip() != '# Sphinx build info version 1':
  158. raise ValueError
  159. fp.readline() # skip commentary
  160. cfg, old_config_hash = fp.readline().strip().split(': ')
  161. if cfg != 'config':
  162. raise ValueError
  163. tag, old_tags_hash = fp.readline().strip().split(': ')
  164. if tag != 'tags':
  165. raise ValueError
  166. finally:
  167. fp.close()
  168. except ValueError:
  169. self.warn('unsupported build info format in %r, building all' %
  170. path.join(self.outdir, '.buildinfo'))
  171. except Exception:
  172. pass
  173. if old_config_hash != self.config_hash or \
  174. old_tags_hash != self.tags_hash:
  175. for docname in self.env.found_docs:
  176. yield docname
  177. return
  178. if self.templates:
  179. template_mtime = self.templates.newest_template_mtime()
  180. else:
  181. template_mtime = 0
  182. for docname in self.env.found_docs:
  183. if docname not in self.env.all_docs:
  184. yield docname
  185. continue
  186. targetname = self.get_outfilename(docname)
  187. try:
  188. targetmtime = path.getmtime(targetname)
  189. except Exception:
  190. targetmtime = 0
  191. try:
  192. srcmtime = max(path.getmtime(self.env.doc2path(docname)),
  193. template_mtime)
  194. if srcmtime > targetmtime:
  195. yield docname
  196. except EnvironmentError:
  197. # source doesn't exist anymore
  198. pass
  199. def render_partial(self, node):
  200. """Utility: Render a lone doctree node."""
  201. if node is None:
  202. return {'fragment': ''}
  203. doc = new_document(b('<partial node>'))
  204. doc.append(node)
  205. if self._publisher is None:
  206. self._publisher = Publisher(
  207. source_class = DocTreeInput,
  208. destination_class=StringOutput)
  209. self._publisher.set_components('standalone',
  210. 'restructuredtext', 'pseudoxml')
  211. pub = self._publisher
  212. pub.reader = DoctreeReader()
  213. pub.writer = HTMLWriter(self)
  214. pub.process_programmatic_settings(
  215. None, {'output_encoding': 'unicode'}, None)
  216. pub.set_source(doc, None)
  217. pub.set_destination(None, None)
  218. pub.publish()
  219. return pub.writer.parts
  220. def prepare_writing(self, docnames):
  221. # create the search indexer
  222. from sphinx.search import IndexBuilder, languages
  223. lang = self.config.html_search_language or self.config.language
  224. if not lang or lang not in languages:
  225. lang = 'en'
  226. self.indexer = IndexBuilder(self.env, lang,
  227. self.config.html_search_options,
  228. self.config.html_search_scorer)
  229. self.load_indexer(docnames)
  230. self.docwriter = HTMLWriter(self)
  231. self.docsettings = OptionParser(
  232. defaults=self.env.settings,
  233. components=(self.docwriter,),
  234. read_config_files=True).get_default_values()
  235. self.docsettings.compact_lists = bool(self.config.html_compact_lists)
  236. # determine the additional indices to include
  237. self.domain_indices = []
  238. # html_domain_indices can be False/True or a list of index names
  239. indices_config = self.config.html_domain_indices
  240. if indices_config:
  241. for domain in self.env.domains.itervalues():
  242. for indexcls in domain.indices:
  243. indexname = '%s-%s' % (domain.name, indexcls.name)
  244. if isinstance(indices_config, list):
  245. if indexname not in indices_config:
  246. continue
  247. # deprecated config value
  248. if indexname == 'py-modindex' and \
  249. not self.config.html_use_modindex:
  250. continue
  251. content, collapse = indexcls(domain).generate()
  252. if content:
  253. self.domain_indices.append(
  254. (indexname, indexcls, content, collapse))
  255. # format the "last updated on" string, only once is enough since it
  256. # typically doesn't include the time of day
  257. lufmt = self.config.html_last_updated_fmt
  258. if lufmt is not None:
  259. self.last_updated = ustrftime(lufmt or _('%b %d, %Y'))
  260. else:
  261. self.last_updated = None
  262. logo = self.config.html_logo and \
  263. path.basename(self.config.html_logo) or ''
  264. favicon = self.config.html_favicon and \
  265. path.basename(self.config.html_favicon) or ''
  266. if favicon and os.path.splitext(favicon)[1] != '.ico':
  267. self.warn('html_favicon is not an .ico file')
  268. if not isinstance(self.config.html_use_opensearch, basestring):
  269. self.warn('html_use_opensearch config value must now be a string')
  270. self.relations = self.env.collect_relations()
  271. rellinks = []
  272. if self.get_builder_config('use_index', 'html'):
  273. rellinks.append(('genindex', _('General Index'), 'I', _('index')))
  274. for indexname, indexcls, content, collapse in self.domain_indices:
  275. # if it has a short name
  276. if indexcls.shortname:
  277. rellinks.append((indexname, indexcls.localname,
  278. '', indexcls.shortname))
  279. if self.config.html_style is not None:
  280. stylename = self.config.html_style
  281. elif self.theme:
  282. stylename = self.theme.get_confstr('theme', 'stylesheet')
  283. else:
  284. stylename = 'default.css'
  285. self.globalcontext = dict(
  286. embedded = self.embedded,
  287. project = self.config.project,
  288. release = self.config.release,
  289. version = self.config.version,
  290. last_updated = self.last_updated,
  291. copyright = self.config.copyright,
  292. master_doc = self.config.master_doc,
  293. use_opensearch = self.config.html_use_opensearch,
  294. docstitle = self.config.html_title,
  295. shorttitle = self.config.html_short_title,
  296. show_copyright = self.config.html_show_copyright,
  297. show_sphinx = self.config.html_show_sphinx,
  298. has_source = self.config.html_copy_source,
  299. show_source = self.config.html_show_sourcelink,
  300. file_suffix = self.out_suffix,
  301. script_files = self.script_files,
  302. css_files = self.css_files,
  303. sphinx_version = __version__,
  304. style = stylename,
  305. rellinks = rellinks,
  306. builder = self.name,
  307. parents = [],
  308. logo = logo,
  309. favicon = favicon,
  310. )
  311. if self.theme:
  312. self.globalcontext.update(
  313. ('theme_' + key, val) for (key, val) in
  314. self.theme.get_options(self.theme_options).iteritems())
  315. self.globalcontext.update(self.config.html_context)
  316. def get_doc_context(self, docname, body, metatags):
  317. """Collect items for the template context of a page."""
  318. # find out relations
  319. prev = next = None
  320. parents = []
  321. rellinks = self.globalcontext['rellinks'][:]
  322. related = self.relations.get(docname)
  323. titles = self.env.titles
  324. if related and related[2]:
  325. try:
  326. next = {
  327. 'link': self.get_relative_uri(docname, related[2]),
  328. 'title': self.render_partial(titles[related[2]])['title']
  329. }
  330. rellinks.append((related[2], next['title'], 'N', _('next')))
  331. except KeyError:
  332. next = None
  333. if related and related[1]:
  334. try:
  335. prev = {
  336. 'link': self.get_relative_uri(docname, related[1]),
  337. 'title': self.render_partial(titles[related[1]])['title']
  338. }
  339. rellinks.append((related[1], prev['title'], 'P', _('previous')))
  340. except KeyError:
  341. # the relation is (somehow) not in the TOC tree, handle
  342. # that gracefully
  343. prev = None
  344. while related and related[0]:
  345. try:
  346. parents.append(
  347. {'link': self.get_relative_uri(docname, related[0]),
  348. 'title': self.render_partial(titles[related[0]])['title']})
  349. except KeyError:
  350. pass
  351. related = self.relations.get(related[0])
  352. if parents:
  353. parents.pop() # remove link to the master file; we have a generic
  354. # "back to index" link already
  355. parents.reverse()
  356. # title rendered as HTML
  357. title = self.env.longtitles.get(docname)
  358. title = title and self.render_partial(title)['title'] or ''
  359. # the name for the copied source
  360. sourcename = self.config.html_copy_source and docname + '.txt' or ''
  361. # metadata for the document
  362. meta = self.env.metadata.get(docname)
  363. # local TOC and global TOC tree
  364. self_toc = self.env.get_toc_for(docname, self)
  365. toc = self.render_partial(self_toc)['fragment']
  366. return dict(
  367. parents = parents,
  368. prev = prev,
  369. next = next,
  370. title = title,
  371. meta = meta,
  372. body = body,
  373. metatags = metatags,
  374. rellinks = rellinks,
  375. sourcename = sourcename,
  376. toc = toc,
  377. # only display a TOC if there's more than one item to show
  378. display_toc = (self.env.toc_num_entries[docname] > 1),
  379. )
  380. def write_doc(self, docname, doctree):
  381. destination = StringOutput(encoding='utf-8')
  382. doctree.settings = self.docsettings
  383. self.secnumbers = self.env.toc_secnumbers.get(docname, {})
  384. self.imgpath = relative_uri(self.get_target_uri(docname), '_images')
  385. self.dlpath = relative_uri(self.get_target_uri(docname), '_downloads')
  386. self.current_docname = docname
  387. self.docwriter.write(doctree, destination)
  388. self.docwriter.assemble_parts()
  389. body = self.docwriter.parts['fragment']
  390. metatags = self.docwriter.clean_meta
  391. ctx = self.get_doc_context(docname, body, metatags)
  392. self.handle_page(docname, ctx, event_arg=doctree)
  393. def write_doc_serialized(self, docname, doctree):
  394. self.imgpath = relative_uri(self.get_target_uri(docname), '_images')
  395. self.post_process_images(doctree)
  396. title = self.env.longtitles.get(docname)
  397. title = title and self.render_partial(title)['title'] or ''
  398. self.index_page(docname, doctree, title)
  399. def finish(self):
  400. self.info(bold('writing additional files...'), nonl=1)
  401. # pages from extensions
  402. for pagelist in self.app.emit('html-collect-pages'):
  403. for pagename, context, template in pagelist:
  404. self.handle_page(pagename, context, template)
  405. # the global general index
  406. if self.get_builder_config('use_index', 'html'):
  407. self.write_genindex()
  408. # the global domain-specific indices
  409. self.write_domain_indices()
  410. # the search page
  411. if self.name != 'htmlhelp':
  412. self.info(' search', nonl=1)
  413. self.handle_page('search', {}, 'search.html')
  414. # additional pages from conf.py
  415. for pagename, template in self.config.html_additional_pages.items():
  416. self.info(' '+pagename, nonl=1)
  417. self.handle_page(pagename, {}, template)
  418. if self.config.html_use_opensearch and self.name != 'htmlhelp':
  419. self.info(' opensearch', nonl=1)
  420. fn = path.join(self.outdir, '_static', 'opensearch.xml')
  421. self.handle_page('opensearch', {}, 'opensearch.xml', outfilename=fn)
  422. self.info()
  423. self.copy_image_files()
  424. self.copy_download_files()
  425. self.copy_static_files()
  426. self.copy_extra_files()
  427. self.write_buildinfo()
  428. # dump the search index
  429. self.handle_finish()
  430. def write_genindex(self):
  431. # the total count of lines for each index letter, used to distribute
  432. # the entries into two columns
  433. genindex = self.env.create_index(self)
  434. indexcounts = []
  435. for _, entries in genindex:
  436. indexcounts.append(sum(1 + len(subitems)
  437. for _, (_, subitems) in entries))
  438. genindexcontext = dict(
  439. genindexentries = genindex,
  440. genindexcounts = indexcounts,
  441. split_index = self.config.html_split_index,
  442. )
  443. self.info(' genindex', nonl=1)
  444. if self.config.html_split_index:
  445. self.handle_page('genindex', genindexcontext,
  446. 'genindex-split.html')
  447. self.handle_page('genindex-all', genindexcontext,
  448. 'genindex.html')
  449. for (key, entries), count in zip(genindex, indexcounts):
  450. ctx = {'key': key, 'entries': entries, 'count': count,
  451. 'genindexentries': genindex}
  452. self.handle_page('genindex-' + key, ctx,
  453. 'genindex-single.html')
  454. else:
  455. self.handle_page('genindex', genindexcontext, 'genindex.html')
  456. def write_domain_indices(self):
  457. for indexname, indexcls, content, collapse in self.domain_indices:
  458. indexcontext = dict(
  459. indextitle = indexcls.localname,
  460. content = content,
  461. collapse_index = collapse,
  462. )
  463. self.info(' ' + indexname, nonl=1)
  464. self.handle_page(indexname, indexcontext, 'domainindex.html')
  465. def copy_image_files(self):
  466. # copy image files
  467. if self.images:
  468. ensuredir(path.join(self.outdir, '_images'))
  469. for src in self.status_iterator(self.images, 'copying images... ',
  470. brown, len(self.images)):
  471. dest = self.images[src]
  472. try:
  473. copyfile(path.join(self.srcdir, src),
  474. path.join(self.outdir, '_images', dest))
  475. except Exception, err:
  476. self.warn('cannot copy image file %r: %s' %
  477. (path.join(self.srcdir, src), err))
  478. def copy_download_files(self):
  479. # copy downloadable files
  480. if self.env.dlfiles:
  481. ensuredir(path.join(self.outdir, '_downloads'))
  482. for src in self.status_iterator(self.env.dlfiles,
  483. 'copying downloadable files... ',
  484. brown, len(self.env.dlfiles)):
  485. dest = self.env.dlfiles[src][1]
  486. try:
  487. copyfile(path.join(self.srcdir, src),
  488. path.join(self.outdir, '_downloads', dest))
  489. except Exception, err:
  490. self.warn('cannot copy downloadable file %r: %s' %
  491. (path.join(self.srcdir, src), err))
  492. def copy_static_files(self):
  493. # copy static files
  494. self.info(bold('copying static files... '), nonl=True)
  495. ensuredir(path.join(self.outdir, '_static'))
  496. # first, create pygments style file
  497. f = open(path.join(self.outdir, '_static', 'pygments.css'), 'w')
  498. f.write(self.highlighter.get_stylesheet())
  499. f.close()
  500. # then, copy translations JavaScript file
  501. if self.config.language is not None:
  502. jsfile = self._get_translations_js()
  503. if jsfile:
  504. copyfile(jsfile, path.join(self.outdir, '_static',
  505. 'translations.js'))
  506. # add context items for search function used in searchtools.js_t
  507. ctx = self.globalcontext.copy()
  508. ctx.update(self.indexer.context_for_searchtool())
  509. # then, copy over theme-supplied static files
  510. if self.theme:
  511. themeentries = [path.join(themepath, 'static')
  512. for themepath in self.theme.get_dirchain()[::-1]]
  513. for entry in themeentries:
  514. copy_static_entry(entry, path.join(self.outdir, '_static'),
  515. self, ctx)
  516. # then, copy over all user-supplied static files
  517. staticentries = [path.join(self.confdir, spath)
  518. for spath in self.config.html_static_path]
  519. matchers = compile_matchers(
  520. self.config.exclude_patterns +
  521. ['**/' + d for d in self.config.exclude_dirnames]
  522. )
  523. for entry in staticentries:
  524. if not path.exists(entry):
  525. self.warn('html_static_path entry %r does not exist' % entry)
  526. continue
  527. copy_static_entry(entry, path.join(self.outdir, '_static'), self,
  528. ctx, exclude_matchers=matchers)
  529. # copy logo and favicon files if not already in static path
  530. if self.config.html_logo:
  531. logobase = path.basename(self.config.html_logo)
  532. logotarget = path.join(self.outdir, '_static', logobase)
  533. if not path.isfile(path.join(self.confdir, self.config.html_logo)):
  534. self.warn('logo file %r does not exist' % self.config.html_logo)
  535. elif not path.isfile(logotarget):
  536. copyfile(path.join(self.confdir, self.config.html_logo),
  537. logotarget)
  538. if self.config.html_favicon:
  539. iconbase = path.basename(self.config.html_favicon)
  540. icontarget = path.join(self.outdir, '_static', iconbase)
  541. if not path.isfile(path.join(self.confdir, self.config.html_favicon)):
  542. self.warn('favicon file %r does not exist' % self.config.html_favicon)
  543. elif not path.isfile(icontarget):
  544. copyfile(path.join(self.confdir, self.config.html_favicon),
  545. icontarget)
  546. self.info('done')
  547. def copy_extra_files(self):
  548. # copy html_extra_path files
  549. self.info(bold('copying extra files... '), nonl=True)
  550. extraentries = [path.join(self.confdir, epath)
  551. for epath in self.config.html_extra_path]
  552. for entry in extraentries:
  553. if not path.exists(entry):
  554. self.warn('html_extra_path entry %r does not exist' % entry)
  555. continue
  556. copy_static_entry(entry, self.outdir, self)
  557. self.info('done')
  558. def write_buildinfo(self):
  559. # write build info file
  560. fp = open(path.join(self.outdir, '.buildinfo'), 'w')
  561. try:
  562. fp.write('# Sphinx build info version 1\n'
  563. '# This file hashes the configuration used when building'
  564. ' these files. When it is not found, a full rebuild will'
  565. ' be done.\nconfig: %s\ntags: %s\n' %
  566. (self.config_hash, self.tags_hash))
  567. finally:
  568. fp.close()
  569. def cleanup(self):
  570. # clean up theme stuff
  571. if self.theme:
  572. self.theme.cleanup()
  573. def post_process_images(self, doctree):
  574. """Pick the best candidate for an image and link down-scaled images to
  575. their high res version.
  576. """
  577. Builder.post_process_images(self, doctree)
  578. for node in doctree.traverse(nodes.image):
  579. scale_keys = ('scale', 'width', 'height')
  580. if not any((key in node) for key in scale_keys) or \
  581. isinstance(node.parent, nodes.reference):
  582. # docutils does unfortunately not preserve the
  583. # ``target`` attribute on images, so we need to check
  584. # the parent node here.
  585. continue
  586. uri = node['uri']
  587. reference = nodes.reference('', '', internal=True)
  588. if uri in self.images:
  589. reference['refuri'] = posixpath.join(self.imgpath,
  590. self.images[uri])
  591. else:
  592. reference['refuri'] = uri
  593. node.replace_self(reference)
  594. reference.append(node)
  595. def load_indexer(self, docnames):
  596. keep = set(self.env.all_docs) - set(docnames)
  597. try:
  598. searchindexfn = path.join(self.outdir, self.searchindex_filename)
  599. if self.indexer_dumps_unicode:
  600. f = codecs.open(searchindexfn, 'r', encoding='utf-8')
  601. else:
  602. f = open(searchindexfn, 'rb')
  603. try:
  604. self.indexer.load(f, self.indexer_format)
  605. finally:
  606. f.close()
  607. except (IOError, OSError, ValueError):
  608. if keep:
  609. self.warn('search index couldn\'t be loaded, but not all '
  610. 'documents will be built: the index will be '
  611. 'incomplete.')
  612. # delete all entries for files that will be rebuilt
  613. self.indexer.prune(keep)
  614. def index_page(self, pagename, doctree, title):
  615. # only index pages with title
  616. if self.indexer is not None and title:
  617. self.indexer.feed(pagename, title, doctree)
  618. def _get_local_toctree(self, docname, collapse=True, **kwds):
  619. if 'includehidden' not in kwds:
  620. kwds['includehidden'] = False
  621. return self.render_partial(self.env.get_toctree_for(
  622. docname, self, collapse, **kwds))['fragment']
  623. def get_outfilename(self, pagename):
  624. return path.join(self.outdir, os_path(pagename) + self.out_suffix)
  625. def add_sidebars(self, pagename, ctx):
  626. def has_wildcard(pattern):
  627. return any(char in pattern for char in '*?[')
  628. sidebars = None
  629. matched = None
  630. customsidebar = None
  631. for pattern, patsidebars in self.config.html_sidebars.iteritems():
  632. if patmatch(pagename, pattern):
  633. if matched:
  634. if has_wildcard(pattern):
  635. # warn if both patterns contain wildcards
  636. if has_wildcard(matched):
  637. self.warn('page %s matches two patterns in '
  638. 'html_sidebars: %r and %r' %
  639. (pagename, matched, pattern))
  640. # else the already matched pattern is more specific
  641. # than the present one, because it contains no wildcard
  642. continue
  643. matched = pattern
  644. sidebars = patsidebars
  645. if sidebars is None:
  646. # keep defaults
  647. pass
  648. elif isinstance(sidebars, basestring):
  649. # 0.x compatible mode: insert custom sidebar before searchbox
  650. customsidebar = sidebars
  651. sidebars = None
  652. ctx['sidebars'] = sidebars
  653. ctx['customsidebar'] = customsidebar
  654. # --------- these are overwritten by the serialization builder
  655. def get_target_uri(self, docname, typ=None):
  656. return docname + self.link_suffix
  657. def handle_page(self, pagename, addctx, templatename='page.html',
  658. outfilename=None, event_arg=None):
  659. ctx = self.globalcontext.copy()
  660. # current_page_name is backwards compatibility
  661. ctx['pagename'] = ctx['current_page_name'] = pagename
  662. default_baseuri = self.get_target_uri(pagename)
  663. # in the singlehtml builder, default_baseuri still contains an #anchor
  664. # part, which relative_uri doesn't really like...
  665. default_baseuri = default_baseuri.rsplit('#', 1)[0]
  666. def pathto(otheruri, resource=False, baseuri=default_baseuri):
  667. if resource and '://' in otheruri:
  668. # allow non-local resources given by scheme
  669. return otheruri
  670. elif not resource:
  671. otheruri = self.get_target_uri(otheruri)
  672. uri = relative_uri(baseuri, otheruri) or '#'
  673. return uri
  674. ctx['pathto'] = pathto
  675. ctx['hasdoc'] = lambda name: name in self.env.all_docs
  676. if self.name != 'htmlhelp':
  677. ctx['encoding'] = encoding = self.config.html_output_encoding
  678. else:
  679. ctx['encoding'] = encoding = self.encoding
  680. ctx['toctree'] = lambda **kw: self._get_local_toctree(pagename, **kw)
  681. self.add_sidebars(pagename, ctx)
  682. ctx.update(addctx)
  683. self.app.emit('html-page-context', pagename, templatename,
  684. ctx, event_arg)
  685. try:
  686. output = self.templates.render(templatename, ctx)
  687. except UnicodeError:
  688. self.warn("a Unicode error occurred when rendering the page %s. "
  689. "Please make sure all config values that contain "
  690. "non-ASCII content are Unicode strings." % pagename)
  691. return
  692. if not outfilename:
  693. outfilename = self.get_outfilename(pagename)
  694. # outfilename's path is in general different from self.outdir
  695. ensuredir(path.dirname(outfilename))
  696. try:
  697. f = codecs.open(outfilename, 'w', encoding, 'xmlcharrefreplace')
  698. try:
  699. f.write(output)
  700. finally:
  701. f.close()
  702. except (IOError, OSError), err:
  703. self.warn("error writing file %s: %s" % (outfilename, err))
  704. if self.copysource and ctx.get('sourcename'):
  705. # copy the source file for the "show source" link
  706. source_name = path.join(self.outdir, '_sources',
  707. os_path(ctx['sourcename']))
  708. ensuredir(path.dirname(source_name))
  709. copyfile(self.env.doc2path(pagename), source_name)
  710. def handle_finish(self):
  711. self.dump_search_index()
  712. self.dump_inventory()
  713. def dump_inventory(self):
  714. self.info(bold('dumping object inventory... '), nonl=True)
  715. f = open(path.join(self.outdir, INVENTORY_FILENAME), 'wb')
  716. try:
  717. f.write((u'# Sphinx inventory version 2\n'
  718. u'# Project: %s\n'
  719. u'# Version: %s\n'
  720. u'# The remainder of this file is compressed using zlib.\n'
  721. % (self.config.project, self.config.version)
  722. ).encode('utf-8'))
  723. compressor = zlib.compressobj(9)
  724. for domainname, domain in self.env.domains.iteritems():
  725. for name, dispname, type, docname, anchor, prio in \
  726. domain.get_objects():
  727. if anchor.endswith(name):
  728. # this can shorten the inventory by as much as 25%
  729. anchor = anchor[:-len(name)] + '$'
  730. uri = self.get_target_uri(docname) + '#' + anchor
  731. if dispname == name:
  732. dispname = u'-'
  733. f.write(compressor.compress(
  734. (u'%s %s:%s %s %s %s\n' % (name, domainname, type,
  735. prio, uri, dispname)
  736. ).encode('utf-8')))
  737. f.write(compressor.flush())
  738. finally:
  739. f.close()
  740. self.info('done')
  741. def dump_search_index(self):
  742. self.info(bold('dumping search index... '), nonl=True)
  743. self.indexer.prune(self.env.all_docs)
  744. searchindexfn = path.join(self.outdir, self.searchindex_filename)
  745. # first write to a temporary file, so that if dumping fails,
  746. # the existing index won't be overwritten
  747. if self.indexer_dumps_unicode:
  748. f = codecs.open(searchindexfn + '.tmp', 'w', encoding='utf-8')
  749. else:
  750. f = open(searchindexfn + '.tmp', 'wb')
  751. try:
  752. self.indexer.dump(f, self.indexer_format)
  753. finally:
  754. f.close()
  755. movefile(searchindexfn + '.tmp', searchindexfn)
  756. self.info('done')
  757. class DirectoryHTMLBuilder(StandaloneHTMLBuilder):
  758. """
  759. A StandaloneHTMLBuilder that creates all HTML pages as "index.html" in
  760. a directory given by their pagename, so that generated URLs don't have
  761. ``.html`` in them.
  762. """
  763. name = 'dirhtml'
  764. def get_target_uri(self, docname, typ=None):
  765. if docname == 'index':
  766. return ''
  767. if docname.endswith(SEP + 'index'):
  768. return docname[:-5] # up to sep
  769. return docname + SEP
  770. def get_outfilename(self, pagename):
  771. if pagename == 'index' or pagename.endswith(SEP + 'index'):
  772. outfilename = path.join(self.outdir, os_path(pagename)
  773. + self.out_suffix)
  774. else:
  775. outfilename = path.join(self.outdir, os_path(pagename),
  776. 'index' + self.out_suffix)
  777. return outfilename
  778. def prepare_writing(self, docnames):
  779. StandaloneHTMLBuilder.prepare_writing(self, docnames)
  780. self.globalcontext['no_search_suffix'] = True
  781. class SingleFileHTMLBuilder(StandaloneHTMLBuilder):
  782. """
  783. A StandaloneHTMLBuilder subclass that puts the whole document tree on one
  784. HTML page.
  785. """
  786. name = 'singlehtml'
  787. copysource = False
  788. def get_outdated_docs(self):
  789. return 'all documents'
  790. def get_target_uri(self, docname, typ=None):
  791. if docname in self.env.all_docs:
  792. # all references are on the same page...
  793. return self.config.master_doc + self.out_suffix + \
  794. '#document-' + docname
  795. else:
  796. # chances are this is a html_additional_page
  797. return docname + self.out_suffix
  798. def get_relative_uri(self, from_, to, typ=None):
  799. # ignore source
  800. return self.get_target_uri(to, typ)
  801. def fix_refuris(self, tree):
  802. # fix refuris with double anchor
  803. fname = self.config.master_doc + self.out_suffix
  804. for refnode in tree.traverse(nodes.reference):
  805. if 'refuri' not in refnode:
  806. continue
  807. refuri = refnode['refuri']
  808. hashindex = refuri.find('#')
  809. if hashindex < 0:
  810. continue
  811. hashindex = refuri.find('#', hashindex+1)
  812. if hashindex >= 0:
  813. refnode['refuri'] = fname + refuri[hashindex:]
  814. def assemble_doctree(self):
  815. master = self.config.master_doc
  816. tree = self.env.get_doctree(master)
  817. tree = inline_all_toctrees(self, set(), master, tree, darkgreen)
  818. tree['docname'] = master
  819. self.env.resolve_references(tree, master, self)
  820. self.fix_refuris(tree)
  821. return tree
  822. def get_doc_context(self, docname, body, metatags):
  823. # no relation links...
  824. toc = self.env.get_toctree_for(self.config.master_doc, self, False)
  825. # if there is no toctree, toc is None
  826. if toc:
  827. self.fix_refuris(toc)
  828. toc = self.render_partial(toc)['fragment']
  829. display_toc = True
  830. else:
  831. toc = ''
  832. display_toc = False
  833. return dict(
  834. parents = [],
  835. prev = None,
  836. next = None,
  837. docstitle = None,
  838. title = self.config.html_title,
  839. meta = None,
  840. body = body,
  841. metatags = metatags,
  842. rellinks = [],
  843. sourcename = '',
  844. toc = toc,
  845. display_toc = display_toc,
  846. )
  847. def write(self, *ignored):
  848. docnames = self.env.all_docs
  849. self.info(bold('preparing documents... '), nonl=True)
  850. self.prepare_writing(docnames)
  851. self.info('done')
  852. self.info(bold('assembling single document... '), nonl=True)
  853. doctree = self.assemble_doctree()
  854. self.info()
  855. self.info(bold('writing... '), nonl=True)
  856. self.write_doc_serialized(self.config.master_doc, doctree)
  857. self.write_doc(self.config.master_doc, doctree)
  858. self.info('done')
  859. def finish(self):
  860. # no indices or search pages are supported
  861. self.info(bold('writing additional files...'), nonl=1)
  862. # additional pages from conf.py
  863. for pagename, template in self.config.html_additional_pages.items():
  864. self.info(' '+pagename, nonl=1)
  865. self.handle_page(pagename, {}, template)
  866. if self.config.html_use_opensearch:
  867. self.info(' opensearch', nonl=1)
  868. fn = path.join(self.outdir, '_static', 'opensearch.xml')
  869. self.handle_page('opensearch', {}, 'opensearch.xml', outfilename=fn)
  870. self.info()
  871. self.copy_image_files()
  872. self.copy_download_files()
  873. self.copy_static_files()
  874. self.copy_extra_files()
  875. self.write_buildinfo()
  876. self.dump_inventory()
  877. class SerializingHTMLBuilder(StandaloneHTMLBuilder):
  878. """
  879. An abstract builder that serializes the generated HTML.
  880. """
  881. #: the serializing implementation to use. Set this to a module that
  882. #: implements a `dump`, `load`, `dumps` and `loads` functions
  883. #: (pickle, simplejson etc.)
  884. implementation = None
  885. implementation_dumps_unicode = False
  886. #: additional arguments for dump()
  887. additional_dump_args = ()
  888. #: the filename for the global context file
  889. globalcontext_filename = None
  890. supported_image_types = ['image/svg+xml', 'image/png',
  891. 'image/gif', 'image/jpeg']
  892. def init(self):
  893. self.config_hash = ''
  894. self.tags_hash = ''
  895. self.theme = None # no theme necessary
  896. self.templates = None # no template bridge necessary
  897. self.init_translator_class()
  898. self.init_highlighter()
  899. def get_target_uri(self, docname, typ=None):
  900. if docname == 'index':
  901. return ''
  902. if docname.endswith(SEP + 'index'):
  903. return docname[:-5] # up to sep
  904. return docname + SEP
  905. def dump_context(self, context, filename):
  906. if self.implementation_dumps_unicode:
  907. f = codecs.open(filename, 'w', encoding='utf-8')
  908. else:
  909. f = open(filename, 'wb')
  910. try:
  911. self.implementation.dump(context, f, *self.additional_dump_args)
  912. finally:
  913. f.close()
  914. def handle_page(self, pagename, ctx, templatename='page.html',
  915. outfilename=None, event_arg=None):
  916. ctx['current_page_name'] = pagename
  917. self.add_sidebars(pagename, ctx)
  918. if not outfilename:
  919. outfilename = path.join(self.outdir,
  920. os_path(pagename) + self.out_suffix)
  921. self.app.emit('html-page-context', pagename, templatename,
  922. ctx, event_arg)
  923. ensuredir(path.dirname(outfilename))
  924. self.dump_context(ctx, outfilename)
  925. # if there is a source file, copy the source file for the
  926. # "show source" link
  927. if ctx.get('sourcename'):
  928. source_name = path.join(self.outdir, '_sources',
  929. os_path(ctx['sourcename']))
  930. ensuredir(path.dirname(source_name))
  931. copyfile(self.env.doc2path(pagename), source_name)
  932. def handle_finish(self):
  933. # dump the global context
  934. outfilename = path.join(self.outdir, self.globalcontext_filename)
  935. self.dump_context(self.globalcontext, outfilename)
  936. # super here to dump the search index
  937. StandaloneHTMLBuilder.handle_finish(self)
  938. # copy the environment file from the doctree dir to the output dir
  939. # as needed by the web app
  940. copyfile(path.join(self.doctreedir, ENV_PICKLE_FILENAME),
  941. path.join(self.outdir, ENV_PICKLE_FILENAME))
  942. # touch 'last build' file, used by the web application to determine
  943. # when to reload its environment and clear the cache
  944. open(path.join(self.outdir, LAST_BUILD_FILENAME), 'w').close()
  945. class PickleHTMLBuilder(SerializingHTMLBuilder):
  946. """
  947. A Builder that dumps the generated HTML into pickle files.
  948. """
  949. implementation = pickle
  950. implementation_dumps_unicode = False
  951. additional_dump_args = (pickle.HIGHEST_PROTOCOL,)
  952. indexer_format = pickle
  953. indexer_dumps_unicode = False
  954. name = 'pickle'
  955. out_suffix = '.fpickle'
  956. globalcontext_filename = 'globalcontext.pickle'
  957. searchindex_filename = 'searchindex.pickle'
  958. # compatibility alias
  959. WebHTMLBuilder = PickleHTMLBuilder
  960. class JSONHTMLBuilder(SerializingHTMLBuilder):
  961. """
  962. A builder that dumps the generated HTML into JSON files.
  963. """
  964. implementation = jsonimpl
  965. implementation_dumps_unicode = True
  966. indexer_format = jsonimpl
  967. indexer_dumps_unicode = True
  968. name = 'json'
  969. out_suffix = '.fjson'
  970. globalcontext_filename = 'globalcontext.json'
  971. searchindex_filename = 'searchindex.json'
  972. def init(self):
  973. if jsonimpl.json is None:
  974. raise SphinxError(
  975. 'The module simplejson (or json in Python >= 2.6) '
  976. 'is not available. The JSONHTMLBuilder builder will not work.')
  977. SerializingHTMLBuilder.init(self)