This commit is contained in:
Georg Brandl 2010-02-23 21:35:31 +01:00
commit f6260eb6e0
14 changed files with 186 additions and 45 deletions

10
CHANGES
View File

@ -1,11 +1,16 @@
Release 1.0 (in development) Release 1.0 (in development)
============================ ============================
* Sphinx now requires Jinja2 version 2.2 or greater.
* Added ``needs_sphinx`` config value and ``Sphinx.require_sphinx`` * Added ``needs_sphinx`` config value and ``Sphinx.require_sphinx``
application API function. application API function.
* Added single-file HTML builder. * Added single-file HTML builder.
* Added ``autodoc_default_flags`` config value, which can be used
to select default flags for all autodoc directives.
* Added ``tab-width`` option to ``literalinclude`` directive. * Added ``tab-width`` option to ``literalinclude`` directive.
* The ``html_sidebars`` config value can now contain patterns as * The ``html_sidebars`` config value can now contain patterns as
@ -84,6 +89,11 @@ Release 1.0 (in development)
Release 0.6.5 (in development) Release 0.6.5 (in development)
============================== ==============================
* In autodoc, allow customizing the signature of an object where
the built-in mechanism fails.
* #331: Fix output for enumerated lists with start values in LaTeX.
* Make the ``start-after`` and ``end-before`` options to the * Make the ``start-after`` and ``end-before`` options to the
``literalinclude`` directive work correctly if not used together. ``literalinclude`` directive work correctly if not used together.

View File

@ -81,6 +81,11 @@ The builder's "name" must be given to the **-b** command-line option of
details about it. For definition of the epub format, have a look at details about it. For definition of the epub format, have a look at
`<http://www.idpf.org/specs.htm>`_ or `<http://en.wikipedia.org/wiki/EPUB>`_. `<http://www.idpf.org/specs.htm>`_ or `<http://en.wikipedia.org/wiki/EPUB>`_.
Some ebook readers do not show the link targets of references. Therefore
this builder adds the targets after the link when necessary. The display
of the URLs can be customized by adding CSS rules for the class
``link-target``.
Its name is ``epub``. Its name is ``epub``.
.. module:: sphinx.builders.latex .. module:: sphinx.builders.latex

View File

@ -308,9 +308,8 @@ Project information
.. confval:: pygments_style .. confval:: pygments_style
The style name to use for Pygments highlighting of source code. Default is The style name to use for Pygments highlighting of source code. The default
``'sphinx'``, which is a builtin style designed to match Sphinx' default style is selected by the theme for HTML output, and ``'sphinx'`` otherwise.
style.
.. versionchanged:: 0.3 .. versionchanged:: 0.3
If the value is a fully-qualified name of a custom Pygments style class, If the value is a fully-qualified name of a custom Pygments style class,
@ -708,6 +707,12 @@ the `Dublin Core metadata <http://dublincore.org/>`_.
A list of files that are generated/copied in the build directory but should 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 ``[]``. not be included in the epub file. The default value is ``[]``.
.. confval:: epub_tocdepth
The depth of the table of contents in the file :file:`toc.ncx`. It should
be an integer greater than zero. The default value is 3. Note: A deeply
nested table of contents may be difficult to navigate.
.. _latex-options: .. _latex-options:

View File

@ -228,6 +228,24 @@ There are also new config values that you can set:
.. versionadded:: 0.6 .. versionadded:: 0.6
.. confval:: autodoc_default_flags
This value is a list of autodoc directive flags that should be automatically
applied to all autodoc directives. The supported flags are ``'members'``,
``'undoc-members'``, ``'inherited-members'`` and ``'show-inheritance'``.
If you set one of these flags in this config value, you can use a negated
form, :samp:`'no-{flag}'`, in an autodoc directive, to disable it once.
For example, if ``autodoc_default_flags`` is set to ``['members',
'undoc-members']``, and you write a directive like this::
.. automodule:: foo
:no-undoc-members:
the directive will be interpreted as if only ``:members:`` was given.
.. versionadded:: 1.0
Docstring preprocessing Docstring preprocessing
----------------------- -----------------------

View File

