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 9312382b2..046da6620 100644 --- a/CHANGES +++ b/CHANGES @@ -1,8 +1,14 @@ Release 1.0 (in development) ============================ +.. XXX add short info about domains + +* Support for domains has been added. + * Support for docutils 0.4 has been removed. +* Added Epub builder. + * #284: All docinfo metadata is now put into the document metadata, not just the author. @@ -54,6 +60,12 @@ Release 1.0 (in development) 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. + * #299: Fix the mangling of quotes in some literal blocks. * #292: Fix path to the search index for the ``dirhtml`` builder. 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/_templates/layout.html b/doc/_templates/layout.html index aa33576a7..60d217df5 100644 --- a/doc/_templates/layout.html +++ b/doc/_templates/layout.html @@ -1,10 +1,13 @@ {% extends "!layout.html" %} {% block extrahead %} +{{ super() }} +{%- if not embedded %} +{%- endif %} {% endblock %} {% block rootrellink %} diff --git a/doc/builders.rst b/doc/builders.rst index 7c1f03d3d..e8dccc430 100644 --- a/doc/builders.rst +++ b/doc/builders.rst @@ -63,6 +63,16 @@ 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. See :ref:`epub-faq` for + details about it. For definition of the epub format, have a look at + ``_ or ``_. + + Its name is ``epub``. + .. module:: sphinx.builders.latex .. class:: LaTeXBuilder diff --git a/doc/conf.py b/doc/conf.py index e8db04500..9cd3803ae 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -64,6 +64,19 @@ html_use_opensearch = 'http://sphinx.pocoo.org' # Output file base name for HTML help builder. htmlhelp_basename = 'Sphinxdoc' +# Epub fields +epub_theme = 'epub' +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 529f9cf8d..daceaf789 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -550,6 +550,96 @@ 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 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. + +.. 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. + +.. 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. 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'``. + +.. 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 d429ba6fe..05b39ef4d 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -66,3 +66,40 @@ github pages .. _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-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 +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_, 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. + +.. _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 7af54a0be..c73b4f45d 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 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/__init__.py b/sphinx/builders/__init__.py index 08628c7b6..d02b58acc 100644 --- a/sphinx/builders/__init__.py +++ b/sphinx/builders/__init__.py @@ -323,6 +323,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..7a11b291d --- /dev/null +++ b/sphinx/builders/epub.py @@ -0,0 +1,421 @@ +# -*- 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 codecs +from os import path +import zipfile + +from docutils import nodes + +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 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.""" + 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): + 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 + + def get_refnodes(self, doctree, result): + """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'] + 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/builders/html.py b/sphinx/builders/html.py index 641db9c02..e5b0adb48 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -103,9 +103,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) @@ -169,8 +174,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: @@ -283,8 +287,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/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/sphinx/config.py b/sphinx/config.py index dbe569fb7..5bdf817d0 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -98,6 +98,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), diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py index 4436b306e..01bd74ce1 100644 --- a/sphinx/directives/other.py +++ b/sphinx/directives/other.py @@ -148,7 +148,7 @@ class Index(Directive): option_spec = {} indextypes = [ - 'single', 'pair', 'triple', + 'single', 'pair', 'double', 'triple', ] def run(self): @@ -171,6 +171,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/sphinx/quickstart.py b/sphinx/quickstart.py index 7b11dfdd4..e09bee33f 100644 --- a/sphinx/quickstart.py +++ b/sphinx/quickstart.py @@ -223,6 +223,40 @@ latex_documents = [ # If false, no module index is generated. #latex_use_modindex = True + + +# -- Options for Epub output --------------------------------------------------- + +# Bibliographic Dublin Core info. +#epub_title = '' +#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 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 = '' + +# 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 +304,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 +316,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 +372,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 +431,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 +499,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/root/_templates/layout.html b/tests/root/_templates/layout.html index d312238fa..82125df8e 100644 --- a/tests/root/_templates/layout.html +++ b/tests/root/_templates/layout.html @@ -1,7 +1,10 @@ {% 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 13b267994..e1ebc489c 100644 --- a/tests/root/conf.py +++ b/tests/root/conf.py @@ -36,7 +36,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/root/markup.txt b/tests/root/markup.txt index c5022b32f..65156e7e6 100644 --- a/tests/root/markup.txt +++ b/tests/root/markup.txt @@ -227,6 +227,7 @@ Index markup .. index:: single: entry pair: entry; pair + double: entry; double triple: index; entry; triple keyword: with diff --git a/tests/test_build.py b/tests/test_build.py index 9d3210e75..bceba3c0f 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -124,6 +124,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_build_html.py b/tests/test_build_html.py index 7079971c4..ebd38cb2d 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -152,6 +152,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']": '', @@ -228,6 +229,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() 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)