Add single-file HTML builder. Closes #151.

This commit is contained in:
Georg Brandl 2010-01-17 16:48:02 +01:00
parent f3fc36b9f0
commit 744a519c92
8 changed files with 343 additions and 177 deletions

View File

@ -1,6 +1,8 @@
Release 1.0 (in development) Release 1.0 (in development)
============================ ============================
* Added single-file HTML builder.
* Added ``tab-width`` option to ``literalinclude`` directive. * Added ``tab-width`` option to ``literalinclude`` directive.
* The ``html_sidebars`` config value can now contain patterns as * The ``html_sidebars`` config value can now contain patterns as

View File

@ -37,6 +37,11 @@ dirhtml:
@echo @echo
@echo "Build finished. The HTML pages are in _build/dirhtml." @echo "Build finished. The HTML pages are in _build/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) _build/singlehtml
@echo
@echo "Build finished. The HTML page is in _build/singlehtml."
text: text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) _build/text $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) _build/text
@echo @echo

View File

@ -36,6 +36,16 @@ The builder's "name" must be given to the **-b** command-line option of
.. versionadded:: 0.6 .. versionadded:: 0.6
.. class:: SingleFileHTMLBuilder
This is an HTML builder that combines the whole project in one output file.
(Obviously this only works with smaller projects.) The file is named like
the master document. No indices will be generated.
Its name is ``singlehtml``.
.. versionadded:: 1.0
.. module:: sphinx.builders.htmlhelp .. module:: sphinx.builders.htmlhelp
.. class:: HTMLHelpBuilder .. class:: HTMLHelpBuilder

View File