@ -97,6 +97,8 @@ The :mod:`sphinx.ext.autosummary` extension does this in two parts:
:confval:`templates_path` to generate the pages for all entries :confval:`templates_path` to generate the pages for all entries
listed. See `Customizing templates`_ below. listed. See `Customizing templates`_ below.
.. versionadded:: 1.0
:program:`sphinx-autogen` -- generate autodoc stub pages :program:`sphinx-autogen` -- generate autodoc stub pages
-------------------------------------------------------- --------------------------------------------------------
@ -142,6 +144,8 @@ also use this new config value:
Customizing templates Customizing templates
--------------------- ---------------------
.. versionadded:: 1.0
You can customize the stub page templates, in a similar way as the HTML Jinja You can customize the stub page templates, in a similar way as the HTML Jinja
templates, see :ref:`templating`. (:class:`~sphinx.application.TemplateBridge` templates, see :ref:`templating`. (:class:`~sphinx.application.TemplateBridge`
is not supported.) is not supported.)

View File

@ -40,7 +40,7 @@ A development egg can be found `here
<http://bitbucket.org/birkenfeld/sphinx/get/tip.gz#egg=Sphinx-dev>`_. <http://bitbucket.org/birkenfeld/sphinx/get/tip.gz#egg=Sphinx-dev>`_.
''' '''
requires = ['Pygments>=0.8', 'Jinja2>=2.1', 'docutils>=0.4'] requires = ['Pygments>=0.8', 'Jinja2>=2.2', 'docutils>=0.4']
if sys.version_info < (2, 4): if sys.version_info < (2, 4):
print 'ERROR: Sphinx requires at least Python 2.4 to run.' print 'ERROR: Sphinx requires at least Python 2.4 to run.'

View File

