From 706e12da65b1ae240d66c24f910d7f081d700c26 Mon Sep 17 00:00:00 2001 From: Roland Meister Date: Mon, 28 Dec 2009 17:09:09 +0100 Subject: [PATCH 01/11] Initial import from local repository --- doc/Makefile | 6 + doc/builders.rst | 12 + doc/conf.py | 12 + doc/config.rst | 93 ++++++ doc/faq.rst | 41 +++ doc/theming.rst | 4 + sphinx/builders/__init__.py | 1 + sphinx/builders/epub.py | 409 ++++++++++++++++++++++++++ sphinx/config.py | 14 + sphinx/quickstart.py | 68 ++++- sphinx/themes/epub/layout.html | 7 + sphinx/themes/epub/static/epub.css | 445 +++++++++++++++++++++++++++++ sphinx/themes/epub/theme.conf | 4 + tests/test_build.py | 4 + tests/test_theming.py | 2 +- 15 files changed, 1119 insertions(+), 3 deletions(-) create mode 100644 sphinx/builders/epub.py create mode 100644 sphinx/themes/epub/layout.html create mode 100644 sphinx/themes/epub/static/epub.css create mode 100644 sphinx/themes/epub/theme.conf diff --git a/doc/Makefile b/doc/Makefile index 07cee7449..ffd51dc64 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -19,6 +19,7 @@ help: @echo " dirhtml to make HTML files called index.html in directories" @echo " pickle to make pickle files" @echo " htmlhelp to make HTML files and a HTML help project" + @echo " epub to make an epub file" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview over all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @@ -65,6 +66,11 @@ qthelp: @echo "To view the help collection:" @echo "# assistant -collectionFile _build/qthelp/Sphinx.qhc" +epub: + mkdir -p _build/epub _build/doctrees + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) _build/epub + @echo + @echo "Build finished. The epub file is in _build/epub." latex: mkdir -p _build/latex _build/doctrees $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) _build/latex diff --git a/doc/builders.rst b/doc/builders.rst index 7c1f03d3d..3ce8f56a7 100644 --- a/doc/builders.rst +++ b/doc/builders.rst @@ -63,6 +63,18 @@ The builder's "name" must be given to the **-b** command-line option of Its name is ``devhelp``. +.. module:: sphinx.builders.epub +.. class:: EpubBuilder + + This builder produces the same output as the standalone HTML builder, but + also generates an *epub* file for ebook readers. + This builder is meant to be used together with the + :confval:`html_theme` ``'epub'``. + See ``_ or + ``_ for the definition of epubs. + + Its name is ``epub``. + .. module:: sphinx.builders.latex .. class:: LaTeXBuilder diff --git a/doc/conf.py b/doc/conf.py index e3952a0b2..f8310746c 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -64,6 +64,18 @@ html_use_opensearch = 'http://sphinx.pocoo.org' # Output file base name for HTML help builder. htmlhelp_basename = 'Sphinxdoc' +# Epub fields +epub_basename = 'sphinx' +epub_author = 'Georg Brandl' +epub_publisher = 'http://sphinx.pocoo.org/' +epub_scheme = 'url' +epub_identifier = epub_publisher +epub_pre_files = [ ('index', 'Welcome')] +epub_exclude_files = ['_static/opensearch.xml', '_static/doctools.js', + '_static/jquery.js', '_static/searchtools.js', + '_static/basic.css', 'search.html'] + + # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [('contents', 'sphinx.tex', 'Sphinx Documentation', diff --git a/doc/config.rst b/doc/config.rst index 3ce8d53bb..99b484c85 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -549,6 +549,99 @@ that use Sphinx' HTMLWriter class. Output file base name for HTML help builder. Default is ``'pydoc'``. +.. _epub-options: + +Options for epub output +----------------------- + +These options influence the epub output. As this writer derives from the +HTMLWriter the HTML options also apply where appropriate. +The actual values for some of the options is not really important, +they just have to be entered into +the `Dublin Core metadata `_. + +.. confval:: epub_basename + + The basename for the epub file. It defaults to the :confval:`project` name. + +.. confval:: epub_title + + The title of the document. + It defaults to the :confval:`html_title` option + but can be set independently for epub creation. + +.. confval:: epub_author + + The author of the document. + This is put in the Dublin Core metadata. + The default value is ``'unknown'``. + +.. confval:: epub_language + + The language of the document. + This is put in the Dublin Core metadata. + The default is the :confval:`language` option or ``'en'`` if unset. + +.. confval:: epub_publisher + + The publisher of the document. + This is put in the Dublin Core metadata. + You may use any sensible string, e.g. the project homepage. + The default value is ``'unknown'``. + +.. confval:: epub_copyright + + The copyright of the document. + It defaults to the :confval:`copyright` option + but can be set independently for epub creation. + +.. confval:: epub_identifier + + An identifier for the document. + This is put in the Dublin Core metadata. + For published documents this is the ISBN number, but you can + also use an alternative scheme, e.g. the project homepage. + The default value is ``'unknown'``. + +.. confval:: epub_scheme + + The publication scheme for the :confval:`epub_identifier`. + This is put in the Dublin Core metadata. + For published books the scheme is ``'ISBN'``. + If you use the project homepage, ``'URL'`` seems reasonable. + +.. confval:: epub_uid + + A unique identifier for the document. + This is put in the Dublin Core metadata. + You may use a random string. + The default value is ``'unknown'``. + +.. confval:: epub_pre_files + + Additional files that should be inserted before the text generated by + Sphinx. It is a list of tuples containing the file name and the title. + Example:: + + epub_pre_files = [ + ('index.html', 'Welcome'), + ] + + The default value is ``[]``. + +.. confval:: epub_post_files + + Additional files that should be inserted after the text generated by + Sphinx. It is a list of tuples containing the file name and the title. + The default value is ``[]``. + +.. confval:: epub_exclude_files + + A list of files that are generated/copied in the build directory + but should not be included in the epub file. + The default value is ``[]``. + + .. _latex-options: Options for LaTeX output diff --git a/doc/faq.rst b/doc/faq.rst index 34502dc41..46a714dd0 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -54,5 +54,46 @@ github pages Sphinx HTML output. +Epub +---- + +The EpubBuilder is currently in an experimental stage. +It has only been tested with the Sphinx documentation itself. +If you want to create epubs, here are some notes: + +* Split the text into several files. The longer the individual HTML files + are, the longer it takes the ebook reader to render them. + In extreme cases, the rendering can take up to one minute. + +* Try to minimize the markup. This also pays in rendering time. + +* For some readers you can use embedded or external fonts + using the CSS ``@font-face`` directive. + This is *extremely* useful for code listings which are often cut + at the right margin. The default Courier font (or variant) is quite + wide and you can only display up to 60 characters on a line. + If you replace it with a narrower font, you can get more characters + on a line. You may even use + `fontforge `_ + and create narrow variants + of some free font. In my case I get up to 70 characters on a line. + + You may have to experiment a little until you get reasonable results. + +* Test the created epubs. You can use several alternatives. + The ones I am aware of are + Epubcheck + (`http://code.google.com/p/epubcheck/ + `_), + Calibre + (`http://calibre-ebook.com/ `_), + FBreader (`http://www.fbreader.org/ `_, + although it does not render the CSS), and + Bookworm (`http://bookworm.oreilly.com/ `_). + For bookworm you can download the source from + `http://code.google.com/p/threepress/ `_ + and run you own local server. + + .. _api role: http://git.savannah.gnu.org/cgit/kenozooid.git/tree/doc/extapi.py .. _xhtml to reST: http://docutils.sourceforge.net/sandbox/xhtml2rest/xhtml2rest.py diff --git a/doc/theming.rst b/doc/theming.rst index 7af54a0be..4f7cadd1d 100644 --- a/doc/theming.rst +++ b/doc/theming.rst @@ -160,6 +160,10 @@ These themes are: * **traditional** -- A theme resembling the old Python documentation. There are currently no options beyond *nosidebar*. +* **epub** -- A theme for the epub formatter. There are currently no + options. This theme tries to reduce visual space which is a sparse + resource on ebook readers. + Creating themes --------------- diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py index a8fc8871c..b35d0fd16 100644 --- a/sphinx/builders/__init__.py +++ b/sphinx/builders/__init__.py @@ -388,6 +388,7 @@ BUILTIN_BUILDERS = { 'htmlhelp': ('htmlhelp', 'HTMLHelpBuilder'), 'devhelp': ('devhelp', 'DevhelpBuilder'), 'qthelp': ('qthelp', 'QtHelpBuilder'), + 'epub': ('epub', 'EpubBuilder'), 'latex': ('latex', 'LaTeXBuilder'), 'text': ('text', 'TextBuilder'), 'changes': ('changes', 'ChangesBuilder'), diff --git a/sphinx/builders/epub.py b/sphinx/builders/epub.py new file mode 100644 index 000000000..57e2dff9a --- /dev/null +++ b/sphinx/builders/epub.py @@ -0,0 +1,409 @@ +# -*- coding: utf-8 -*- +""" + sphinx.builders.epub + ~~~~~~~~~~~~~~~~~~~~ + + Build epub files. + Originally derived from qthelp.py. + + :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import os +import re +import codecs +from os import path +import zipfile + +from docutils import nodes + +from sphinx import addnodes +from sphinx.builders.html import StandaloneHTMLBuilder + + +# (Fragment) templates from which the metainfo files +# content.opf, toc.ncx, mimetype, and META-INF/container.xml +# are created. + +_mimetype_template = 'application/epub+zip' # no EOL! + +_container_template = u'''\ + + + + + + +''' + +_toc_template = u'''\ + + + + + + + + + + %(title)s + + +%(navpoints)s + + +''' + +_navpoint_template = u'''\ +%(indent)s +%(indent)s +%(indent)s %(text)s +%(indent)s +%(indent)s +%(indent)s ''' + +_navpoint_indent = ' ' +_navPoint_template = 'navPoint%d' + +_content_template = u'''\ + + + + %(lang)s + %(title)s + %(author)s + %(publisher)s + %(copyright)s + %(id)s + + + +%(files)s + + +%(spine)s + + +''' + +_file_template = u'''\ + ''' + +_spine_template = u'''\ + ''' + +_toctree_template = u'toctree-l%d' + +_media_types = { + '.html': 'application/xhtml+xml', + '.css': 'text/css', + '.png': 'image/png', + '.gif': 'image/gif', + '.svg': 'image/svg+xml', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.otf': 'application/x-font-otf', + '.ttf': 'application/x-font-ttf', +} + + +# The epub publisher + +class EpubBuilder(StandaloneHTMLBuilder): + """Builder that outputs epub files. + It creates the metainfo files + container.opf, toc.ncx, mimetype, and META-INF/container.xml. + Afterwards, all necessary files are zipped to an epub file. + """ + name = 'epub' + + # don't copy the reST source + copysource = False + supported_image_types = ['image/svg+xml', 'image/png', 'image/gif', + 'image/jpeg'] + + # don't add links + add_permalinks = False + # don't add sidebar etc. + embedded = True + + def init(self): + StandaloneHTMLBuilder.init(self) + # the output files for HTML help must be .html only + self.out_suffix = '.html' + self.playorder = 0 + + + # generic support functions + def make_id(self, name): + """Replace all characters not allowed for (X)HTML ids""" + return name.replace('/', '_') + + def esc(self, name): + """Replace all characters not allowed in text an attribute values""" + # Like cgi.escape, but also replace apostrophe + name = name.replace('&', '&') + name = name.replace('<', '<') + name = name.replace('>', '>') + name = name.replace('"', '"') + name = name.replace('\'', ''') + return name + + def collapse_text(self, doctree, result): + """Remove all HTML markup and return only the text nodes""" + for c in doctree.children: + if isinstance(c, nodes.Text): + result.append(c.data) + else: + result = self.collapse_text(c, result) + return result + + def get_refnodes(self, doctree, result): + """Collect section titles, their depth in the toc and the refuri""" + # XXX: is there a betterr way than checking the attribute + # toctree-l[1-6] on the parent node? + if isinstance(doctree, nodes.reference): + classes = doctree.parent.attributes['classes'] + level = 1 + for l in range(5,0,-1): # or range(1,6)? + if (_toctree_template % l) in classes: + level = l + result.append({ + 'level': level, + 'refuri': self.esc(doctree['refuri']), + 'text': self.esc(''.join(self.collapse_text(doctree, []))) + }) + else: + for elem in doctree.children: + result = self.get_refnodes(elem, result) + return result + + def get_toc(self): + """Get the total table of contents, containg the master_doc + and pre and post files not managed by sphinx""" + doctree = self.env.get_and_resolve_doctree(self.config.master_doc, self) + self.refnodes = self.get_refnodes(doctree, []) + self.refnodes.insert(0, { + 'level': 1, + 'refuri': self.esc(self.config.master_doc + '.html'), + 'text': self.esc(''.join(self.collapse_text( + self.env.titles[self.config.master_doc], [] + ))), + }) + # XXX: is reversed ok? + for file, text in reversed(self.config.epub_pre_files): + self.refnodes.insert(0, { + 'level': 1, + 'refuri': self.esc(file + '.html'), + 'text': self.esc(text) + }) + for file, text in self.config.epub_post_files: + self.refnodes.append({ + 'level': 1, + 'refuri': self.esc(file + '.html'), + 'text': self.esc(text) + }) + + + # Finish by building the epub file + def handle_finish(self): + """Create the metainfo files and finally the epub""" + self.get_toc() + self.build_mimetype(self.outdir, 'mimetype') + self.build_container(self.outdir, 'META-INF/container.xml') + self.build_content(self.outdir, 'content.opf') + self.build_toc(self.outdir, 'toc.ncx') + self.build_epub(self.outdir, self.config.epub_basename + '.epub') + + def build_mimetype(self, outdir, outname): + """Write the metainfo file mimetype""" + self.info('writing %s file...' % outname) + f = codecs.open(path.join(outdir, outname), 'w', 'utf-8') + try: + f.write(_mimetype_template) + finally: + f.close() + + def build_container(self, outdir, outname): + """Write the metainfo file META-INF/cointainer.xml""" + self.info('writing %s file...' % outname) + fn = path.join(outdir, outname) + try: + os.mkdir(path.dirname(fn)) + except OSError, err: + if err.errno != os.errno.EEXIST: + raise + f = codecs.open(path.join(outdir, outname), 'w', 'utf-8') + try: + f.write(_container_template) + finally: + f.close() + + def content_metadata(self, files, spine): + """Create a dictionary with all metadata for the content.opf + file properly escaped""" + metadata = {} + metadata['title'] = self.esc(self.config.epub_title) + metadata['author'] = self.esc(self.config.epub_author) + metadata['uid'] = self.esc(self.config.epub_uid) + metadata['lang'] = self.esc(self.config.epub_language) + metadata['publisher'] = self.esc(self.config.epub_publisher) + metadata['copyright'] = self.esc(self.config.epub_copyright) + metadata['scheme'] = self.esc(self.config.epub_scheme) + metadata['id'] = self.esc(self.config.epub_identifier) + metadata['files'] = files + metadata['spine'] = spine + return metadata + + def build_content(self, outdir, outname): + """Write the metainfo file content.opf + It contains bibliographic data, a file list and + the spine (the reading order).""" + self.info('writing %s file...' % outname) + + # files + if not outdir.endswith(os.sep): + outdir += os.sep + olen = len(outdir) + projectfiles = [] + self.files = [] + self.ignored_files = ['.buildinfo', + 'mimetype', 'content.opf', 'toc.ncx', 'META-INF/container.xml', + self.config.epub_basename + '.epub'] + \ + self.config.epub_exclude_files + for root, dirs, files in os.walk(outdir): + for fn in files: + filename = path.join(root, fn)[olen:] + if filename in self.ignored_files: + # self.warn("ignoring %s" % filename) + continue + ext = path.splitext(filename)[-1] + if ext not in _media_types: + self.warn("unknown mimetype for %s, ignoring" % filename) + continue + projectfiles.append(_file_template % { + 'href': self.esc(filename), + 'id': self.esc(self.make_id(filename)), + 'media_type': self.esc(_media_types[ext]) + }) + self.files.append(filename) + projectfiles = '\n'.join(projectfiles) + + # spine + spine = [] + for item in self.refnodes: + if '#' in item['refuri']: + continue + if item['refuri'] in self.ignored_files: + continue + spine.append(_spine_template % { + 'idref': self.esc(self.make_id(item['refuri'])) + }) + spine = '\n'.join(spine) + + # write the project file + f = codecs.open(path.join(outdir, outname), 'w', 'utf-8') + try: + f.write(_content_template % \ + self.content_metadata(projectfiles, spine)) + finally: + f.close() + + def new_navpoint(self, node, level, incr=True): + """Create a new entry in the toc from the node at given level""" + # XXX Modifies the node + if incr: + self.playorder += 1 + node['indent'] = _navpoint_indent * level + node['navpoint'] = self.esc(_navPoint_template % self.playorder) + node['playorder'] = self.playorder + return _navpoint_template % node + + def insert_subnav(self, node, subnav): + """Insert nested navpoints for given node + The node and subnav are already rendered to text""" + nlist = node.split('\n') + nlist.insert(-1, subnav) + return '\n'.join(nlist) + + def build_navpoints(self, nodes): + """Create the toc navigation structure + Subelements of a node are nested inside the navpoint. + For nested nodes the parent node is reinserted in the subnav.""" + navstack = [] + navlist = [] + level = 1 + lastnode = None + for node in nodes: + file = node['refuri'].split('#')[0] + if file in self.ignored_files: + continue + if node['level'] == level: + navlist.append(self.new_navpoint(node,level)) + elif node['level'] == level + 1: + navstack.append(navlist) + navlist = [] + level += 1 + if lastnode: + # Insert starting point in subtoc with same playOrder + navlist.append(self.new_navpoint(lastnode, level, False)) + navlist.append(self.new_navpoint(node, level)) + else: + while node['level'] < level: + subnav = '\n'.join(navlist) + navlist = navstack.pop() + navlist[-1] = self.insert_subnav(navlist[-1], subnav) + level -= 1 + navlist.append(self.new_navpoint(node, level)) + lastnode = node + while level != 1: + subnav = '\n'.join(navlist) + navlist = navstack.pop() + navlist[-1] = self.insert_subnav(navlist[-1], subnav) + level -= 1 + return '\n'.join(navlist) + + def toc_metadata(self, level, navpoints): + """Create a dictionary with all metadata for the toc.ncx + file properly escaped""" + metadata = {} + metadata['uid'] = self.config.epub_uid + metadata['title'] = self.config.epub_title + metadata['level'] = level + metadata['navpoints'] = navpoints + return metadata + + def build_toc(self, outdir, outname): + """Write the metainfo file toc.ncx""" + self.info('writing %s file...' % outname) + + navpoints = self.build_navpoints(self.refnodes) + level = max(item['level'] for item in self.refnodes) + f = codecs.open(path.join(outdir, outname), 'w', 'utf-8') + try: + f.write(_toc_template % self.toc_metadata(level, navpoints)) + finally: + f.close() + + def build_epub(self, outdir, outname): + """Write the epub file + It is a zip file with the mimetype file stored uncompressed + as the first entry.""" + self.info('writing %s file...' % outname) + projectfiles = ['META-INF/container.xml', 'content.opf', 'toc.ncx'] \ + + self.files + epub = zipfile.ZipFile(path.join(outdir, outname), 'w', \ + zipfile.ZIP_DEFLATED) + epub.write(path.join(outdir, 'mimetype'), 'mimetype', \ + zipfile.ZIP_STORED) + for file in projectfiles: + epub.write(path.join(outdir, file), file, zipfile.ZIP_DEFLATED) + epub.close() + diff --git a/sphinx/config.py b/sphinx/config.py index b2ef29fb1..5050a7796 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -112,6 +112,20 @@ class Config(object): latex_docclass = ({}, None), # now deprecated - use latex_elements latex_preamble = ('', None), + + # Epub options + epub_basename = (lambda self: make_filename(self.project), None), + epub_title = (lambda self: self.html_title, 'html'), + epub_author = ('unknown', 'html'), + epub_language = (lambda self: self.language or 'en', 'html'), + epub_publisher = ('unknown', 'html'), + epub_copyright = (lambda self: self.copyright, 'html'), + epub_identifier = ('unknown', 'html'), + epub_scheme = ('unknown', 'html'), + epub_uid = ('unknown', 'env'), + epub_pre_files = ([], 'env'), + epub_post_files = ([], 'env'), + epub_exclude_files = ([], 'env'), ) def __init__(self, dirname, filename, overrides, tags): diff --git a/sphinx/quickstart.py b/sphinx/quickstart.py index 7b11dfdd4..eaf9f55a0 100644 --- a/sphinx/quickstart.py +++ b/sphinx/quickstart.py @@ -223,6 +223,56 @@ latex_documents = [ # If false, no module index is generated. #latex_use_modindex = True + + +# -- Options for Epub output --------------------------------------------------- + +# Please also set the html_theme to 'epub' or any other approriate theme. +# The display size is quite small for ebook readers. +# The default themes may take too much space. + +# bibliographic Dublin Core description of the content.opf and +# in the toc.ncx file. It defaults to the html_title option. +#epub_title = '' + +# The author of the text. The author is inserted into the +# bibliographic Dublin Core description of the content.opf file. +#epub_author = '' + +# The language of the text. It defaults to the language option +# or en if the language is not set. +#epub_language = '' + +# The publisher of the text. The publisher is inserted into the +# bibliographic Dublin Core description of the content.opf file. +# You may use the project homepage. +#epub_publisher = '' + +# The copyright of the text. The copyright is inserted into the +# bibliographci Dublin Core description of the content.opf file. +# It defaults to the copyright option. +#epub_copyright = '' + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +#epub_identifier = '' + +# The scheme of the identifier. Typical schemes are ISBN or URL. +#epub_scheme = '' + +# A unique identification for the text. +#epub_uid = '' + +# HTML files that should be inserted before the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_pre_files = [] + +# HTML files shat should be inserted after the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_post_files = [] + +# A list of files that should not be packed into the epub file. +#epub_exclude_files = [] ''' INTERSPHINX_CONFIG = ''' @@ -270,8 +320,8 @@ PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) \ $(SPHINXOPTS) %(rsrcdir)s -.PHONY: help clean html dirhtml pickle json htmlhelp qthelp latex changes \ -linkcheck doctest +.PHONY: help clean html dirhtml pickle json htmlhelp qthelp epub \ +latex changes linkcheck doctest help: \t@echo "Please use \\`make ' where is one of" @@ -282,6 +332,7 @@ help: \t@echo " htmlhelp to make HTML files and a HTML help project" \t@echo " qthelp to make HTML files and a qthelp project" \t@echo " devhelp to make HTML files and a Devhelp project" +\t@echo " epub to make an epub" \t@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" \t@echo " latexpdf to make LaTeX files and run them through pdflatex" \t@echo " changes to make an overview of all changed/added/deprecated items" @@ -337,6 +388,11 @@ devhelp: $$HOME/.local/share/devhelp/%(project_fn)s" \t@echo "# devhelp" +epub: +\t$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub +\t@echo +\t@echo "Build finished. The epub file is in $(BUILDDIR)/epub." + latex: \t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex \t@echo @@ -391,6 +447,7 @@ if "%%1" == "help" ( \techo. htmlhelp to make HTML files and a HTML help project \techo. qthelp to make HTML files and a qthelp project \techo. devhelp to make HTML files and a Devhelp project +\techo. epub to make an epub \techo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter \techo. changes to make an overview over all changed/added/deprecated items \techo. linkcheck to check all external links for integrity @@ -458,6 +515,13 @@ if "%%1" == "devhelp" ( \tgoto end ) +if "%%1" == "epub" ( +\t%%SPHINXBUILD%% -b epub %%ALLSPHINXOPTS%% %%BUILDDIR%%/epub +\techo. +\techo.Build finished. The epub file is in %%BUILDDIR%%/epub. +\tgoto end +) + if "%%1" == "latex" ( \t%%SPHINXBUILD%% -b latex %%ALLSPHINXOPTS%% %%BUILDDIR%%/latex \techo. diff --git a/sphinx/themes/epub/layout.html b/sphinx/themes/epub/layout.html new file mode 100644 index 000000000..64b1a4cb2 --- /dev/null +++ b/sphinx/themes/epub/layout.html @@ -0,0 +1,7 @@ +{% extends "basic/layout.html" %} + +{# add only basic navigation links #} +{% block sidebar1 %}{% endblock %} +{% block sidebar2 %}{% endblock %} +{% block relbar2 %}{% endblock %} +{% block linktags %}{% endblock %} diff --git a/sphinx/themes/epub/static/epub.css b/sphinx/themes/epub/static/epub.css new file mode 100644 index 000000000..72b771905 --- /dev/null +++ b/sphinx/themes/epub/static/epub.css @@ -0,0 +1,445 @@ +/** + * Sphinx stylesheet -- epub theme + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +a:link, a:visited { + color: #3333ff; + text-decoration: underline; +} + +img { + border: 0; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-family: sans-serif; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 100%; +} + +img { + border: 0; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 130%; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable dl, table.indextable dd { + margin-top: 0; + margin-bottom: 0; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +/* -- general body styles --------------------------------------------------- */ + +a.headerlink { + visibility: hidden; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.field-list ul { + padding-left: 100%; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px 7px 0 7px; + background-color: #ffe; + width: 40%; + float: right; +} + +p.sidebar-title { + font-weight: bold; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px 7px 0 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 110%; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +div.admonition dl { + margin-bottom: 0; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + border: 0; + border-collapse: collapse; +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.field-list td, table.field-list th { + border: 0 !important; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +/* -- other body styles ----------------------------------------------------- */ + +dl { + margin-bottom: 15px; +} + +dd p { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dt:target, .highlight { + background-color: #ddd; +} + +dl.glossary dt { + font-weight: bold; + font-size: 110%; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.refcount { + color: #060; +} + +.optional { + font-size: 130%; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #dddddd; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + font-family: "LiberationNarrow", monospace; + overflow: auto; +} + +td.linenos pre { + padding: 5px 0px; + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + margin-left: 0.5em; +} + +table.highlighttable td { + padding: 0 0.5em 0 0.5em; +} + +tt { + font-family: "LiberationNarrow", monospace; +} + +tt.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; +} + +tt.descclassname { + background-color: transparent; +} + +tt.xref, a tt { + background-color: transparent; + font-weight: bold; +} + +h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { + background-color: transparent; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +/* -- special divs --------------------------------------------------------- */ + +div.quotebar { + background-color: #e3eff1; + max-width: 250px; + float: right; + font-family: sans-serif; + padding: 7px 7px; + border: 1px solid #ccc; +} +div.footer { + background-color: #e3eff1; + padding: 3px 8px 3px 0; + clear: both; + font-family: sans-serif; + font-size: 80%; + text-align: right; +} + +div.footer a { + text-decoration: underline; +} + +@font-face { + font-family: "LiberationNarrow"; + font-style: normal; + font-weight: normal; + src: url("res:///Data/fonts/LiberationNarrow-Regular.otf") + format("opentype"); +} +@font-face { + font-family: "LiberationNarrow"; + font-style: oblique, italic; + font-weight: normal; + src: url("res:///Data/fonts/LiberationNarrow-Italic.otf") + format("opentype"); +} +@font-face { + font-family: "LiberationNarrow"; + font-style: normal; + font-weight: bold; + src: url("res:///Data/fonts/LiberationNarrow-Bold.otf") + format("opentype"); +} +@font-face { + font-family: "LiberationNarrow"; + font-style: oblique, italic; + font-weight: bold; + src: url("res:///Data/fonts/LiberationNarrow-BoldItalic.otf") + format("opentype"); +} + diff --git a/sphinx/themes/epub/theme.conf b/sphinx/themes/epub/theme.conf new file mode 100644 index 000000000..d5806ec50 --- /dev/null +++ b/sphinx/themes/epub/theme.conf @@ -0,0 +1,4 @@ +[theme] +inherit = basic +stylesheet = epub.css +pygments_style = none diff --git a/tests/test_build.py b/tests/test_build.py index 6668e2453..6d0cb9334 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -277,6 +277,10 @@ def test_htmlhelp(app): def test_qthelp(app): app.builder.build_all() +@with_app(buildername='epub') +def test_epub(app): + app.builder.build_all() + @with_app(buildername='changes', cleanenv=True) def test_changes(app): app.builder.build_all() diff --git a/tests/test_theming.py b/tests/test_theming.py index 065468b07..cdaf2baff 100644 --- a/tests/test_theming.py +++ b/tests/test_theming.py @@ -25,7 +25,7 @@ def test_theme_api(app): # test Theme class API assert set(Theme.themes.keys()) == \ set(['basic', 'default', 'scrolls', 'agogo', 'sphinxdoc', - 'traditional', 'testtheme', 'ziptheme']) + 'traditional', 'testtheme', 'ziptheme', 'epub']) assert Theme.themes['testtheme'][1] is None assert isinstance(Theme.themes['ziptheme'][1], zipfile.ZipFile) From eee47685fa9b98c3aa994ea74a906f44c3f706ea Mon Sep 17 00:00:00 2001 From: Roland Meister Date: Mon, 28 Dec 2009 17:11:15 +0100 Subject: [PATCH 02/11] Activated html_theme 'epub' --- doc/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index f8310746c..7393643a1 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -34,7 +34,7 @@ release = version show_authors = True # The HTML template theme. -html_theme = 'sphinxdoc' +html_theme = 'epub' # A list of ignored prefixes names for module index sorting. modindex_common_prefix = ['sphinx.'] From 6ced1ce311666fc33ec3b75835b4ef31b2ce7eae Mon Sep 17 00:00:00 2001 From: Roland Meister Date: Mon, 28 Dec 2009 17:16:19 +0100 Subject: [PATCH 03/11] Disable embedded css for embedded builders. Note: this change also affects all other embedded builders (but only for the Shinx doc itself). --- doc/_templates/layout.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/_templates/layout.html b/doc/_templates/layout.html index aa33576a7..e41b68b17 100644 --- a/doc/_templates/layout.html +++ b/doc/_templates/layout.html @@ -1,10 +1,12 @@ {% extends "!layout.html" %} {% block extrahead %} +{%- if not embedded %} +{% endif %} {% endblock %} {% block rootrellink %} From b6f93999617c73163e881bd29a40d98b49df58ac Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 28 Dec 2009 19:08:32 +0100 Subject: [PATCH 04/11] Add change and author info for epub builder. --- AUTHORS | 1 + CHANGES | 2 ++ 2 files changed, 3 insertions(+) diff --git a/AUTHORS b/AUTHORS index 09be75330..ebda43077 100644 --- a/AUTHORS +++ b/AUTHORS @@ -15,6 +15,7 @@ Other contributors, listed alphabetically, are: * Thomas Lamb -- linkcheck builder * Dan MacKinlay -- metadata fixes * Will Maier -- directory HTML builder +* Roland Meister -- epub builder * Christopher Perkins -- autosummary integration * Benjamin Peterson -- unittests * Stefan Seefeld -- toctree improvements diff --git a/CHANGES b/CHANGES index 85982d05e..e02438132 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,8 @@ Release 1.0 (in development) ============================ +* Added Epub builder. + * #284: All docinfo metadata is now put into the document metadata, not just the author. From 24fb46226fbb8ca08ff0a1bd6e32f322515b16db Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 29 Dec 2009 12:32:42 +0100 Subject: [PATCH 05/11] Review of epub builder. Add a separate "epub_theme" config value for selecting a different theme. --- doc/_templates/layout.html | 3 +- doc/builders.rst | 8 ++-- doc/conf.py | 7 +-- doc/config.rst | 87 ++++++++++++++++++-------------------- doc/faq.rst | 60 ++++++++++++-------------- doc/theming.rst | 6 +-- sphinx/builders/epub.py | 73 +++++++++++++++++--------------- sphinx/builders/html.py | 10 +++-- sphinx/config.py | 29 +++++++------ sphinx/quickstart.py | 26 +++--------- 10 files changed, 148 insertions(+), 161 deletions(-) diff --git a/doc/_templates/layout.html b/doc/_templates/layout.html index e41b68b17..60d217df5 100644 --- a/doc/_templates/layout.html +++ b/doc/_templates/layout.html @@ -1,12 +1,13 @@ {% extends "!layout.html" %} {% block extrahead %} +{{ super() }} {%- if not embedded %} -{% endif %} +{%- endif %} {% endblock %} {% block rootrellink %} diff --git a/doc/builders.rst b/doc/builders.rst index 3ce8f56a7..23556fb18 100644 --- a/doc/builders.rst +++ b/doc/builders.rst @@ -67,11 +67,9 @@ The builder's "name" must be given to the **-b** command-line option of .. class:: EpubBuilder This builder produces the same output as the standalone HTML builder, but - also generates an *epub* file for ebook readers. - This builder is meant to be used together with the - :confval:`html_theme` ``'epub'``. - See ``_ or - ``_ for the definition of epubs. + also generates an *epub* file for ebook readers. See + ``_ or ``_ + for the definition of the epub format. Its name is ``epub``. diff --git a/doc/conf.py b/doc/conf.py index 7393643a1..de7fc59cd 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -34,7 +34,7 @@ release = version show_authors = True # The HTML template theme. -html_theme = 'epub' +html_theme = 'sphinxdoc' # A list of ignored prefixes names for module index sorting. modindex_common_prefix = ['sphinx.'] @@ -65,12 +65,13 @@ html_use_opensearch = 'http://sphinx.pocoo.org' htmlhelp_basename = 'Sphinxdoc' # Epub fields +epub_theme = 'epub' epub_basename = 'sphinx' -epub_author = 'Georg Brandl' +epub_author = 'Georg Brandl' epub_publisher = 'http://sphinx.pocoo.org/' epub_scheme = 'url' epub_identifier = epub_publisher -epub_pre_files = [ ('index', 'Welcome')] +epub_pre_files = [('index', 'Welcome')] epub_exclude_files = ['_static/opensearch.xml', '_static/doctools.js', '_static/jquery.js', '_static/searchtools.js', '_static/basic.css', 'search.html'] diff --git a/doc/config.rst b/doc/config.rst index 99b484c85..dbcf799c1 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -554,92 +554,89 @@ that use Sphinx' HTMLWriter class. Options for epub output ----------------------- -These options influence the epub output. As this writer derives from the -HTMLWriter the HTML options also apply where appropriate. -The actual values for some of the options is not really important, -they just have to be entered into +These options influence the epub output. As this builder derives from the HTML +builder, the HTML options also apply where appropriate. The actual values for +some of the options is not really important, they just have to be entered into the `Dublin Core metadata `_. .. confval:: epub_basename - The basename for the epub file. It defaults to the :confval:`project` name. + The basename for the epub file. It defaults to the :confval:`project` name. + +.. confval:: epub_theme + + The HTML theme for the epub output. Since the default themes are not + optimized for small screen space, using the same theme for HTML and epub + output is usually not wise. This defaults to ``'epub'``, a theme designed to + save visual space. .. confval:: epub_title - The title of the document. - It defaults to the :confval:`html_title` option - but can be set independently for epub creation. + The title of the document. It defaults to the :confval:`html_title` option + but can be set independently for epub creation. .. confval:: epub_author - The author of the document. - This is put in the Dublin Core metadata. - The default value is ``'unknown'``. + The author of the document. This is put in the Dublin Core metadata. The + default value is ``'unknown'``. .. confval:: epub_language - The language of the document. - This is put in the Dublin Core metadata. - The default is the :confval:`language` option or ``'en'`` if unset. + The language of the document. This is put in the Dublin Core metadata. The + default is the :confval:`language` option or ``'en'`` if unset. .. confval:: epub_publisher - The publisher of the document. - This is put in the Dublin Core metadata. - You may use any sensible string, e.g. the project homepage. - The default value is ``'unknown'``. + The publisher of the document. This is put in the Dublin Core metadata. You + may use any sensible string, e.g. the project homepage. The default value is + ``'unknown'``. .. confval:: epub_copyright - The copyright of the document. - It defaults to the :confval:`copyright` option - but can be set independently for epub creation. + The copyright of the document. It defaults to the :confval:`copyright` + option but can be set independently for epub creation. .. confval:: epub_identifier - An identifier for the document. - This is put in the Dublin Core metadata. - For published documents this is the ISBN number, but you can - also use an alternative scheme, e.g. the project homepage. - The default value is ``'unknown'``. + An identifier for the document. This is put in the Dublin Core metadata. + For published documents this is the ISBN number, but you can also use an + alternative scheme, e.g. the project homepage. The default value is + ``'unknown'``. .. confval:: epub_scheme - The publication scheme for the :confval:`epub_identifier`. - This is put in the Dublin Core metadata. - For published books the scheme is ``'ISBN'``. - If you use the project homepage, ``'URL'`` seems reasonable. + The publication scheme for the :confval:`epub_identifier`. This is put in + the Dublin Core metadata. For published books the scheme is ``'ISBN'``. If + you use the project homepage, ``'URL'`` seems reasonable. The default value + is ``'unknown'``. .. confval:: epub_uid - A unique identifier for the document. - This is put in the Dublin Core metadata. - You may use a random string. - The default value is ``'unknown'``. + A unique identifier for the document. This is put in the Dublin Core + metadata. You may use a random string. The default value is ``'unknown'``. .. confval:: epub_pre_files - Additional files that should be inserted before the text generated by - Sphinx. It is a list of tuples containing the file name and the title. - Example:: + Additional files that should be inserted before the text generated by + Sphinx. It is a list of tuples containing the file name and the title. + Example:: epub_pre_files = [ - ('index.html', 'Welcome'), + ('index.html', 'Welcome'), ] - The default value is ``[]``. + The default value is ``[]``. .. confval:: epub_post_files - Additional files that should be inserted after the text generated by - Sphinx. It is a list of tuples containing the file name and the title. - The default value is ``[]``. + Additional files that should be inserted after the text generated by Sphinx. + It is a list of tuples containing the file name and the title. The default + value is ``[]``. .. confval:: epub_exclude_files - A list of files that are generated/copied in the build directory - but should not be included in the epub file. - The default value is ``[]``. + A list of files that are generated/copied in the build directory but should + not be included in the epub file. The default value is ``[]``. .. _latex-options: diff --git a/doc/faq.rst b/doc/faq.rst index 46a714dd0..20b9b62cb 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -54,46 +54,40 @@ github pages Sphinx HTML output. +.. _api role: http://git.savannah.gnu.org/cgit/kenozooid.git/tree/doc/extapi.py +.. _xhtml to reST: http://docutils.sourceforge.net/sandbox/xhtml2rest/xhtml2rest.py + + Epub ---- -The EpubBuilder is currently in an experimental stage. -It has only been tested with the Sphinx documentation itself. -If you want to create epubs, here are some notes: +The epub builder is currently in an experimental stage. It has only been tested +with the Sphinx documentation itself. If you want to create epubs, here are +some notes: -* Split the text into several files. The longer the individual HTML files - are, the longer it takes the ebook reader to render them. - In extreme cases, the rendering can take up to one minute. +* Split the text into several files. The longer the individual HTML files are, + the longer it takes the ebook reader to render them. In extreme cases, the + rendering can take up to one minute. -* Try to minimize the markup. This also pays in rendering time. +* Try to minimize the markup. This also pays in rendering time. -* For some readers you can use embedded or external fonts - using the CSS ``@font-face`` directive. - This is *extremely* useful for code listings which are often cut - at the right margin. The default Courier font (or variant) is quite - wide and you can only display up to 60 characters on a line. - If you replace it with a narrower font, you can get more characters - on a line. You may even use - `fontforge `_ - and create narrow variants - of some free font. In my case I get up to 70 characters on a line. +* For some readers you can use embedded or external fonts using the CSS + ``@font-face`` directive. This is *extremely* useful for code listings which + are often cut at the right margin. The default Courier font (or variant) is + quite wide and you can only display up to 60 characters on a line. If you + replace it with a narrower font, you can get more characters on a line. You + may even use `FontForge `_ and create + narrow variants of some free font. In my case I get up to 70 characters on a + line. You may have to experiment a little until you get reasonable results. -* Test the created epubs. You can use several alternatives. - The ones I am aware of are - Epubcheck - (`http://code.google.com/p/epubcheck/ - `_), - Calibre - (`http://calibre-ebook.com/ `_), - FBreader (`http://www.fbreader.org/ `_, - although it does not render the CSS), and - Bookworm (`http://bookworm.oreilly.com/ `_). - For bookworm you can download the source from - `http://code.google.com/p/threepress/ `_ - and run you own local server. +* Test the created epubs. You can use several alternatives. The ones I am aware + of are Epubcheck_, Calibre_, FBreader_ (although it does not render the CSS), + and Bookworm_. For bookworm you can download the source from + http://code.google.com/p/threepress/ and run your own local server. - -.. _api role: http://git.savannah.gnu.org/cgit/kenozooid.git/tree/doc/extapi.py -.. _xhtml to reST: http://docutils.sourceforge.net/sandbox/xhtml2rest/xhtml2rest.py +.. _Epubcheck: http://code.google.com/p/epubcheck/ +.. _Calibre: http://calibre-ebook.com/ +.. _FBreader: http://www.fbreader.org/ +.. _Bookworm: http://bookworm.oreilly.com/ diff --git a/doc/theming.rst b/doc/theming.rst index 4f7cadd1d..c73b4f45d 100644 --- a/doc/theming.rst +++ b/doc/theming.rst @@ -160,9 +160,9 @@ These themes are: * **traditional** -- A theme resembling the old Python documentation. There are currently no options beyond *nosidebar*. -* **epub** -- A theme for the epub formatter. There are currently no - options. This theme tries to reduce visual space which is a sparse - resource on ebook readers. +* **epub** -- A theme for the epub builder. There are currently no options. + This theme tries to save visual space which is a sparse resource on ebook + readers. Creating themes diff --git a/sphinx/builders/epub.py b/sphinx/builders/epub.py index 57e2dff9a..cfe6a7372 100644 --- a/sphinx/builders/epub.py +++ b/sphinx/builders/epub.py @@ -11,20 +11,17 @@ """ import os -import re import codecs from os import path import zipfile from docutils import nodes -from sphinx import addnodes from sphinx.builders.html import StandaloneHTMLBuilder -# (Fragment) templates from which the metainfo files -# content.opf, toc.ncx, mimetype, and META-INF/container.xml -# are created. +# (Fragment) templates from which the metainfo files content.opf, toc.ncx, +# mimetype, and META-INF/container.xml are created. _mimetype_template = 'application/epub+zip' # no EOL! @@ -118,9 +115,10 @@ _media_types = { class EpubBuilder(StandaloneHTMLBuilder): """Builder that outputs epub files. - It creates the metainfo files - container.opf, toc.ncx, mimetype, and META-INF/container.xml. - Afterwards, all necessary files are zipped to an epub file. + + It creates the metainfo files container.opf, toc.ncx, mimetype, and + META-INF/container.xml. Afterwards, all necessary files are zipped to an + epub file. """ name = 'epub' @@ -136,18 +134,20 @@ class EpubBuilder(StandaloneHTMLBuilder): def init(self): StandaloneHTMLBuilder.init(self) - # the output files for HTML help must be .html only + # the output files for epub must be .html only self.out_suffix = '.html' self.playorder = 0 + def get_theme_config(self): + return self.config.epub_theme, {} # generic support functions def make_id(self, name): - """Replace all characters not allowed for (X)HTML ids""" + """Replace all characters not allowed for (X)HTML ids.""" return name.replace('/', '_') def esc(self, name): - """Replace all characters not allowed in text an attribute values""" + """Replace all characters not allowed in text an attribute values.""" # Like cgi.escape, but also replace apostrophe name = name.replace('&', '&') name = name.replace('<', '<') @@ -157,17 +157,17 @@ class EpubBuilder(StandaloneHTMLBuilder): return name def collapse_text(self, doctree, result): - """Remove all HTML markup and return only the text nodes""" + """Remove all HTML markup and return only the text nodes.""" for c in doctree.children: if isinstance(c, nodes.Text): - result.append(c.data) + result.append(unicode(c)) else: result = self.collapse_text(c, result) return result def get_refnodes(self, doctree, result): - """Collect section titles, their depth in the toc and the refuri""" - # XXX: is there a betterr way than checking the attribute + """Collect section titles, their depth in the toc and the refuri.""" + # XXX: is there a better way than checking the attribute # toctree-l[1-6] on the parent node? if isinstance(doctree, nodes.reference): classes = doctree.parent.attributes['classes'] @@ -187,7 +187,8 @@ class EpubBuilder(StandaloneHTMLBuilder): def get_toc(self): """Get the total table of contents, containg the master_doc - and pre and post files not managed by sphinx""" + and pre and post files not managed by sphinx. + """ doctree = self.env.get_and_resolve_doctree(self.config.master_doc, self) self.refnodes = self.get_refnodes(doctree, []) self.refnodes.insert(0, { @@ -214,7 +215,7 @@ class EpubBuilder(StandaloneHTMLBuilder): # Finish by building the epub file def handle_finish(self): - """Create the metainfo files and finally the epub""" + """Create the metainfo files and finally the epub.""" self.get_toc() self.build_mimetype(self.outdir, 'mimetype') self.build_container(self.outdir, 'META-INF/container.xml') @@ -223,7 +224,7 @@ class EpubBuilder(StandaloneHTMLBuilder): self.build_epub(self.outdir, self.config.epub_basename + '.epub') def build_mimetype(self, outdir, outname): - """Write the metainfo file mimetype""" + """Write the metainfo file mimetype.""" self.info('writing %s file...' % outname) f = codecs.open(path.join(outdir, outname), 'w', 'utf-8') try: @@ -232,7 +233,7 @@ class EpubBuilder(StandaloneHTMLBuilder): f.close() def build_container(self, outdir, outname): - """Write the metainfo file META-INF/cointainer.xml""" + """Write the metainfo file META-INF/cointainer.xml.""" self.info('writing %s file...' % outname) fn = path.join(outdir, outname) try: @@ -248,7 +249,8 @@ class EpubBuilder(StandaloneHTMLBuilder): def content_metadata(self, files, spine): """Create a dictionary with all metadata for the content.opf - file properly escaped""" + file properly escaped. + """ metadata = {} metadata['title'] = self.esc(self.config.epub_title) metadata['author'] = self.esc(self.config.epub_author) @@ -263,9 +265,9 @@ class EpubBuilder(StandaloneHTMLBuilder): return metadata def build_content(self, outdir, outname): - """Write the metainfo file content.opf - It contains bibliographic data, a file list and - the spine (the reading order).""" + """Write the metainfo file content.opf It contains bibliographic data, + a file list and the spine (the reading order). + """ self.info('writing %s file...' % outname) # files @@ -317,7 +319,7 @@ class EpubBuilder(StandaloneHTMLBuilder): f.close() def new_navpoint(self, node, level, incr=True): - """Create a new entry in the toc from the node at given level""" + """Create a new entry in the toc from the node at given level.""" # XXX Modifies the node if incr: self.playorder += 1 @@ -327,16 +329,19 @@ class EpubBuilder(StandaloneHTMLBuilder): return _navpoint_template % node def insert_subnav(self, node, subnav): - """Insert nested navpoints for given node - The node and subnav are already rendered to text""" + """Insert nested navpoints for given node. + The node and subnav are already rendered to text. + """ nlist = node.split('\n') nlist.insert(-1, subnav) return '\n'.join(nlist) def build_navpoints(self, nodes): - """Create the toc navigation structure + """Create the toc navigation structure. + Subelements of a node are nested inside the navpoint. - For nested nodes the parent node is reinserted in the subnav.""" + For nested nodes the parent node is reinserted in the subnav. + """ navstack = [] navlist = [] level = 1 @@ -372,7 +377,8 @@ class EpubBuilder(StandaloneHTMLBuilder): def toc_metadata(self, level, navpoints): """Create a dictionary with all metadata for the toc.ncx - file properly escaped""" + file properly escaped. + """ metadata = {} metadata['uid'] = self.config.epub_uid metadata['title'] = self.config.epub_title @@ -381,7 +387,7 @@ class EpubBuilder(StandaloneHTMLBuilder): return metadata def build_toc(self, outdir, outname): - """Write the metainfo file toc.ncx""" + """Write the metainfo file toc.ncx.""" self.info('writing %s file...' % outname) navpoints = self.build_navpoints(self.refnodes) @@ -393,9 +399,11 @@ class EpubBuilder(StandaloneHTMLBuilder): f.close() def build_epub(self, outdir, outname): - """Write the epub file + """Write the epub file. + It is a zip file with the mimetype file stored uncompressed - as the first entry.""" + as the first entry. + """ self.info('writing %s file...' % outname) projectfiles = ['META-INF/container.xml', 'content.opf', 'toc.ncx'] \ + self.files @@ -406,4 +414,3 @@ class EpubBuilder(StandaloneHTMLBuilder): for file in projectfiles: epub.write(path.join(outdir, file), file, zipfile.ZIP_DEFLATED) epub.close() - diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index 54327fa1e..04b6cbc39 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -101,9 +101,14 @@ class StandaloneHTMLBuilder(Builder): if path.isfile(jsfile): self.script_files.append('_static/translations.js') + def get_theme_config(self): + return self.config.html_theme, self.config.html_theme_options + def init_templates(self): Theme.init_themes(self) - self.theme = Theme(self.config.html_theme) + themename, themeoptions = self.get_theme_config() + self.theme = Theme(themename) + self.theme_options = themeoptions.copy() self.create_template_bridge() self.templates.init(self, self.theme) @@ -279,8 +284,7 @@ class StandaloneHTMLBuilder(Builder): if self.theme: self.globalcontext.update( ('theme_' + key, val) for (key, val) in - self.theme.get_options( - self.config.html_theme_options).iteritems()) + self.theme.get_options(self.theme_options).iteritems()) self.globalcontext.update(self.config.html_context) def get_doc_context(self, docname, body, metatags): diff --git a/sphinx/config.py b/sphinx/config.py index 5050a7796..d7edb004d 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -97,6 +97,21 @@ class Config(object): # Devhelp only options devhelp_basename = (lambda self: make_filename(self.project), None), + # Epub options + epub_basename = (lambda self: make_filename(self.project), None), + epub_theme = ('epub', 'html'), + epub_title = (lambda self: self.html_title, 'html'), + epub_author = ('unknown', 'html'), + epub_language = (lambda self: self.language or 'en', 'html'), + epub_publisher = ('unknown', 'html'), + epub_copyright = (lambda self: self.copyright, 'html'), + epub_identifier = ('unknown', 'html'), + epub_scheme = ('unknown', 'html'), + epub_uid = ('unknown', 'env'), + epub_pre_files = ([], 'env'), + epub_post_files = ([], 'env'), + epub_exclude_files = ([], 'env'), + # LaTeX options latex_documents = ([], None), latex_logo = (None, None), @@ -112,20 +127,6 @@ class Config(object): latex_docclass = ({}, None), # now deprecated - use latex_elements latex_preamble = ('', None), - - # Epub options - epub_basename = (lambda self: make_filename(self.project), None), - epub_title = (lambda self: self.html_title, 'html'), - epub_author = ('unknown', 'html'), - epub_language = (lambda self: self.language or 'en', 'html'), - epub_publisher = ('unknown', 'html'), - epub_copyright = (lambda self: self.copyright, 'html'), - epub_identifier = ('unknown', 'html'), - epub_scheme = ('unknown', 'html'), - epub_uid = ('unknown', 'env'), - epub_pre_files = ([], 'env'), - epub_post_files = ([], 'env'), - epub_exclude_files = ([], 'env'), ) def __init__(self, dirname, filename, overrides, tags): diff --git a/sphinx/quickstart.py b/sphinx/quickstart.py index eaf9f55a0..e09bee33f 100644 --- a/sphinx/quickstart.py +++ b/sphinx/quickstart.py @@ -227,39 +227,23 @@ latex_documents = [ # -- Options for Epub output --------------------------------------------------- -# Please also set the html_theme to 'epub' or any other approriate theme. -# The display size is quite small for ebook readers. -# The default themes may take too much space. - -# bibliographic Dublin Core description of the content.opf and -# in the toc.ncx file. It defaults to the html_title option. +# Bibliographic Dublin Core info. #epub_title = '' - -# The author of the text. The author is inserted into the -# bibliographic Dublin Core description of the content.opf file. #epub_author = '' +#epub_publisher = '' +#epub_copyright = '' # The language of the text. It defaults to the language option # or en if the language is not set. #epub_language = '' -# The publisher of the text. The publisher is inserted into the -# bibliographic Dublin Core description of the content.opf file. -# You may use the project homepage. -#epub_publisher = '' - -# The copyright of the text. The copyright is inserted into the -# bibliographci Dublin Core description of the content.opf file. -# It defaults to the copyright option. -#epub_copyright = '' +# The scheme of the identifier. Typical schemes are ISBN or URL. +#epub_scheme = '' # The unique identifier of the text. This can be a ISBN number # or the project homepage. #epub_identifier = '' -# The scheme of the identifier. Typical schemes are ISBN or URL. -#epub_scheme = '' - # A unique identification for the text. #epub_uid = '' From e5561f9b1bbb900622b8125a346e98c4f03cea09 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 29 Dec 2009 12:41:35 +0100 Subject: [PATCH 06/11] Add cross-reference. --- doc/builders.rst | 6 +++--- doc/faq.rst | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/doc/builders.rst b/doc/builders.rst index 23556fb18..e8dccc430 100644 --- a/doc/builders.rst +++ b/doc/builders.rst @@ -67,9 +67,9 @@ The builder's "name" must be given to the **-b** command-line option of .. class:: EpubBuilder This builder produces the same output as the standalone HTML builder, but - also generates an *epub* file for ebook readers. See - ``_ or ``_ - for the definition of the epub format. + also generates an *epub* file for ebook readers. See :ref:`epub-faq` for + details about it. For definition of the epub format, have a look at + ``_ or ``_. Its name is ``epub``. diff --git a/doc/faq.rst b/doc/faq.rst index 20b9b62cb..401e1c751 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -58,8 +58,10 @@ github pages .. _xhtml to reST: http://docutils.sourceforge.net/sandbox/xhtml2rest/xhtml2rest.py -Epub ----- +.. _epub-faq: + +Epub info +--------- The epub builder is currently in an experimental stage. It has only been tested with the Sphinx documentation itself. If you want to create epubs, here are From 51760a0fdedd75bea652acb2a666f943ea947f30 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 29 Dec 2009 12:48:48 +0100 Subject: [PATCH 07/11] Try fixing problems with docutils 0.4. --- sphinx/builders/epub.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/builders/epub.py b/sphinx/builders/epub.py index cfe6a7372..46cf7641e 100644 --- a/sphinx/builders/epub.py +++ b/sphinx/builders/epub.py @@ -160,7 +160,7 @@ class EpubBuilder(StandaloneHTMLBuilder): """Remove all HTML markup and return only the text nodes.""" for c in doctree.children: if isinstance(c, nodes.Text): - result.append(unicode(c)) + result.append(c) else: result = self.collapse_text(c, result) return result From b4ef4da181c629ccc596e55ee1c2ef310c086c35 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 29 Dec 2009 17:06:35 +0100 Subject: [PATCH 08/11] Another try to fix docutils version differences. --- sphinx/builders/epub.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sphinx/builders/epub.py b/sphinx/builders/epub.py index 46cf7641e..7a11b291d 100644 --- a/sphinx/builders/epub.py +++ b/sphinx/builders/epub.py @@ -160,7 +160,12 @@ class EpubBuilder(StandaloneHTMLBuilder): """Remove all HTML markup and return only the text nodes.""" for c in doctree.children: if isinstance(c, nodes.Text): - result.append(c) + try: + # docutils 0.4 and 0.5: Text is a UserString subclass + result.append(c.data) + except AttributeError: + # docutils 0.6: Text is a unicode subclass + result.append(c) else: result = self.collapse_text(c, result) return result From 4c19161b5a8b15e9b7e743941d9c8754adbee005 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 29 Dec 2009 22:31:28 +0100 Subject: [PATCH 09/11] Fix a bug preventing incremental rebuilds for the ``dirhtml`` builder -- the target file name wasn't determined portably between HTML builders. --- CHANGES | 3 +++ sphinx/builders/html.py | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index d901ed72b..7ef7b7c10 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,9 @@ Release 0.6.4 (in development) ============================== +* Fix a bug preventing incremental rebuilds for the ``dirhtml`` + builder. + * #299: Fix the mangling of quotes in some literal blocks. * #292: Fix path to the search index for the ``dirhtml`` builder. diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index c2b00fb5a..0fcd3bf30 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -161,8 +161,7 @@ class StandaloneHTMLBuilder(Builder): if docname not in self.env.all_docs: yield docname continue - targetname = self.env.doc2path(docname, self.outdir, - self.out_suffix) + targetname = self.get_outfilename(docname) try: targetmtime = path.getmtime(targetname) except Exception: From 93cca3d4ac1218d7187083b544528a336f394e73 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 30 Dec 2009 13:13:57 +0100 Subject: [PATCH 10/11] #308: allow "double" as alias for "pair" in index entries. --- sphinx/directives/other.py | 4 +++- tests/root/markup.txt | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py index 5eefa48e4..47ec1d333 100644 --- a/sphinx/directives/other.py +++ b/sphinx/directives/other.py @@ -235,7 +235,7 @@ class Index(Directive): option_spec = {} indextypes = [ - 'single', 'pair', 'triple', + 'single', 'pair', 'double', 'triple', ] def run(self): @@ -259,6 +259,8 @@ class Index(Directive): for type in self.indextypes: if entry.startswith(type+':'): value = entry[len(type)+1:].strip() + if type == 'double': + type = 'pair' ne.append((type, value, targetid, value)) break # shorthand notation for single entries diff --git a/tests/root/markup.txt b/tests/root/markup.txt index 32b037ee1..cdb8c5fcd 100644 --- a/tests/root/markup.txt +++ b/tests/root/markup.txt @@ -162,6 +162,7 @@ Index markup .. index:: single: entry pair: entry; pair + double: entry; double triple: index; entry; triple keyword: with From 096725922a8a9c6b1a905bae4f5ca1f2f8b4edb9 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 31 Dec 2009 09:47:56 +0100 Subject: [PATCH 11/11] #303: ``html_context`` values given on the command line via ``-A`` should not override other values given in conf.py. --- CHANGES | 3 +++ sphinx/cmdline.py | 4 +--- tests/root/_templates/layout.html | 3 +++ tests/root/conf.py | 2 +- tests/test_build.py | 2 ++ 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 7ef7b7c10..e01f480fa 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,9 @@ Release 0.6.4 (in development) ============================== +* #303: ``html_context`` values given on the command line via ``-A`` + should not override other values given in conf.py. + * Fix a bug preventing incremental rebuilds for the ``dirhtml`` builder. diff --git a/sphinx/cmdline.py b/sphinx/cmdline.py index 898678b72..ae8b076b2 100644 --- a/sphinx/cmdline.py +++ b/sphinx/cmdline.py @@ -96,7 +96,6 @@ def main(argv): error = sys.stderr warnfile = None confoverrides = {} - htmlcontext = {} tags = [] doctreedir = path.join(outdir, '.doctrees') for opt, val in opts: @@ -142,7 +141,7 @@ def main(argv): val = int(val) except ValueError: pass - htmlcontext[key] = val + confoverrides['html_context.%s' % key] = val elif opt == '-N': nocolor() elif opt == '-E': @@ -158,7 +157,6 @@ def main(argv): warnfile = val elif opt == '-P': use_pdb = True - confoverrides['html_context'] = htmlcontext if warning and warnfile: warnfp = open(warnfile, 'w') diff --git a/tests/root/_templates/layout.html b/tests/root/_templates/layout.html index e8920025d..7f290fc1e 100644 --- a/tests/root/_templates/layout.html +++ b/tests/root/_templates/layout.html @@ -1,5 +1,8 @@ {% extends "!layout.html" %} {% block extrahead %} +{# html_context variable from conf.py #} +{# html_context variable from confoverrides (as if given on cmdline) #} + {{ super() }} {% endblock %} diff --git a/tests/root/conf.py b/tests/root/conf.py index fd82be7d7..192a18832 100644 --- a/tests/root/conf.py +++ b/tests/root/conf.py @@ -35,7 +35,7 @@ html_theme_options = {'testopt': 'testoverride'} html_style = 'default.css' html_static_path = ['_static'] html_last_updated_fmt = '%b %d, %Y' -html_context = {'hckey': 'hcval'} +html_context = {'hckey': 'hcval', 'hckey_co': 'wrong_hcval_co'} htmlhelp_basename = 'SphinxTestsdoc' diff --git a/tests/test_build.py b/tests/test_build.py index ea7c3e43d..7b70c6823 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -108,6 +108,7 @@ HTML_XPATH = { }, 'contents.html': { ".//meta[@name='hc'][@content='hcval']": '', + ".//meta[@name='hc_co'][@content='hcval_co']": '', ".//meta[@name='testopt'][@content='testoverride']": '', #".//td[@class='label']": r'\[Ref1\]', # docutils 0.5 only ".//td[@class='label']": '', @@ -179,6 +180,7 @@ def check_xpath(etree, fname, path, check): [node.text for node in nodes])) @gen_with_app(buildername='html', warning=html_warnfile, cleanenv=True, + confoverrides={'html_context.hckey_co': 'hcval_co'}, tags=['testtag']) def test_html(app): app.builder.build_all()