@ -382,6 +382,7 @@ class Builder(object):
BUILTIN_BUILDERS = { BUILTIN_BUILDERS = {
'html': ('html', 'StandaloneHTMLBuilder'), 'html': ('html', 'StandaloneHTMLBuilder'),
'dirhtml': ('html', 'DirectoryHTMLBuilder'), 'dirhtml': ('html', 'DirectoryHTMLBuilder'),
'singlehtml': ('html', 'SingleFileHTMLBuilder'),
'pickle': ('html', 'PickleHTMLBuilder'), 'pickle': ('html', 'PickleHTMLBuilder'),
'json': ('html', 'JSONHTMLBuilder'), 'json': ('html', 'JSONHTMLBuilder'),
'web': ('html', 'PickleHTMLBuilder'), 'web': ('html', 'PickleHTMLBuilder'),

View File

@ -28,14 +28,16 @@ from docutils.frontend import OptionParser
from docutils.readers.doctree import Reader as DoctreeReader from docutils.readers.doctree import Reader as DoctreeReader
from sphinx import package_dir, __version__ from sphinx import package_dir, __version__
from sphinx import addnodes
from sphinx.util import SEP, os_path, relative_uri, ensuredir, patmatch, \ from sphinx.util import SEP, os_path, relative_uri, ensuredir, patmatch, \
movefile, ustrftime, copy_static_entry, copyfile, compile_matchers, any movefile, ustrftime, copy_static_entry, copyfile, compile_matchers, any, \
inline_all_toctrees
from sphinx.errors import SphinxError from sphinx.errors import SphinxError
from sphinx.search import js_index from sphinx.search import js_index
from sphinx.theming import Theme from sphinx.theming import Theme
from sphinx.builders import Builder, ENV_PICKLE_FILENAME from sphinx.builders import Builder, ENV_PICKLE_FILENAME
from sphinx.highlighting import PygmentsBridge from sphinx.highlighting import PygmentsBridge
from sphinx.util.console import bold from sphinx.util.console import bold, darkgreen
from sphinx.writers.html import HTMLWriter, HTMLTranslator, \ from sphinx.writers.html import HTMLWriter, HTMLTranslator, \
SmartyPantsHTMLTranslator SmartyPantsHTMLTranslator
@ -379,8 +381,39 @@ class StandaloneHTMLBuilder(Builder):
self.info(bold('writing additional files...'), nonl=1) self.info(bold('writing additional files...'), nonl=1)
# the global general index # the global general index
if self.config.html_use_index: if self.config.html_use_index:
self.write_genindex()
# the global module index
if self.config.html_use_modindex and self.env.modules:
self.write_modindex()
# the search page
if self.name != 'htmlhelp':
self.info(' search', nonl=1)
self.handle_page('search', {}, 'search.html')
# additional pages from conf.py
for pagename, template in self.config.html_additional_pages.items():
self.info(' '+pagename, nonl=1)
self.handle_page(pagename, {}, template)
if self.config.html_use_opensearch and self.name != 'htmlhelp':
self.info(' opensearch', nonl=1)
fn = path.join(self.outdir, '_static', 'opensearch.xml')
self.handle_page('opensearch', {}, 'opensearch.xml', outfilename=fn)
self.info()
self.copy_image_files()
self.copy_download_files()
self.copy_static_files()
self.write_buildinfo()
# dump the search index
self.handle_finish()
def write_genindex(self):
# the total count of lines for each index letter, used to distribute # the total count of lines for each index letter, used to distribute
# the entries into two columns # the entries into two columns
genindex = self.env.create_index(self) genindex = self.env.create_index(self)
@ -409,9 +442,7 @@ class StandaloneHTMLBuilder(Builder):
else: else:
self.handle_page('genindex', genindexcontext, 'genindex.html') self.handle_page('genindex', genindexcontext, 'genindex.html')
# the global module index def write_modindex(self):
if self.config.html_use_modindex and self.env.modules:
# the sorted list of all modules, for the global module index # the sorted list of all modules, for the global module index
modules = sorted(((mn, (self.get_relative_uri('modindex', fn) + modules = sorted(((mn, (self.get_relative_uri('modindex', fn) +
'#module-' + mn, sy, pl, dep)) '#module-' + mn, sy, pl, dep))
@ -501,23 +532,7 @@ class StandaloneHTMLBuilder(Builder):
self.info(' modindex', nonl=1) self.info(' modindex', nonl=1)
self.handle_page('modindex', modindexcontext, 'modindex.html') self.handle_page('modindex', modindexcontext, 'modindex.html')
# the search page def copy_image_files(self):
if self.name != 'htmlhelp':
self.info(' search', nonl=1)
self.handle_page('search', {}, 'search.html')
# additional pages from conf.py
for pagename, template in self.config.html_additional_pages.items():
self.info(' '+pagename, nonl=1)
self.handle_page(pagename, {}, template)
if self.config.html_use_opensearch and self.name != 'htmlhelp':
self.info(' opensearch', nonl=1)
fn = path.join(self.outdir, '_static', 'opensearch.xml')
self.handle_page('opensearch', {}, 'opensearch.xml', outfilename=fn)
self.info()
# copy image files # copy image files
if self.images: if self.images:
self.info(bold('copying images...'), nonl=True) self.info(bold('copying images...'), nonl=True)
@ -532,6 +547,7 @@ class StandaloneHTMLBuilder(Builder):
(path.join(self.srcdir, src), err)) (path.join(self.srcdir, src), err))
self.info() self.info()
def copy_download_files(self):
# copy downloadable files # copy downloadable files
if self.env.dlfiles: if self.env.dlfiles:
self.info(bold('copying downloadable files...'), nonl=True) self.info(bold('copying downloadable files...'), nonl=True)
@ -546,6 +562,7 @@ class StandaloneHTMLBuilder(Builder):
(path.join(self.srcdir, src), err)) (path.join(self.srcdir, src), err))
self.info() self.info()
def copy_static_files(self):
# copy static files # copy static files
self.info(bold('copying static files... '), nonl=True) self.info(bold('copying static files... '), nonl=True)
ensuredir(path.join(self.outdir, '_static')) ensuredir(path.join(self.outdir, '_static'))
@ -593,7 +610,9 @@ class StandaloneHTMLBuilder(Builder):
if not path.isfile(icontarget): if not path.isfile(icontarget):
copyfile(path.join(self.confdir, self.config.html_favicon), copyfile(path.join(self.confdir, self.config.html_favicon),
icontarget) icontarget)
self.info('done')
def write_buildinfo(self):
# write build info file # write build info file
fp = open(path.join(self.outdir, '.buildinfo'), 'w') fp = open(path.join(self.outdir, '.buildinfo'), 'w')
try: try:
@ -605,11 +624,6 @@ class StandaloneHTMLBuilder(Builder):
finally: finally:
fp.close() fp.close()
self.info('done')
# dump the search index
self.handle_finish()
def cleanup(self): def cleanup(self):
# clean up theme stuff # clean up theme stuff
if self.theme: if self.theme:
@ -751,19 +765,10 @@ class StandaloneHTMLBuilder(Builder):
copyfile(self.env.doc2path(pagename), source_name) copyfile(self.env.doc2path(pagename), source_name)
def handle_finish(self): def handle_finish(self):
self.info(bold('dumping search index... '), nonl=True) self.dump_search_index()
self.indexer.prune(self.env.all_docs) self.dump_inventory()
searchindexfn = path.join(self.outdir, self.searchindex_filename)
# first write to a temporary file, so that if dumping fails,
# the existing index won't be overwritten
f = open(searchindexfn + '.tmp', 'wb')
try:
self.indexer.dump(f, self.indexer_format)
finally:
f.close()
movefile(searchindexfn + '.tmp', searchindexfn)
self.info('done')
def dump_inventory(self):
self.info(bold('dumping object inventory... '), nonl=True) self.info(bold('dumping object inventory... '), nonl=True)
f = open(path.join(self.outdir, INVENTORY_FILENAME), 'w') f = open(path.join(self.outdir, INVENTORY_FILENAME), 'w')
try: try:
@ -779,6 +784,20 @@ class StandaloneHTMLBuilder(Builder):
f.close() f.close()
self.info('done') self.info('done')
def dump_search_index(self):
self.info(bold('dumping search index... '), nonl=True)
self.indexer.prune(self.env.all_docs)
searchindexfn = path.join(self.outdir, self.searchindex_filename)
# first write to a temporary file, so that if dumping fails,
# the existing index won't be overwritten
f = open(searchindexfn + '.tmp', 'wb')
try:
self.indexer.dump(f, self.indexer_format)
finally:
f.close()
movefile(searchindexfn + '.tmp', searchindexfn)
self.info('done')
class DirectoryHTMLBuilder(StandaloneHTMLBuilder): class DirectoryHTMLBuilder(StandaloneHTMLBuilder):
""" """
@ -806,6 +825,110 @@ class DirectoryHTMLBuilder(StandaloneHTMLBuilder):
return outfilename return outfilename
class SingleFileHTMLBuilder(StandaloneHTMLBuilder):
"""
A StandaloneHTMLBuilder subclass that puts the whole document tree on one
HTML page.
"""
name = 'singlehtml'
copysource = False
def get_outdated_docs(self):
return 'all documents'
def get_target_uri(self, docname, typ=None):
if docname in self.env.all_docs:
# all references are on the same page...
return self.config.master_doc + self.out_suffix + \
'#document-' + docname
else:
# chances are this is a html_additional_page
return docname + self.out_suffix
def get_relative_uri(self, from_, to, typ=None):
# ignore source
return self.get_target_uri(to, typ)
def fix_refuris(self, tree):
# fix refuris with double anchor
fname = self.config.master_doc + self.out_suffix
for refnode in tree.traverse(nodes.reference):
if 'refuri' not in refnode:
continue
refuri = refnode['refuri']
hashindex = refuri.find('#')
if hashindex < 0:
continue
hashindex = refuri.find('#', hashindex+1)
if hashindex >= 0:
refnode['refuri'] = fname + refuri[hashindex:]
def assemble_doctree(self):
master = self.config.master_doc
tree = self.env.get_doctree(master)
tree = inline_all_toctrees(self, set(), master, tree, darkgreen)
tree['docname'] = master
self.env.resolve_references(tree, master, self)
self.fix_refuris(tree)
return tree
def get_doc_context(self, docname, body, metatags):
# no relation links...
toc = self.env.get_toctree_for(self.config.master_doc, self, False)
self.fix_refuris(toc)
toc = self.render_partial(toc)['fragment']
return dict(
parents = [],
prev = None,
next = None,
docstitle = None,
title = self.config.html_title,
meta = None,
body = body,
metatags = metatags,
rellinks = [],
sourcename = '',
toc = toc,
display_toc = True,
)
def write(self, *ignored):
docnames = self.env.all_docs
self.info(bold('preparing documents... '), nonl=True)
self.prepare_writing(docnames)
self.info('done')
self.info(bold('assembling single document... '), nonl=True)
doctree = self.assemble_doctree()
self.info()
self.info(bold('writing... '), nonl=True)
self.write_doc(self.config.master_doc, doctree)
self.info('done')
def finish(self):
# no indices or search pages are supported
self.info(bold('writing additional files...'), nonl=1)
# additional pages from conf.py
for pagename, template in self.config.html_additional_pages.items():
self.info(' '+pagename, nonl=1)
self.handle_page(pagename, {}, template)
if self.config.html_use_opensearch:
self.info(' opensearch', nonl=1)
fn = path.join(self.outdir, '_static', 'opensearch.xml')
self.handle_page('opensearch', {}, 'opensearch.xml', outfilename=fn)
self.info()
self.copy_image_files()
self.copy_download_files()
self.copy_static_files()
self.write_buildinfo()
self.dump_inventory()
class SerializingHTMLBuilder(StandaloneHTMLBuilder): class SerializingHTMLBuilder(StandaloneHTMLBuilder):
""" """
An abstract builder that serializes the generated HTML. An abstract builder that serializes the generated HTML.

View File

@ -14,6 +14,7 @@ from os import path
TERM_ENCODING = getattr(sys.stdin, 'encoding', None) TERM_ENCODING = getattr(sys.stdin, 'encoding', None)
from sphinx import __version__
from sphinx.util import make_filename from sphinx.util import make_filename
from sphinx.util.console import purple, bold, red, turquoise, \ from sphinx.util.console import purple, bold, red, turquoise, \
nocolor, color_terminal nocolor, color_terminal
@ -301,13 +302,14 @@ PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) \ ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) \
$(SPHINXOPTS) %(rsrcdir)s $(SPHINXOPTS) %(rsrcdir)s
.PHONY: help clean html dirhtml pickle json htmlhelp qthelp epub \ .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp epub \
latex changes linkcheck doctest latex changes linkcheck doctest
help: help:
\t@echo "Please use \\`make <target>' where <target> is one of" \t@echo "Please use \\`make <target>' where <target> is one of"
\t@echo " html to make standalone HTML files" \t@echo " html to make standalone HTML files"
\t@echo " dirhtml to make HTML files named index.html in directories" \t@echo " dirhtml to make HTML files named index.html in directories"
\t@echo " singlehtml to make a single large HTML file"
\t@echo " pickle to make pickle files" \t@echo " pickle to make pickle files"
\t@echo " json to make JSON files" \t@echo " json to make JSON files"
\t@echo " htmlhelp to make HTML files and a HTML help project" \t@echo " htmlhelp to make HTML files and a HTML help project"
@ -334,6 +336,11 @@ dirhtml:
\t@echo \t@echo
\t@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." \t@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
\t$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
\t@echo
\t@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle: pickle:
\t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle \t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
\t@echo \t@echo
@ -423,6 +430,7 @@ if "%%1" == "help" (
\techo.Please use `make ^<target^>` where ^<target^> is one of \techo.Please use `make ^<target^>` where ^<target^> is one of
\techo. html to make standalone HTML files \techo. html to make standalone HTML files
\techo. dirhtml to make HTML files named index.html in directories \techo. dirhtml to make HTML files named index.html in directories
\techo. singlehtml to make a single large HTML file
\techo. pickle to make pickle files \techo. pickle to make pickle files
\techo. json to make JSON files \techo. json to make JSON files
\techo. htmlhelp to make HTML files and a HTML help project \techo. htmlhelp to make HTML files and a HTML help project
@ -456,6 +464,13 @@ if "%%1" == "dirhtml" (
\tgoto end \tgoto end
) )
if "%%1" == "singlehtml" (
\t%%SPHINXBUILD%% -b singlehtml %%ALLSPHINXOPTS%% %%BUILDDIR%%/singlehtml
\techo.
\techo.Build finished. The HTML pages are in %%BUILDDIR%%/singlehtml.
\tgoto end
)
if "%%1" == "pickle" ( if "%%1" == "pickle" (
\t%%SPHINXBUILD%% -b pickle %%ALLSPHINXOPTS%% %%BUILDDIR%%/pickle \t%%SPHINXBUILD%% -b pickle %%ALLSPHINXOPTS%% %%BUILDDIR%%/pickle
\techo. \techo.
@ -614,7 +629,7 @@ def inner_main(args):
if not color_terminal(): if not color_terminal():
nocolor() nocolor()
print bold('Welcome to the Sphinx quickstart utility.') print bold('Welcome to the Sphinx %s quickstart utility.') % __version__
print ''' print '''
Please enter values for the following settings (just press Enter to Please enter values for the following settings (just press Enter to
accept a default value, if one is given in brackets).''' accept a default value, if one is given in brackets).'''

View File

@ -60,6 +60,12 @@ class HTMLTranslator(BaseTranslator):
self.protect_literal_text = 0 self.protect_literal_text = 0
self.add_permalinks = builder.config.html_add_permalinks self.add_permalinks = builder.config.html_add_permalinks
def visit_start_of_file(self, node):
# only occurs in the single-file builder
self.body.append('<span id="document-%s"></span>' % node['docname'])
def depart_start_of_file(self, node):
pass
def visit_desc(self, node): def visit_desc(self, node):
self.body.append(self.starttag(node, 'dl', CLASS=node['desctype'])) self.body.append(self.starttag(node, 'dl', CLASS=node['desctype']))
def depart_desc(self, node): def depart_desc(self, node):

View File

@ -128,6 +128,10 @@ def test_qthelp(app):
def test_epub(app): def test_epub(app):
app.builder.build_all() app.builder.build_all()
@with_app(buildername='changes', cleanenv=True) @with_app(buildername='changes')
def test_changes(app): def test_changes(app):
app.builder.build_all() app.builder.build_all()
@with_app(buildername='singlehtml', cleanenv=True)
def test_singlehtml(app):
app.builder.build_all()