@ -16,6 +16,7 @@ from os import path
import zipfile import zipfile
from docutils import nodes from docutils import nodes
from docutils.transforms import Transform
from sphinx.builders.html import StandaloneHTMLBuilder from sphinx.builders.html import StandaloneHTMLBuilder
from sphinx.util.osutil import EEXIST from sphinx.util.osutil import EEXIST
@ -23,6 +24,9 @@ from sphinx.util.osutil import EEXIST
# (Fragment) templates from which the metainfo files content.opf, toc.ncx, # (Fragment) templates from which the metainfo files content.opf, toc.ncx,
# mimetype, and META-INF/container.xml are created. # mimetype, and META-INF/container.xml are created.
# This template section also defines strings that are embedded in the html
# output but that may be customized by (re-)setting module attributes,
# e.g. from conf.py.
_mimetype_template = 'application/epub+zip' # no EOL! _mimetype_template = 'application/epub+zip' # no EOL!
@ -99,6 +103,10 @@ _spine_template = u'''\
_toctree_template = u'toctree-l%d' _toctree_template = u'toctree-l%d'
_link_target_template = u' [%(uri)s]'
_css_link_target_class = u'link-target'
_media_types = { _media_types = {
'.html': 'application/xhtml+xml', '.html': 'application/xhtml+xml',
'.css': 'text/css', '.css': 'text/css',
@ -112,6 +120,30 @@ _media_types = {
} }
# The transform to show link targets
class VisibleLinksTransform(Transform):
"""
Add the link target of referances to the text, unless it is already
present in the description.
"""
# This transform must run after the references transforms
default_priority = 680
def apply(self):
for ref in self.document.traverse(nodes.reference):
uri = ref.get('refuri', '')
if ( uri.startswith('http:') or uri.startswith('https:') or \
uri.startswith('ftp:') ) and uri not in ref.astext():
uri = _link_target_template % {'uri': uri}
if uri:
idx = ref.parent.index(ref) + 1
link = nodes.inline(uri, uri)
link['classes'].append(_css_link_target_class)
ref.parent.insert(idx, link)
# The epub publisher # The epub publisher
class EpubBuilder(StandaloneHTMLBuilder): class EpubBuilder(StandaloneHTMLBuilder):
@ -138,6 +170,7 @@ class EpubBuilder(StandaloneHTMLBuilder):
# the output files for epub must be .html only # the output files for epub must be .html only
self.out_suffix = '.html' self.out_suffix = '.html'
self.playorder = 0 self.playorder = 0
self.app.add_transform(VisibleLinksTransform)
def get_theme_config(self): def get_theme_config(self):
return self.config.epub_theme, {} return self.config.epub_theme, {}
@ -145,7 +178,7 @@ class EpubBuilder(StandaloneHTMLBuilder):
# generic support functions # generic support functions
def make_id(self, name): 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('/', '_') return name.replace('/', '_').replace(' ', '')
def esc(self, name): def esc(self, name):
"""Replace all characters not allowed in text an attribute values.""" """Replace all characters not allowed in text an attribute values."""
@ -157,34 +190,20 @@ class EpubBuilder(StandaloneHTMLBuilder):
name = name.replace('\'', '&apos;') name = name.replace('\'', '&apos;')
return name 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): def get_refnodes(self, doctree, result):
"""Collect section titles, their depth in the toc and the refuri.""" """Collect section titles, their depth in the toc and the refuri."""
# XXX: is there a better way than checking the attribute # XXX: is there a better way than checking the attribute
# toctree-l[1-6] on the parent node? # toctree-l[1-8] on the parent node?
if isinstance(doctree, nodes.reference): if isinstance(doctree, nodes.reference):
classes = doctree.parent.attributes['classes'] classes = doctree.parent.attributes['classes']
level = 1 level = 1
for l in range(5,0,-1): # or range(1,6)? for l in range(8, 0, -1): # or range(1, 8)?
if (_toctree_template % l) in classes: if (_toctree_template % l) in classes:
level = l level = l
result.append({ result.append({
'level': level, 'level': level,
'refuri': self.esc(doctree['refuri']), 'refuri': self.esc(doctree['refuri']),
'text': self.esc(''.join(self.collapse_text(doctree, []))) 'text': self.esc(doctree.astext())
}) })
else: else:
for elem in doctree.children: for elem in doctree.children:
@ -195,16 +214,14 @@ class EpubBuilder(StandaloneHTMLBuilder):
"""Get the total table of contents, containg the master_doc """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) doctree = self.env.get_and_resolve_doctree(self.config.master_doc,
self, prune_toctrees=False)
self.refnodes = self.get_refnodes(doctree, []) self.refnodes = self.get_refnodes(doctree, [])
self.refnodes.insert(0, { self.refnodes.insert(0, {
'level': 1, 'level': 1,
'refuri': self.esc(self.config.master_doc + '.html'), 'refuri': self.esc(self.config.master_doc + '.html'),
'text': self.esc(''.join(self.collapse_text( 'text': self.esc(self.env.titles[self.config.master_doc].astext())
self.env.titles[self.config.master_doc], []
))),
}) })
# XXX: is reversed ok?
for file, text in reversed(self.config.epub_pre_files): for file, text in reversed(self.config.epub_pre_files):
self.refnodes.insert(0, { self.refnodes.insert(0, {
'level': 1, 'level': 1,
@ -290,11 +307,10 @@ class EpubBuilder(StandaloneHTMLBuilder):
for fn in files: for fn in files:
filename = path.join(root, fn)[olen:] filename = path.join(root, fn)[olen:]
if filename in self.ignored_files: if filename in self.ignored_files:
# self.warn("ignoring %s" % filename)
continue continue
ext = path.splitext(filename)[-1] ext = path.splitext(filename)[-1]
if ext not in _media_types: if ext not in _media_types:
self.warn("unknown mimetype for %s, ignoring" % filename) self.warn('unknown mimetype for %s, ignoring' % filename)
continue continue
projectfiles.append(_file_template % { projectfiles.append(_file_template % {
'href': self.esc(filename), 'href': self.esc(filename),
@ -338,7 +354,7 @@ class EpubBuilder(StandaloneHTMLBuilder):
"""Insert nested navpoints for given node. """Insert nested navpoints for given node.
The node and subnav are already rendered to text. The node and subnav are already rendered to text.
""" """
nlist = node.split('\n') nlist = node.rsplit('\n', 1)
nlist.insert(-1, subnav) nlist.insert(-1, subnav)
return '\n'.join(nlist) return '\n'.join(nlist)
@ -356,8 +372,10 @@ class EpubBuilder(StandaloneHTMLBuilder):
file = node['refuri'].split('#')[0] file = node['refuri'].split('#')[0]
if file in self.ignored_files: if file in self.ignored_files:
continue continue
if node['level'] > self.config.epub_tocdepth:
continue
if node['level'] == level: if node['level'] == level:
navlist.append(self.new_navpoint(node,level)) navlist.append(self.new_navpoint(node, level))
elif node['level'] == level + 1: elif node['level'] == level + 1:
navstack.append(navlist) navstack.append(navlist)
navlist = [] navlist = []
@ -398,6 +416,7 @@ class EpubBuilder(StandaloneHTMLBuilder):
navpoints = self.build_navpoints(self.refnodes) navpoints = self.build_navpoints(self.refnodes)
level = max(item['level'] for item in self.refnodes) level = max(item['level'] for item in self.refnodes)
level = min(level, self.config.epub_tocdepth)
f = codecs.open(path.join(outdir, outname), 'w', 'utf-8') f = codecs.open(path.join(outdir, outname), 'w', 'utf-8')
try: try:
f.write(_toc_template % self.toc_metadata(level, navpoints)) f.write(_toc_template % self.toc_metadata(level, navpoints))

View File

@ -117,6 +117,7 @@ class Config(object):
epub_pre_files = ([], 'env'), epub_pre_files = ([], 'env'),
epub_post_files = ([], 'env'), epub_post_files = ([], 'env'),
epub_exclude_files = ([], 'env'), epub_exclude_files = ([], 'env'),
epub_tocdepth = (3, 'env'),
# LaTeX options # LaTeX options
latex_documents = ([], None), latex_documents = ([], None),

View File

@ -377,7 +377,12 @@ class Documenter(object):
args = "(%s)" % self.args args = "(%s)" % self.args
else: else:
# try to introspect the signature # try to introspect the signature
try:
args = self.format_args() args = self.format_args()
except Exception, err:
self.directive.warn('error while formatting arguments for '
'%s: %s' % (self.fullname, err))
args = None
retann = self.retann retann = self.retann
@ -663,12 +668,7 @@ class Documenter(object):
self.add_line(u'', '') self.add_line(u'', '')
# format the object's signature, if any # format the object's signature, if any
try:
sig = self.format_signature() sig = self.format_signature()
except Exception, err:
self.directive.warn('error while formatting signature for '
'%s: %s' % (self.fullname, err))
sig = ''
# generate the directive header and options, if applicable # generate the directive header and options, if applicable
self.add_directive_header(sig) self.add_directive_header(sig)
@ -868,7 +868,6 @@ class ClassDocumenter(ModuleLevelDocumenter):
return ret return ret
def format_args(self): def format_args(self):
args = None
# for classes, the relevant signature is the __init__ method's # for classes, the relevant signature is the __init__ method's
initmeth = self.get_attr(self.object, '__init__', None) initmeth = self.get_attr(self.object, '__init__', None)
# classes without __init__ method, default __init__ or # classes without __init__ method, default __init__ or
@ -1098,6 +1097,10 @@ class AutoDirective(Directive):
# a registry of type -> getattr function # a registry of type -> getattr function
_special_attrgetters = {} _special_attrgetters = {}
# flags that can be given in autodoc_default_flags
_default_flags = set(['members', 'undoc-members', 'inherited-members',
'show-inheritance'])
# standard docutils directive settings # standard docutils directive settings
has_content = True has_content = True
required_arguments = 1 required_arguments = 1
@ -1120,6 +1123,14 @@ class AutoDirective(Directive):
# find out what documenter to call # find out what documenter to call
objtype = self.name[4:] objtype = self.name[4:]
doc_class = self._registry[objtype] doc_class = self._registry[objtype]
# add default flags
for flag in self._default_flags:
if flag not in doc_class.option_spec:
continue
negated = self.options.pop('no-' + flag, 'not given') is None
if flag in self.env.config.autodoc_default_flags and \
not negated:
self.options[flag] = None
# process the options with the selected documenter's option_spec # process the options with the selected documenter's option_spec
self.genopt = Options(assemble_option_dict( self.genopt = Options(assemble_option_dict(
self.options.items(), doc_class.option_spec)) self.options.items(), doc_class.option_spec))
@ -1177,6 +1188,7 @@ def setup(app):
app.add_config_value('autoclass_content', 'class', True) app.add_config_value('autoclass_content', 'class', True)
app.add_config_value('autodoc_member_order', 'alphabetic', True) app.add_config_value('autodoc_member_order', 'alphabetic', True)
app.add_config_value('autodoc_default_flags', [], True)
app.add_event('autodoc-process-docstring') app.add_event('autodoc-process-docstring')
app.add_event('autodoc-process-signature') app.add_event('autodoc-process-signature')
app.add_event('autodoc-skip-member') app.add_event('autodoc-skip-member')

View File

@ -258,6 +258,9 @@ latex_documents = [
# A list of files that should not be packed into the epub file. # A list of files that should not be packed into the epub file.
#epub_exclude_files = [] #epub_exclude_files = []
# The depth of the table of contents in toc.ncx.
#epub_tocdepth = 3
''' '''
INTERSPHINX_CONFIG = ''' INTERSPHINX_CONFIG = '''

View File

@ -22,7 +22,41 @@ from sphinx.util.console import darkred, nocolor, color_terminal
class BuildDoc(Command): class BuildDoc(Command):
"""Distutils command to build Sphinx documentation.""" """Distutils command to build Sphinx documentation.
The Sphinx build can then be triggered from distutils, and some Sphinx
options can be set in ``setup.py`` or ``setup.cfg`` instead of Sphinx own
configuration file.
For instance, from `setup.py`::
# this is only necessary when not using setuptools/distribute
from sphinx.setup_command import BuildDoc
cmdclass = {'build_sphinx': BuildDoc}
name = 'My project'
version = 1.2
release = 1.2.0
setup(
name=name,
author='Bernard Montgomery',
version=release,
cmdclass={'build_sphinx': BuildDoc},
# these are optional and override conf.py settings
command_options={
'build_sphinx': {
'project': ('setup.py', name),
'version': ('setup.py', version),
'release': ('setup.py', release)}},
)
Or add this section in ``setup.cfg``::
[build_sphinx]
project = 'My project'
version = 1.2
release = 1.2.0
"""
description = 'Build Sphinx documentation' description = 'Build Sphinx documentation'
user_options = [ user_options = [
@ -31,6 +65,11 @@ class BuildDoc(Command):
('source-dir=', 's', 'Source directory'), ('source-dir=', 's', 'Source directory'),
('build-dir=', None, 'Build directory'), ('build-dir=', None, 'Build directory'),
('builder=', 'b', 'The builder to use. Defaults to "html"'), ('builder=', 'b', 'The builder to use. Defaults to "html"'),
('project=', None, 'The documented project\'s name'),
('version=', None, 'The short X.Y version'),
('release=', None, 'The full version, including alpha/beta/rc tags'),
('today=', None, 'How to format the current date, used as the '
'replacement for |today|'),
] ]
boolean_options = ['fresh-env', 'all-files'] boolean_options = ['fresh-env', 'all-files']
@ -38,8 +77,10 @@ class BuildDoc(Command):
def initialize_options(self): def initialize_options(self):
self.fresh_env = self.all_files = False self.fresh_env = self.all_files = False
self.source_dir = self.build_dir = None self.source_dir = self.build_dir = None
self.conf_file_name = 'conf.py'
self.builder = 'html' self.builder = 'html'
self.version = ''
self.release = ''
self.today = ''
def _guess_source_dir(self): def _guess_source_dir(self):
for guess in ('doc', 'docs'): for guess in ('doc', 'docs'):
@ -74,9 +115,16 @@ class BuildDoc(Command):
status_stream = StringIO() status_stream = StringIO()
else: else:
status_stream = sys.stdout status_stream = sys.stdout
confoverrides = {}
if self.version:
confoverrides['version'] = self.version
if self.release:
confoverrides['release'] = self.release
if self.today:
confoverrides['today'] = self.today
app = Sphinx(self.source_dir, self.source_dir, app = Sphinx(self.source_dir, self.source_dir,
self.builder_target_dir, self.doctree_dir, self.builder_target_dir, self.doctree_dir,
self.builder, {}, status_stream, self.builder, confoverrides, status_stream,
freshenv=self.fresh_env) freshenv=self.fresh_env)
try: try:

View File

@ -420,6 +420,19 @@ div.footer a {
text-decoration: underline; text-decoration: underline;
} }
/* -- link-target ----------------------------------------------------------- */
.link-target {
font-size: 80%;
}
table .link-target {
/* Do not show links in tables, there is not enough space */
display: none;
}
/* -- font-face ------------------------------------------------------------- */
@font-face { @font-face {
font-family: "LiberationNarrow"; font-family: "LiberationNarrow";
font-style: normal; font-style: normal;

View File

@ -741,6 +741,8 @@ class LaTeXTranslator(nodes.NodeVisitor):
def visit_enumerated_list(self, node): def visit_enumerated_list(self, node):
self.body.append('\\begin{enumerate}\n' ) self.body.append('\\begin{enumerate}\n' )
if 'start' in node:
self.body.append('\\setcounter{enumi}{%d}\n' % (node['start'] - 1))
def depart_enumerated_list(self, node): def depart_enumerated_list(self, node):
self.body.append('\\end{enumerate}\n' ) self.body.append('\\end{enumerate}\n' )

View File

@ -169,8 +169,9 @@ def test_format_signature():
assert formatsig('method', 'H.foo', H.foo1, 'a', None) == '(a)' assert formatsig('method', 'H.foo', H.foo1, 'a', None) == '(a)'
assert formatsig('method', 'H.foo', H.foo2, None, None) == '(b, *c)' assert formatsig('method', 'H.foo', H.foo2, None, None) == '(b, *c)'
# test exception handling # test exception handling (exception is caught and args is '')
raises(TypeError, formatsig, 'function', 'int', int, None, None) assert formatsig('function', 'int', int, None, None) == ''
del _warnings[:]
# test processing by event handler # test processing by event handler
assert formatsig('method', 'bar', H.foo1, None, None) == '42' assert formatsig('method', 'bar', H.foo1, None, None) == '42'