From b3242486337e9ea3ff0b09750e44b63a26679619 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 21 Mar 2009 00:34:46 +0200 Subject: Some autosummary fixes --- doc/ext/autosummary.rst | 4 ++++ sphinx/ext/autosummary/__init__.py | 36 +++++++++++++++++++++--------------- sphinx/ext/autosummary/generate.py | 4 ++-- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/doc/ext/autosummary.rst b/doc/ext/autosummary.rst index a9255857..7ad46d27 100644 --- a/doc/ext/autosummary.rst +++ b/doc/ext/autosummary.rst @@ -6,4 +6,8 @@ .. module:: sphinx.ext.autosummary :synopsis: Generate autodoc summaries +This extension can be used to generate function/method/attribute +summary lists, similar to those output eg. by Epydoc and other API doc +generation tools. + TBW. diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index 3d260fd2..df46b71c 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -68,6 +68,8 @@ from sphinx import addnodes, roles from sphinx.util import patfilter from sphinx.util.compat import Directive +from sphinx.ext.autodoc import FunctionDocumenter + # -- autosummary_toc node ------------------------------------------------------ @@ -132,7 +134,7 @@ class Autosummary(Directive): names += [x.strip() for x in self.content if x.strip()] table, warnings, real_names = get_autosummary( - names, self.state, 'nosignatures' in self.options) + names, self, 'nosignatures' in self.options) node = table env = self.state.document.settings.env @@ -168,7 +170,7 @@ class Autosummary(Directive): return warnings + [node] -def get_autosummary(names, state, no_signatures=False): +def get_autosummary(names, directive, no_signatures=False): """ Generate a proper table node for autosummary:: directive. @@ -176,14 +178,12 @@ def get_autosummary(names, state, no_signatures=False): table. *document* is the Docutils document object. """ + state = directive.state document = state.document real_names = {} warnings = [] - prefixes = [''] - prefixes.insert(0, document.settings.env.currmodule) - table = nodes.table('') group = nodes.tgroup('', cols=2) table.append(group) @@ -203,19 +203,25 @@ def get_autosummary(names, state, no_signatures=False): body.append(row) for name in names: - try: - obj, real_name = import_by_name(name, prefixes=prefixes) - except ImportError: - warnings.append(document.reporter.warning( - 'failed to import %s' % name)) - append_row(':obj:`%s`' % name, '') - continue + documenter = FunctionDocumenter(self, name) + documenter.parse_name() + + real_names[name] = documenter.fullname - real_names[name] = real_name + sig = documenter.format_signature() + if sig: + pass + else: + sig = '' + + doc = list(documenter.process_doc([documenter.get_doc()])) + if doc: + title = doc[0] + else: + title = '' - title = '' qualifier = 'obj' - col1 = ':'+qualifier+':`%s <%s>`' % (name, real_name) + col1 = ':' + qualifier + r':`%s <%s>`\ %s' % (name, real_name, sig) col2 = title append_row(col1, col2) diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index c21d71ac..0026d21d 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -211,10 +211,10 @@ def get_documented(filenames): return documented -def main(argv): +def main(): usage = 'usage: %s [-o output_dir] [-s suffix] sourcefile ...' % sys.argv[0] try: - opts, args = getopt.getopt(argv[1:], 'o:s:') + opts, args = getopt.getopt(sys.argv[1:], 'o:s:') except getopt.error: print >>sys.stderr, usage return 1 -- cgit v1.2.1 From b1b8a234f563b5c8c41c2f5407bccdcec8109198 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 24 Mar 2009 21:54:05 +0100 Subject: Update for 0.7dev. --- CHANGES | 3 +++ sphinx/__init__.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 501461e9..35bf8efd 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,6 @@ +Release 0.7 (in development) +============================ + Release 0.6 (Mar 24, 2009) ========================== diff --git a/sphinx/__init__.py b/sphinx/__init__.py index 4e1d6924..725faef7 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -13,8 +13,8 @@ import sys from os import path __revision__ = '$Revision$' -__version__ = '0.6' -__released__ = '0.6' +__version__ = '0.7' +__released__ = '0.7 (hg)' package_dir = path.abspath(path.dirname(__file__)) -- cgit v1.2.1 From 29662c78cba4d7efed6e20ba86b55c9e6c147e25 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Fri, 27 Mar 2009 12:05:00 -0500 Subject: #134: fix pending_xref leftovers when using the todolist directive. --- sphinx/ext/todo.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sphinx/ext/todo.py b/sphinx/ext/todo.py index 61dd4d67..e2a1bb4a 100644 --- a/sphinx/ext/todo.py +++ b/sphinx/ext/todo.py @@ -111,8 +111,13 @@ def process_todo_nodes(app, doctree, fromdocname): para += newnode para += nodes.Text('.)', '.)') + # (Recursively) resolve references in the todo content + todo_entry = todo_info['todo'] + env.resolve_references(todo_entry, todo_info['docname'], + app.builder) + # Insert into the todolist - content.append(todo_info['todo']) + content.append(todo_entry) content.append(para) node.replace_self(content) -- cgit v1.2.1 From 61a7a3f6f3b5a2de005aa11ef622caeb78e5f216 Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Sat, 4 Apr 2009 21:45:22 +0900 Subject: Use PyStemmer instead of PorterStemmer. PorterStemmer consumes a lot of time. So use PyStemmer implemented in C. --- sphinx/search.py | 42 +++++++++++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/sphinx/search.py b/sphinx/search.py index fe20c24a..4aec4183 100644 --- a/sphinx/search.py +++ b/sphinx/search.py @@ -14,8 +14,14 @@ from cStringIO import StringIO from docutils.nodes import Text, NodeVisitor -from sphinx.util.stemmer import PorterStemmer from sphinx.util import jsdump, rpartition +try: + # PyStemmer is wrapper for stemmer in c + import Stemmer as PyStemmer + PYSTEMMER = True +except ImportError: + from sphinx.util.stemmer import PorterStemmer + PYSTEMMER = False word_re = re.compile(r'\w+(?u)') @@ -62,15 +68,29 @@ class _JavaScriptIndex(object): js_index = _JavaScriptIndex() -class Stemmer(PorterStemmer): - """ - All those porter stemmer implementations look hideous. - make at least the stem method nicer. - """ +if PYSTEMMER: + class Stemmer(object): + + def __init__(self): + self._stemmer = PyStemmer.Stemmer('english') + + def stem(self, word): + return self._stemmer.stemWord(word.lower()) + + def stemWords(self, iter): + import itertools + return self._stemmer.stemWords(itertools.imap(lambda x: x.lower(), iter)) +else: + class Stemmer(PorterStemmer): + """ + All those porter stemmer implementations look hideous. + make at least the stem method nicer. + """ + + def stem(self, word): + word = word.lower() + return PorterStemmer.stem(self, word, 0, len(word) - 1) - def stem(self, word): - word = word.lower() - return PorterStemmer.stem(self, word, 0, len(word) - 1) class WordCollector(NodeVisitor): @@ -196,11 +216,11 @@ class IndexBuilder(object): visitor = WordCollector(doctree) doctree.walk(visitor) - def add_term(word, prefix='', stem=self._stemmer.stem): + def add_term(word, stem=self._stemmer.stem): word = stem(word) if len(word) < 3 or word in stopwords or word.isdigit(): return - self._mapping.setdefault(prefix + word, set()).add(filename) + self._mapping.setdefault(word, set()).add(filename) for word in word_re.findall(title): add_term(word) -- cgit v1.2.1 From bce9c62661ae6bb163570fbff3d9f3d8695cb98b Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Sat, 4 Apr 2009 21:47:05 +0900 Subject: Don't use publish_parts to avoid building OptionParser many times. --- sphinx/builders/html.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index 365cf5f9..27483657 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -23,7 +23,7 @@ except ImportError: from docutils import nodes from docutils.io import DocTreeInput, StringOutput -from docutils.core import publish_parts +from docutils.core import Publisher, publish_parts from docutils.utils import new_document from docutils.frontend import OptionParser from docutils.readers.doctree import Reader as DoctreeReader @@ -181,14 +181,26 @@ class StandaloneHTMLBuilder(Builder): """Utility: Render a lone doctree node.""" doc = new_document('') doc.append(node) - return publish_parts( - doc, - source_class=DocTreeInput, - reader=DoctreeReader(), - writer=HTMLWriter(self), - settings_overrides={'output_encoding': 'unicode'} - ) + # cache publisher object. + if 'publisher' not in self.__dict__: + self.publisher = Publisher( + source_class = DocTreeInput, + destination_class=StringOutput) + self.publisher.set_components('standalone', + 'restructuredtext', 'pseudoxml') + + pub = self.publisher + + pub.reader = DoctreeReader() + pub.writer = HTMLWriter(self) + pub.process_programmatic_settings( + None, {'output_encoding': 'unicode'}, None) + pub.set_source(doc, None) + pub.set_destination(None, None) + pub.publish() + return pub.writer.parts + def prepare_writing(self, docnames): from sphinx.search import IndexBuilder -- cgit v1.2.1 From 63c141fc0b145844e67c4801efca9480bf682be1 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 4 Apr 2009 22:05:04 +0300 Subject: autosummary: write documentation --- doc/conf.py | 3 +- doc/ext/autosummary.rst | 103 ++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 101 insertions(+), 5 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index e1a48aa2..be79c8f9 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -6,7 +6,8 @@ import sys, os, re # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.addons.*') or your custom ones. -extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo'] +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', + 'sphinx.ext.autosummary'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/doc/ext/autosummary.rst b/doc/ext/autosummary.rst index 7ad46d27..692eb911 100644 --- a/doc/ext/autosummary.rst +++ b/doc/ext/autosummary.rst @@ -6,8 +6,103 @@ .. module:: sphinx.ext.autosummary :synopsis: Generate autodoc summaries -This extension can be used to generate function/method/attribute -summary lists, similar to those output eg. by Epydoc and other API doc -generation tools. +.. versionadded: 0.6 -TBW. +This extension generates function/method/attribute summary lists, +similar to those output eg. by Epydoc and other API doc generation +tools. This is especially useful when your docstrings are long and +detailed, and putting each one of them on a separate page makes +them easier to read. + +The :mod:`sphinx.ext.autosummary` extension does this in two parts: + + 1. There is an :dir:`autosummary` directive for generating summary + listings that contain links to the documented items, and short + summary blurbs extracted from their docstrings. + + 2. The convenience script :program:`sphinx-autogen` can be used to + generate short "stub" files for the entries listed in the + :dir:`autosummary` directives. These by default contain only the + corresponding :mod:`sphinx.ext.autodoc` directive. + +.. directive:: autosummary + + Insert a table that contains links to documented items, and a short + summary blurb (the first sentence of the docstring) for each of them. + The :dir:`autosummary` directive can also optionally serve as + a :dir:`toctree` entry for the included items. + + For example,:: + + .. currentmodule:: sphinx + + .. autosummary:: + + environment.BuildEnvironment + util.relative_uri + + produces a table like this: + + .. currentmodule:: sphinx + + .. autosummary:: + + environment.BuildEnvironment + util.relative_uri + + .. currentmodule:: sphinx.ext.autosummary + + Autosummary preprocesses the docstrings and signatures with the same + :event:`autodoc-process-docstring` and + :event:`autodoc-process-signature` hooks as *autodoc*. + + + **Options** + + * If you want the :dir:`autosummary` table to also serve as a + :dir:`toctree` entry, use the ``toctree`` option, for example:: + + .. autosummary:: + :toctree: DIRNAME + + sphinx.environment.BuildEnvironment + sphinx.util.relative_uri + + The ``toctree`` option also signals to the :program:`sphinx-autogen` + script that stub pages should be generated for the entries listed + in this directive. The option accepts a directory name as an + argument; :program:`sphinx-autogen` will by default place its output + in this directory. + + * If you don't want the :dir:`autosummary` to show function signatures + in the listing, include the ``nosignatures`` option:: + + .. autosummary:: + :nosignatures: + + sphinx.environment.BuildEnvironment + sphinx.util.relative_uri + +:program:`sphinx-autogen` -- generate autodoc stub pages +-------------------------------------------------------- + +The :program:`sphinx-autogen` script can be used to conveniently +generate stub documentation pages for items included in +:dir:`autosummary` listings. + +For example, the command:: + + $ sphinx-autogen -o generated *.rst + +will read all :dir:`autosummary` tables in the :file:`*.rst` files +that have the ``:toctree:`` option set, and output corresponding stub +pages in directory ``generated`` for all documented items. +The generated pages by default contain text of the form:: + + sphinx.util.relative_uri + ======================== + + .. autofunction:: sphinx.util.relative_uri + +If the ``-o`` option is not given, the script will place the output +files to the directories specified in the ``:toctree:`` options. -- cgit v1.2.1 From b0692c999284da975b2ec5ae476312b598bafce2 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 4 Apr 2009 22:05:32 +0300 Subject: autosummary: fix bugs, and include features from the Numpy version --- sphinx/ext/autosummary/__init__.py | 203 ++++++++++++++++++++++++++----------- sphinx/ext/autosummary/generate.py | 40 ++++---- tests/test_autosummary.py | 32 ++++++ 3 files changed, 192 insertions(+), 83 deletions(-) create mode 100644 tests/test_autosummary.py diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index df46b71c..b5e2e143 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -68,7 +68,7 @@ from sphinx import addnodes, roles from sphinx.util import patfilter from sphinx.util.compat import Directive -from sphinx.ext.autodoc import FunctionDocumenter +import sphinx.ext.autodoc # -- autosummary_toc node ------------------------------------------------------ @@ -110,6 +110,27 @@ def autosummary_toc_visit_latex(self, node): def autosummary_noop(self, node): pass +# -- autodoc integration ------------------------------------------------------- + +def get_documenter(obj): + """ + Get an autodoc.Documenter class suitable for documenting the given object + """ + reg = sphinx.ext.autodoc.AutoDirective._registry + if inspect.isclass(obj): + if issubclass(obj, Exception): + return reg.get('exception') + return reg.get('class') + elif inspect.ismodule(obj): + return reg.get('module') + elif inspect.ismethod(obj) or inspect.ismethoddescriptor(obj): + return reg.get('method') + elif inspect.ismemberdescriptor(obj) or inspect.isgetsetdescriptor(obj): + return reg.get('attribute') + elif inspect.isroutine(obj): + return reg.get('function') + else: + return reg.get('data') # -- .. autosummary:: ---------------------------------------------------------- @@ -129,15 +150,21 @@ class Autosummary(Directive): 'nosignatures': directives.flag, } + def warn(self, msg): + self.warnings.append(self.state.document.reporter.warning( + msg, line=self.lineno)) + def run(self): + self.env = env = self.state.document.settings.env + self.genopt = {} + self.warnings = [] + names = [] names += [x.strip() for x in self.content if x.strip()] - table, warnings, real_names = get_autosummary( - names, self, 'nosignatures' in self.options) - node = table + table, real_names = self.get_table(names) + nodes = [table] - env = self.state.document.settings.env suffix = env.config.source_suffix all_docnames = env.found_docs.copy() dirname = posixpath.dirname(env.docname) @@ -153,9 +180,8 @@ class Autosummary(Directive): docname = docname[:-len(suffix)] docname = posixpath.normpath(posixpath.join(dirname, docname)) if docname not in env.found_docs: - warnings.append(self.state.document.reporter.warning( - 'toctree references unknown document %r' % docname, - line=self.lineno)) + self.warn('toctree references unknown document %r' + % docname) docnames.append(docname) tocnode = addnodes.toctree() @@ -165,67 +191,124 @@ class Autosummary(Directive): tocnode['glob'] = None tocnode = autosummary_toc('', '', tocnode) - return warnings + [node] + [tocnode] - else: - return warnings + [node] + nodes.append(tocnode) + return self.warnings + nodes -def get_autosummary(names, directive, no_signatures=False): - """ - Generate a proper table node for autosummary:: directive. + def get_table(self, names, no_signatures=False): + """ + Generate a proper table node for autosummary:: directive. + + *names* is a list of names of Python objects to be imported + and added to the table. + + """ + state = self.state + document = state.document - *names* is a list of names of Python objects to be imported and added to the - table. *document* is the Docutils document object. + prefixes = [''] + prefixes.insert(0, document.settings.env.currmodule) + real_names = {} + + table = nodes.table('') + group = nodes.tgroup('', cols=2) + table.append(group) + group.append(nodes.colspec('', colwidth=10)) + group.append(nodes.colspec('', colwidth=90)) + body = nodes.tbody('') + group.append(body) + + def append_row(*column_texts): + row = nodes.row('') + for text in column_texts: + node = nodes.paragraph('') + vl = ViewList() + vl.append(text, '') + state.nested_parse(vl, 0, node) + row.append(nodes.entry('', node)) + body.append(row) + + for name in names: + try: + obj, real_name = import_by_name(name, prefixes=prefixes) + except ImportError: + self.warn('failed to import %s' % name) + append_row(':obj:`%s`' % name, '') + continue + + documenter = get_documenter(obj)(self, real_name) + if not documenter.parse_name(): + append_row(':obj:`%s`' % name, '') + continue + if not documenter.import_object(): + append_row(':obj:`%s`' % name, '') + continue + + real_names[name] = documenter.fullname + + sig = documenter.format_signature() + if not sig or 'nosignatures' in self.options: + sig = '' + else: + sig = mangle_signature(sig) + + doc = list(documenter.process_doc(documenter.get_doc())) + if doc: + # grab the summary + while doc and not doc[0].strip(): + doc.pop(0) + m = re.search(r"^([A-Z].*?\.\s)", " ".join(doc).strip()) + if m: + summary = m.group(1).strip() + else: + summary = doc[0].strip() + else: + summary = '' + + qualifier = 'obj' + col1 = ':' + qualifier + r':`%s <%s>`\ %s' % (name, real_name, sig) + col2 = summary + append_row(col1, col2) + + return table, real_names + +def mangle_signature(sig, max_chars=30): """ - state = directive.state - document = state.document - - real_names = {} - warnings = [] - - table = nodes.table('') - group = nodes.tgroup('', cols=2) - table.append(group) - group.append(nodes.colspec('', colwidth=30)) - group.append(nodes.colspec('', colwidth=70)) - body = nodes.tbody('') - group.append(body) - - def append_row(*column_texts): - row = nodes.row('') - for text in column_texts: - node = nodes.paragraph('') - vl = ViewList() - vl.append(text, '') - state.nested_parse(vl, 0, node) - row.append(nodes.entry('', node)) - body.append(row) - - for name in names: - documenter = FunctionDocumenter(self, name) - documenter.parse_name() - - real_names[name] = documenter.fullname - - sig = documenter.format_signature() - if sig: - pass - else: - sig = '' + Reformat function signature to a more compact form. + + """ + sig = re.sub(r"^\((.*)\)$", r"\1", sig) + ", " + r = re.compile(r"(?P[a-zA_Z0-9_*]+)(?P=.*?)?, ") + items = r.findall(sig) - doc = list(documenter.process_doc([documenter.get_doc()])) - if doc: - title = doc[0] + args = [] + opts = [] + + total_len = 4 + for name, default in items: + if default: + opts.append(name) else: - title = '' + args.append(name) + total_len += len(name) + 2 + + if total_len > max_chars: + if opts: + opts.append('...') + else: + args.append('...') + break + + if opts: + sig = ", ".join(args) + "[, " + ", ".join(opts) + "]" + else: + sig = ", ".join(args) - qualifier = 'obj' - col1 = ':' + qualifier + r':`%s <%s>`\ %s' % (name, real_name, sig) - col2 = title - append_row(col1, col2) + sig = unicode(sig).replace(u" ", u"\u00a0") + return u"(%s)" % sig - return table, warnings, real_names +# -- Importing items ----------------------------------------------------------- def import_by_name(name, prefixes=[None]): """ diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index 0026d21d..ccc7eab8 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -20,7 +20,7 @@ import os import re import sys -import getopt +import optparse import inspect from jinja2 import Environment, PackageLoader @@ -38,7 +38,7 @@ def _simple_info(msg): def _simple_warn(msg): print >>sys.stderr, 'WARNING: ' + msg -def generate_autosummary_docs(sources, output_dir=None, suffix=None, +def generate_autosummary_docs(sources, output_dir=None, suffix='.rst', warn=_simple_warn, info=_simple_info): info('generating autosummary for: %s' % ', '.join(sources)) if output_dir: @@ -62,7 +62,7 @@ def generate_autosummary_docs(sources, output_dir=None, suffix=None, warn('failed to import %r: %s' % (name, e)) continue - fn = os.path.join(path, name + (suffix or '.rst')) + fn = os.path.join(path, name + suffix) # skip it if it exists if os.path.isfile(fn): continue @@ -211,28 +211,22 @@ def get_documented(filenames): return documented -def main(): - usage = 'usage: %s [-o output_dir] [-s suffix] sourcefile ...' % sys.argv[0] - try: - opts, args = getopt.getopt(sys.argv[1:], 'o:s:') - except getopt.error: - print >>sys.stderr, usage - return 1 - - output_dir = None - suffix = None - for opt, val in opts: - if opt == '-o': - output_dir = val - elif opt == '-s': - suffix = val +def main(argv): + usage = """%prog [OPTIONS] SOURCEFILE ...""" + p = optparse.OptionParser(usage.strip()) + p.add_option("-o", "--output-dir", action="store", type="string", + dest="output_dir", default=None, + help="Directory to place all output in") + p.add_option("-s", "--suffix", action="store", type="string", + dest="suffix", default="rst", + help="Default suffix for files (default: %default)") + options, args = p.parse_args(argv[1:]) if len(args) < 1: - print >>sys.stderr, usage - return 1 - - generate_autosummary_docs(args, output_dir, suffix) + p.error('no input files given') + generate_autosummary_docs(args, options.output_dir, + "." + options.suffix) if __name__ == '__main__': - main(sys.argv) + main() diff --git a/tests/test_autosummary.py b/tests/test_autosummary.py new file mode 100644 index 00000000..a748b2b7 --- /dev/null +++ b/tests/test_autosummary.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +""" + test_autosummary + ~~~~~~~~~~~~~~~~ + + Test the autosummary extension. + + :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" +import string + +from util import * + +from sphinx.ext.autosummary import mangle_signature + + +def test_mangle_signature(): + TEST = """ + () :: () + (a, b, c, d, e) :: (a, b, c, d, e) + (a, b, c=1, d=2, e=3) :: (a, b[, c, d, e]) + (a, b, aaa=1, bbb=1, ccc=1, eee=1, fff=1, ggg=1, hhh=1, iii=1, jjj=1) :: (a, b[, aaa, bbb, ccc, eee, fff, ...]) + (a, b, c=(), d=) :: (a, b[, c, d]) + (a, b, c='foobar()', d=123) :: (a, b[, c, d]) + """ + + TEST = [map(string.strip, x.split("::")) for x in TEST.split("\n") + if '::' in x] + for inp, outp in TEST: + res = mangle_signature(inp).strip().replace(u"\u00a0", " ") + assert res == outp, (u"'%s' -> '%s' != '%s'" % (inp, res, outp)) -- cgit v1.2.1 From 851c304c2bda7a37b0af34564100ba709d05fc4e Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 4 Apr 2009 22:22:16 +0300 Subject: autosummary: fix bugs in summary extraction, and attribute detection --- sphinx/ext/autosummary/__init__.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index b5e2e143..ff7e8d9d 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -125,7 +125,8 @@ def get_documenter(obj): return reg.get('module') elif inspect.ismethod(obj) or inspect.ismethoddescriptor(obj): return reg.get('method') - elif inspect.ismemberdescriptor(obj) or inspect.isgetsetdescriptor(obj): + elif (inspect.ismemberdescriptor(obj) or inspect.isgetsetdescriptor(obj) + or inspect.isdatadescriptor(obj)): return reg.get('attribute') elif inspect.isroutine(obj): return reg.get('function') @@ -245,7 +246,7 @@ class Autosummary(Directive): append_row(':obj:`%s`' % name, '') continue - real_names[name] = documenter.fullname + real_names[name] = real_name sig = documenter.format_signature() if not sig or 'nosignatures' in self.options: @@ -254,15 +255,15 @@ class Autosummary(Directive): sig = mangle_signature(sig) doc = list(documenter.process_doc(documenter.get_doc())) - if doc: - # grab the summary - while doc and not doc[0].strip(): - doc.pop(0) - m = re.search(r"^([A-Z].*?\.\s)", " ".join(doc).strip()) - if m: - summary = m.group(1).strip() - else: - summary = doc[0].strip() + + # grab the summary + while doc and not doc[0].strip(): + doc.pop(0) + m = re.search(r"^([A-Z].*?\.\s)", " ".join(doc).strip()) + if m: + summary = m.group(1).strip() + elif doc: + summary = doc[0].strip() else: summary = '' -- cgit v1.2.1 From 4c9fd9c6020ab6a3f09d78f656ee0d51f06e0099 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 4 Apr 2009 22:30:29 +0300 Subject: autosummary: refactor generate.py a bit --- sphinx/ext/autosummary/__init__.py | 19 +++++++++---------- sphinx/ext/autosummary/generate.py | 39 +++++++++++++++----------------------- 2 files changed, 24 insertions(+), 34 deletions(-) diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index ff7e8d9d..1c94bae0 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -68,8 +68,6 @@ from sphinx import addnodes, roles from sphinx.util import patfilter from sphinx.util.compat import Directive -import sphinx.ext.autodoc - # -- autosummary_toc node ------------------------------------------------------ @@ -116,22 +114,23 @@ def get_documenter(obj): """ Get an autodoc.Documenter class suitable for documenting the given object """ - reg = sphinx.ext.autodoc.AutoDirective._registry + import sphinx.ext.autodoc as autodoc + if inspect.isclass(obj): if issubclass(obj, Exception): - return reg.get('exception') - return reg.get('class') + return autodoc.ExceptionDocumenter + return autodoc.ClassDocumenter elif inspect.ismodule(obj): - return reg.get('module') + return autodoc.ModuleDocumenter elif inspect.ismethod(obj) or inspect.ismethoddescriptor(obj): - return reg.get('method') + return autodoc.MethodDocumenter elif (inspect.ismemberdescriptor(obj) or inspect.isgetsetdescriptor(obj) or inspect.isdatadescriptor(obj)): - return reg.get('attribute') + return autodoc.AttributeDocumenter elif inspect.isroutine(obj): - return reg.get('function') + return autodoc.FunctionDocumenter else: - return reg.get('data') + return autodoc.DataDocumenter # -- .. autosummary:: ---------------------------------------------------------- diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index ccc7eab8..61e41392 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -25,7 +25,7 @@ import inspect from jinja2 import Environment, PackageLoader -from sphinx.ext.autosummary import import_by_name +from sphinx.ext.autosummary import import_by_name, get_documenter from sphinx.util import ensuredir # create our own templating environment, for module template only @@ -73,17 +73,16 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst', if inspect.ismodule(obj): # XXX replace this with autodoc's API? tmpl = env.get_template('module') - functions = [getattr(obj, item).__name__ - for item in dir(obj) - if inspect.isfunction(getattr(obj, item))] - classes = [getattr(obj, item).__name__ - for item in dir(obj) - if inspect.isclass(getattr(obj, item)) - and not issubclass(getattr(obj, item), Exception)] - exceptions = [getattr(obj, item).__name__ - for item in dir(obj) - if inspect.isclass(getattr(obj, item)) - and issubclass(getattr(obj, item), Exception)] + + def get_items(mod, typ): + return [getattr(mod, name).__name__ + for name in dir(mod) + if get_documenter(getattr(mod,name)).objtype==typ] + + functions = get_items(obj, 'function') + classes = get_items(obj, 'class') + exceptions = get_items(obj, 'exception') + rendered = tmpl.render(name=name, underline='='*len(name), functions=functions, @@ -96,19 +95,11 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst', else: f.write('%s\n%s\n\n' % (name, '='*len(name))) - if inspect.isclass(obj): - if issubclass(obj, Exception): - f.write(format_modulemember(name, 'autoexception')) - else: - f.write(format_modulemember(name, 'autoclass')) - elif inspect.ismethod(obj) or inspect.ismethoddescriptor(obj): - f.write(format_classmember(name, 'automethod')) - elif callable(obj): - f.write(format_modulemember(name, 'autofunction')) - elif hasattr(obj, '__get__'): - f.write(format_classmember(name, 'autoattribute')) + doc = get_documenter(obj) + if doc.objtype in ('method', 'attribute'): + f.write(format_classmember(name, 'auto%s' % doc.objtype)) else: - f.write(format_modulemember(name, 'autofunction')) + f.write(format_modulemember(name, 'auto%s' % doc.objtype)) finally: f.close() -- cgit v1.2.1 From 85036a3876e55531c09c2bba7c9b9d7500d08ec5 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 4 Apr 2009 22:48:26 +0300 Subject: autosummary: allow trailing comments in autosummary lists --- sphinx/ext/autosummary/__init__.py | 4 ++-- sphinx/ext/autosummary/generate.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index 1c94bae0..16dbd0bf 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -159,8 +159,8 @@ class Autosummary(Directive): self.genopt = {} self.warnings = [] - names = [] - names += [x.strip() for x in self.content if x.strip()] + names = [x.strip().split()[0] for x in self.content + if x.strip() and re.search(r'^[a-zA-Z_]', x.strip()[0])] table, real_names = self.get_table(names) nodes = [table] diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index 61e41392..c21a7b45 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -43,6 +43,7 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst', info('generating autosummary for: %s' % ', '.join(sources)) if output_dir: info('writing to %s' % output_dir) + # read names = {} for name, loc in get_documented(sources).items(): @@ -121,7 +122,7 @@ autodoc_re = re.compile(r'.. auto(function|method|attribute|class|exception' '|module)::\s*([A-Za-z0-9_.]+)\s*$') autosummary_re = re.compile(r'^\.\.\s+autosummary::\s*') module_re = re.compile(r'^\.\.\s+(current)?module::\s*([a-zA-Z0-9_.]+)\s*$') -autosummary_item_re = re.compile(r'^\s+([_a-zA-Z][a-zA-Z0-9_.]*)\s*') +autosummary_item_re = re.compile(r'^\s+([_a-zA-Z][a-zA-Z0-9_.]*)\s*.*?') toctree_arg_re = re.compile(r'^\s+:toctree:\s*(.*?)\s*$') def get_documented(filenames): -- cgit v1.2.1 From 1cbbf38bdc52c5084c6d60f8a08c8fc22c2f0ff1 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 4 Apr 2009 23:01:11 +0300 Subject: autosummary: make generate.py recurse into automodule:: docstrings --- sphinx/ext/autosummary/generate.py | 215 ++++++++++++++++++++++--------------- 1 file changed, 127 insertions(+), 88 deletions(-) diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index c21a7b45..6b00f45f 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -22,14 +22,29 @@ import re import sys import optparse import inspect +import pydoc from jinja2 import Environment, PackageLoader from sphinx.ext.autosummary import import_by_name, get_documenter from sphinx.util import ensuredir -# create our own templating environment, for module template only -env = Environment(loader=PackageLoader('sphinx.ext.autosummary', 'templates')) +def main(argv): + usage = """%prog [OPTIONS] SOURCEFILE ...""" + p = optparse.OptionParser(usage.strip()) + p.add_option("-o", "--output-dir", action="store", type="string", + dest="output_dir", default=None, + help="Directory to place all output in") + p.add_option("-s", "--suffix", action="store", type="string", + dest="suffix", default="rst", + help="Default suffix for files (default: %default)") + options, args = p.parse_args(argv[1:]) + + if len(args) < 1: + p.error('no input files given') + + generate_autosummary_docs(args, options.output_dir, + "." + options.suffix) def _simple_info(msg): @@ -38,6 +53,13 @@ def _simple_info(msg): def _simple_warn(msg): print >>sys.stderr, 'WARNING: ' + msg +#------------------------------------------------------------------------------ +# Generating output +#------------------------------------------------------------------------------ + +# create our own templating environment, for module template only +env = Environment(loader=PackageLoader('sphinx.ext.autosummary', 'templates')) + def generate_autosummary_docs(sources, output_dir=None, suffix='.rst', warn=_simple_warn, info=_simple_info): info('generating autosummary for: %s' % ', '.join(sources)) @@ -46,7 +68,7 @@ def generate_autosummary_docs(sources, output_dir=None, suffix='.rst', # read names = {} - for name, loc in get_documented(sources).items(): + for name, loc in get_documented_in_files(sources).items(): for (filename, sec_title, keyword, toctree) in loc: if toctree is not None: path = os.path.join(os.path.dirname(filename), toctree) @@ -117,108 +139,125 @@ def format_classmember(name, directive): return '.. currentmodule:: %s\n\n.. %s:: %s\n' % (mod, directive, name) -title_underline_re = re.compile('^[-=*_^#]{3,}\s*$') -autodoc_re = re.compile(r'.. auto(function|method|attribute|class|exception' - '|module)::\s*([A-Za-z0-9_.]+)\s*$') -autosummary_re = re.compile(r'^\.\.\s+autosummary::\s*') -module_re = re.compile(r'^\.\.\s+(current)?module::\s*([a-zA-Z0-9_.]+)\s*$') -autosummary_item_re = re.compile(r'^\s+([_a-zA-Z][a-zA-Z0-9_.]*)\s*.*?') -toctree_arg_re = re.compile(r'^\s+:toctree:\s*(.*?)\s*$') +#------------------------------------------------------------------------------ +# Finding documented entries in files +#------------------------------------------------------------------------------ -def get_documented(filenames): +def get_documented_in_files(filenames): """ - Find out what items are documented in the given filenames. + Find out what items are documented in source/*.rst + See `get_documented_in_lines`. - Returns a dict of list of (filename, title, keyword, toctree) Keys are - documented names of objects. The value is a list of locations where the - object was documented. Each location is a tuple of filename, the current - section title, the name of the directive, and the value of the :toctree: - argument (if present) of the directive. """ - documented = {} - for filename in filenames: - current_title = [] - last_line = None - toctree = None - current_module = None - in_autosummary = False - f = open(filename, 'r') - for line in f: - try: - if in_autosummary: - m = toctree_arg_re.match(line) - if m: - toctree = m.group(1) - continue - - if line.strip().startswith(':'): - continue # skip options - - m = autosummary_item_re.match(line) - - if m: - name = m.group(1).strip() - if current_module and \ - not name.startswith(current_module + '.'): - name = '%s.%s' % (current_module, name) - documented.setdefault(name, []).append( - (filename, current_title, 'autosummary', toctree)) - continue - if line.strip() == '': - continue - in_autosummary = False - - m = autosummary_re.match(line) + lines = f.read().splitlines() + documented.update(get_documented_in_lines(lines, filename=filename)) + f.close() + return documented + +def get_documented_in_docstring(name, module=None, filename=None): + """ + Find out what items are documented in the given object's docstring. + See `get_documented_in_lines`. + + """ + try: + obj, real_name = import_by_name(name) + lines = pydoc.getdoc(obj).splitlines() + return get_documented_in_lines(lines, module=name, filename=filename) + except AttributeError: + pass + except ImportError, e: + print "Failed to import '%s': %s" % (name, e) + return {} + +def get_documented_in_lines(lines, module=None, filename=None): + """ + Find out what items are documented in the given lines + + Returns + ------- + documented : dict of list of (filename, title, keyword, toctree) + Dictionary whose keys are documented names of objects. + The value is a list of locations where the object was documented. + Each location is a tuple of filename, the current section title, + the name of the directive, and the value of the :toctree: argument + (if present) of the directive. + + """ + title_underline_re = re.compile("^[-=*_^#]{3,}\s*$") + autodoc_re = re.compile(".. auto(function|method|attribute|class|exception|module)::\s*([A-Za-z0-9_.]+)\s*$") + autosummary_re = re.compile(r'^\.\.\s+autosummary::\s*') + module_re = re.compile(r'^\.\.\s+(current)?module::\s*([a-zA-Z0-9_.]+)\s*$') + autosummary_item_re = re.compile(r'^\s+([_a-zA-Z][a-zA-Z0-9_.]*)\s*.*?') + toctree_arg_re = re.compile(r'^\s+:toctree:\s*(.*?)\s*$') + + documented = {} + + current_title = [] + last_line = None + toctree = None + current_module = module + in_autosummary = False + + for line in lines: + try: + if in_autosummary: + m = toctree_arg_re.match(line) if m: - in_autosummary = True + toctree = m.group(1) continue - m = autodoc_re.search(line) + if line.strip().startswith(':'): + continue # skip options + + m = autosummary_item_re.match(line) if m: - name = m.group(2).strip() - # XXX look in newer generate.py - if current_module and \ - not name.startswith(current_module + '.'): - name = '%s.%s' % (current_module, name) - if m.group(1) == 'module': - current_module = name + name = m.group(1).strip() + if current_module and not name.startswith(current_module + '.'): + name = "%s.%s" % (current_module, name) documented.setdefault(name, []).append( - (filename, current_title, 'auto' + m.group(1), None)) + (filename, current_title, 'autosummary', toctree)) continue - - m = title_underline_re.match(line) - if m and last_line: - current_title = last_line.strip() + if line.strip() == '': continue + in_autosummary = False + + m = autosummary_re.match(line) + if m: + in_autosummary = True + continue + + m = autodoc_re.search(line) + if m: + name = m.group(2).strip() + if m.group(1) == "module": + current_module = name + documented.update(get_documented_in_docstring( + name, filename=filename)) + elif current_module and not name.startswith(current_module+'.'): + name = "%s.%s" % (current_module, name) + documented.setdefault(name, []).append( + (filename, current_title, "auto" + m.group(1), None)) + continue + + m = title_underline_re.match(line) + if m and last_line: + current_title = last_line.strip() + continue + + m = module_re.match(line) + if m: + current_module = m.group(2) + continue + finally: + last_line = line - m = module_re.match(line) - if m: - current_module = m.group(2) - continue - finally: - last_line = line return documented - -def main(argv): - usage = """%prog [OPTIONS] SOURCEFILE ...""" - p = optparse.OptionParser(usage.strip()) - p.add_option("-o", "--output-dir", action="store", type="string", - dest="output_dir", default=None, - help="Directory to place all output in") - p.add_option("-s", "--suffix", action="store", type="string", - dest="suffix", default="rst", - help="Default suffix for files (default: %default)") - options, args = p.parse_args(argv[1:]) - - if len(args) < 1: - p.error('no input files given') - - generate_autosummary_docs(args, options.output_dir, - "." + options.suffix) +#------------------------------------------------------------------------------ if __name__ == '__main__': main() -- cgit v1.2.1 From 1b478242c6e86a9a2bfbd11b5b1ca7810bd6b4a3 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 4 Apr 2009 23:26:37 +0300 Subject: autosummary: refactoring, split table generation from input parsing --- sphinx/ext/autosummary/__init__.py | 108 ++++++++++++++++++++----------------- 1 file changed, 60 insertions(+), 48 deletions(-) diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index 16dbd0bf..d82c7088 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -161,21 +161,18 @@ class Autosummary(Directive): names = [x.strip().split()[0] for x in self.content if x.strip() and re.search(r'^[a-zA-Z_]', x.strip()[0])] - - table, real_names = self.get_table(names) - nodes = [table] - - suffix = env.config.source_suffix - all_docnames = env.found_docs.copy() - dirname = posixpath.dirname(env.docname) + items = self.get_items(names) + nodes = [self.get_table(items)] if 'toctree' in self.options: + suffix = env.config.source_suffix + all_docnames = env.found_docs.copy() + dirname = posixpath.dirname(env.docname) + tree_prefix = self.options['toctree'].strip() docnames = [] - for name in names: - name = real_names.get(name, name) - - docname = posixpath.join(tree_prefix, name) + for name, sig, summary, real_name in items: + docname = posixpath.join(tree_prefix, real_name) if docname.endswith(suffix): docname = docname[:-len(suffix)] docname = posixpath.normpath(posixpath.join(dirname, docname)) @@ -195,70 +192,52 @@ class Autosummary(Directive): return self.warnings + nodes - def get_table(self, names, no_signatures=False): + def get_items(self, names): """ - Generate a proper table node for autosummary:: directive. - - *names* is a list of names of Python objects to be imported - and added to the table. + Try to import the given names, and return a list of + ``[(name, signature, summary_string, real_name), ...]`` """ - state = self.state - document = state.document - prefixes = [''] - prefixes.insert(0, document.settings.env.currmodule) + prefixes.insert(0, self.state.document.settings.env.currmodule) - real_names = {} - - table = nodes.table('') - group = nodes.tgroup('', cols=2) - table.append(group) - group.append(nodes.colspec('', colwidth=10)) - group.append(nodes.colspec('', colwidth=90)) - body = nodes.tbody('') - group.append(body) - - def append_row(*column_texts): - row = nodes.row('') - for text in column_texts: - node = nodes.paragraph('') - vl = ViewList() - vl.append(text, '') - state.nested_parse(vl, 0, node) - row.append(nodes.entry('', node)) - body.append(row) + items = [] for name in names: try: obj, real_name = import_by_name(name, prefixes=prefixes) except ImportError: self.warn('failed to import %s' % name) - append_row(':obj:`%s`' % name, '') + items.append((name, '', '', name)) continue + # NB. using real_name here is important, since Documenters + # don't handle module prefixes slightly differently documenter = get_documenter(obj)(self, real_name) if not documenter.parse_name(): - append_row(':obj:`%s`' % name, '') + self.warn('failed to parse name %s' % real_name) + items.append((name, '', '', real_name)) continue if not documenter.import_object(): - append_row(':obj:`%s`' % name, '') + self.warn('failed to import object %s' % real_name) + items.append((name, '', '', real_name)) continue - real_names[name] = real_name + # -- Grab the signature sig = documenter.format_signature() - if not sig or 'nosignatures' in self.options: + if not sig: sig = '' else: sig = mangle_signature(sig) + # -- Grab the summary + doc = list(documenter.process_doc(documenter.get_doc())) - # grab the summary while doc and not doc[0].strip(): doc.pop(0) - m = re.search(r"^([A-Z].*?\.\s)", " ".join(doc).strip()) + m = re.search(r"^([A-Z][^A-Z]*?\.\s)", " ".join(doc).strip()) if m: summary = m.group(1).strip() elif doc: @@ -266,12 +245,45 @@ class Autosummary(Directive): else: summary = '' + items.append((name, sig, summary, real_name)) + + return items + + def get_table(self, items): + """ + Generate a proper table node for autosummary:: directive. + + *items* is a list produced by :meth:`get_items` + + """ + table = nodes.table('') + group = nodes.tgroup('', cols=2) + table.append(group) + group.append(nodes.colspec('', colwidth=10)) + group.append(nodes.colspec('', colwidth=90)) + body = nodes.tbody('') + group.append(body) + + def append_row(*column_texts): + row = nodes.row('') + for text in column_texts: + node = nodes.paragraph('') + vl = ViewList() + vl.append(text, '') + self.state.nested_parse(vl, 0, node) + row.append(nodes.entry('', node)) + body.append(row) + + for name, sig, summary, real_name in items: qualifier = 'obj' - col1 = ':' + qualifier + r':`%s <%s>`\ %s' % (name, real_name, sig) + if 'nosignatures' not in self.options: + col1 = ':%s:`%s <%s>`\ %s' % (qualifier, name, real_name, sig) + else: + col1 = ':%s:`%s <%s>`' % (qualifier, name, real_name) col2 = summary append_row(col1, col2) - return table, real_names + return table def mangle_signature(sig, max_chars=30): """ -- cgit v1.2.1 From b746c5291d90b6bac380fcf18f94a1b32bb4382d Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sat, 4 Apr 2009 23:47:58 +0300 Subject: autodoc: call autodoc-process-signature hook even if introspecting item signature failed --- sphinx/ext/autodoc.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc.py index 53f53397..caf34345 100644 --- a/sphinx/ext/autodoc.py +++ b/sphinx/ext/autodoc.py @@ -372,8 +372,7 @@ class Documenter(object): else: # try to introspect the signature args = self.format_args() - if args is None: - return '' + retann = self.retann result = self.env.app.emit_firstresult( -- cgit v1.2.1 From eb193eb5b05770d63bf15c1f76539684170e796a Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Sun, 5 Apr 2009 05:53:49 +0900 Subject: Use porterstemmer instead of PyStemmer. --- sphinx/search.py | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/sphinx/search.py b/sphinx/search.py index 4aec4183..8bde1326 100644 --- a/sphinx/search.py +++ b/sphinx/search.py @@ -16,12 +16,12 @@ from docutils.nodes import Text, NodeVisitor from sphinx.util import jsdump, rpartition try: - # PyStemmer is wrapper for stemmer in c - import Stemmer as PyStemmer - PYSTEMMER = True + # http://bitbucket.org/methane/porterstemmer/ + from porterstemmer import Stemmer as CStemmer + CSTEMMER = True except ImportError: from sphinx.util.stemmer import PorterStemmer - PYSTEMMER = False + CSTEMMER = False word_re = re.compile(r'\w+(?u)') @@ -68,18 +68,12 @@ class _JavaScriptIndex(object): js_index = _JavaScriptIndex() -if PYSTEMMER: - class Stemmer(object): - - def __init__(self): - self._stemmer = PyStemmer.Stemmer('english') +if CSTEMMER: + class Stemmer(CStemmer): def stem(self, word): - return self._stemmer.stemWord(word.lower()) + return self(word.lower()) - def stemWords(self, iter): - import itertools - return self._stemmer.stemWords(itertools.imap(lambda x: x.lower(), iter)) else: class Stemmer(PorterStemmer): """ -- cgit v1.2.1 From fcf56eb1b79e84b4e5f0ed42c8f4f573999281a5 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 5 Apr 2009 00:01:49 +0300 Subject: autosummary: small signature formatting fixes --- sphinx/ext/autosummary/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index d82c7088..00e4307e 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -229,7 +229,7 @@ class Autosummary(Directive): if not sig: sig = '' else: - sig = mangle_signature(sig) + sig = mangle_signature(sig).replace('*', r'\*') # -- Grab the summary @@ -312,8 +312,10 @@ def mangle_signature(sig, max_chars=30): args.append('...') break - if opts: + if opts and args: sig = ", ".join(args) + "[, " + ", ".join(opts) + "]" + elif opts and not args: + sig = "[" + ", ".join(opts) + "]" else: sig = ", ".join(args) -- cgit v1.2.1 From 18b9cf04b0679889a30ed1debd68e519be7a0ff0 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 5 Apr 2009 00:10:28 +0300 Subject: autosummary: don't insert UTF-8 non-breaking spaces to signatures; these seem to cause problems for latex builds --- sphinx/ext/autosummary/__init__.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index 00e4307e..b1db7863 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -259,8 +259,8 @@ class Autosummary(Directive): table = nodes.table('') group = nodes.tgroup('', cols=2) table.append(group) - group.append(nodes.colspec('', colwidth=10)) - group.append(nodes.colspec('', colwidth=90)) + group.append(nodes.colspec('', colwidth=30)) + group.append(nodes.colspec('', colwidth=70)) body = nodes.tbody('') group.append(body) @@ -319,8 +319,7 @@ def mangle_signature(sig, max_chars=30): else: sig = ", ".join(args) - sig = unicode(sig).replace(u" ", u"\u00a0") - return u"(%s)" % sig + return "(%s)" % sig # -- Importing items ----------------------------------------------------------- -- cgit v1.2.1 From e18c3442b54d821b3c8633cedb3fd32b4f3be05f Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 5 Apr 2009 00:17:47 +0300 Subject: latex: add support for UTF-8 non-breaking spaces, and reintroduce them in autosummary --- sphinx/ext/autosummary/__init__.py | 7 ++++--- sphinx/writers/latex.py | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index b1db7863..00e4307e 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -259,8 +259,8 @@ class Autosummary(Directive): table = nodes.table('') group = nodes.tgroup('', cols=2) table.append(group) - group.append(nodes.colspec('', colwidth=30)) - group.append(nodes.colspec('', colwidth=70)) + group.append(nodes.colspec('', colwidth=10)) + group.append(nodes.colspec('', colwidth=90)) body = nodes.tbody('') group.append(body) @@ -319,7 +319,8 @@ def mangle_signature(sig, max_chars=30): else: sig = ", ".join(args) - return "(%s)" % sig + sig = unicode(sig).replace(u" ", u"\u00a0") + return u"(%s)" % sig # -- Importing items ----------------------------------------------------------- diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 37281fa9..a0ea1961 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -30,6 +30,7 @@ from sphinx.util.smartypants import educateQuotesLatex HEADER = r'''%% Generated by Sphinx. \documentclass[%(papersize)s,%(pointsize)s%(classoptions)s]{%(docclass)s} %(inputenc)s +%(utf8extra)s %(fontenc)s %(babel)s %(fontpkg)s @@ -136,6 +137,7 @@ class LaTeXTranslator(nodes.NodeVisitor): 'pointsize': '10pt', 'classoptions': '', 'inputenc': '\\usepackage[utf8]{inputenc}', + 'utf8extra': '\\DeclareUnicodeCharacter{00A0}{\nobreakspace}', 'fontenc': '\\usepackage[T1]{fontenc}', 'babel': '\\usepackage{babel}', 'fontpkg': '\\usepackage{times}', -- cgit v1.2.1 From 9be22d3b9d5e39e6dd460dc81242ab820edb042f Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 5 Apr 2009 00:26:32 +0300 Subject: latex: fix bad escaping in 3e1f33ab8245 --- sphinx/writers/latex.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index a0ea1961..339e827e 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -137,7 +137,7 @@ class LaTeXTranslator(nodes.NodeVisitor): 'pointsize': '10pt', 'classoptions': '', 'inputenc': '\\usepackage[utf8]{inputenc}', - 'utf8extra': '\\DeclareUnicodeCharacter{00A0}{\nobreakspace}', + 'utf8extra': '\\DeclareUnicodeCharacter{00A0}{\\nobreakspace}', 'fontenc': '\\usepackage[T1]{fontenc}', 'babel': '\\usepackage{babel}', 'fontpkg': '\\usepackage{times}', -- cgit v1.2.1 From 87f769c5fbe6299d5a91bad93ea90a5610480402 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 5 Apr 2009 01:06:03 +0300 Subject: autosummary: don't push nested paragraphs in table entries --- sphinx/ext/autosummary/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index 00e4307e..ef53394b 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -271,7 +271,7 @@ class Autosummary(Directive): vl = ViewList() vl.append(text, '') self.state.nested_parse(vl, 0, node) - row.append(nodes.entry('', node)) + row.append(nodes.entry('', node[0])) body.append(row) for name, sig, summary, real_name in items: -- cgit v1.2.1 From e603e7dc8115494e42b8cf808ef6b119b97dfca1 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 5 Apr 2009 01:21:52 +0300 Subject: autosummary: fix bug introduced in last commit --- sphinx/ext/autosummary/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index ef53394b..b34d3526 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -271,7 +271,12 @@ class Autosummary(Directive): vl = ViewList() vl.append(text, '') self.state.nested_parse(vl, 0, node) - row.append(nodes.entry('', node[0])) + try: + if isinstance(node[0], nodes.paragraph): + node = node[0] + except IndexError: + pass + row.append(nodes.entry('', node)) body.append(row) for name, sig, summary, real_name in items: -- cgit v1.2.1 From 5ea365b6e6a4c0f5dff19b8722460017c317fc04 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 5 Apr 2009 10:45:00 +0200 Subject: Add NOC. --- EXAMPLES | 1 + 1 file changed, 1 insertion(+) diff --git a/EXAMPLES b/EXAMPLES index 41b5774a..e290f61c 100644 --- a/EXAMPLES +++ b/EXAMPLES @@ -30,6 +30,7 @@ included, please mail to `the Google group * mpmath: http://mpmath.googlecode.com/svn/trunk/doc/build/index.html * MyHDL: http://www.myhdl.org/doc/0.6/ * NetworkX: http://networkx.lanl.gov/ +* NOC: http://trac.nocproject.org/trac/wiki/NocGuide * NumPy: http://docs.scipy.org/doc/numpy/reference/ * ObjectListView: http://objectlistview.sourceforge.net/python * OpenLayers: http://docs.openlayers.org/ -- cgit v1.2.1 From 147a7c364d830420de67cd338e2e92d578ab2c0b Mon Sep 17 00:00:00 2001 From: INADA Naoki Date: Sun, 5 Apr 2009 23:30:33 +0900 Subject: tune traversing. 29s->24s --- sphinx/util/__init__.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index 7e4b1092..1eb8383a 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -416,30 +416,28 @@ def copy_static_entry(source, target, builder, context={}): # traverse() is called so many times during a build that it saves # on average 20-25% overall build time! -def _all_traverse(self): +def _all_traverse(self, result): """Version of Node.traverse() that doesn't need a condition.""" - result = [] result.append(self) for child in self.children: - result.extend(child._all_traverse()) + child._all_traverse(result) return result -def _fast_traverse(self, cls): +def _fast_traverse(self, cls, result): """Version of Node.traverse() that only supports instance checks.""" - result = [] if isinstance(self, cls): result.append(self) for child in self.children: - result.extend(child._fast_traverse(cls)) + child._fast_traverse(cls, result) return result def _new_traverse(self, condition=None, include_self=1, descend=1, siblings=0, ascend=0): if include_self and descend and not siblings and not ascend: if condition is None: - return self._all_traverse() + return self._all_traverse([]) elif isinstance(condition, (types.ClassType, type)): - return self._fast_traverse(condition) + return self._fast_traverse(condition, []) return self._old_traverse(condition, include_self, descend, siblings, ascend) -- cgit v1.2.1 From 1b5073d92ddd465a7d58c3a17cfc4bf49abc88ad Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 9 Apr 2009 19:01:20 +0000 Subject: Improve table formatting. --- sphinx/themes/basic/static/basic.css | 2 +- sphinx/themes/default/static/default.css_t | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/sphinx/themes/basic/static/basic.css b/sphinx/themes/basic/static/basic.css index ed737d3b..b57a783a 100644 --- a/sphinx/themes/basic/static/basic.css +++ b/sphinx/themes/basic/static/basic.css @@ -261,7 +261,7 @@ table.docutils { } table.docutils td, table.docutils th { - padding: 1px 8px 1px 0; + padding: 1px 8px 1px 5px; border-top: 0; border-left: 0; border-right: 0; diff --git a/sphinx/themes/default/static/default.css_t b/sphinx/themes/default/static/default.css_t index ab2aeb0c..cbfa36e9 100644 --- a/sphinx/themes/default/static/default.css_t +++ b/sphinx/themes/default/static/default.css_t @@ -232,3 +232,7 @@ tt { padding: 0 1px 0 1px; font-size: 0.95em; } + +th { + background-color: #ede; +} -- cgit v1.2.1 From 2163ddc3d334e20d4caf40ae5b3b24f484b947fa Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 9 Apr 2009 19:04:44 +0000 Subject: Add html_show_copyright config val, patch by Wojtek Walczak. --- CHANGES | 1 + doc/config.rst | 6 ++++++ sphinx/builders/changes.py | 1 + sphinx/builders/html.py | 1 + sphinx/config.py | 1 + sphinx/quickstart.py | 6 ++++++ sphinx/themes/basic/layout.html | 10 ++++++---- 7 files changed, 22 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index c0be798e..adf94855 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,7 @@ Release 0.7 (in development) ============================ +* Added ``html_show_copyright`` config value. Release 0.6.2 (in development) ============================== diff --git a/doc/config.rst b/doc/config.rst index 276f97d3..54fb7c7f 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -514,6 +514,12 @@ that use Sphinx' HTMLWriter class. to translate document trees to HTML. Default is ``None`` (use the builtin translator). +.. confval:: html_show_copyright + + If true, "(C) Copyright ..." is shown in the HTML footer. Default is ``True``. + + .. versionadded:: 0.7 + .. confval:: html_show_sphinx If true, "Created using Sphinx" is shown in the HTML footer. Default is diff --git a/sphinx/builders/changes.py b/sphinx/builders/changes.py index e07b06d8..ad0b8d19 100644 --- a/sphinx/builders/changes.py +++ b/sphinx/builders/changes.py @@ -94,6 +94,7 @@ class ChangesBuilder(Builder): 'libchanges': sorted(libchanges.iteritems()), 'apichanges': sorted(apichanges), 'otherchanges': sorted(otherchanges.iteritems()), + 'show_copyright': self.config.html_show_copyright, 'show_sphinx': self.config.html_show_sphinx, } f = codecs.open(path.join(self.outdir, 'index.html'), 'w', 'utf8') diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index 365cf5f9..32dd2f24 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -245,6 +245,7 @@ class StandaloneHTMLBuilder(Builder): use_opensearch = self.config.html_use_opensearch, docstitle = self.config.html_title, shorttitle = self.config.html_short_title, + show_copyright = self.config.html_show_copyright, show_sphinx = self.config.html_show_sphinx, has_source = self.config.html_copy_source, show_source = self.config.html_show_sourcelink, diff --git a/sphinx/config.py b/sphinx/config.py index 3e3abb0d..3cedfd64 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -82,6 +82,7 @@ class Config(object): html_use_opensearch = ('', 'html'), html_file_suffix = (None, 'html'), html_link_suffix = (None, 'html'), + html_show_copyright = (True, 'html'), html_show_sphinx = (True, 'html'), html_context = ({}, 'html'), diff --git a/sphinx/quickstart.py b/sphinx/quickstart.py index e5b7cfaf..0e6b3032 100644 --- a/sphinx/quickstart.py +++ b/sphinx/quickstart.py @@ -174,6 +174,12 @@ html_static_path = ['%(dot)sstatic'] # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. diff --git a/sphinx/themes/basic/layout.html b/sphinx/themes/basic/layout.html index dd2e2d51..5aaaeff2 100644 --- a/sphinx/themes/basic/layout.html +++ b/sphinx/themes/basic/layout.html @@ -179,10 +179,12 @@ {%- block footer %} {% endblock %} @@ -25,19 +25,19 @@ {% block content %}
-
- {%- block document %} +
+ {%- block document %} {{ super() }} - {%- endblock %} -
- + -
+
+
{% endblock %} @@ -56,23 +56,23 @@ {% block footer %} {% endblock %} -- cgit v1.2.1 From 61efd3adec422b09e5d87d44f7f1a4d0238d9169 Mon Sep 17 00:00:00 2001 From: Dan MacKinlay Date: Mon, 21 Dec 2009 23:24:33 +1100 Subject: attempt at test suite number 1 - this one doesn't seem to generate any metadata --- tests/root/contents.txt | 1 + tests/test_metadata.py | 143 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 tests/test_metadata.py diff --git a/tests/root/contents.txt b/tests/root/contents.txt index 9891c64e..6133f887 100644 --- a/tests/root/contents.txt +++ b/tests/root/contents.txt @@ -21,6 +21,7 @@ Contents: math autodoc autosummary + metadata Python diff --git a/tests/test_metadata.py b/tests/test_metadata.py new file mode 100644 index 00000000..7da544a6 --- /dev/null +++ b/tests/test_metadata.py @@ -0,0 +1,143 @@ +# -*- coding: utf-8 -*- +""" + test_metadata + ~~~~~~~~~~~~~ + + Test our ahndling of metadata in files with bibliographic metadata + + :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + + + +# adapted from an example of bibliographic metadata at http://docutils.sourceforge.net/docs/user/rst/demo.txt +BIBLIOGRAPHIC_FIELDS_REST = """:Author: David Goodger +:Address: 123 Example Street + Example, EX Canada + A1B 2C3 +:Contact: goodger@python.org +:Authors: Me; Myself; I +:organization: humankind +:date: $Date: 2006-05-21 22:44:42 +0200 (Son, 21 Mai 2006) $ +:status: This is a "work in progress" +:revision: $Revision: 4564 $ +:version: 1 +:copyright: This document has been placed in the public domain. You + may do with it as you wish. You may copy, modify, + redistribute, reattribute, sell, buy, rent, lease, + destroy, or improve it, quote it at length, excerpt, + incorporate, collate, fold, staple, or mutilate it, or do + anything else to it that your or anyone else's heart + desires. +:field name: This is a generic bibliographic field. +:field name 2: + Generic bibliographic fields may contain multiple body elements. + + Like this. + +:Dedication: + + For Docutils users & co-developers. + +:abstract: + + This document is a demonstration of the reStructuredText markup + language, containing examples of all basic reStructuredText + constructs and many advanced constructs. + +.. meta:: + :keywords: reStructuredText, demonstration, demo, parser + :description lang=en: A demonstration of the reStructuredText + markup language, containing examples of all basic + constructs and many advanced constructs. + +================================ +reStructuredText Demonstration +================================ + +Above is the document title. +""" + +import re + +from util import * + +from docutils import frontend, utils, nodes +from docutils.parsers import rst + +from sphinx import addnodes +from sphinx.util import texescape +from sphinx.writers.html import HTMLWriter, SmartyPantsHTMLTranslator +from sphinx.writers.latex import LaTeXWriter, LaTeXTranslator + +def setup_module(): + global app, settings, parser + texescape.init() # otherwise done by the latex builder + app = TestApp(cleanenv=True) + optparser = frontend.OptionParser( + components=(rst.Parser, HTMLWriter, LaTeXWriter)) + settings = optparser.get_default_values() + settings.env = app.builder.env + parser = rst.Parser() + +def teardown_module(): + app.cleanup() + +# since we're not resolving the markup afterwards, these nodes may remain +class ForgivingTranslator: + def visit_pending_xref(self, node): + pass + def depart_pending_xref(self, node): + pass + +class ForgivingHTMLTranslator(SmartyPantsHTMLTranslator, ForgivingTranslator): + pass + +class ForgivingLaTeXTranslator(LaTeXTranslator, ForgivingTranslator): + pass + + +def verify_re(rst, html_expected, latex_expected): + document = utils.new_document('test data', settings) + document['file'] = 'dummy' + parser.parse(rst, document) + for msg in document.traverse(nodes.system_message): + if msg['level'] == 1: + msg.replace_self([]) + + if html_expected: + html_translator = ForgivingHTMLTranslator(app.builder, document) + document.walkabout(html_translator) + html_translated = ''.join(html_translator.fragment).strip() + assert re.match(html_expected, html_translated), 'from' + rst + + if latex_expected: + latex_translator = ForgivingLaTeXTranslator(document, app.builder) + latex_translator.first_document = -1 # don't write \begin{document} + document.walkabout(latex_translator) + latex_translated = ''.join(latex_translator.body).strip() + assert re.match(latex_expected, latex_translated), 'from ' + repr(rst) + +def verify(rst, html_expected, latex_expected): + if html_expected: + html_expected = re.escape(html_expected) + '$' + if latex_expected: + latex_expected = re.escape(latex_expected) + '$' + verify_re(rst, html_expected, latex_expected) + + +def test_bibliographic_fields(): + # correct parsing of doc metadata + document = utils.new_document('test data', settings) + document['file'] = 'dummy' + parser.parse(BIBLIOGRAPHIC_FIELDS_REST, document) + import pdb; pdb.set_trace() + for msg in document.traverse(nodes.system_message): + if msg['level'] == 1: + msg.replace_self([]) + _html = ('

' + 'code   sample

') + yield verify, '``code sample``', _html, '\\code{code sample}' + yield verify, ':samp:`code sample`', _html, '\\samp{code sample}' + -- cgit v1.2.1 From c14ea20f49a3968ec3cc38d19ecef488817b0a5c Mon Sep 17 00:00:00 2001 From: Dan MacKinlay Date: Mon, 21 Dec 2009 23:40:44 +1100 Subject: using test_environment as a test example, I get closer, but it turns out that I have no metadata at all where I expect it --- tests/root/metadata.txt | 55 ++++++++++++++++++++++++++ tests/test_metadata.py | 103 +++++++++++++++--------------------------------- 2 files changed, 86 insertions(+), 72 deletions(-) create mode 100644 tests/root/metadata.txt diff --git a/tests/root/metadata.txt b/tests/root/metadata.txt new file mode 100644 index 00000000..8eceb9e0 --- /dev/null +++ b/tests/root/metadata.txt @@ -0,0 +1,55 @@ +.. This is a comment. Note how any initial comments are moved by + transforms to after the document title, subtitle, and docinfo. + +================================ + reStructuredText Demonstration +================================ + +.. Above is the document title, and below is the subtitle. + They are transformed from section titles after parsing. + +-------------------------------- + Examples of Syntax Constructs +-------------------------------- + +.. bibliographic fields (which also require a transform): + +:Author: David Goodger +:Address: 123 Example Street + Example, EX Canada + A1B 2C3 +:Contact: goodger@python.org +:Authors: Me; Myself; I +:organization: humankind +:date: $Date: 2006-05-21 22:44:42 +0200 (Son, 21 Mai 2006) $ +:status: This is a "work in progress" +:revision: $Revision: 4564 $ +:version: 1 +:copyright: This document has been placed in the public domain. You + may do with it as you wish. You may copy, modify, + redistribute, reattribute, sell, buy, rent, lease, + destroy, or improve it, quote it at length, excerpt, + incorporate, collate, fold, staple, or mutilate it, or do + anything else to it that your or anyone else's heart + desires. +:field name: This is a generic bibliographic field. +:field name 2: + Generic bibliographic fields may contain multiple body elements. + + Like this. + +:Dedication: + + For Docutils users & co-developers. + +:abstract: + + This document is a demonstration of the reStructuredText markup + language, containing examples of all basic reStructuredText + constructs and many advanced constructs. + +.. meta:: + :keywords: reStructuredText, demonstration, demo, parser + :description lang=en: A demonstration of the reStructuredText + markup language, containing examples of all basic + constructs and many advanced constructs. diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 7da544a6..a490e682 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -59,85 +59,44 @@ reStructuredText Demonstration Above is the document title. """ -import re - from util import * -from docutils import frontend, utils, nodes -from docutils.parsers import rst +from sphinx.environment import BuildEnvironment +from sphinx.builders.html import StandaloneHTMLBuilder +from sphinx.builders.latex import LaTeXBuilder -from sphinx import addnodes -from sphinx.util import texescape -from sphinx.writers.html import HTMLWriter, SmartyPantsHTMLTranslator -from sphinx.writers.latex import LaTeXWriter, LaTeXTranslator +app = env = None +warnings = [] def setup_module(): - global app, settings, parser - texescape.init() # otherwise done by the latex builder - app = TestApp(cleanenv=True) - optparser = frontend.OptionParser( - components=(rst.Parser, HTMLWriter, LaTeXWriter)) - settings = optparser.get_default_values() - settings.env = app.builder.env - parser = rst.Parser() + global app, env + app = TestApp(srcdir='(temp)') + env = BuildEnvironment(app.srcdir, app.doctreedir, app.config) + env.set_warnfunc(lambda *args: warnings.append(args)) + msg, num, it = env.update(app.config, app.srcdir, app.doctreedir, app) + for docname in it: + pass def teardown_module(): app.cleanup() -# since we're not resolving the markup afterwards, these nodes may remain -class ForgivingTranslator: - def visit_pending_xref(self, node): - pass - def depart_pending_xref(self, node): - pass - -class ForgivingHTMLTranslator(SmartyPantsHTMLTranslator, ForgivingTranslator): - pass - -class ForgivingLaTeXTranslator(LaTeXTranslator, ForgivingTranslator): - pass - - -def verify_re(rst, html_expected, latex_expected): - document = utils.new_document('test data', settings) - document['file'] = 'dummy' - parser.parse(rst, document) - for msg in document.traverse(nodes.system_message): - if msg['level'] == 1: - msg.replace_self([]) - - if html_expected: - html_translator = ForgivingHTMLTranslator(app.builder, document) - document.walkabout(html_translator) - html_translated = ''.join(html_translator.fragment).strip() - assert re.match(html_expected, html_translated), 'from' + rst - - if latex_expected: - latex_translator = ForgivingLaTeXTranslator(document, app.builder) - latex_translator.first_document = -1 # don't write \begin{document} - document.walkabout(latex_translator) - latex_translated = ''.join(latex_translator.body).strip() - assert re.match(latex_expected, latex_translated), 'from ' + repr(rst) - -def verify(rst, html_expected, latex_expected): - if html_expected: - html_expected = re.escape(html_expected) + '$' - if latex_expected: - latex_expected = re.escape(latex_expected) + '$' - verify_re(rst, html_expected, latex_expected) - - -def test_bibliographic_fields(): - # correct parsing of doc metadata - document = utils.new_document('test data', settings) - document['file'] = 'dummy' - parser.parse(BIBLIOGRAPHIC_FIELDS_REST, document) - import pdb; pdb.set_trace() - for msg in document.traverse(nodes.system_message): - if msg['level'] == 1: - msg.replace_self([]) - _html = ('

' - 'code   sample

') - yield verify, '``code sample``', _html, '\\code{code sample}' - yield verify, ':samp:`code sample`', _html, '\\samp{code sample}' +def test_second_update(): + # delete, add and "edit" (change saved mtime) some files and update again + env.all_docs['contents'] = 0 + root = path(app.srcdir) + # important: using "autodoc" because it is the last one to be included in + # the contents.txt toctree; otherwise section numbers would shift + (root / 'autodoc.txt').unlink() + (root / 'new.txt').write_text('New file\n========\n') + msg, num, it = env.update(app.config, app.srcdir, app.doctreedir, app) + assert '1 added, 3 changed, 1 removed' in msg + docnames = set() + for docname in it: + docnames.add(docname) + # "includes" and "images" are in there because they contain references + # to nonexisting downloadable or image files, which are given another + # chance to exist + assert docnames == set(['contents', 'new', 'includes', 'images']) + assert 'autodoc' not in env.all_docs + assert 'autodoc' not in env.found_docs -- cgit v1.2.1 From e53e9b89323a51f0d911166a7e4f4b221cc355f4 Mon Sep 17 00:00:00 2001 From: Dan MacKinlay Date: Mon, 21 Dec 2009 23:43:06 +1100 Subject: only deal with metadata in the place sphinx expects it - at the head of the document --- tests/root/metadata.txt | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/tests/root/metadata.txt b/tests/root/metadata.txt index 8eceb9e0..9b3044ba 100644 --- a/tests/root/metadata.txt +++ b/tests/root/metadata.txt @@ -1,19 +1,3 @@ -.. This is a comment. Note how any initial comments are moved by - transforms to after the document title, subtitle, and docinfo. - -================================ - reStructuredText Demonstration -================================ - -.. Above is the document title, and below is the subtitle. - They are transformed from section titles after parsing. - --------------------------------- - Examples of Syntax Constructs --------------------------------- - -.. bibliographic fields (which also require a transform): - :Author: David Goodger :Address: 123 Example Street Example, EX Canada @@ -53,3 +37,17 @@ :description lang=en: A demonstration of the reStructuredText markup language, containing examples of all basic constructs and many advanced constructs. + +================================ + reStructuredText Demonstration +================================ + +.. Above is the document title, and below is the subtitle. + They are transformed from section titles after parsing. + +-------------------------------- + Examples of Syntax Constructs +-------------------------------- + +.. bibliographic fields (which also require a transform): + -- cgit v1.2.1 From fc6bab2e8d5bfe6f78b36f7dc4f3c17b1ba8894b Mon Sep 17 00:00:00 2001 From: Dan MacKinlay Date: Tue, 22 Dec 2009 00:06:25 +1100 Subject: working tests for docinfo metadata --- tests/test_metadata.py | 83 ++++++++++---------------------------------------- 1 file changed, 16 insertions(+), 67 deletions(-) diff --git a/tests/test_metadata.py b/tests/test_metadata.py index a490e682..d106fcc4 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -12,55 +12,11 @@ # adapted from an example of bibliographic metadata at http://docutils.sourceforge.net/docs/user/rst/demo.txt -BIBLIOGRAPHIC_FIELDS_REST = """:Author: David Goodger -:Address: 123 Example Street - Example, EX Canada - A1B 2C3 -:Contact: goodger@python.org -:Authors: Me; Myself; I -:organization: humankind -:date: $Date: 2006-05-21 22:44:42 +0200 (Son, 21 Mai 2006) $ -:status: This is a "work in progress" -:revision: $Revision: 4564 $ -:version: 1 -:copyright: This document has been placed in the public domain. You - may do with it as you wish. You may copy, modify, - redistribute, reattribute, sell, buy, rent, lease, - destroy, or improve it, quote it at length, excerpt, - incorporate, collate, fold, staple, or mutilate it, or do - anything else to it that your or anyone else's heart - desires. -:field name: This is a generic bibliographic field. -:field name 2: - Generic bibliographic fields may contain multiple body elements. - - Like this. - -:Dedication: - - For Docutils users & co-developers. - -:abstract: - - This document is a demonstration of the reStructuredText markup - language, containing examples of all basic reStructuredText - constructs and many advanced constructs. - -.. meta:: - :keywords: reStructuredText, demonstration, demo, parser - :description lang=en: A demonstration of the reStructuredText - markup language, containing examples of all basic - constructs and many advanced constructs. - -================================ -reStructuredText Demonstration -================================ - -Above is the document title. -""" from util import * +from nose.tools import assert_equals + from sphinx.environment import BuildEnvironment from sphinx.builders.html import StandaloneHTMLBuilder from sphinx.builders.latex import LaTeXBuilder @@ -69,10 +25,12 @@ app = env = None warnings = [] def setup_module(): + # Is there a better way of generating this doctree than manually iterating? global app, env app = TestApp(srcdir='(temp)') env = BuildEnvironment(app.srcdir, app.doctreedir, app.config) - env.set_warnfunc(lambda *args: warnings.append(args)) + # Huh. Why do I need to do this? + env.set_warnfunc(lambda *args: warnings.append(args)) msg, num, it = env.update(app.config, app.srcdir, app.doctreedir, app) for docname in it: pass @@ -80,23 +38,14 @@ def setup_module(): def teardown_module(): app.cleanup() - -def test_second_update(): - # delete, add and "edit" (change saved mtime) some files and update again - env.all_docs['contents'] = 0 - root = path(app.srcdir) - # important: using "autodoc" because it is the last one to be included in - # the contents.txt toctree; otherwise section numbers would shift - (root / 'autodoc.txt').unlink() - (root / 'new.txt').write_text('New file\n========\n') - msg, num, it = env.update(app.config, app.srcdir, app.doctreedir, app) - assert '1 added, 3 changed, 1 removed' in msg - docnames = set() - for docname in it: - docnames.add(docname) - # "includes" and "images" are in there because they contain references - # to nonexisting downloadable or image files, which are given another - # chance to exist - assert docnames == set(['contents', 'new', 'includes', 'images']) - assert 'autodoc' not in env.all_docs - assert 'autodoc' not in env.found_docs +def test_docinfo(): + exampledocinfo = env.metadata['metadata'] + expected_metadata = { + 'author': u'David Goodger', + u'field name': u'This is a generic bibliographic field.', + u'field name 2': u'Generic bibliographic fields may contain multiple body elements.\n\nLike this.'} + # I like this way of comparing dicts - easier to see the error. + for key in exampledocinfo: + yield assert_equals, exampledocinfo[key], expected_metadata[key] + #but then we still have to check for missing keys + yield assert_equals, expected_metadata.keys(), exampledocinfo.keys() -- cgit v1.2.1 From 677b9bdcd30b01fe517b6412d210ed37e0acc817 Mon Sep 17 00:00:00 2001 From: Dan MacKinlay Date: Tue, 22 Dec 2009 16:35:30 +1100 Subject: handle key ordering silliness in tests --- tests/test_metadata.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_metadata.py b/tests/test_metadata.py index d106fcc4..481e35af 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -48,4 +48,4 @@ def test_docinfo(): for key in exampledocinfo: yield assert_equals, exampledocinfo[key], expected_metadata[key] #but then we still have to check for missing keys - yield assert_equals, expected_metadata.keys(), exampledocinfo.keys() + yield assert_equals, set(expected_metadata.keys()), set(exampledocinfo.keys()) -- cgit v1.2.1 From bcea1a1d1c70120884905572066769a2c06c5aa8 Mon Sep 17 00:00:00 2001 From: Dan MacKinlay Date: Tue, 22 Dec 2009 16:49:40 +1100 Subject: sphinx now preserves lots of useful document metadata --- sphinx/environment.py | 11 +++++++---- tests/test_metadata.py | 22 +++++++++++++++++++--- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/sphinx/environment.py b/sphinx/environment.py index d4f16819..64c745a7 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -757,6 +757,7 @@ class BuildEnvironment: def process_metadata(self, docname, doctree): """ Process the docinfo part of the doctree as metadata. + Keep processing minimal - just return what docutils says. """ self.metadata[docname] = md = {} try: @@ -768,10 +769,12 @@ class BuildEnvironment: # nothing to see here return for node in docinfo: - if node.__class__ is nodes.author: - # handled specially by docutils - md['author'] = node.astext() - elif node.__class__ is nodes.field: + # nodes are multiply inherited... + if isinstance(node, nodes.authors): + md['authors'] = [author.astext() for author in node] + elif isinstance(node, nodes.TextElement): #e.g. author + md[node.__class__.__name__] = node.astext() + else: name, body = node md[name.astext()] = body.astext() del doctree[0] diff --git a/tests/test_metadata.py b/tests/test_metadata.py index 481e35af..ea41fc11 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -39,13 +39,29 @@ def teardown_module(): app.cleanup() def test_docinfo(): + """ + inspect the 'docinfo' metadata stored in the first node of the document. + Note this doesn't give us access to data stored in subsequence blocks + that might be considered document metadata, such as 'abstract' or + 'dedication' blocks, or the 'meta' role. Doing otherwise is probably more + messing with the internals of sphinx than this rare use case merits. + """ exampledocinfo = env.metadata['metadata'] expected_metadata = { 'author': u'David Goodger', - u'field name': u'This is a generic bibliographic field.', - u'field name 2': u'Generic bibliographic fields may contain multiple body elements.\n\nLike this.'} + 'authors': [u'Me', u'Myself', u'I'], + 'address': u'123 Example Street\nExample, EX Canada\nA1B 2C3', + 'field name': u'This is a generic bibliographic field.', + 'field name 2': u'Generic bibliographic fields may contain multiple body elements.\n\nLike this.', + 'status': u'This is a "work in progress"', + 'version': u'1', + 'copyright': u"This document has been placed in the public domain. You\nmay do with it as you wish. You may copy, modify,\nredistribute, reattribute, sell, buy, rent, lease,\ndestroy, or improve it, quote it at length, excerpt,\nincorporate, collate, fold, staple, or mutilate it, or do\nanything else to it that your or anyone else's heart\ndesires.", + 'contact': u'goodger@python.org', + 'date': u'2006-05-21', + 'organization': u'humankind', + 'revision': u'4564'} # I like this way of comparing dicts - easier to see the error. for key in exampledocinfo: - yield assert_equals, exampledocinfo[key], expected_metadata[key] + yield assert_equals, exampledocinfo.get(key), expected_metadata.get(key) #but then we still have to check for missing keys yield assert_equals, set(expected_metadata.keys()), set(exampledocinfo.keys()) -- cgit v1.2.1 From 2d64562350dbc4079f045f534b0a204e12fb21c9 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 23 Dec 2009 11:53:19 +0100 Subject: Fix some nits after pulling from http://bitbucket.org/howthebodyworks/sphinx/ and add changelog and authors entries. --- AUTHORS | 1 + CHANGES | 3 +++ sphinx/environment.py | 6 ++---- tests/test_metadata.py | 53 +++++++++++++++++++++++++++----------------------- 4 files changed, 35 insertions(+), 28 deletions(-) diff --git a/AUTHORS b/AUTHORS index 259c0949..09be7533 100644 --- a/AUTHORS +++ b/AUTHORS @@ -13,6 +13,7 @@ Other contributors, listed alphabetically, are: * Martin Hans -- autodoc improvements * Dave Kuhlman -- original LaTeX writer * Thomas Lamb -- linkcheck builder +* Dan MacKinlay -- metadata fixes * Will Maier -- directory HTML builder * Christopher Perkins -- autosummary integration * Benjamin Peterson -- unittests diff --git a/CHANGES b/CHANGES index 8d4e4f86..66d1ab08 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,9 @@ Release 1.0 (in development) ============================ +* All docinfo metadata is now put into the document metadata, not just + the author. + * Added new HTML theme ``agogo``, created by Andi Albrecht. * Added new minimal theme called scrolls, created by Armin Ronacher. diff --git a/sphinx/environment.py b/sphinx/environment.py index 64c745a7..426d78c2 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -12,12 +12,10 @@ import re import os import time -import heapq import types import codecs import imghdr import string -import difflib import cPickle as pickle from os import path from glob import glob @@ -757,7 +755,7 @@ class BuildEnvironment: def process_metadata(self, docname, doctree): """ Process the docinfo part of the doctree as metadata. - Keep processing minimal - just return what docutils says. + Keep processing minimal -- just return what docutils says. """ self.metadata[docname] = md = {} try: @@ -772,7 +770,7 @@ class BuildEnvironment: # nodes are multiply inherited... if isinstance(node, nodes.authors): md['authors'] = [author.astext() for author in node] - elif isinstance(node, nodes.TextElement): #e.g. author + elif isinstance(node, nodes.TextElement): # e.g. author md[node.__class__.__name__] = node.astext() else: name, body = node diff --git a/tests/test_metadata.py b/tests/test_metadata.py index ea41fc11..9675947a 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -3,23 +3,20 @@ test_metadata ~~~~~~~~~~~~~ - Test our ahndling of metadata in files with bibliographic metadata + Test our handling of metadata in files with bibliographic metadata. :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ - - -# adapted from an example of bibliographic metadata at http://docutils.sourceforge.net/docs/user/rst/demo.txt +# adapted from an example of bibliographic metadata at +# http://docutils.sourceforge.net/docs/user/rst/demo.txt from util import * from nose.tools import assert_equals from sphinx.environment import BuildEnvironment -from sphinx.builders.html import StandaloneHTMLBuilder -from sphinx.builders.latex import LaTeXBuilder app = env = None warnings = [] @@ -30,7 +27,7 @@ def setup_module(): app = TestApp(srcdir='(temp)') env = BuildEnvironment(app.srcdir, app.doctreedir, app.config) # Huh. Why do I need to do this? - env.set_warnfunc(lambda *args: warnings.append(args)) + env.set_warnfunc(lambda *args: warnings.append(args)) msg, num, it = env.update(app.config, app.srcdir, app.doctreedir, app) for docname in it: pass @@ -40,28 +37,36 @@ def teardown_module(): def test_docinfo(): """ - inspect the 'docinfo' metadata stored in the first node of the document. + Inspect the 'docinfo' metadata stored in the first node of the document. Note this doesn't give us access to data stored in subsequence blocks that might be considered document metadata, such as 'abstract' or 'dedication' blocks, or the 'meta' role. Doing otherwise is probably more messing with the internals of sphinx than this rare use case merits. """ exampledocinfo = env.metadata['metadata'] - expected_metadata = { - 'author': u'David Goodger', - 'authors': [u'Me', u'Myself', u'I'], - 'address': u'123 Example Street\nExample, EX Canada\nA1B 2C3', - 'field name': u'This is a generic bibliographic field.', - 'field name 2': u'Generic bibliographic fields may contain multiple body elements.\n\nLike this.', - 'status': u'This is a "work in progress"', - 'version': u'1', - 'copyright': u"This document has been placed in the public domain. You\nmay do with it as you wish. You may copy, modify,\nredistribute, reattribute, sell, buy, rent, lease,\ndestroy, or improve it, quote it at length, excerpt,\nincorporate, collate, fold, staple, or mutilate it, or do\nanything else to it that your or anyone else's heart\ndesires.", - 'contact': u'goodger@python.org', - 'date': u'2006-05-21', - 'organization': u'humankind', - 'revision': u'4564'} + expecteddocinfo = { + 'author': u'David Goodger', + 'authors': [u'Me', u'Myself', u'I'], + 'address': u'123 Example Street\nExample, EX Canada\nA1B 2C3', + 'field name': u'This is a generic bibliographic field.', + 'field name 2': (u'Generic bibliographic fields may contain multiple ' + u'body elements.\n\nLike this.'), + 'status': u'This is a "work in progress"', + 'version': u'1', + 'copyright': (u'This document has been placed in the public domain. ' + u'You\nmay do with it as you wish. You may copy, modify,' + u'\nredistribute, reattribute, sell, buy, rent, lease,\n' + u'destroy, or improve it, quote it at length, excerpt,\n' + u'incorporate, collate, fold, staple, or mutilate it, or ' + u'do\nanything else to it that your or anyone else\'s ' + u'heart\ndesires.'), + 'contact': u'goodger@python.org', + 'date': u'2006-05-21', + 'organization': u'humankind', + 'revision': u'4564', + } # I like this way of comparing dicts - easier to see the error. for key in exampledocinfo: - yield assert_equals, exampledocinfo.get(key), expected_metadata.get(key) - #but then we still have to check for missing keys - yield assert_equals, set(expected_metadata.keys()), set(exampledocinfo.keys()) + yield assert_equals, exampledocinfo.get(key), expecteddocinfo.get(key) + # but then we still have to check for missing keys + yield assert_equals, set(expecteddocinfo.keys()), set(exampledocinfo.keys()) -- cgit v1.2.1 From 3da86fbd9f97336855eb488617e7a1530fa673e1 Mon Sep 17 00:00:00 2001 From: gbrandl Date: Wed, 23 Dec 2009 12:00:37 +0100 Subject: Add issue number. --- CHANGES | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 66d1ab08..54fe76bb 100644 --- a/CHANGES +++ b/CHANGES @@ -1,8 +1,8 @@ Release 1.0 (in development) ============================ -* All docinfo metadata is now put into the document metadata, not just - the author. +* #284: All docinfo metadata is now put into the document metadata, not + just the author. * Added new HTML theme ``agogo``, created by Andi Albrecht. -- cgit v1.2.1 From b2f7a7a5152634b0d92b4c89311778a612b39658 Mon Sep 17 00:00:00 2001 From: Roland Meister Date: Mon, 28 Dec 2009 17:09:09 +0100 Subject: 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 07cee744..ffd51dc6 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 7c1f03d3..3ce8f56a 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 e3952a0b..f8310746 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 3ce8d53b..99b484c8 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 34502dc4..46a714dd 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 7af54a0b..4f7cadd1 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 a8fc8871..b35d0fd1 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 00000000..57e2dff9 --- /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 b2ef29fb..5050a779 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 7b11dfdd..eaf9f55a 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 00000000..64b1a4cb --- /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 00000000..72b77190 --- /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 00000000..d5806ec5 --- /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 6668e245..6d0cb933 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 065468b0..cdaf2baf 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) -- cgit v1.2.1 From 23964a69da9f9b81420e86729031e9b4c88861eb Mon Sep 17 00:00:00 2001 From: Roland Meister Date: Mon, 28 Dec 2009 17:11:15 +0100 Subject: 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 f8310746..7393643a 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.'] -- cgit v1.2.1 From 4adae092b0d5d167450e4f6c7288811ea5a4c6d1 Mon Sep 17 00:00:00 2001 From: Roland Meister Date: Mon, 28 Dec 2009 17:16:19 +0100 Subject: 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 aa33576a..e41b68b1 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 %} -- cgit v1.2.1 From 5346bd30f96daa3888936c8038069d02435f85df Mon Sep 17 00:00:00 2001 From: gbrandl Date: Mon, 28 Dec 2009 19:08:32 +0100 Subject: Add change and author info for epub builder. --- AUTHORS | 1 + CHANGES | 2 ++ 2 files changed, 3 insertions(+) diff --git a/AUTHORS b/AUTHORS index 09be7533..ebda4307 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 85982d05..e0243813 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. -- cgit v1.2.1 From ed446a2b73b799458e314e326178036e50210286 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 29 Dec 2009 12:32:42 +0100 Subject: 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 e41b68b1..60d217df 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 3ce8f56a..23556fb1 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 7393643a..de7fc59c 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 99b484c8..dbcf799c 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 46a714dd..20b9b62c 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 4f7cadd1..c73b4f45 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 57e2dff9..cfe6a737 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 54327fa1..04b6cbc3 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 5050a779..d7edb004 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 eaf9f55a..e09bee33 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 = '' -- cgit v1.2.1 From f75b0f294fe4bc569f3463bbb6f3ec054174e928 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 29 Dec 2009 12:41:35 +0100 Subject: 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 23556fb1..e8dccc43 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 20b9b62c..401e1c75 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 -- cgit v1.2.1 From 1acaecd923235356a2c9ac36072cd38c9dcc6126 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 29 Dec 2009 12:48:48 +0100 Subject: 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 cfe6a737..46cf7641 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 -- cgit v1.2.1 From 243577791f45619eb2a20b68bf0cd2b7a9e28ed6 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 29 Dec 2009 17:06:35 +0100 Subject: 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 46cf7641..7a11b291 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 -- cgit v1.2.1 From 5e16dd1887cd40985476aee5072cd492f0071491 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sat, 2 Jan 2010 20:51:22 +0100 Subject: Make extlinks more flexible: use string substitution to build the full URL. --- doc/conf.py | 2 -- doc/ext/extlinks.rst | 4 +++- sphinx/ext/extlinks.py | 19 ++++++++++++------- tests/root/conf.py | 7 +++++-- tests/root/contents.txt | 1 + tests/root/extensions.txt | 8 ++++++++ tests/test_build.py | 5 +++++ 7 files changed, 34 insertions(+), 12 deletions(-) create mode 100644 tests/root/extensions.txt diff --git a/doc/conf.py b/doc/conf.py index eeb9fbff..ce87c54e 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -9,8 +9,6 @@ import sys, os, re extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', 'sphinx.ext.autosummary'] -extlinks = {'issue': ('http://bugs.python.org/issue', 'issue ')} - # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/doc/ext/extlinks.rst b/doc/ext/extlinks.rst index fb101ff3..ffe60e10 100644 --- a/doc/ext/extlinks.rst +++ b/doc/ext/extlinks.rst @@ -27,11 +27,13 @@ The extension adds one new config value: short alias names to a base URL and a *prefix*. For example, to create an alias for the above mentioned issues, you would add :: - extlinks = {'issue': ('http://bitbucket.org/birkenfeld/sphinx/issue/', + extlinks = {'issue': ('http://bitbucket.org/birkenfeld/sphinx/issue/%s', 'issue ')} Now, you can use the alias name as a new role, e.g. ``:issue:`123```. This then inserts a link to http://bitbucket.org/birkenfeld/sphinx/issue/123. + As you can see, the target given in the role is substituted in the base URL + in the place of ``%s``. The link *caption* depends on the second item in the tuple, the *prefix*: diff --git a/sphinx/ext/extlinks.py b/sphinx/ext/extlinks.py index 7a1c32f7..423b6447 100644 --- a/sphinx/ext/extlinks.py +++ b/sphinx/ext/extlinks.py @@ -8,10 +8,10 @@ This adds a new config value called ``extlinks`` that is created like this:: - extlinks = {'exmpl': ('http://example.com/', prefix), ...} + extlinks = {'exmpl': ('http://example.com/%s.html', prefix), ...} Now you can use e.g. :exmpl:`foo` in your documents. This will create a - link to ``http://example.com/foo``. The link caption depends on the + link to ``http://example.com/foo.html``. The link caption depends on the *prefix* value given: - If it is ``None``, the caption will be the full URL. @@ -32,15 +32,20 @@ from sphinx.util import split_explicit_title def make_link_role(base_url, prefix): def role(typ, rawtext, text, lineno, inliner, options={}, content=[]): text = utils.unescape(text) - has_explicit_title, title, url = split_explicit_title(text) - # NOTE: not using urlparse.urljoin() here, to allow something like - # base_url = 'bugs.python.org/issue' and url = '1024' - full_url = base_url + url + has_explicit_title, title, part = split_explicit_title(text) + try: + full_url = base_url % part + except (TypeError, ValueError): + env = inliner.document.settings.env + env.warn(env.docname, 'unable to expand %s extlink with base ' + 'URL %r, please make sure the base contains \'%%s\' ' + 'exactly once' % (typ, base_url)) + full_url = base_url + part if not has_explicit_title: if prefix is None: title = full_url else: - title = prefix + url + title = prefix + part pnode = nodes.reference(title, title, refuri=full_url) return [pnode], [] return role diff --git a/tests/root/conf.py b/tests/root/conf.py index 192a1883..7c0e2c68 100644 --- a/tests/root/conf.py +++ b/tests/root/conf.py @@ -6,7 +6,7 @@ sys.path.append(os.path.abspath('.')) extensions = ['ext', 'sphinx.ext.autodoc', 'sphinx.ext.jsmath', 'sphinx.ext.coverage', 'sphinx.ext.todo', - 'sphinx.ext.autosummary'] + 'sphinx.ext.autosummary', 'sphinx.ext.extlinks'] jsmath_path = 'dummy.js' @@ -16,7 +16,7 @@ master_doc = 'contents' source_suffix = '.txt' project = 'Sphinx ' -copyright = '2008, Georg Brandl & Team' +copyright = '2010, Georg Brandl & Team' # If this is changed, remember to update the versionchanges! version = '0.6' release = '0.6alpha1' @@ -53,6 +53,9 @@ coverage_c_regexes = {'cfunction': r'^PyAPI_FUNC\(.*\)\s+([^_][\w_]+)'} autosummary_generate = ['autosummary'] +extlinks = {'issue': ('http://bugs.python.org/issue%s', 'issue '), + 'pyurl': ('http://python.org/%s', None)} + # modify tags from conf.py tags.add('confpytag') diff --git a/tests/root/contents.txt b/tests/root/contents.txt index 6133f887..7035b73d 100644 --- a/tests/root/contents.txt +++ b/tests/root/contents.txt @@ -22,6 +22,7 @@ Contents: autodoc autosummary metadata + extensions Python diff --git a/tests/root/extensions.txt b/tests/root/extensions.txt new file mode 100644 index 00000000..c141cdd5 --- /dev/null +++ b/tests/root/extensions.txt @@ -0,0 +1,8 @@ +Test for diverse extensions +=========================== + +extlinks +-------- + +Test diverse links: :issue:`1000` and :pyurl:`dev/`, also with +:issue:`explicit caption <1042>`. diff --git a/tests/test_build.py b/tests/test_build.py index a54d188d..fd2c56a4 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -121,6 +121,11 @@ HTML_XPATH = { 'bom.html': { ".//title": " File with UTF-8 BOM", }, + 'extensions.html': { + ".//a[@href='http://python.org/dev/']": "http://python.org/dev/", + ".//a[@href='http://bugs.python.org/issue1000']": "issue 1000", + ".//a[@href='http://bugs.python.org/issue1042']": "explicit caption", + }, '_static/statictmpl.html': { ".//project": 'Sphinx ', }, -- cgit v1.2.1 From 33caddde6badc35681275902ac3e563e38e8b485 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sat, 2 Jan 2010 21:08:09 +0100 Subject: Split off HTML tests from test_build. --- tests/test_build.py | 166 +---------------------------------------- tests/test_build_html.py | 188 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 190 insertions(+), 164 deletions(-) create mode 100644 tests/test_build_html.py diff --git a/tests/test_build.py b/tests/test_build.py index fd2c56a4..b282b629 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -13,28 +13,19 @@ import os import re import sys import difflib -import htmlentitydefs from StringIO import StringIO from subprocess import Popen, PIPE -from util import * -from etree13 import ElementTree as ET - -try: - import pygments -except ImportError: - pygments = None - -from sphinx.builders.html import StandaloneHTMLBuilder from sphinx.builders.latex import LaTeXBuilder from sphinx.writers.latex import LaTeXTranslator +from util import * + def teardown_module(): (test_root / '_build').rmtree(True) -html_warnfile = StringIO() latex_warnfile = StringIO() ENV_WARNINGS = """\ @@ -46,164 +37,11 @@ included file u'wrongenc.inc' seems to be wrong, try giving an :encoding: option %(root)s/includes.txt:4: WARNING: download file not readable: nonexisting.png """ -HTML_WARNINGS = ENV_WARNINGS + """\ -%(root)s/images.txt:20: WARNING: no matching candidate for image URI u'foo.*' -%(root)s/markup.txt:: WARNING: invalid index entry u'' -%(root)s/markup.txt:: WARNING: invalid pair index entry u'' -%(root)s/markup.txt:: WARNING: invalid pair index entry u'keyword; ' -""" - LATEX_WARNINGS = ENV_WARNINGS + """\ None:None: WARNING: no matching candidate for image URI u'foo.*' WARNING: invalid pair index entry u'' """ -HTML_XPATH = { - 'images.html': { - ".//img[@src='_images/img.png']": '', - ".//img[@src='_images/img1.png']": '', - ".//img[@src='_images/simg.png']": '', - ".//object[@data='_images/svgimg.svg']": '', - ".//embed[@src='_images/svgimg.svg']": '', - }, - 'subdir/images.html': { - ".//img[@src='../_images/img1.png']": '', - ".//img[@src='../_images/rimg.png']": '', - }, - 'subdir/includes.html': { - ".//a[@href='../_downloads/img.png']": '', - }, - 'includes.html': { - ".//pre": u'Max Strauß', - ".//a[@href='_downloads/img.png']": '', - ".//a[@href='_downloads/img1.png']": '', - ".//pre": u'"quotes"', - ".//pre": u"'included'", - }, - 'autodoc.html': { - ".//dt[@id='test_autodoc.Class']": '', - ".//dt[@id='test_autodoc.function']/em": r'\*\*kwds', - ".//dd": r'Return spam\.', - }, - 'markup.html': { - ".//meta[@name='author'][@content='Me']": '', - ".//meta[@name='keywords'][@content='docs, sphinx']": '', - ".//a[@href='contents.html#ref1']": '', - ".//div[@id='label']": '', - ".//span[@class='option']": '--help', - ".//p": 'A global substitution.', - ".//p": 'In HTML.', - ".//p": 'In both.', - ".//p": 'Always present', - ".//title": 'set by title directive', - ".//span[@class='pre']": 'CFunction()', - }, - 'desc.html': { - ".//dt[@id='mod.Cls.meth1']": '', - ".//dt[@id='errmod.Error']": '', - ".//a[@href='#mod.Cls']": '', - ".//dl[@class='userdesc']": '', - ".//dt[@id='userdescrole-myobj']": '', - ".//a[@href='#userdescrole-myobj']": '', - }, - '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']": '', - ".//li[@class='toctree-l1']/a": 'Testing various markup', - ".//li[@class='toctree-l2']/a": 'Admonitions', - ".//title": 'Sphinx ', - ".//div[@class='footer']": 'Georg Brandl & Team', - ".//a[@href='http://python.org/']": '', - }, - 'bom.html': { - ".//title": " File with UTF-8 BOM", - }, - 'extensions.html': { - ".//a[@href='http://python.org/dev/']": "http://python.org/dev/", - ".//a[@href='http://bugs.python.org/issue1000']": "issue 1000", - ".//a[@href='http://bugs.python.org/issue1042']": "explicit caption", - }, - '_static/statictmpl.html': { - ".//project": 'Sphinx ', - }, -} - -if pygments: - HTML_XPATH['includes.html'].update({ - ".//pre/span[@class='s']": u'üöä', - ".//div[@class='inc-pyobj1 highlight-text']/div/pre": - r'^class Foo:\n pass\n\s*$', - ".//div[@class='inc-pyobj2 highlight-text']/div/pre": - r'^ def baz\(\):\n pass\n\s*$', - ".//div[@class='inc-lines highlight-text']/div/pre": - r'^class Foo:\n pass\nclass Bar:\n$', - ".//div[@class='inc-startend highlight-text']/div/pre": - ur'^foo = u"Including Unicode characters: üöä"\n$', - ".//div[@class='inc-preappend highlight-text']/div/pre": - r'(?m)^START CODE$', - ".//div[@class='inc-pyobj-dedent highlight-python']/div/pre/span": - r'def', - }) - HTML_XPATH['subdir/includes.html'].update({ - ".//pre/span": 'line 1', - ".//pre/span": 'line 2', - }) - -class NslessParser(ET.XMLParser): - """XMLParser that throws away namespaces in tag names.""" - - def _fixname(self, key): - try: - return self._names[key] - except KeyError: - name = key - br = name.find('}') - if br > 0: - name = name[br+1:] - self._names[key] = name = self._fixtext(name) - return name - - -def check_xpath(etree, fname, path, check): - nodes = list(etree.findall(path)) - assert nodes != [], ('did not find any node matching xpath ' - '%r in file %s' % (path, fname)) - if hasattr(check, '__call__'): - check(nodes) - elif not check: - # only check for node presence - pass - else: - rex = re.compile(check) - for node in nodes: - if node.text and rex.search(node.text): - break - else: - assert False, ('%r not found in any node matching ' - 'path %s in %s: %r' % (check, path, fname, - [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() - html_warnings = html_warnfile.getvalue().replace(os.sep, '/') - html_warnings_exp = HTML_WARNINGS % {'root': app.srcdir} - assert html_warnings == html_warnings_exp, 'Warnings don\'t match:\n' + \ - '\n'.join(difflib.ndiff(html_warnings_exp.splitlines(), - html_warnings.splitlines())) - - for fname, paths in HTML_XPATH.iteritems(): - parser = NslessParser() - parser.entity.update(htmlentitydefs.entitydefs) - etree = ET.parse(os.path.join(app.outdir, fname), parser) - for path, check in paths.iteritems(): - yield check_xpath, etree, fname, path, check - @with_app(buildername='latex', warning=latex_warnfile, cleanenv=True) def test_latex(app): diff --git a/tests/test_build_html.py b/tests/test_build_html.py new file mode 100644 index 00000000..267950be --- /dev/null +++ b/tests/test_build_html.py @@ -0,0 +1,188 @@ +# -*- coding: utf-8 -*- +""" + test_build_html + ~~~~~~~~~~~~~~~ + + Test the HTML builder and check output against XPath. + + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import os +import re +import sys +import difflib +import htmlentitydefs +from StringIO import StringIO + +try: + import pygments +except ImportError: + pygments = None + +from sphinx.builders.html import StandaloneHTMLBuilder + +from util import * +from test_build import ENV_WARNINGS +from etree13 import ElementTree as ET + + +def teardown_module(): + (test_root / '_build').rmtree(True) + + +html_warnfile = StringIO() + +HTML_WARNINGS = ENV_WARNINGS + """\ +%(root)s/images.txt:20: WARNING: no matching candidate for image URI u'foo.*' +%(root)s/markup.txt:: WARNING: invalid index entry u'' +%(root)s/markup.txt:: WARNING: invalid pair index entry u'' +%(root)s/markup.txt:: WARNING: invalid pair index entry u'keyword; ' +""" + +HTML_XPATH = { + 'images.html': { + ".//img[@src='_images/img.png']": '', + ".//img[@src='_images/img1.png']": '', + ".//img[@src='_images/simg.png']": '', + ".//object[@data='_images/svgimg.svg']": '', + ".//embed[@src='_images/svgimg.svg']": '', + }, + 'subdir/images.html': { + ".//img[@src='../_images/img1.png']": '', + ".//img[@src='../_images/rimg.png']": '', + }, + 'subdir/includes.html': { + ".//a[@href='../_downloads/img.png']": '', + }, + 'includes.html': { + ".//pre": u'Max Strauß', + ".//a[@href='_downloads/img.png']": '', + ".//a[@href='_downloads/img1.png']": '', + ".//pre": u'"quotes"', + ".//pre": u"'included'", + }, + 'autodoc.html': { + ".//dt[@id='test_autodoc.Class']": '', + ".//dt[@id='test_autodoc.function']/em": r'\*\*kwds', + ".//dd": r'Return spam\.', + }, + 'markup.html': { + ".//meta[@name='author'][@content='Me']": '', + ".//meta[@name='keywords'][@content='docs, sphinx']": '', + ".//a[@href='contents.html#ref1']": '', + ".//div[@id='label']": '', + ".//span[@class='option']": '--help', + ".//p": 'A global substitution.', + ".//p": 'In HTML.', + ".//p": 'In both.', + ".//p": 'Always present', + ".//title": 'set by title directive', + ".//span[@class='pre']": 'CFunction()', + }, + 'desc.html': { + ".//dt[@id='mod.Cls.meth1']": '', + ".//dt[@id='errmod.Error']": '', + ".//a[@href='#mod.Cls']": '', + ".//dl[@class='userdesc']": '', + ".//dt[@id='userdescrole-myobj']": '', + ".//a[@href='#userdescrole-myobj']": '', + }, + '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']": '', + ".//li[@class='toctree-l1']/a": 'Testing various markup', + ".//li[@class='toctree-l2']/a": 'Admonitions', + ".//title": 'Sphinx ', + ".//div[@class='footer']": 'Georg Brandl & Team', + ".//a[@href='http://python.org/']": '', + }, + 'bom.html': { + ".//title": " File with UTF-8 BOM", + }, + 'extensions.html': { + ".//a[@href='http://python.org/dev/']": "http://python.org/dev/", + ".//a[@href='http://bugs.python.org/issue1000']": "issue 1000", + ".//a[@href='http://bugs.python.org/issue1042']": "explicit caption", + }, + '_static/statictmpl.html': { + ".//project": 'Sphinx ', + }, +} + +if pygments: + HTML_XPATH['includes.html'].update({ + ".//pre/span[@class='s']": u'üöä', + ".//div[@class='inc-pyobj1 highlight-text']/div/pre": + r'^class Foo:\n pass\n\s*$', + ".//div[@class='inc-pyobj2 highlight-text']/div/pre": + r'^ def baz\(\):\n pass\n\s*$', + ".//div[@class='inc-lines highlight-text']/div/pre": + r'^class Foo:\n pass\nclass Bar:\n$', + ".//div[@class='inc-startend highlight-text']/div/pre": + ur'^foo = u"Including Unicode characters: üöä"\n$', + ".//div[@class='inc-preappend highlight-text']/div/pre": + r'(?m)^START CODE$', + ".//div[@class='inc-pyobj-dedent highlight-python']/div/pre/span": + r'def', + }) + HTML_XPATH['subdir/includes.html'].update({ + ".//pre/span": 'line 1', + ".//pre/span": 'line 2', + }) + +class NslessParser(ET.XMLParser): + """XMLParser that throws away namespaces in tag names.""" + + def _fixname(self, key): + try: + return self._names[key] + except KeyError: + name = key + br = name.find('}') + if br > 0: + name = name[br+1:] + self._names[key] = name = self._fixtext(name) + return name + + +def check_xpath(etree, fname, path, check): + nodes = list(etree.findall(path)) + assert nodes != [], ('did not find any node matching xpath ' + '%r in file %s' % (path, fname)) + if hasattr(check, '__call__'): + check(nodes) + elif not check: + # only check for node presence + pass + else: + rex = re.compile(check) + for node in nodes: + if node.text and rex.search(node.text): + break + else: + assert False, ('%r not found in any node matching ' + 'path %s in %s: %r' % (check, path, fname, + [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() + html_warnings = html_warnfile.getvalue().replace(os.sep, '/') + html_warnings_exp = HTML_WARNINGS % {'root': app.srcdir} + assert html_warnings == html_warnings_exp, 'Warnings don\'t match:\n' + \ + '\n'.join(difflib.ndiff(html_warnings_exp.splitlines(), + html_warnings.splitlines())) + + for fname, paths in HTML_XPATH.iteritems(): + parser = NslessParser() + parser.entity.update(htmlentitydefs.entitydefs) + etree = ET.parse(os.path.join(app.outdir, fname), parser) + for path, check in paths.iteritems(): + yield check_xpath, etree, fname, path, check -- cgit v1.2.1 From b055defb7917017af8e0255973d94b51baacc020 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sat, 2 Jan 2010 21:14:10 +0100 Subject: Update copyrights. --- sphinx/domains/__init__.py | 2 +- sphinx/domains/c.py | 2 +- sphinx/domains/python.py | 2 +- sphinx/domains/std.py | 2 +- tests/test_intersphinx.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sphinx/domains/__init__.py b/sphinx/domains/__init__.py index 0ec0622f..9e77cce8 100644 --- a/sphinx/domains/__init__.py +++ b/sphinx/domains/__init__.py @@ -6,7 +6,7 @@ Support for domains, which are groupings of description directives and roles describing e.g. constructs of one programming language. - :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index 0778a1d9..7a29ff68 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -5,7 +5,7 @@ The C language domain. - :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index c95f787a..1540f90b 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -5,7 +5,7 @@ The Python domain. - :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 37ae9378..4e7cca3a 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -5,7 +5,7 @@ The standard domain. - :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ diff --git a/tests/test_intersphinx.py b/tests/test_intersphinx.py index 2d521914..87f2704a 100644 --- a/tests/test_intersphinx.py +++ b/tests/test_intersphinx.py @@ -5,7 +5,7 @@ Test the intersphinx extension. - :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS. + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ -- cgit v1.2.1 From 0a1aa785f4bb26fd944ef957f67c6e1b630158fb Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sat, 2 Jan 2010 21:16:07 +0100 Subject: Activate test that depends on docutils >= 0.5. --- tests/test_build_html.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_build_html.py b/tests/test_build_html.py index 4691e2c3..44ecf3e2 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -154,7 +154,7 @@ HTML_XPATH = { ".//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']": r'\[Ref1\]', ".//td[@class='label']": '', ".//li[@class='toctree-l1']/a": 'Testing various markup', ".//li[@class='toctree-l2']/a": 'Inline markup', -- cgit v1.2.1 From b2ef99b6c2bc791466083ae05e38a22e0636b24e Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sat, 2 Jan 2010 21:38:27 +0100 Subject: #309: The ``graphviz`` extension can now output SVG instead of PNG images, controlled by the ``graphviz_output_format`` config value. Patch by Henrique Bastos. --- AUTHORS | 1 + CHANGES | 3 ++ doc/ext/graphviz.rst | 13 +++++++-- sphinx/ext/graphviz.py | 77 +++++++++++++++++++++++++++++++++++++++----------- 4 files changed, 75 insertions(+), 19 deletions(-) diff --git a/AUTHORS b/AUTHORS index ebda4307..54f53db2 100644 --- a/AUTHORS +++ b/AUTHORS @@ -5,6 +5,7 @@ Substantial parts of the templates were written by Armin Ronacher Other contributors, listed alphabetically, are: +* Henrique Bastos -- SVG support for graphviz extension * Daniel Bültmann -- todo extension * Michael Droettboom -- inheritance_diagram extension * Charles Duffy -- original graphviz extension diff --git a/CHANGES b/CHANGES index 3c03e89b..b56b6294 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,9 @@ Release 1.0 (in development) * Added Epub builder. +* #309: The ``graphviz`` extension can now output SVG instead of PNG + images, controlled by the ``graphviz_output_format`` config value. + * #284: All docinfo metadata is now put into the document metadata, not just the author. diff --git a/doc/ext/graphviz.rst b/doc/ext/graphviz.rst index d007bf25..64d2023b 100644 --- a/doc/ext/graphviz.rst +++ b/doc/ext/graphviz.rst @@ -25,8 +25,9 @@ It adds these directives: "bar" -> "baz"; } - In HTML output, the code will be rendered to a PNG image. In LaTeX output, - the code will be rendered to an embeddable PDF file. + In HTML output, the code will be rendered to a PNG or SVG image (see + :confval:`graphviz_output_format`). In LaTeX output, the code will be + rendered to an embeddable PDF file. .. directive:: graph @@ -75,3 +76,11 @@ There are also these new config values: Additional command-line arguments to give to dot, as a list. The default is an empty list. This is the right place to set global graph, node or edge attributes via dot's ``-G``, ``-N`` and ``-E`` options. + +.. confval:: graphviz_output_format + + The output format for Graphviz when building HTML files. This must be either + ``'png'`` or ``'svg'``; the default is ``'png'``. + + .. versionadded:: 1.0 + Previously, output always was PNG. diff --git a/sphinx/ext/graphviz.py b/sphinx/ext/graphviz.py index 05ec4ec1..509ea64a 100644 --- a/sphinx/ext/graphviz.py +++ b/sphinx/ext/graphviz.py @@ -13,6 +13,7 @@ import re import posixpath from os import path +from math import ceil from subprocess import Popen, PIPE try: from hashlib import sha1 as sha @@ -27,6 +28,7 @@ from sphinx.util.compat import Directive mapname_re = re.compile(r'\n' % \ + (svgref, imgcss, style) + + def render_dot_html(self, node, code, options, prefix='graphviz', imgcls=None): + format = self.builder.config.graphviz_output_format try: - fname, outfn = render_dot(self, code, options, 'png', prefix) + if format not in ('png', 'svg'): + raise GraphvizError("graphviz_output_format must be one of 'png', " + "'svg', but is %r" % format) + fname, outfn = render_dot(self, code, options, format, prefix) except GraphvizError, exc: self.builder.warn('dot code %r: ' % code + str(exc)) raise nodes.SkipNode @@ -139,23 +176,28 @@ def render_dot_html(self, node, code, options, prefix='graphviz', imgcls=None): if fname is None: self.body.append(self.encode(code)) else: - mapfile = open(outfn + '.map', 'rb') - try: - imgmap = mapfile.readlines() - finally: - mapfile.close() - imgcss = imgcls and 'class="%s"' % imgcls or '' - if len(imgmap) == 2: - # nothing in image map (the lines are and ) - self.body.append('%s\n' % - (fname, self.encode(code).strip(), imgcss)) + if format == 'svg': + svgtag = get_svg_tag(fname, outfn, imgcls) + self.body.append(svgtag) else: - # has a map: get the name of the map and connect the parts - mapname = mapname_re.match(imgmap[0]).group(1) - self.body.append('%s\n' % - (fname, self.encode(code).strip(), - mapname, imgcss)) - self.body.extend(imgmap) + mapfile = open(outfn + '.map', 'rb') + try: + imgmap = mapfile.readlines() + finally: + mapfile.close() + imgcss = imgcls and 'class="%s"' % imgcls or '' + if len(imgmap) == 2: + # nothing in image map (the lines are and ) + self.body.append('%s\n' % + (fname, self.encode(code).strip(), imgcss)) + else: + # has a map: get the name of the map and connect the parts + mapname = mapname_re.match(imgmap[0]).group(1) + self.body.append('%s\n' % + (fname, self.encode(code).strip(), + mapname, imgcss)) + self.body.extend(imgmap) + self.body.append('

\n') raise nodes.SkipNode @@ -188,3 +230,4 @@ def setup(app): app.add_directive('digraph', GraphvizSimple) app.add_config_value('graphviz_dot', 'dot', 'html') app.add_config_value('graphviz_dot_args', [], 'html') + app.add_config_value('graphviz_output_format', 'png', 'html') -- cgit v1.2.1 From f557af833d30187a2ea16f8b2e410d158794c5b5 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 3 Jan 2010 10:22:03 +0100 Subject: Fix link. --- doc/theming.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/theming.rst b/doc/theming.rst index c73b4f45..c2566ac3 100644 --- a/doc/theming.rst +++ b/doc/theming.rst @@ -128,7 +128,7 @@ These themes are: on the right side. There are currently no options beyond *nosidebar*. * **scrolls** -- A more lightweight theme, based on `the Jinja documentation - `_. The following color options are + `_. The following color options are available: - **headerbordercolor** -- cgit v1.2.1 From 3fef68e4b0cd4c1108e93fd6c6f7cd9c27eae5ac Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 3 Jan 2010 10:24:49 +0100 Subject: Note about linkcheck builder. --- doc/ext/extlinks.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/ext/extlinks.rst b/doc/ext/extlinks.rst index ffe60e10..d4478c5b 100644 --- a/doc/ext/extlinks.rst +++ b/doc/ext/extlinks.rst @@ -47,3 +47,8 @@ The extension adds one new config value: You can also use the usual "explicit title" syntax supported by other roles that generate links, i.e. ``:issue:`this issue <123>```. In this case, the *prefix* is not relevant. + +.. note:: + + Since links are generated from the role in the reading stage, they appear as + ordinary links to e.g. the ``linkcheck`` builder. -- cgit v1.2.1 From 86cbe0b3595454df34f9c52fb22ca163a02f1977 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 3 Jan 2010 10:25:21 +0100 Subject: linkcheck builder now shows the line number in console output. --- sphinx/builders/linkcheck.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index 300a13a1..3f9f16d9 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -70,6 +70,8 @@ class CheckExternalLinksBuilder(Builder): lineno = node.line if uri[0:5] == 'http:' or uri[0:6] == 'https:': + if lineno: + self.info('(line %3d) ' % lineno, nonl=1) self.info(uri, nonl=1) if uri in self.broken: -- cgit v1.2.1 From 4255dfb5bc32ce864bdb26baa5ed711bf6369b31 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 3 Jan 2010 12:02:31 +0100 Subject: Added ``alt`` option to ``graphviz`` extension directives. --- CHANGES | 2 ++ doc/ext/graphviz.rst | 6 ++++++ sphinx/ext/graphviz.py | 15 ++++++++++++--- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/CHANGES b/CHANGES index e9e9e069..0af33b6c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,8 @@ Release 1.0 (in development) ============================ +* Added ``alt`` option to ``graphviz`` extension directives. + * Added Epub builder. * #309: The ``graphviz`` extension can now output SVG instead of PNG diff --git a/doc/ext/graphviz.rst b/doc/ext/graphviz.rst index 64d2023b..77eae7d8 100644 --- a/doc/ext/graphviz.rst +++ b/doc/ext/graphviz.rst @@ -56,6 +56,12 @@ It adds these directives: "bar" -> "baz" -> "quux"; +.. versionadded:: 1.0 + All three directives support an ``alt`` option that determines the image's + alternate text for HTML output. If not given, the alternate text defaults to + the graphviz code. + + There are also these new config values: .. confval:: graphviz_dot diff --git a/sphinx/ext/graphviz.py b/sphinx/ext/graphviz.py index b83925c7..2f78d088 100644 --- a/sphinx/ext/graphviz.py +++ b/sphinx/ext/graphviz.py @@ -21,6 +21,7 @@ except ImportError: from sha import sha from docutils import nodes +from docutils.parsers.rst import directives from sphinx.errors import SphinxError from sphinx.util import ensuredir, ENOENT @@ -47,7 +48,9 @@ class Graphviz(Directive): required_arguments = 0 optional_arguments = 0 final_argument_whitespace = False - option_spec = {} + option_spec = { + 'alt': directives.unchanged, + } def run(self): dotcode = '\n'.join(self.content) @@ -58,6 +61,8 @@ class Graphviz(Directive): node = graphviz() node['code'] = dotcode node['options'] = [] + if 'alt' in self.options: + node['alt'] = self.options['alt'] return [node] @@ -69,13 +74,17 @@ class GraphvizSimple(Directive): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = False - option_spec = {} + option_spec = { + 'alt': directives.unchanged, + } def run(self): node = graphviz() node['code'] = '%s %s {\n%s\n}\n' % \ (self.name, self.arguments[0], '\n'.join(self.content)) node['options'] = [] + if 'alt' in self.options: + node['alt'] = self.options['alt'] return [node] @@ -178,7 +187,7 @@ def render_dot_html(self, node, code, options, prefix='graphviz', self.body.append(self.encode(code)) else: if alt is None: - alt = self.encode(code).strip() + alt = node.get('alt', self.encode(code).strip()) if format == 'svg': svgtag = get_svg_tag(fname, outfn, imgcls) self.body.append(svgtag) -- cgit v1.2.1 From 2398be848090f7158e79c8c4fd20a8456b531587 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 3 Jan 2010 13:59:30 +0100 Subject: #280: Autodoc can now document instance attributes assigned in ``__init__`` methods. --- CHANGES | 3 +++ sphinx/ext/autodoc.py | 51 ++++++++++++++++++++++++++++++++++++++++++----- sphinx/pycode/__init__.py | 39 ++++++++++++++++++++++++++++-------- tests/test_autodoc.py | 14 +++++++++++-- 4 files changed, 92 insertions(+), 15 deletions(-) diff --git a/CHANGES b/CHANGES index 7bd8b39d..4d221160 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,9 @@ Release 1.0 (in development) ============================ +* #280: Autodoc can now document instance attributes assigned in + ``__init__`` methods. + * Added ``alt`` option to ``graphviz`` extension directives. * Added Epub builder. diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc.py index 5d71d25c..cf38dbb8 100644 --- a/sphinx/ext/autodoc.py +++ b/sphinx/ext/autodoc.py @@ -72,6 +72,7 @@ class Options(dict): ALL = object() +INSTANCEATTR = object() def members_option(arg): """Used to convert the :members: option to auto directives.""" @@ -474,19 +475,30 @@ class Documenter(object): self.directive.warn('missing attribute %s in object %s' % (mname, self.fullname)) return False, ret - elif self.options.inherited_members: + + if self.options.inherited_members: # safe_getmembers() uses dir() which pulls in members from all # base classes - return False, safe_getmembers(self.object) + members = safe_getmembers(self.object) else: # __dict__ contains only the members directly defined in # the class (but get them via getattr anyway, to e.g. get # unbound method objects instead of function objects); # using keys() because apparently there are objects for which # __dict__ changes while getting attributes - return False, sorted([ - (mname, self.get_attr(self.object, mname, None)) - for mname in self.get_attr(self.object, '__dict__').keys()]) + obj_dict = self.get_attr(self.object, '__dict__') + members = [(mname, self.get_attr(self.object, mname, None)) + for mname in obj_dict.keys()] + membernames = set(m[0] for m in members) + # add instance attributes from the analyzer + if self.analyzer: + attr_docs = self.analyzer.find_attr_docs() + namespace = '.'.join(self.objpath) + for item in attr_docs.iteritems(): + if item[0][0] == namespace: + if item[0][1] not in membernames: + members.append((item[0][1], INSTANCEATTR)) + return False, sorted(members) def filter_members(self, members, want_all): """ @@ -1036,6 +1048,34 @@ class AttributeDocumenter(ClassLevelDocumenter): pass +class InstanceAttributeDocumenter(AttributeDocumenter): + """ + Specialized Documenter subclass for attributes that cannot be imported + because they are instance attributes (e.g. assigned in __init__). + """ + objtype = 'instanceattribute' + directivetype = 'attribute' + member_order = 60 + + # must be higher than AttributeDocumenter + priority = 11 + + @classmethod + def can_document_member(cls, member, membername, isattr, parent): + """This documents only INSTANCEATTR members.""" + return isattr and (member is INSTANCEATTR) + + def import_object(self): + """Never import anything.""" + # disguise as an attribute + self.objtype = 'attribute' + return True + + def add_content(self, more_content, no_docstring=False): + """Never try to get a docstring from the object.""" + AttributeDocumenter.add_content(self, more_content, no_docstring=True) + + class AutoDirective(Directive): """ The AutoDirective class is used for all autodoc directives. It dispatches @@ -1132,6 +1172,7 @@ def setup(app): app.add_autodocumenter(FunctionDocumenter) app.add_autodocumenter(MethodDocumenter) app.add_autodocumenter(AttributeDocumenter) + app.add_autodocumenter(InstanceAttributeDocumenter) app.add_config_value('autoclass_content', 'class', True) app.add_config_value('autodoc_member_order', 'alphabetic', True) diff --git a/sphinx/pycode/__init__.py b/sphinx/pycode/__init__.py index b7473bf2..19fd09e1 100644 --- a/sphinx/pycode/__init__.py +++ b/sphinx/pycode/__init__.py @@ -45,22 +45,33 @@ _eq = nodes.Leaf(token.EQUAL, '=') class AttrDocVisitor(nodes.NodeVisitor): """ Visitor that collects docstrings for attribute assignments on toplevel and - in classes. + in classes (class attributes and attributes set in __init__). The docstrings can either be in special '#:' comments before the assignment or in a docstring after it. """ def init(self, scope, encoding): self.scope = scope + self.in_init = 0 self.encoding = encoding self.namespace = [] self.collected = {} def visit_classdef(self, node): + """Visit a class.""" self.namespace.append(node[1].value) self.generic_visit(node) self.namespace.pop() + def visit_funcdef(self, node): + """Visit a function (or method).""" + # usually, don't descend into functions -- nothing interesting there + if node[1].value == '__init__': + # however, collect attributes set in __init__ methods + self.in_init += 1 + self.generic_visit(node) + self.in_init -= 1 + def visit_expr_stmt(self, node): """Visit an assignment which may have a special comment before it.""" if _eq not in node.children: @@ -97,20 +108,32 @@ class AttrDocVisitor(nodes.NodeVisitor): docstring = prepare_docstring(docstring) self.add_docstring(prev[0], docstring) - def visit_funcdef(self, node): - # don't descend into functions -- nothing interesting there - return - def add_docstring(self, node, docstring): # add an item for each assignment target for i in range(0, len(node) - 1, 2): target = node[i] - if target.type != token.NAME: - # don't care about complex targets + if self.in_init and self.number2name[target.type] == 'power': + # maybe an attribute assignment -- check necessary conditions + if (# node must have two children + len(target) != 2 or + # first child must be "self" + target[0].type != token.NAME or target[0].value != 'self' or + # second child must be a "trailer" with two children + self.number2name[target[1].type] != 'trailer' or + len(target[1]) != 2 or + # first child must be a dot, second child a name + target[1][0].type != token.DOT or + target[1][1].type != token.NAME): + continue + name = target[1][1].value + elif target.type != token.NAME: + # don't care about other complex targets continue + else: + name = target.value namespace = '.'.join(self.namespace) if namespace.startswith(self.scope): - self.collected[namespace, target.value] = docstring + self.collected[namespace, name] = docstring class PycodeError(Exception): diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index 73394bf2..b505b06a 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -382,7 +382,10 @@ def test_generate(): ('attribute', 'test_autodoc.Class.descr'), ('attribute', 'test_autodoc.Class.attr'), ('attribute', 'test_autodoc.Class.docattr'), - ('attribute', 'test_autodoc.Class.udocattr')]) + ('attribute', 'test_autodoc.Class.udocattr'), + ('attribute', 'test_autodoc.Class.inst_attr_comment'), + ('attribute', 'test_autodoc.Class.inst_attr_string') + ]) options.members = ALL assert_processes(should, 'class', 'Class') options.undoc_members = True @@ -403,7 +406,7 @@ def test_generate(): assert_result_contains(' :platform: Platform', 'module', 'test_autodoc') # test if __all__ is respected for modules options.members = ALL - assert_result_contains('.. class:: Class', 'module', 'test_autodoc') + assert_result_contains('.. class:: Class(arg)', 'module', 'test_autodoc') try: assert_result_contains('.. exception:: CustomEx', 'module', 'test_autodoc') @@ -499,6 +502,13 @@ class Class(Base): udocattr = 'quux' u"""should be documented as well - süß""" + def __init__(self, arg): + #: a documented instance attribute + self.inst_attr_comment = None + self.inst_attr_string = None + """a documented instance attribute""" + + class CustomDict(dict): """Docstring.""" -- cgit v1.2.1 From 66febe79c7881126b13fc380b5b66685bd661993 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 3 Jan 2010 15:29:41 +0100 Subject: #129: Wrap toctrees in a div tag with class ``toctree-wrapper`` in HTML output. --- CHANGES | 3 +++ sphinx/directives/other.py | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 4d221160..73048c3f 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,9 @@ Release 1.0 (in development) ============================ +* #129: Wrap toctrees in a div tag with class ``toctree-wrapper`` + in HTML output. + * #280: Autodoc can now document instance attributes assigned in ``__init__`` methods. diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py index 1c831158..d746ea62 100644 --- a/sphinx/directives/other.py +++ b/sphinx/directives/other.py @@ -100,7 +100,9 @@ class TocTree(Directive): subnode['hidden'] = 'hidden' in self.options subnode['numbered'] = 'numbered' in self.options subnode['titlesonly'] = 'titlesonly' in self.options - ret.append(subnode) + wrappernode = nodes.compound(classes=['toctree-wrapper']) + wrappernode.append(subnode) + ret.append(wrappernode) return ret -- cgit v1.2.1 From 0afca45dbc97654e489df0367bf92d9b4803cf78 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 3 Jan 2010 19:30:32 +0100 Subject: Make inventory dumping a separate method. --- sphinx/builders/html.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index acab7518..c19cc4e7 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -23,7 +23,7 @@ except ImportError: from docutils import nodes from docutils.io import DocTreeInput, StringOutput -from docutils.core import Publisher, publish_parts +from docutils.core import Publisher from docutils.utils import new_document from docutils.frontend import OptionParser from docutils.readers.doctree import Reader as DoctreeReader @@ -701,20 +701,7 @@ class StandaloneHTMLBuilder(Builder): ensuredir(path.dirname(source_name)) copyfile(self.env.doc2path(pagename), source_name) - def handle_finish(self): - self.info(bold('dumping search index... '), nonl=True) - self.indexer.prune(self.env.all_docs) - searchindexfn = path.join(self.outdir, self.searchindex_filename) - # first write to a temporary file, so that if dumping fails, - # the existing index won't be overwritten - f = open(searchindexfn + '.tmp', 'wb') - try: - self.indexer.dump(f, self.indexer_format) - finally: - f.close() - movefile(searchindexfn + '.tmp', searchindexfn) - self.info('done') - + def dump_inventory(self): self.info(bold('dumping object inventory... '), nonl=True) f = open(path.join(self.outdir, INVENTORY_FILENAME), 'wb') try: @@ -736,6 +723,22 @@ class StandaloneHTMLBuilder(Builder): f.close() self.info('done') + def handle_finish(self): + self.info(bold('dumping search index... '), nonl=True) + self.indexer.prune(self.env.all_docs) + searchindexfn = path.join(self.outdir, self.searchindex_filename) + # first write to a temporary file, so that if dumping fails, + # the existing index won't be overwritten + f = open(searchindexfn + '.tmp', 'wb') + try: + self.indexer.dump(f, self.indexer_format) + finally: + f.close() + movefile(searchindexfn + '.tmp', searchindexfn) + self.info('done') + + self.dump_inventory() + class DirectoryHTMLBuilder(StandaloneHTMLBuilder): """ -- cgit v1.2.1 From 49e8e51a319f5344ab25c8fa5523123153ad8ae3 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 3 Jan 2010 19:40:07 +0100 Subject: Add "nature" theme, as used by PIP and distribute. --- CHANGES | 4 +- doc/ext/appapi.rst | 8 +- doc/theming.rst | 8 +- sphinx/themes/nature/static/nature.css_t | 229 +++++++++++++++++++++++++++++++ sphinx/themes/nature/theme.conf | 4 + 5 files changed, 248 insertions(+), 5 deletions(-) create mode 100644 sphinx/themes/nature/static/nature.css_t create mode 100644 sphinx/themes/nature/theme.conf diff --git a/CHANGES b/CHANGES index 46685a06..9a35b420 100644 --- a/CHANGES +++ b/CHANGES @@ -17,9 +17,11 @@ Release 1.0 (in development) * #284: All docinfo metadata is now put into the document metadata, not just the author. +* Added new HTML theme ``nature``. + * Added new HTML theme ``agogo``, created by Andi Albrecht. -* Added new minimal theme called scrolls, created by Armin Ronacher. +* Added new HTML theme called scrolls, created by Armin Ronacher. * Added ``html_output_encoding`` config value. diff --git a/doc/ext/appapi.rst b/doc/ext/appapi.rst index 0a3dae8f..9b2d039c 100644 --- a/doc/ext/appapi.rst +++ b/doc/ext/appapi.rst @@ -56,7 +56,9 @@ the following public API: given as keyword arguments: the keyword must be one or more of ``'html'``, ``'latex'``, ``'text'``, the value a 2-tuple of ``(visit, depart)`` methods. ``depart`` can be ``None`` if the ``visit`` function raises - :exc:`docutils.nodes.SkipNode`. Example:: + :exc:`docutils.nodes.SkipNode`. Example: + + .. code-block:: python class math(docutils.nodes.Element) @@ -98,7 +100,9 @@ the following public API: support directive classes otherwise). For example, the (already existing) :dir:`literalinclude` directive would be - added like this:: + added like this: + + .. code-block:: python from docutils.parsers.rst import directives add_directive('literalinclude', literalinclude_directive, diff --git a/doc/theming.rst b/doc/theming.rst index c2566ac3..4771dbec 100644 --- a/doc/theming.rst +++ b/doc/theming.rst @@ -65,9 +65,9 @@ Builtin themes | | | | *scrolls* | *agogo* | +--------------------+--------------------+ -| |traditional| | | +| |traditional| | |nature| | | | | -| *traditional* | | +| *traditional* | *nature* | +--------------------+--------------------+ .. |default| image:: themes/default.png @@ -75,6 +75,7 @@ Builtin themes .. |scrolls| image:: themes/scrolls.png .. |agogo| image:: themes/agogo.png .. |traditional| image:: themes/traditional.png +.. |nature| image:: themes/nature.png Sphinx comes with a selection of themes to choose from. @@ -157,6 +158,9 @@ These themes are: - **headerlinkcolor** (CSS color): Color for the backreference link in headings. +* **nature** -- A greenish theme. There are currently no options beyond + *nosidebar*. + * **traditional** -- A theme resembling the old Python documentation. There are currently no options beyond *nosidebar*. diff --git a/sphinx/themes/nature/static/nature.css_t b/sphinx/themes/nature/static/nature.css_t new file mode 100644 index 00000000..31d9ec62 --- /dev/null +++ b/sphinx/themes/nature/static/nature.css_t @@ -0,0 +1,229 @@ +/** + * Sphinx stylesheet -- nature theme + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +body { + font-family: Arial, sans-serif; + font-size: 100%; + background-color: #111; + color: #555; + margin: 0; + padding: 0; +} + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 230px; +} + +hr { + border: 1px solid #B1B4B6; +} + +div.document { + background-color: #eee; +} + +div.body { + background-color: #ffffff; + color: #3E4349; + padding: 0 30px 30px 30px; + font-size: 0.9em; +} + +div.footer { + color: #555; + width: 100%; + padding: 13px 0; + text-align: center; + font-size: 75%; +} + +div.footer a { + color: #444; + text-decoration: underline; +} + +div.related { + background-color: #6BA81E; + line-height: 32px; + color: #fff; + text-shadow: 0px 1px 0 #444; + font-size: 0.9em; +} + +div.related a { + color: #E2F3CC; +} + +div.sphinxsidebar { + font-size: 0.75em; + line-height: 1.5em; +} + +div.sphinxsidebarwrapper{ + padding: 20px 0; +} + +div.sphinxsidebar h3, +div.sphinxsidebar h4 { + font-family: Arial, sans-serif; + color: #222; + font-size: 1.2em; + font-weight: normal; + margin: 0; + padding: 5px 10px; + background-color: #ddd; + text-shadow: 1px 1px 0 white +} + +div.sphinxsidebar h4{ + font-size: 1.1em; +} + +div.sphinxsidebar h3 a { + color: #444; +} + + +div.sphinxsidebar p { + color: #888; + padding: 5px 20px; +} + +div.sphinxsidebar p.topless { +} + +div.sphinxsidebar ul { + margin: 10px 20px; + padding: 0; + color: #000; +} + +div.sphinxsidebar a { + color: #444; +} + +div.sphinxsidebar input { + border: 1px solid #ccc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar input[type=text]{ + margin-left: 20px; +} + +/* -- body styles ----------------------------------------------------------- */ + +a { + color: #005B81; + text-decoration: none; +} + +a:hover { + color: #E32E00; + text-decoration: underline; +} + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: Arial, sans-serif; + background-color: #BED4EB; + font-weight: normal; + color: #212224; + margin: 30px 0px 10px 0px; + padding: 5px 0 5px 10px; + text-shadow: 0px 1px 0 white +} + +div.body h1 { border-top: 20px solid white; margin-top: 0; font-size: 200%; } +div.body h2 { font-size: 150%; background-color: #C8D5E3; } +div.body h3 { font-size: 120%; background-color: #D8DEE3; } +div.body h4 { font-size: 110%; background-color: #D8DEE3; } +div.body h5 { font-size: 100%; background-color: #D8DEE3; } +div.body h6 { font-size: 100%; background-color: #D8DEE3; } + +a.headerlink { + color: #c60f0f; + font-size: 0.8em; + padding: 0 4px 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + background-color: #c60f0f; + color: white; +} + +div.body p, div.body dd, div.body li { + line-height: 1.5em; +} + +div.admonition p.admonition-title + p { + display: inline; +} + +div.highlight{ + background-color: white; +} + +div.note { + background-color: #eee; + border: 1px solid #ccc; +} + +div.seealso { + background-color: #ffc; + border: 1px solid #ff6; +} + +div.topic { + background-color: #eee; +} + +div.warning { + background-color: #ffe4e4; + border: 1px solid #f66; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre { + padding: 10px; + background-color: White; + color: #222; + line-height: 1.2em; + border: 1px solid #C6C9CB; + font-size: 1.1em; + margin: 1.5em 0 1.5em 0; + -webkit-box-shadow: 1px 1px 1px #d8d8d8; + -moz-box-shadow: 1px 1px 1px #d8d8d8; +} + +tt { + background-color: #ecf0f3; + color: #222; + /* padding: 1px 2px; */ + font-size: 1.1em; + font-family: monospace; +} diff --git a/sphinx/themes/nature/theme.conf b/sphinx/themes/nature/theme.conf new file mode 100644 index 00000000..1cc40044 --- /dev/null +++ b/sphinx/themes/nature/theme.conf @@ -0,0 +1,4 @@ +[theme] +inherit = basic +stylesheet = nature.css +pygments_style = tango -- cgit v1.2.1 From 0e65a6b522ec22ee8c29a5a15dff110a28e76c7f Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 3 Jan 2010 19:44:40 +0100 Subject: Add preview images for nature theme. --- doc/themes/fullsize/nature.png | Bin 0 -> 44469 bytes doc/themes/nature.png | Bin 0 -> 40786 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/themes/fullsize/nature.png create mode 100644 doc/themes/nature.png diff --git a/doc/themes/fullsize/nature.png b/doc/themes/fullsize/nature.png new file mode 100644 index 00000000..d42957e3 Binary files /dev/null and b/doc/themes/fullsize/nature.png differ diff --git a/doc/themes/nature.png b/doc/themes/nature.png new file mode 100644 index 00000000..053970d8 Binary files /dev/null and b/doc/themes/nature.png differ -- cgit v1.2.1 From 71e5eb3569e1146602d6976cbd86ef3d0346469a Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 3 Jan 2010 22:22:33 +0100 Subject: Fix long line. --- sphinx/environment.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sphinx/environment.py b/sphinx/environment.py index d3e3196b..d22cf5e4 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -1235,7 +1235,8 @@ class BuildEnvironment: # keywords are referenced by named labels docname, labelid, _ = self.labels.get(target, ('','','')) if not docname: - #self.warn(node['refdoc'], 'unknown keyword: %s' % target) + #self.warn(node['refdoc'], + # 'unknown keyword: %s' % target) newnode = contnode else: newnode = nodes.reference('', '') -- cgit v1.2.1 From c800c6d658f6d3979144bd32ddb80b110048a092 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 3 Jan 2010 22:24:30 +0100 Subject: Fix test, adding the new theme. --- tests/test_theming.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_theming.py b/tests/test_theming.py index 6dd5e12c..be5105a6 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', 'epub']) + 'traditional', 'testtheme', 'ziptheme', 'epub', 'nature']) assert Theme.themes['testtheme'][1] is None assert isinstance(Theme.themes['ziptheme'][1], zipfile.ZipFile) -- cgit v1.2.1 From 818b990fcd3fbcfddb5d8fb84e5f23ffc75ed1ed Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 6 Jan 2010 15:54:34 +0100 Subject: Remove the deprecated ``exclude_dirs`` config value. --- CHANGES | 2 ++ doc/config.rst | 10 ---------- sphinx/config.py | 1 - sphinx/environment.py | 2 -- tests/test_config.py | 4 ++-- 5 files changed, 4 insertions(+), 15 deletions(-) diff --git a/CHANGES b/CHANGES index c03429c2..cb4f4a97 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,8 @@ Release 1.0 (in development) ============================ +* Remove the deprecated ``exclude_dirs`` config value. + * #129: Wrap toctrees in a div tag with class ``toctree-wrapper`` in HTML output. diff --git a/doc/config.rst b/doc/config.rst index dbcf799c..60dc0499 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -110,16 +110,6 @@ General configuration .. versionadded:: 0.5 -.. confval:: exclude_dirs - - A list of directory names, relative to the source directory, that are to be - excluded from the search for source files. - - .. deprecated:: 0.5 - This does not take subdirs of the excluded directories into account. Use - :confval:`exclude_trees` or :confval:`exclude_dirnames`, which match the - expectations. - .. confval:: locale_dirs .. versionadded:: 0.5 diff --git a/sphinx/config.py b/sphinx/config.py index d8865d62..b86083f7 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -39,7 +39,6 @@ class Config(object): source_suffix = ('.rst', 'env'), source_encoding = ('utf-8-sig', 'env'), unused_docs = ([], 'env'), - exclude_dirs = ([], 'env'), exclude_trees = ([], 'env'), exclude_dirnames = ([], 'env'), default_role = (None, 'env'), diff --git a/sphinx/environment.py b/sphinx/environment.py index d22cf5e4..77c84bc6 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -393,12 +393,10 @@ class BuildEnvironment: """ Find all source files in the source dir and put them in self.found_docs. """ - exclude_dirs = [d.replace(SEP, path.sep) for d in config.exclude_dirs] exclude_trees = [d.replace(SEP, path.sep) for d in config.exclude_trees] self.found_docs = set(get_matching_docs( self.srcdir, config.source_suffix, exclude_docs=set(config.unused_docs), - exclude_dirs=exclude_dirs, exclude_trees=exclude_trees, exclude_dirnames=['_sources'] + config.exclude_dirnames)) diff --git a/tests/test_config.py b/tests/test_config.py index 76f7920b..b1c50f70 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -30,8 +30,8 @@ def test_core_config(app): assert cfg.latex_elements['docclass'] == 'scrartcl' # simple default values - assert 'exclude_dirs' not in cfg.__dict__ - assert cfg.exclude_dirs == [] + assert 'locale_dirs' not in cfg.__dict__ + assert cfg.locale_dirs == [] assert cfg.show_authors == False # complex default values -- cgit v1.2.1 From d8ecadd172a7b6bb42540da4543301279846d521 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 6 Jan 2010 15:48:39 +0100 Subject: Add new universal config value ``exclude_patterns``, with glob-style exclude patterns. This makes the old ``unused_docs``, ``exclude_trees`` and ``exclude_dirnames`` obsolete. --- CHANGES | 4 +++ doc/conf.py | 2 ++ doc/config.rst | 39 +++++++++++++++++++++++++++ sphinx/config.py | 2 ++ sphinx/environment.py | 11 ++++---- sphinx/quickstart.py | 13 ++++----- sphinx/util/__init__.py | 60 ++++++++++++++++++++++++------------------ tests/root/conf.py | 2 +- tests/root/subdir/excluded.txt | 2 ++ tests/test_env.py | 2 ++ 10 files changed, 98 insertions(+), 39 deletions(-) create mode 100644 tests/root/subdir/excluded.txt diff --git a/CHANGES b/CHANGES index cb4f4a97..05a162cc 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,10 @@ Release 1.0 (in development) ============================ +* The new universal config value ``exclude_patterns`` makes the + old ``unused_docs``, ``exclude_trees`` and ``exclude_dirnames`` + obsolete. + * Remove the deprecated ``exclude_dirs`` config value. * #129: Wrap toctrees in a div tag with class ``toctree-wrapper`` diff --git a/doc/conf.py b/doc/conf.py index ce87c54e..feda5fd1 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -18,6 +18,8 @@ source_suffix = '.rst' # The master toctree document. master_doc = 'contents' +exclude_patterns = ['_build'] + # General substitutions. project = 'Sphinx' copyright = '2007-2010, Georg Brandl' diff --git a/doc/config.rst b/doc/config.rst index 60dc0499..be956ffa 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -87,12 +87,37 @@ General configuration The document name of the "master" document, that is, the document that contains the root :dir:`toctree` directive. Default is ``'contents'``. +.. confval:: exclude_patterns + + A list of glob-style patterns that should be excluded when looking for source + files. [#]_ They are matched against the source file names relative to the + source directory, using slashes as directory separators on all platforms. + + Example patterns: + + - ``'library/xml.rst'`` -- ignores the ``library/xml.rst`` file (replaces + entry in :confval:`unused_docs` + - ``'library/xml'`` -- ignores the ``library/xml`` directory (replaces entry + in :confval:`exclude_trees`) + - ``'library/xml*'`` -- ignores all files and directories starting with + ``library/xml`` + - ``'**/.svn'`` -- ignores all ``.svn`` directories (replaces entry in + :confval:`exclude_dirnames`) + + :confval:`exclude_patterns` is also consulted when looking for static files + in :confval:`html_static_path`. + + .. versionadded:: 1.0 + .. confval:: unused_docs A list of document names that are present, but not currently included in the toctree. Use this setting to suppress the warning that is normally emitted in that case. + .. deprecated:: 1.0 + Use :confval:`exclude_patterns` instead. + .. confval:: exclude_trees A list of directory paths, relative to the source directory, that are to be @@ -101,6 +126,9 @@ General configuration .. versionadded:: 0.4 + .. deprecated:: 1.0 + Use :confval:`exclude_patterns` instead. + .. confval:: exclude_dirnames A list of directory names that are to be excluded from any recursive @@ -110,6 +138,9 @@ General configuration .. versionadded:: 0.5 + .. deprecated:: 1.0 + Use :confval:`exclude_patterns` instead. + .. confval:: locale_dirs .. versionadded:: 0.5 @@ -797,3 +828,11 @@ These options influence LaTeX output. .. deprecated:: 0.5 Use the ``'pointsize'`` key in the :confval:`latex_elements` value. + + +.. rubric:: Footnotes + +.. [#] A note on available globbing syntax: you can use the standard shell + constructs ``*``, ``?``, ``[...]`` and ``[!...]`` with the feature that + these all don't match slashes. A double star ``**`` can be used to match + any sequence of characters *including* slashes. diff --git a/sphinx/config.py b/sphinx/config.py index b86083f7..5fe3f63f 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -38,6 +38,8 @@ class Config(object): master_doc = ('contents', 'env'), source_suffix = ('.rst', 'env'), source_encoding = ('utf-8-sig', 'env'), + exclude_patterns = ([], 'env'), + # the next three are all deprecated now unused_docs = ([], 'env'), exclude_trees = ([], 'env'), exclude_dirnames = ([], 'env'), diff --git a/sphinx/environment.py b/sphinx/environment.py index 77c84bc6..781d8b5c 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -393,12 +393,13 @@ class BuildEnvironment: """ Find all source files in the source dir and put them in self.found_docs. """ - exclude_trees = [d.replace(SEP, path.sep) for d in config.exclude_trees] + patterns = config.exclude_patterns[:] + patterns += config.exclude_trees + patterns += [d + config.source_suffix for d in config.unused_docs] + patterns += ['**/' + d for d in config.exclude_dirnames] + patterns += ['**/_sources'] self.found_docs = set(get_matching_docs( - self.srcdir, config.source_suffix, - exclude_docs=set(config.unused_docs), - exclude_trees=exclude_trees, - exclude_dirnames=['_sources'] + config.exclude_dirnames)) + self.srcdir, config.source_suffix, exclude_patterns=patterns)) def get_outdated_files(self, config_changed): """ diff --git a/sphinx/quickstart.py b/sphinx/quickstart.py index 51103b5a..faea2c2e 100644 --- a/sphinx/quickstart.py +++ b/sphinx/quickstart.py @@ -84,12 +84,9 @@ release = '%(release_str)s' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%%B %%d, %%Y' -# List of documents that shouldn't be included in the build. -#unused_docs = [] - -# List of directories, relative to source directory, that shouldn't be searched -# for source files. -exclude_trees = [%(exclude_trees)s] +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [%(exclude_patterns)s] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None @@ -743,10 +740,10 @@ directly.''' mkdir_p(srcdir) if d['sep']: builddir = path.join(d['path'], 'build') - d['exclude_trees'] = '' + d['exclude_patterns'] = '' else: builddir = path.join(srcdir, d['dot'] + 'build') - d['exclude_trees'] = repr(d['dot'] + 'build') + d['exclude_patterns'] = repr(d['dot'] + 'build') mkdir_p(builddir) mkdir_p(path.join(srcdir, d['dot'] + 'templates')) mkdir_p(path.join(srcdir, d['dot'] + 'static')) diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index e04137ee..0d88ddea 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -101,38 +101,48 @@ def walk(top, topdown=True, followlinks=False): yield top, dirs, nondirs -def get_matching_docs(dirname, suffix, exclude_docs=(), exclude_dirs=(), - exclude_trees=(), exclude_dirnames=()): +def get_matching_files(dirname, exclude_patterns=()): """ - Get all file names (without suffix) matching a suffix in a - directory, recursively. + Get all file names in a directory, recursively. - Exclude docs in *exclude_docs*, exclude dirs in *exclude_dirs*, - prune dirs in *exclude_trees*, prune dirnames in *exclude_dirnames*. + Exclude files and dirs matching a pattern in *exclude_patterns*. """ - pattern = '*' + suffix # dirname is a normalized absolute path. dirname = path.normpath(path.abspath(dirname)) - dirlen = len(dirname) + 1 # exclude slash + dirlen = len(dirname) + 1 # exclude final os.path.sep + + matchers = [re.compile(_translate_pattern(pat)).match + for pat in exclude_patterns] + for root, dirs, files in walk(dirname, followlinks=True): - if root[dirlen:] in exclude_dirs: - continue - if root[dirlen:] in exclude_trees: - del dirs[:] + relativeroot = root[dirlen:] + + qdirs = enumerate(path.join(relativeroot, dir).replace(os.path.sep, SEP) + for dir in dirs) + qfiles = enumerate(path.join(relativeroot, file).replace(os.path.sep, SEP) + for file in files) + for matcher in matchers: + qdirs = [entry for entry in qdirs if not matcher(entry[1])] + qfiles = [entry for entry in qfiles if not matcher(entry[1])] + + dirs[:] = sorted(dirs[i] for (i, _) in qdirs) + + for i, filename in sorted(qfiles): + yield filename + + +def get_matching_docs(dirname, suffix, exclude_patterns=()): + """ + Get all file names (without suffix) matching a suffix in a + directory, recursively. + + Exclude files and dirs matching a pattern in *exclude_patterns*. + """ + suffixpattern = '*' + suffix + for filename in get_matching_files(dirname, exclude_patterns): + if not fnmatch.fnmatch(filename, suffixpattern): continue - dirs.sort() - files.sort() - for prunedir in exclude_dirnames: - if prunedir in dirs: - dirs.remove(prunedir) - for sfile in files: - if not fnmatch.fnmatch(sfile, pattern): - continue - qualified_name = path.join(root[dirlen:], sfile[:-len(suffix)]) - qualified_name = qualified_name.replace(os.path.sep, SEP) - if qualified_name in exclude_docs: - continue - yield qualified_name + yield filename[:-len(suffix)] def mtimes_of_files(dirnames, suffix): diff --git a/tests/root/conf.py b/tests/root/conf.py index 4a90f986..abd53dbe 100644 --- a/tests/root/conf.py +++ b/tests/root/conf.py @@ -22,7 +22,7 @@ version = '0.6' release = '0.6alpha1' today_fmt = '%B %d, %Y' #unused_docs = [] -exclude_trees = ['_build'] +exclude_patterns = ['_build', '**/excluded.*'] keep_warnings = True pygments_style = 'sphinx' diff --git a/tests/root/subdir/excluded.txt b/tests/root/subdir/excluded.txt new file mode 100644 index 00000000..5df3139b --- /dev/null +++ b/tests/root/subdir/excluded.txt @@ -0,0 +1,2 @@ +Excluded file -- should *not* be read as source +----------------------------------------------- diff --git a/tests/test_env.py b/tests/test_env.py index da129d9a..0090d6b6 100644 --- a/tests/test_env.py +++ b/tests/test_env.py @@ -43,6 +43,8 @@ def test_first_update(): for docname in it: # the generator does all the work docnames.add(docname) assert docnames == env.found_docs == set(env.all_docs) + # test if exclude_patterns works ok + assert 'subdir/excluded' not in env.found_docs def test_images(): assert warning_emitted('images.txt', 'image file not readable: foo.png') -- cgit v1.2.1 From 8a391252ff436fc84d23a10c6319fa1d8ebd82a5 Mon Sep 17 00:00:00 2001 From: roland Date: Wed, 6 Jan 2010 18:14:48 +0100 Subject: Include full toctree in get_toc. See also http://bitbucket.org/rolmei/sphinx-epub/issue/2/missing-files-in-spine --- sphinx/builders/epub.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sphinx/builders/epub.py b/sphinx/builders/epub.py index 57e2dff9..e22b1520 100644 --- a/sphinx/builders/epub.py +++ b/sphinx/builders/epub.py @@ -188,7 +188,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""" - 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.insert(0, { 'level': 1, -- cgit v1.2.1 From c9142cefdfde0d520c07e59c077fd1971b13ce54 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 7 Jan 2010 11:43:51 +0100 Subject: Remove all template content from the scrolls theme that was almost the same as the basic. Apply a few real modifications to the basic templates. --- sphinx/themes/basic/genindex.html | 41 ++- sphinx/themes/basic/modindex.html | 5 +- sphinx/themes/basic/static/basic.css | 18 ++ sphinx/themes/scrolls/genindex.html | 36 --- sphinx/themes/scrolls/layout.html | 80 +----- sphinx/themes/scrolls/modindex.html | 43 --- sphinx/themes/scrolls/opensearch.xml | 9 - sphinx/themes/scrolls/page.html | 4 - sphinx/themes/scrolls/search.html | 35 --- sphinx/themes/scrolls/static/scrolls.css_t | 410 +++++++++++++++++++++++++++++ sphinx/themes/scrolls/static/style.css_t | 410 ----------------------------- sphinx/themes/scrolls/theme.conf | 2 +- 12 files changed, 458 insertions(+), 635 deletions(-) delete mode 100644 sphinx/themes/scrolls/genindex.html delete mode 100644 sphinx/themes/scrolls/modindex.html delete mode 100644 sphinx/themes/scrolls/opensearch.xml delete mode 100644 sphinx/themes/scrolls/page.html delete mode 100644 sphinx/themes/scrolls/search.html create mode 100644 sphinx/themes/scrolls/static/scrolls.css_t delete mode 100644 sphinx/themes/scrolls/static/style.css_t diff --git a/sphinx/themes/basic/genindex.html b/sphinx/themes/basic/genindex.html index a19aa80f..6b3d1a64 100644 --- a/sphinx/themes/basic/genindex.html +++ b/sphinx/themes/basic/genindex.html @@ -4,41 +4,36 @@

{{ _('Index') }}

+
{% for key, dummy in genindexentries -%} {{ key }} {% if not loop.last %}| {% endif %} {%- endfor %} +

- {% for key, entries in genindexentries %} + {%- for key, entries in genindexentries %}

{{ key }}

- +{%- endfor %} +
-
-{%- set breakat = genindexcounts[loop.index0] // 2 %} -{%- set numcols = 1 %} -{%- set numitems = 0 %} -{% for entryname, (links, subitems) in entries %} -
{%- if links -%}{{ entryname|e }} - {%- for link in links[1:] %}, [{{ loop.index }}]{% endfor -%} - {%- else -%} -{{ entryname|e }} - {%- endif -%}
- {%- if subitems %} -
+ + {%- for column in entries|slice(2) if column %} +
+ {%- for entryname, (links, subitems) in column %} +
{% if links %}{{ entryname|e }} + {% for link in links[1:] %}, [{{ loop.index }}]{% endfor %} + {%- else %}{{ entryname|e }}{% endif %}
+ {%- if subitems %} +
{%- for subentryname, subentrylinks in subitems %} -
{{ subentryname|e }} - {%- for link in subentrylinks[1:] %}, [{{ loop.index }}]{% endfor -%} -
+
{{ subentryname|e }} + {%- for link in subentrylinks[1:] %}, [{{ loop.index }}]{% endfor -%} +
{%- endfor %}
{%- endif -%} -{%- set numitems = numitems + 1 + (subitems|length) -%} -{%- if numcols < 2 and numitems > breakat -%} -{%- set numcols = numcols+1 -%} -
-{%- endif -%} {%- endfor %} -
+
{% endfor %} {% endblock %} diff --git a/sphinx/themes/basic/modindex.html b/sphinx/themes/basic/modindex.html index 0392edc8..03ed604e 100644 --- a/sphinx/themes/basic/modindex.html +++ b/sphinx/themes/basic/modindex.html @@ -12,12 +12,13 @@

{{ _('Global Module Index') }}

+
{%- for letter in letters %} {{ letter }} {% if not loop.last %}| {% endif %} {%- endfor %} -
+
- +
{%- for modname, collapse, cgroup, indent, fname, synops, pform, dep, stripped in modindexentries %} {%- if not modname -%} diff --git a/sphinx/themes/basic/static/basic.css b/sphinx/themes/basic/static/basic.css index 344c81cd..29b8450f 100644 --- a/sphinx/themes/basic/static/basic.css +++ b/sphinx/themes/basic/static/basic.css @@ -127,6 +127,10 @@ span.linkdescr { /* -- general index --------------------------------------------------------- */ +table.indextable { + width: 100%; +} + table.indextable td { text-align: left; vertical-align: top; @@ -152,6 +156,20 @@ img.toggler { cursor: pointer; } +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + /* -- general body styles --------------------------------------------------- */ a.headerlink { diff --git a/sphinx/themes/scrolls/genindex.html b/sphinx/themes/scrolls/genindex.html deleted file mode 100644 index 9add6e95..00000000 --- a/sphinx/themes/scrolls/genindex.html +++ /dev/null @@ -1,36 +0,0 @@ -{% extends "layout.html" %} -{% set title = 'Index' %} -{% block body %} - -

Index

- - {% for key, dummy in genindexentries -%} - {{ key }} {% if not loop.last %}| {% endif %} - {%- endfor %} -
- - {% for key, entries in genindexentries %} -

{{ key }}

-
 
- {%- for column in entries|slice(2) if column %} - - {%- endfor %} -
- {%- for entryname, (links, subitems) in column %} -
{% if links %}{{ entryname|e }} - {% for link in links[1:] %}, [Link]{% endfor %} - {%- else %}{{ entryname|e }}{% endif %}
- {%- if subitems %} -
- {%- for subentryname, subentrylinks in subitems %} -
{{ subentryname|e }} - {%- for link in subentrylinks[1:] %}, [Link]{% endfor -%} -
- {%- endfor %} -
- {%- endif -%} - {%- endfor %} -
- {% endfor %} - -{% endblock %} diff --git a/sphinx/themes/scrolls/layout.html b/sphinx/themes/scrolls/layout.html index d1c66ae2..07052e87 100644 --- a/sphinx/themes/scrolls/layout.html +++ b/sphinx/themes/scrolls/layout.html @@ -1,58 +1,10 @@ - - - - {%- if not embedded %} - {%- set titlesuffix = " — "|safe + docstitle|e %} - {%- else %} - {%- set titlesuffix = "" %} - {%- endif %} - {{ title|striptags }}{{ titlesuffix }} - - - - - {%- if builder != 'htmlhelp' %} - - - - - - {%- endif %} - {%- if use_opensearch and builder != 'htmlhelp' %} - - {%- endif %} - {%- if hasdoc('about') %} - - {%- endif %} - - - - {%- if hasdoc('copyright') %} - - {%- endif %} - - {%- if parents %} - - {%- endif %} - {%- if next %} - - {%- endif %} - {%- if prev %} - - {%- endif %} - {% block extrahead %}{% endblock %} - - +{% extends "basic/layout.html" %} +{% set script_files = script_files + ['_static/theme_extras.js'] %} +{% set css_files = css_files + ['_static/print.css'] %} +{# do not display relbars #} +{% block relbar1 %}{% endblock %} +{% block relbar2 %}{% endblock %} +{% block content %}
- - - +{% endblock %} \ No newline at end of file diff --git a/sphinx/themes/scrolls/modindex.html b/sphinx/themes/scrolls/modindex.html deleted file mode 100644 index 314ebdd9..00000000 --- a/sphinx/themes/scrolls/modindex.html +++ /dev/null @@ -1,43 +0,0 @@ -{% extends "layout.html" %} -{% set title = _('Global Module Index') %} -{% block extrahead %} -{{ super() }} -{% if not embedded and collapse_modindex %} - -{% endif %} -{% endblock %} -{% block body %} - -

{{ _('Global Module Index') }}

- -
- {%- for letter in letters %} - {{ letter }} {% if not loop.last %}| {% endif %} - {%- endfor %} -
- - - {%- for modname, collapse, cgroup, indent, fname, synops, pform, dep, stripped in modindexentries %} - {%- if not modname -%} - - - {%- else -%} - - - - {%- endif -%} - {% endfor %} -
 
{{ fname }}
{% if collapse -%} - - {%- endif %}{% if indent %}   {% endif %} - {% if fname %}{% endif -%} - {{ stripped|e }}{{ modname|e }} - {%- if fname %}{% endif %} - {%- if pform and pform[0] %} ({{ pform|join(', ') }}){% endif -%} - {% if dep %}{{ _('Deprecated')}}:{% endif %} - {{ synops|e }}
- -{% endblock %} diff --git a/sphinx/themes/scrolls/opensearch.xml b/sphinx/themes/scrolls/opensearch.xml deleted file mode 100644 index 9f2fa427..00000000 --- a/sphinx/themes/scrolls/opensearch.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - {{ project }} - Search {{ docstitle }} - utf-8 - - {{ docstitle }} - diff --git a/sphinx/themes/scrolls/page.html b/sphinx/themes/scrolls/page.html deleted file mode 100644 index ee6cad3d..00000000 --- a/sphinx/themes/scrolls/page.html +++ /dev/null @@ -1,4 +0,0 @@ -{% extends 'layout.html' %} -{% block body %} - {{ body }} -{% endblock %} diff --git a/sphinx/themes/scrolls/search.html b/sphinx/themes/scrolls/search.html deleted file mode 100644 index 0c942b70..00000000 --- a/sphinx/themes/scrolls/search.html +++ /dev/null @@ -1,35 +0,0 @@ -{% extends "layout.html" %} -{% set title = 'Search' %} -{% block extrahead %} - -{% endblock %} -{% block body %} -

Search

-

- From here you can search these documents. Enter your search - words into the box below and click "search". Note that the search - function will automatically search for all of the words. Pages - containing less words won't appear in the result list. -

-

- - -

- {% if search_performed %} -

Search Results

- {% if not search_results %} -

Your search did not match any results.

- {% endif %} - {% endif %} -
- {% if search_results %} -
    - {% for href, caption, context in search_results %} -
  • {{ caption }} -
    {{ context|e }}
    -
  • - {% endfor %} -
- {% endif %} -
-{% endblock %} diff --git a/sphinx/themes/scrolls/static/scrolls.css_t b/sphinx/themes/scrolls/static/scrolls.css_t new file mode 100644 index 00000000..80ac664e --- /dev/null +++ b/sphinx/themes/scrolls/static/scrolls.css_t @@ -0,0 +1,410 @@ +body { + background-color: #222; + margin: 0; + padding: 0; + font-family: 'Georgia', serif; + font-size: 15px; + color: #eee; +} + +div.footer { + border-top: 1px solid #111; + padding: 8px; + font-size: 11px; + text-align: center; + letter-spacing: 0.5px; +} + +div.footer a { + color: #eee; +} + +div.header { + margin: 0 -15px 0 -15px; + background: url(headerbg.png) repeat-x; + border-top: 6px solid {{ theme_headerbordercolor }}; +} + +div.relnav { + border-bottom: 1px solid #111; + background: url(navigation.png); + margin: 0 -15px 0 -15px; + padding: 2px 20px 0 28px; + line-height: 25px; + color: #aaa; + font-size: 12px; + text-align: center; +} + +div.relnav a { + color: #eee; + font-weight: bold; + text-decoration: none; +} + +div.relnav a:hover { + text-decoration: underline; +} + +#content { + background-color: white; + color: #111; + border-bottom: 1px solid black; + background: url(watermark.png) center 0; + padding: 0 15px 0 15px; + margin: 0; +} + +h1 { + margin: 0; + padding: 15px 0 0 0; +} + +h1.heading { + margin: 0; + padding: 0; + height: 80px; +} + +h1.heading:hover { + background: #222; +} + +h1.heading a { + background: url({{ logo if logo else 'logo.png' }}) no-repeat center 0; + display: block; + width: 100%; + height: 80px; +} + +h1.heading a:focus { + -moz-outline: none; + outline: none; +} + +h1.heading span { + display: none; +} + +#jinjalogo { + background-image: url(jinjalogo.png); + background-repeat: no-repeat; + width: 400px; + height: 160px; +} + +#contentwrapper { + max-width: 680px; + padding: 0 18px 20px 18px; + margin: 0 auto 0 auto; + border-right: 1px solid #eee; + border-left: 1px solid #eee; + background: url(watermark_blur.png) center -114px; +} + +#contentwrapper h2, +#contentwrapper h2 a { + color: #222; + font-size: 24px; + margin: 20px 0 0 0; +} + +#contentwrapper h3, +#contentwrapper h3 a { + color: {{ theme_subheadlinecolor }}; + font-size: 20px; + margin: 20px 0 0 0; +} + +table.docutils { + border-collapse: collapse; + border: 2px solid #aaa; + margin: 0.5em 1.5em 0.5em 1.5em; +} + +table.docutils td { + padding: 2px; + border: 1px solid #ddd; +} + +p, li, dd, dt, blockquote { + color: #333; +} + +blockquote { + margin: 10px 0 10px 20px; +} + +p { + line-height: 20px; + margin-bottom: 0; + margin-top: 10px; +} + +hr { + border-top: 1px solid #ccc; + border-bottom: 0; + border-right: 0; + border-left: 0; + margin-bottom: 10px; + margin-top: 20px; +} + +dl { + margin-left: 10px; +} + +li, dt { + margin-top: 5px; +} + +dt { + font-weight: bold; + color: #000; +} + +dd { + margin-top: 10px; + line-height: 20px; +} + +th { + text-align: left; + padding: 3px; + background-color: #f2f2f2; +} + +a { + color: {{ theme_linkcolor }}; +} + +a:hover { + color: {{ theme_visitedlinkcolor }}; +} + +pre { + background: #ededed url(metal.png); + border-top: 1px solid #ccc; + border-bottom: 1px solid #ccc; + padding: 5px; + font-size: 13px; + font-family: 'Bitstream Vera Sans Mono', 'Monaco', monospace; +} + +tt { + font-size: 13px; + font-family: 'Bitstream Vera Sans Mono', 'Monaco', monospace; + color: black; + padding: 1px 2px 1px 2px; + background-color: #fafafa; + border-bottom: 1px solid #eee; +} + +a.reference:hover tt { + border-bottom-color: #aaa; +} + +cite { + /* abusing , it's generated by ReST for `x` */ + font-size: 13px; + font-family: 'Bitstream Vera Sans Mono', 'Monaco', monospace; + font-weight: bold; + font-style: normal; +} + +div.admonition { + margin: 10px 0 10px 0; + padding: 10px; + border: 1px solid #ccc; +} + +div.admonition p.admonition-title { + background-color: {{ theme_admonitioncolor }}; + color: white; + margin: -10px -10px 10px -10px; + padding: 4px 10px 4px 10px; + font-weight: bold; + font-size: 15px; +} + +div.admonition p.admonition-title a { + color: white!important; +} + +a.headerlink { + color: #B4B4B4!important; + font-size: 0.8em; + padding: 0 4px 0 4px; + text-decoration: none!important; + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +dt:hover > a.headerlink { + visibility: visible; +} + +a.headerlink:hover { + background-color: #B4B4B4; + color: #F0F0F0!important; +} + +table.indextable { + width: 100%; +} + +table.genindextable td { + vertical-align: top; + width: 50%; +} + +table.indextable dl dd { + font-size: 11px; +} + +table.indextable dl dd a { + color: #000; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +table.modindextable { + width: 100%; + border: none; +} + +table.modindextable img.toggler { + margin-right: 10px; +} + +dl.function dt, +dl.class dt, +dl.exception dt, +dl.method dt, +dl.attribute dt { + font-weight: normal; +} + +dt .descname { + font-weight: bold; + margin-right: 4px; +} + +dt .descname, dt .descclassname { + padding: 0; + background: transparent; + border-bottom: 1px solid #111; +} + +dt .descclassname { + margin-left: 2px; +} + +dl dt big { + font-size: 100%; +} + +ul.search { + margin: 10px 0 0 30px; + padding: 0; +} + +ul.search li { + margin: 10px 0 0 0; + padding: 0; +} + +ul.search div.context { + font-size: 12px; + padding: 4px 0 0 20px; + color: #888; +} + +span.highlight { + background-color: #eee; + border: 1px solid #ccc; +} + +#toc { + margin: 0 -17px 0 -17px; + display: none; +} + +#toc h3 { + float: right; + margin: 5px 5px 0 0; + padding: 0; + font-size: 12px; + color: #777; +} + +#toc h3:hover { + color: #333; + cursor: pointer; +} + +.expandedtoc { + background: #222 url(darkmetal.png); + border-bottom: 1px solid #111; + outline-bottom: 1px solid #000; + padding: 5px; +} + +.expandedtoc h3 { + color: #aaa; + margin: 0!important; +} + +.expandedtoc h3:hover { + color: white!important; +} + +#tod h3:hover { + color: white; +} + +#toc a { + color: #ddd; + text-decoration: none; +} + +#toc a:hover { + color: white; + text-decoration: underline; +} + +#toc ul { + margin: 5px 0 12px 17px; + padding: 0 7px 0 7px; +} + +#toc ul ul { + margin-bottom: 0; +} + +#toc ul li { + margin: 2px 0 0 0; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} diff --git a/sphinx/themes/scrolls/static/style.css_t b/sphinx/themes/scrolls/static/style.css_t deleted file mode 100644 index 00b78aaa..00000000 --- a/sphinx/themes/scrolls/static/style.css_t +++ /dev/null @@ -1,410 +0,0 @@ -body { - background-color: #222; - margin: 0; - padding: 0; - font-family: 'Georgia', serif; - font-size: 15px; - color: #eee; -} - -div.footer { - border-top: 1px solid #111; - padding: 8px; - font-size: 11px; - text-align: center; - letter-spacing: 0.5px; -} - -div.footer a { - color: #eee; -} - -div.header { - margin: 0 -15px 0 -15px; - background: url(headerbg.png) repeat-x; - border-top: 6px solid {{ theme_headerbordercolor }}; -} - -div.relnav { - border-bottom: 1px solid #111; - background: url(navigation.png); - margin: 0 -15px 0 -15px; - padding: 2px 20px 0 28px; - line-height: 25px; - color: #aaa; - font-size: 12px; - text-align: center; -} - -div.relnav a { - color: #eee; - font-weight: bold; - text-decoration: none; -} - -div.relnav a:hover { - text-decoration: underline; -} - -#content { - background-color: white; - color: #111; - border-bottom: 1px solid black; - background: url(watermark.png) center 0; - padding: 0 15px 0 15px; - margin: 0; -} - -h1 { - margin: 0; - padding: 15px 0 0 0; -} - -h1.heading { - margin: 0; - padding: 0; - height: 80px; -} - -h1.heading:hover { - background: #222; -} - -h1.heading a { - background: url({{ logo if logo else 'logo.png' }}) no-repeat center 0; - display: block; - width: 100%; - height: 80px; -} - -h1.heading a:focus { - -moz-outline: none; - outline: none; -} - -h1.heading span { - display: none; -} - -#jinjalogo { - background-image: url(jinjalogo.png); - background-repeat: no-repeat; - width: 400px; - height: 160px; -} - -#contentwrapper { - max-width: 680px; - padding: 0 18px 20px 18px; - margin: 0 auto 0 auto; - border-right: 1px solid #eee; - border-left: 1px solid #eee; - background: url(watermark_blur.png) center -114px; -} - -#contentwrapper h2, -#contentwrapper h2 a { - color: #222; - font-size: 24px; - margin: 20px 0 0 0; -} - -#contentwrapper h3, -#contentwrapper h3 a { - color: {{ theme_subheadlinecolor }}; - font-size: 20px; - margin: 20px 0 0 0; -} - -table.docutils { - border-collapse: collapse; - border: 2px solid #aaa; - margin: 0.5em 1.5em 0.5em 1.5em; -} - -table.docutils td { - padding: 2px; - border: 1px solid #ddd; -} - -p, li, dd, dt, blockquote { - color: #333; -} - -blockquote { - margin: 10px 0 10px 20px; -} - -p { - line-height: 20px; - margin-bottom: 0; - margin-top: 10px; -} - -hr { - border-top: 1px solid #ccc; - border-bottom: 0; - border-right: 0; - border-left: 0; - margin-bottom: 10px; - margin-top: 20px; -} - -dl { - margin-left: 10px; -} - -li, dt { - margin-top: 5px; -} - -dt { - font-weight: bold; - color: #000; -} - -dd { - margin-top: 10px; - line-height: 20px; -} - -th { - text-align: left; - padding: 3px; - background-color: #f2f2f2; -} - -a { - color: {{ theme_linkcolor }}; -} - -a:hover { - color: {{ theme_visitedlinkcolor }}; -} - -pre { - background: #ededed url(metal.png); - border-top: 1px solid #ccc; - border-bottom: 1px solid #ccc; - padding: 5px; - font-size: 13px; - font-family: 'Bitstream Vera Sans Mono', 'Monaco', monospace; -} - -tt { - font-size: 13px; - font-family: 'Bitstream Vera Sans Mono', 'Monaco', monospace; - color: black; - padding: 1px 2px 1px 2px; - background-color: #fafafa; - border-bottom: 1px solid #eee; -} - -a.reference:hover tt { - border-bottom-color: #aaa; -} - -cite { - /* abusing , it's generated by ReST for `x` */ - font-size: 13px; - font-family: 'Bitstream Vera Sans Mono', 'Monaco', monospace; - font-weight: bold; - font-style: normal; -} - -div.admonition { - margin: 10px 0 10px 0; - padding: 10px; - border: 1px solid #ccc; -} - -div.admonition p.admonition-title { - background-color: {{ theme_admonitioncolor }}; - color: white; - margin: -10px -10px 10px -10px; - padding: 4px 10px 4px 10px; - font-weight: bold; - font-size: 15px; -} - -div.admonition p.admonition-title a { - color: white!important; -} - -a.headerlink { - color: #B4B4B4!important; - font-size: 0.8em; - padding: 0 4px 0 4px; - text-decoration: none!important; - visibility: hidden; -} - -h1:hover > a.headerlink, -h2:hover > a.headerlink, -h3:hover > a.headerlink, -h4:hover > a.headerlink, -h5:hover > a.headerlink, -h6:hover > a.headerlink, -dt:hover > a.headerlink, -dt:hover > a.headerlink { - visibility: visible; -} - -a.headerlink:hover { - background-color: #B4B4B4; - color: #F0F0F0!important; -} - -table.indextable { - width: 100%; -} - -table.indextable td { - vertical-align: top; - width: 50%; -} - -table.indextable dl dd { - font-size: 11px; -} - -table.indextable dl dd a { - color: #000; -} - -div.modindex-jumpbox { - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - margin: 1em 0 1em 0; - padding: 0.4em; -} - -table.modindextable { - width: 100%; - border: none; -} - -table.modindextable img.toggler { - margin-right: 10px; -} - -dl.function dt, -dl.class dt, -dl.exception dt, -dl.method dt, -dl.attribute dt { - font-weight: normal; -} - -dt .descname { - font-weight: bold; - margin-right: 4px; -} - -dt .descname, dt .descclassname { - padding: 0; - background: transparent; - border-bottom: 1px solid #111; -} - -dt .descclassname { - margin-left: 2px; -} - -dl dt big { - font-size: 100%; -} - -ul.search { - margin: 10px 0 0 30px; - padding: 0; -} - -ul.search li { - margin: 10px 0 0 0; - padding: 0; -} - -ul.search div.context { - font-size: 12px; - padding: 4px 0 0 20px; - color: #888; -} - -span.highlight { - background-color: #eee; - border: 1px solid #ccc; -} - -#toc { - margin: 0 -17px 0 -17px; - display: none; -} - -#toc h3 { - float: right; - margin: 5px 5px 0 0; - padding: 0; - font-size: 12px; - color: #777; -} - -#toc h3:hover { - color: #333; - cursor: pointer; -} - -.expandedtoc { - background: #222 url(darkmetal.png); - border-bottom: 1px solid #111; - outline-bottom: 1px solid #000; - padding: 5px; -} - -.expandedtoc h3 { - color: #aaa; - margin: 0!important; -} - -.expandedtoc h3:hover { - color: white!important; -} - -#tod h3:hover { - color: white; -} - -#toc a { - color: #ddd; - text-decoration: none; -} - -#toc a:hover { - color: white; - text-decoration: underline; -} - -#toc ul { - margin: 5px 0 12px 17px; - padding: 0 7px 0 7px; -} - -#toc ul ul { - margin-bottom: 0; -} - -#toc ul li { - margin: 2px 0 0 0; -} - -.line-block { - display: block; - margin-top: 1em; - margin-bottom: 1em; -} - -.line-block .line-block { - margin-top: 0; - margin-bottom: 0; - margin-left: 1.5em; -} \ No newline at end of file diff --git a/sphinx/themes/scrolls/theme.conf b/sphinx/themes/scrolls/theme.conf index b4205046..4e7800f9 100644 --- a/sphinx/themes/scrolls/theme.conf +++ b/sphinx/themes/scrolls/theme.conf @@ -1,5 +1,5 @@ [theme] -inherit = default +inherit = basic stylesheet = scrolls.css pygments_style = tango -- cgit v1.2.1 From 79f8c8121f935065a1badb48f19d647ae9409fcd Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 7 Jan 2010 11:49:48 +0100 Subject: Add theme authors. --- AUTHORS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/AUTHORS b/AUTHORS index 54f53db2..6e215c8c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -5,6 +5,7 @@ Substantial parts of the templates were written by Armin Ronacher Other contributors, listed alphabetically, are: +* Andi Albrecht -- agogo theme * Henrique Bastos -- SVG support for graphviz extension * Daniel Bültmann -- todo extension * Michael Droettboom -- inheritance_diagram extension @@ -15,6 +16,7 @@ Other contributors, listed alphabetically, are: * Dave Kuhlman -- original LaTeX writer * Thomas Lamb -- linkcheck builder * Dan MacKinlay -- metadata fixes +* Martin Mahner -- nature theme * Will Maier -- directory HTML builder * Roland Meister -- epub builder * Christopher Perkins -- autosummary integration -- cgit v1.2.1 From cac34085df0f3887ea38e0a658a683d310526987 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 7 Jan 2010 11:57:42 +0100 Subject: Add copyright headers to HTML templates, stylesheets and JavaScript files. --- sphinx/themes/agogo/layout.html | 10 ++++++++++ sphinx/themes/agogo/static/agogo.css_t | 11 +++++++++++ sphinx/themes/basic/defindex.html | 9 +++++++++ sphinx/themes/basic/genindex-single.html | 9 +++++++++ sphinx/themes/basic/genindex-split.html | 9 +++++++++ sphinx/themes/basic/genindex.html | 9 +++++++++ sphinx/themes/basic/layout.html | 9 +++++++++ sphinx/themes/basic/modindex.html | 9 +++++++++ sphinx/themes/basic/page.html | 9 +++++++++ sphinx/themes/basic/search.html | 9 +++++++++ sphinx/themes/basic/static/basic.css | 12 +++++++++--- sphinx/themes/basic/static/doctools.js | 11 ++++++++++- sphinx/themes/basic/static/searchtools.js | 11 +++++++++++ sphinx/themes/default/static/default.css_t | 12 +++++++++--- sphinx/themes/epub/layout.html | 9 +++++++++ sphinx/themes/epub/static/epub.css | 12 +++++++++--- sphinx/themes/nature/static/nature.css_t | 12 +++++++++--- sphinx/themes/scrolls/layout.html | 10 ++++++++++ sphinx/themes/scrolls/static/scrolls.css_t | 11 +++++++++++ sphinx/themes/sphinxdoc/layout.html | 9 +++++++++ sphinx/themes/sphinxdoc/static/sphinxdoc.css | 13 +++++++++---- sphinx/themes/traditional/static/traditional.css | 11 +++++++++-- 22 files changed, 207 insertions(+), 19 deletions(-) diff --git a/sphinx/themes/agogo/layout.html b/sphinx/themes/agogo/layout.html index d8b9d57e..73636376 100644 --- a/sphinx/themes/agogo/layout.html +++ b/sphinx/themes/agogo/layout.html @@ -1,3 +1,13 @@ +{# + agogo/layout.html + ~~~~~~~~~~~~~~~~~ + + Sphinx layout template for the agogo theme, originally written + by Andi Albrecht. + + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} {% extends "basic/layout.html" %} {% block header %} diff --git a/sphinx/themes/agogo/static/agogo.css_t b/sphinx/themes/agogo/static/agogo.css_t index ab257f56..4dd03d92 100644 --- a/sphinx/themes/agogo/static/agogo.css_t +++ b/sphinx/themes/agogo/static/agogo.css_t @@ -1,3 +1,14 @@ +/* + * agogo.css_t + * ~~~~~~~~~~~ + * + * Sphinx stylesheet -- agogo theme. + * + * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + * { margin: 0px; padding: 0px; diff --git a/sphinx/themes/basic/defindex.html b/sphinx/themes/basic/defindex.html index 40f4f4c9..f337faec 100644 --- a/sphinx/themes/basic/defindex.html +++ b/sphinx/themes/basic/defindex.html @@ -1,3 +1,12 @@ +{# + basic/defindex.html + ~~~~~~~~~~~~~~~~~~~ + + Default template for the "index" page. + + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} {% extends "layout.html" %} {% set title = _('Overview') %} {% block body %} diff --git a/sphinx/themes/basic/genindex-single.html b/sphinx/themes/basic/genindex-single.html index 9aaaeb0c..5ca9c273 100644 --- a/sphinx/themes/basic/genindex-single.html +++ b/sphinx/themes/basic/genindex-single.html @@ -1,3 +1,12 @@ +{# + basic/genindex-single.html + ~~~~~~~~~~~~~~~~~~~~~~~~~~ + + Template for a "single" page of a split index. + + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} {% extends "layout.html" %} {% set title = _('Index') %} {% block body %} diff --git a/sphinx/themes/basic/genindex-split.html b/sphinx/themes/basic/genindex-split.html index ab099e5b..c33bda86 100644 --- a/sphinx/themes/basic/genindex-split.html +++ b/sphinx/themes/basic/genindex-split.html @@ -1,3 +1,12 @@ +{# + basic/genindex-split.html + ~~~~~~~~~~~~~~~~~~~~~~~~~ + + Template for a "split" index overview page. + + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} {% extends "layout.html" %} {% set title = _('Index') %} {% block body %} diff --git a/sphinx/themes/basic/genindex.html b/sphinx/themes/basic/genindex.html index 6b3d1a64..ba335fe4 100644 --- a/sphinx/themes/basic/genindex.html +++ b/sphinx/themes/basic/genindex.html @@ -1,3 +1,12 @@ +{# + basic/genindex.html + ~~~~~~~~~~~~~~~~~~~ + + Template for an "all-in-one" index. + + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} {% extends "layout.html" %} {% set title = _('Index') %} {% block body %} diff --git a/sphinx/themes/basic/layout.html b/sphinx/themes/basic/layout.html index c012116c..d86da2cb 100644 --- a/sphinx/themes/basic/layout.html +++ b/sphinx/themes/basic/layout.html @@ -1,3 +1,12 @@ +{# + basic/layout.html + ~~~~~~~~~~~~~~~~~ + + Master layout template for Sphinx themes. + + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} {%- block doctype -%} diff --git a/sphinx/themes/basic/modindex.html b/sphinx/themes/basic/modindex.html index 03ed604e..96f8ac43 100644 --- a/sphinx/themes/basic/modindex.html +++ b/sphinx/themes/basic/modindex.html @@ -1,3 +1,12 @@ +{# + basic/modindex.html + ~~~~~~~~~~~~~~~~~~~ + + Template for the module index. + + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} {% extends "layout.html" %} {% set title = _('Global Module Index') %} {% block extrahead %} diff --git a/sphinx/themes/basic/page.html b/sphinx/themes/basic/page.html index 17a93016..c7188fa5 100644 --- a/sphinx/themes/basic/page.html +++ b/sphinx/themes/basic/page.html @@ -1,3 +1,12 @@ +{# + basic/page.html + ~~~~~~~~~~~~~~~ + + Master template for simple pages. + + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} {% extends "layout.html" %} {% block body %} {{ body }} diff --git a/sphinx/themes/basic/search.html b/sphinx/themes/basic/search.html index 96c40652..eac32605 100644 --- a/sphinx/themes/basic/search.html +++ b/sphinx/themes/basic/search.html @@ -1,3 +1,12 @@ +{# + basic/search.html + ~~~~~~~~~~~~~~~~~ + + Template for the search page. + + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} {% extends "layout.html" %} {% set title = _('Search') %} {% set script_files = script_files + ['_static/searchtools.js'] %} diff --git a/sphinx/themes/basic/static/basic.css b/sphinx/themes/basic/static/basic.css index 29b8450f..bd0a8544 100644 --- a/sphinx/themes/basic/static/basic.css +++ b/sphinx/themes/basic/static/basic.css @@ -1,6 +1,12 @@ -/** - * Sphinx stylesheet -- basic theme - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * */ /* -- main layout ----------------------------------------------------------- */ diff --git a/sphinx/themes/basic/static/doctools.js b/sphinx/themes/basic/static/doctools.js index 9447678c..cb6f30e5 100644 --- a/sphinx/themes/basic/static/doctools.js +++ b/sphinx/themes/basic/static/doctools.js @@ -1,4 +1,13 @@ -/// XXX: make it cross browser +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Sphinx JavaScript utilties for all documentation. + * + * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ /** * make the code below compatible with browsers without diff --git a/sphinx/themes/basic/static/searchtools.js b/sphinx/themes/basic/static/searchtools.js index 36acb12e..2e9b5bee 100644 --- a/sphinx/themes/basic/static/searchtools.js +++ b/sphinx/themes/basic/static/searchtools.js @@ -1,3 +1,14 @@ +/* + * searchtools.js + * ~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilties for the full-text search. + * + * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + /** * helper function to return a node containing the * search summary for a given text. keywords is a list diff --git a/sphinx/themes/default/static/default.css_t b/sphinx/themes/default/static/default.css_t index cd1db7ec..04562d96 100644 --- a/sphinx/themes/default/static/default.css_t +++ b/sphinx/themes/default/static/default.css_t @@ -1,6 +1,12 @@ -/** - * Sphinx stylesheet -- default theme - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/* + * default.css_t + * ~~~~~~~~~~~~~ + * + * Sphinx stylesheet -- default theme. + * + * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * */ @import url("basic.css"); diff --git a/sphinx/themes/epub/layout.html b/sphinx/themes/epub/layout.html index 64b1a4cb..8a348bed 100644 --- a/sphinx/themes/epub/layout.html +++ b/sphinx/themes/epub/layout.html @@ -1,3 +1,12 @@ +{# + epub/layout.html + ~~~~~~~~~~~~~~~~ + + Sphinx layout template for the epub theme. + + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} {% extends "basic/layout.html" %} {# add only basic navigation links #} diff --git a/sphinx/themes/epub/static/epub.css b/sphinx/themes/epub/static/epub.css index 72b77190..f941b79a 100644 --- a/sphinx/themes/epub/static/epub.css +++ b/sphinx/themes/epub/static/epub.css @@ -1,6 +1,12 @@ -/** - * Sphinx stylesheet -- epub theme - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/* + * epub.css_t + * ~~~~~~~~~~ + * + * Sphinx stylesheet -- epub theme. + * + * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * */ /* -- main layout ----------------------------------------------------------- */ diff --git a/sphinx/themes/nature/static/nature.css_t b/sphinx/themes/nature/static/nature.css_t index 31d9ec62..5991e349 100644 --- a/sphinx/themes/nature/static/nature.css_t +++ b/sphinx/themes/nature/static/nature.css_t @@ -1,6 +1,12 @@ -/** - * Sphinx stylesheet -- nature theme - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/* + * nature.css_t + * ~~~~~~~~~~~~ + * + * Sphinx stylesheet -- nature theme. + * + * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * */ @import url("basic.css"); diff --git a/sphinx/themes/scrolls/layout.html b/sphinx/themes/scrolls/layout.html index 07052e87..9c139b88 100644 --- a/sphinx/themes/scrolls/layout.html +++ b/sphinx/themes/scrolls/layout.html @@ -1,3 +1,13 @@ +{# + scrolls/layout.html + ~~~~~~~~~~~~~~~~~~~ + + Sphinx layout template for the scrolls theme, originally written + by Armin Ronacher. + + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} {% extends "basic/layout.html" %} {% set script_files = script_files + ['_static/theme_extras.js'] %} {% set css_files = css_files + ['_static/print.css'] %} diff --git a/sphinx/themes/scrolls/static/scrolls.css_t b/sphinx/themes/scrolls/static/scrolls.css_t index 80ac664e..14390810 100644 --- a/sphinx/themes/scrolls/static/scrolls.css_t +++ b/sphinx/themes/scrolls/static/scrolls.css_t @@ -1,3 +1,14 @@ +/* + * scrolls.css_t + * ~~~~~~~~~~~~~ + * + * Sphinx stylesheet -- scrolls theme. + * + * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + body { background-color: #222; margin: 0; diff --git a/sphinx/themes/sphinxdoc/layout.html b/sphinx/themes/sphinxdoc/layout.html index 48d2118e..2d653f9f 100644 --- a/sphinx/themes/sphinxdoc/layout.html +++ b/sphinx/themes/sphinxdoc/layout.html @@ -1,3 +1,12 @@ +{# + sphinxdoc/layout.html + ~~~~~~~~~~~~~~~~~~~~~ + + Sphinx layout template for the sphinxdoc theme. + + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} {% extends "basic/layout.html" %} {# put the sidebar before the body #} diff --git a/sphinx/themes/sphinxdoc/static/sphinxdoc.css b/sphinx/themes/sphinxdoc/static/sphinxdoc.css index 75b2ae0f..3f1e84e5 100644 --- a/sphinx/themes/sphinxdoc/static/sphinxdoc.css +++ b/sphinx/themes/sphinxdoc/static/sphinxdoc.css @@ -1,8 +1,13 @@ -/** - * Sphinx stylesheet -- sphinxdoc theme - * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/* + * sphinxdoc.css_t + * ~~~~~~~~~~~~~~~ + * + * Sphinx stylesheet -- sphinxdoc theme. Originally created by + * Armin Ronacher for Werkzeug. + * + * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. * - * Originally created by Armin Ronacher for Werkzeug, adapted by Georg Brandl. */ @import url("basic.css"); diff --git a/sphinx/themes/traditional/static/traditional.css b/sphinx/themes/traditional/static/traditional.css index 624a3627..022e55ae 100644 --- a/sphinx/themes/traditional/static/traditional.css +++ b/sphinx/themes/traditional/static/traditional.css @@ -1,5 +1,12 @@ -/** - * Sphinx Doc Design -- traditional python.org style +/* + * traditional.css + * ~~~~~~~~~~~~~~~ + * + * Sphinx stylesheet -- traditional docs.python.org theme. + * + * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * */ body { -- cgit v1.2.1 From be803c0ccf2fd354afba80c91f40e29cd753edc5 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 7 Jan 2010 12:28:02 +0100 Subject: Remove jinja mention. --- sphinx/themes/scrolls/static/scrolls.css_t | 7 ------- 1 file changed, 7 deletions(-) diff --git a/sphinx/themes/scrolls/static/scrolls.css_t b/sphinx/themes/scrolls/static/scrolls.css_t index 14390810..41a725a6 100644 --- a/sphinx/themes/scrolls/static/scrolls.css_t +++ b/sphinx/themes/scrolls/static/scrolls.css_t @@ -97,13 +97,6 @@ h1.heading span { display: none; } -#jinjalogo { - background-image: url(jinjalogo.png); - background-repeat: no-repeat; - width: 400px; - height: 160px; -} - #contentwrapper { max-width: 680px; padding: 0 18px 20px 18px; -- cgit v1.2.1 From 2550de8412eb5b44dcf976d86dd43c1e7a4d3c25 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 7 Jan 2010 13:59:06 +0100 Subject: Fix bogus module prefix for template variables. --- doc/ext/autosummary.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/ext/autosummary.rst b/doc/ext/autosummary.rst index 5a206dd5..20b688c1 100644 --- a/doc/ext/autosummary.rst +++ b/doc/ext/autosummary.rst @@ -163,6 +163,8 @@ Autosummary uses the following template files: The following variables available in the templates: +.. currentmodule:: None + .. data:: name Name of the documented object, excluding the module and class parts. -- cgit v1.2.1 From 6cf86073b1f6d739a068607e25707b8b30904178 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 7 Jan 2010 14:06:14 +0100 Subject: Fix-up genindex templates. --- sphinx/themes/basic/genindex-single.html | 37 +++++++++++++------------------- sphinx/themes/basic/genindex-split.html | 2 ++ sphinx/themes/basic/genindex.html | 4 +--- 3 files changed, 18 insertions(+), 25 deletions(-) diff --git a/sphinx/themes/basic/genindex-single.html b/sphinx/themes/basic/genindex-single.html index 5ca9c273..1e98ba9c 100644 --- a/sphinx/themes/basic/genindex-single.html +++ b/sphinx/themes/basic/genindex-single.html @@ -13,33 +13,26 @@

{% trans key=key %}Index – {{ key }}{% endtrans %}

- +{%- endfor %} +
-
-{%- set breakat = count // 2 %} -{%- set numcols = 1 %} -{%- set numitems = 0 %} -{% for entryname, (links, subitems) in entries %} -
{%- if links -%}{{ entryname|e }} - {%- for link in links[1:] %}, [{{ loop.index }}]{% endfor -%} - {%- else -%} -{{ entryname|e }} - {%- endif -%}
- {%- if subitems %} -
+ + {%- for column in entries|slice(2) if column %} +
+ {%- for entryname, (links, subitems) in column %} +
{% if links %}{{ entryname|e }} + {%- for link in links[1:] %}, [{{ loop.index }}]{% endfor %} + {%- else %}{{ entryname|e }}{% endif %}
+ {%- if subitems %} +
{%- for subentryname, subentrylinks in subitems %} -
{{ subentryname|e }} - {%- for link in subentrylinks[1:] %}, [{{ loop.index }}]{% endfor -%} -
+
{{ subentryname|e }} + {%- for link in subentrylinks[1:] %}, [{{ loop.index }}]{% endfor -%} +
{%- endfor %}
{%- endif -%} -{%- set numitems = numitems + 1 + (subitems|length) -%} -{%- if numcols < 2 and numitems > breakat -%} -{%- set numcols = numcols+1 -%} -
-{%- endif -%} {%- endfor %} -
+
{% endblock %} diff --git a/sphinx/themes/basic/genindex-split.html b/sphinx/themes/basic/genindex-split.html index c33bda86..d068a96a 100644 --- a/sphinx/themes/basic/genindex-split.html +++ b/sphinx/themes/basic/genindex-split.html @@ -15,6 +15,7 @@

{{ _('Index pages by letter') }}:

+

{% for key, dummy in genindexentries -%} {{ key }} {% if not loop.last %}| {% endif %} @@ -22,6 +23,7 @@

{{ _('Full index on one page') }} ({{ _('can be huge') }})

+
{% endblock %} diff --git a/sphinx/themes/basic/genindex.html b/sphinx/themes/basic/genindex.html index ba335fe4..4d46380f 100644 --- a/sphinx/themes/basic/genindex.html +++ b/sphinx/themes/basic/genindex.html @@ -19,8 +19,6 @@ {%- endfor %}
-
- {%- for key, entries in genindexentries %}

{{ key }}

@@ -28,7 +26,7 @@ ","
{%- for entryname, (links, subitems) in column %}
{% if links %}{{ entryname|e }} - {% for link in links[1:] %}, [{{ loop.index }}]{% endfor %} + {%- for link in links[1:] %}, [{{ loop.index }}]{% endfor %} {%- else %}{{ entryname|e }}{% endif %}
{%- if subitems %}
-- cgit v1.2.1 From aac734ec4645d22a2f7fc45c28992e9cb995b36b Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 7 Jan 2010 14:16:53 +0100 Subject: Added new HTML theme ``haiku``, inspired by the Haiku OS user guide. --- CHANGES | 4 +- doc/themes/fullsize/haiku.png | Bin 0 -> 104662 bytes doc/themes/haiku.png | Bin 0 -> 53167 bytes doc/theming.rst | 16 ++ sphinx/themes/haiku/layout.html | 68 +++++ sphinx/themes/haiku/static/alert_info_32.png | Bin 0 -> 1168 bytes sphinx/themes/haiku/static/alert_warning_32.png | Bin 0 -> 1060 bytes sphinx/themes/haiku/static/bg-page.png | Bin 0 -> 164 bytes sphinx/themes/haiku/static/bullet_orange.png | Bin 0 -> 365 bytes sphinx/themes/haiku/static/haiku.css_t | 359 ++++++++++++++++++++++++ sphinx/themes/haiku/theme.conf | 12 + tests/test_theming.py | 2 +- 12 files changed, 459 insertions(+), 2 deletions(-) create mode 100644 doc/themes/fullsize/haiku.png create mode 100644 doc/themes/haiku.png create mode 100644 sphinx/themes/haiku/layout.html create mode 100644 sphinx/themes/haiku/static/alert_info_32.png create mode 100644 sphinx/themes/haiku/static/alert_warning_32.png create mode 100644 sphinx/themes/haiku/static/bg-page.png create mode 100644 sphinx/themes/haiku/static/bullet_orange.png create mode 100644 sphinx/themes/haiku/static/haiku.css_t create mode 100644 sphinx/themes/haiku/theme.conf diff --git a/CHANGES b/CHANGES index 05a162cc..fc2a53c7 100644 --- a/CHANGES +++ b/CHANGES @@ -23,11 +23,13 @@ Release 1.0 (in development) * #284: All docinfo metadata is now put into the document metadata, not just the author. +* Added new HTML theme ``haiku``, inspired by the Haiku OS user guide. + * Added new HTML theme ``nature``. * Added new HTML theme ``agogo``, created by Andi Albrecht. -* Added new HTML theme called scrolls, created by Armin Ronacher. +* Added new HTML theme ``scrolls``, created by Armin Ronacher. * Added ``html_output_encoding`` config value. diff --git a/doc/themes/fullsize/haiku.png b/doc/themes/fullsize/haiku.png new file mode 100644 index 00000000..1590da5d Binary files /dev/null and b/doc/themes/fullsize/haiku.png differ diff --git a/doc/themes/haiku.png b/doc/themes/haiku.png new file mode 100644 index 00000000..af4ef074 Binary files /dev/null and b/doc/themes/haiku.png differ diff --git a/doc/theming.rst b/doc/theming.rst index 4771dbec..01f705e4 100644 --- a/doc/theming.rst +++ b/doc/theming.rst @@ -69,6 +69,10 @@ Builtin themes | | | | *traditional* | *nature* | +--------------------+--------------------+ +| |haiku| | | +| | | +| *haiku* | | ++--------------------+--------------------+ .. |default| image:: themes/default.png .. |sphinxdoc| image:: themes/sphinxdoc.png @@ -76,6 +80,7 @@ Builtin themes .. |agogo| image:: themes/agogo.png .. |traditional| image:: themes/traditional.png .. |nature| image:: themes/nature.png +.. |haiku| image:: themes/haiku.png Sphinx comes with a selection of themes to choose from. @@ -161,6 +166,17 @@ These themes are: * **nature** -- A greenish theme. There are currently no options beyond *nosidebar*. +* **haiku** -- A theme without sidebar inspired by the `Haiku OS user guide + `_. The following + options are supported: + + - **full_logo** (true or false, default false): If this is true, the header + will only show the :confval:`html_logo`. Use this for large logos. If this + is false, the logo (if present) will be shown floating right, and the + documentation title will be put in the header. + - **textcolor**, **headingcolor**, **linkcolor**, **visitedlinkcolor**, + **hoverlinkcolor** (CSS colors): Colors for various body elements. + * **traditional** -- A theme resembling the old Python documentation. There are currently no options beyond *nosidebar*. diff --git a/sphinx/themes/haiku/layout.html b/sphinx/themes/haiku/layout.html new file mode 100644 index 00000000..91c76852 --- /dev/null +++ b/sphinx/themes/haiku/layout.html @@ -0,0 +1,68 @@ +{# + haiku/layout.html + ~~~~~~~~~~~~~~~~~ + + Sphinx layout template for the haiku theme. + + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} +{% extends "basic/layout.html" %} +{% set script_files = script_files + ['_static/theme_extras.js'] %} +{% set css_files = css_files + ['_static/print.css'] %} + +{# do not display relbars #} +{% block relbar1 %}{% endblock %} +{% block relbar2 %}{% endblock %} + +{% macro nav() %} +

+ {%- block haikurel1 %} + {%- endblock %} + {%- if prev %} + «  {{ prev.title }} +   ::   + {%- endif %} + {{ _('Contents') }} + {%- if next %} +   ::   + {{ next.title }}  » + {%- endif %} + {%- block haikurel2 %} + {%- endblock %} +

+{% endmacro %} + +{% block content %} +
+ {%- block haikuheader %} + {%- if theme_full_logo != "false" %} + + + + {%- else %} + {%- if logo -%} + + {%- endif -%} +

+ {{ shorttitle|e }}

+

{{ title|striptags }}

+ {%- endif %} + {%- endblock %} +
+
+ {{ nav() }} +
+
+ {#{%- if display_toc %} +
+

Table Of Contents

+ {{ toc }} +
+ {%- endif %}#} + {% block body %}{% endblock %} +
+
+ {{ nav() }} +
+{% endblock %} \ No newline at end of file diff --git a/sphinx/themes/haiku/static/alert_info_32.png b/sphinx/themes/haiku/static/alert_info_32.png new file mode 100644 index 00000000..05b4fe89 Binary files /dev/null and b/sphinx/themes/haiku/static/alert_info_32.png differ diff --git a/sphinx/themes/haiku/static/alert_warning_32.png b/sphinx/themes/haiku/static/alert_warning_32.png new file mode 100644 index 00000000..f13611cd Binary files /dev/null and b/sphinx/themes/haiku/static/alert_warning_32.png differ diff --git a/sphinx/themes/haiku/static/bg-page.png b/sphinx/themes/haiku/static/bg-page.png new file mode 100644 index 00000000..c6f3bc47 Binary files /dev/null and b/sphinx/themes/haiku/static/bg-page.png differ diff --git a/sphinx/themes/haiku/static/bullet_orange.png b/sphinx/themes/haiku/static/bullet_orange.png new file mode 100644 index 00000000..ad5d02f3 Binary files /dev/null and b/sphinx/themes/haiku/static/bullet_orange.png differ diff --git a/sphinx/themes/haiku/static/haiku.css_t b/sphinx/themes/haiku/static/haiku.css_t new file mode 100644 index 00000000..7adfb0f3 --- /dev/null +++ b/sphinx/themes/haiku/static/haiku.css_t @@ -0,0 +1,359 @@ +/* + * haiku.css_t + * ~~~~~~~~~~~ + * + * Sphinx stylesheet -- haiku theme. + * + * Adapted from http://haiku-os.org/docs/Haiku-doc.css. + * Original copyright message: + * + * Copyright 2008-2009, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Francois Revol + * Stephan Assmus + * Braden Ewing + * Humdinger + * + * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +@import url("basic.css"); + +html { + margin: 0px; + padding: 0px; + background: #FFF url(bg-page.png) top left repeat-x; +} + +body { + line-height: 1.5; + margin: auto; + padding: 0px; + font-family: "DejaVu Sans", Arial, Helvetica, sans-serif; + min-width: 59em; + max-width: 70em; + color: {{ theme_textcolor }}; +} + +div.footer { + padding: 8px; + font-size: 11px; + text-align: center; + letter-spacing: 0.5px; +} + +/* link colors and text decoration */ + +a:link { + font-weight: bold; + text-decoration: none; + color: {{ theme_linkcolor }}; +} + +a:visited { + font-weight: bold; + text-decoration: none; + color: {{ theme_visitedlinkcolor }}; +} + +a:hover, a:active { + text-decoration: underline; + color: {{ theme_hoverlinkcolor }}; +} + +/* Some headers act as anchors, don't give them a hover effect */ + +h1 a:hover, a:active { + text-decoration: none; + color: {{ theme_headingcolor }}; +} + +h2 a:hover, a:active { + text-decoration: none; + color: {{ theme_headingcolor }}; +} + +h3 a:hover, a:active { + text-decoration: none; + color: {{ theme_headingcolor }}; +} + +h4 a:hover, a:active { + text-decoration: none; + color: {{ theme_headingcolor }}; +} + +a.headerlink { + color: #a7ce38; + padding-left: 5px; +} + +a.headerlink:hover { + color: #a7ce38; +} + +/* basic text elements */ + +div.content { + margin-top: 20px; + margin-left: 40px; + margin-right: 40px; + margin-bottom: 50px; + font-size: 0.9em; +} + +/* heading and navigation */ + +div.header { + position: relative; + left: 0px; + top: 0px; + height: 85px; + /* background: #eeeeee; */ + padding: 0 40px; +} +div.header h1 { + font-size: 1.6em; + font-weight: normal; + letter-spacing: 1px; + color: {{ theme_headingcolor }}; + border: 0; + margin: 0; + padding-top: 15px; +} +div.header h1 a { + font-weight: normal; + color: {{ theme_headingcolor }}; +} +div.header h2 { + font-size: 1.3em; + font-weight: normal; + letter-spacing: 1px; + text-transform: uppercase; + color: #aaa; + border: 0; + margin-top: -3px; + padding: 0; +} + +div.header img.rightlogo { + float: right; +} + + +div.title { + font-size: 1.3em; + font-weight: bold; + color: {{ theme_headingcolor }}; + border-bottom: dotted thin #e0e0e0; + margin-bottom: 25px; +} +div.topnav { + /* background: #e0e0e0; */ +} +div.topnav p { + margin-top: 0; + margin-left: 40px; + margin-right: 40px; + margin-bottom: 0px; + text-align: right; + font-size: 0.8em; +} +div.bottomnav { + background: #eeeeee; +} +div.bottomnav p { + margin-right: 40px; + text-align: right; + font-size: 0.8em; +} + +a.uplink { + font-weight: normal; +} + + +/* contents box */ + +table.index { + margin: 0px 0px 30px 30px; + padding: 1px; + border-width: 1px; + border-style: dotted; + border-color: #e0e0e0; +} +table.index tr.heading { + background-color: #e0e0e0; + text-align: center; + font-weight: bold; + font-size: 1.1em; +} +table.index tr.index { + background-color: #eeeeee; +} +table.index td { + padding: 5px 20px; +} + +table.index a:link, table.index a:visited { + font-weight: normal; + text-decoration: none; + color: {{ theme_linkcolor }}; +} +table.index a:hover, table.index a:active { + text-decoration: underline; + color: {{ theme_hoverlinkcolor }}; +} + + +/* Haiku User Guide styles and layout */ + +/* Rounded corner boxes */ +/* Common declarations */ +div.admonition { + -webkit-border-radius: 10px; + -khtml-border-radius: 10px; + -moz-border-radius: 10px; + border-radius: 10px; + border-style: dotted; + border-width: thin; + border-color: #dcdcdc; + padding: 10px 15px 10px 15px; + margin-bottom: 15px; + margin-top: 15px; +} +div.note { + padding: 10px 15px 10px 80px; + background: #e4ffde url(alert_info_32.png) 15px 15px no-repeat; + min-height: 42px; +} +div.warning { + padding: 10px 15px 10px 80px; + background: #fffbc6 url(alert_warning_32.png) 15px 15px no-repeat; + min-height: 42px; +} +div.seealso { + background: #e4ffde; +} + +/* More layout and styles */ +h1 { + font-size: 1.3em; + font-weight: bold; + color: {{ theme_headingcolor }}; + border-bottom: dotted thin #e0e0e0; + margin-top: 30px; +} + +h2 { + font-size: 1.2em; + font-weight: normal; + color: {{ theme_headingcolor }}; + border-bottom: dotted thin #e0e0e0; + margin-top: 30px; +} + +h3 { + font-size: 1.1em; + font-weight: normal; + color: {{ theme_headingcolor }}; + margin-top: 30px; +} + +h4 { + font-size: 1.0em; + font-weight: normal; + color: {{ theme_headingcolor }}; + margin-top: 30px; +} + +p { + text-align: justify; +} + +p.last { + margin-bottom: 0; +} + +ol { + padding-left: 20px; +} + +ul { + padding-left: 5px; + margin-top: 3px; +} + +li { + line-height: 1.3; +} + +div.content li { + -moz-background-clip:border; + -moz-background-inline-policy:continuous; + -moz-background-origin:padding; + background: transparent url(bullet_orange.png) no-repeat scroll left 0.45em; + list-style-image: none; + list-style-type: none; + padding: 0 0 0 1.666em; + margin-bottom: 3px; +} + +td { + vertical-align: top; +} + +tt { + background-color: #e2e2e2; + font-size: 1.0em; + font-family: monospace; +} + +pre { + border-color: #0c3762; + border-style: dotted; + border-width: thin; + margin: 0 0 12px 0; + padding: 0.8em; + background-color: #f0f0f0; +} + +hr { + border-top: 1px solid #ccc; + border-bottom: 0; + border-right: 0; + border-left: 0; + margin-bottom: 10px; + margin-top: 20px; +} + +/* printer only pretty stuff */ +@media print { + .noprint { + display: none; + } + /* for acronyms we want their definitions inlined at print time */ + acronym[title]:after { + font-size: small; + content: " (" attr(title) ")"; + font-style: italic; + } + /* and not have mozilla dotted underline */ + acronym { + border: none; + } + div.topnav, div.bottomnav, div.header, table.index { + display: none; + } + div.content { + margin: 0px; + padding: 0px; + } + html { + background: #FFF; + } +} diff --git a/sphinx/themes/haiku/theme.conf b/sphinx/themes/haiku/theme.conf new file mode 100644 index 00000000..3537da1d --- /dev/null +++ b/sphinx/themes/haiku/theme.conf @@ -0,0 +1,12 @@ +[theme] +inherit = basic +stylesheet = haiku.css +pygments_style = autumn + +[options] +full_logo = false +textcolor = #333333 +headingcolor = #0c3762 +linkcolor = #dc3c01 +visitedlinkcolor = #892601 +hoverlinkcolor = #ff4500 diff --git a/tests/test_theming.py b/tests/test_theming.py index be5105a6..0cad0f8d 100644 --- a/tests/test_theming.py +++ b/tests/test_theming.py @@ -24,7 +24,7 @@ def test_theme_api(app): # test Theme class API assert set(Theme.themes.keys()) == \ - set(['basic', 'default', 'scrolls', 'agogo', 'sphinxdoc', + set(['basic', 'default', 'scrolls', 'agogo', 'sphinxdoc', 'haiku', 'traditional', 'testtheme', 'ziptheme', 'epub', 'nature']) assert Theme.themes['testtheme'][1] is None assert isinstance(Theme.themes['ziptheme'][1], zipfile.ZipFile) -- cgit v1.2.1 From 1e6203f8b8567565f3614af1776d2301874ae052 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 7 Jan 2010 14:27:37 +0100 Subject: Fix-up text. --- doc/theming.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/theming.rst b/doc/theming.rst index 01f705e4..6c650e4a 100644 --- a/doc/theming.rst +++ b/doc/theming.rst @@ -87,9 +87,9 @@ Sphinx comes with a selection of themes to choose from. These themes are: * **basic** -- This is a basically unstyled layout used as the base for the - *default* and *sphinxdoc* themes, and usable as the base for custom themes as - well. The HTML contains all important elements like sidebar and relation bar. - There is one option (which is inherited by *default* and *sphinxdoc*): + other themes, and usable as the base for custom themes as well. The HTML + contains all important elements like sidebar and relation bar. There is one + option (which is inherited by the other themes): - **nosidebar** (true or false): Don't include the sidebar. Defaults to false. -- cgit v1.2.1 From e2b2c18361671e9a4df173a4c75a0a340628e437 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 7 Jan 2010 14:35:30 +0100 Subject: Fix long lines. --- sphinx/util/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index 0d88ddea..b641a8dc 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -117,10 +117,10 @@ def get_matching_files(dirname, exclude_patterns=()): for root, dirs, files in walk(dirname, followlinks=True): relativeroot = root[dirlen:] - qdirs = enumerate(path.join(relativeroot, dir).replace(os.path.sep, SEP) - for dir in dirs) - qfiles = enumerate(path.join(relativeroot, file).replace(os.path.sep, SEP) - for file in files) + qdirs = enumerate(path.join(relativeroot, dn).replace(os.path.sep, SEP) + for dn in dirs) + qfiles = enumerate(path.join(relativeroot, fn).replace(os.path.sep, SEP) + for fn in files) for matcher in matchers: qdirs = [entry for entry in qdirs if not matcher(entry[1])] qfiles = [entry for entry in qfiles if not matcher(entry[1])] -- cgit v1.2.1 From e1322a4b11dfd39f45fdec0c4622c6b4fd626e2c Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 7 Jan 2010 15:10:11 +0100 Subject: Sharpen the thumbnails a bit. --- doc/themes/agogo.png | Bin 36780 -> 38724 bytes doc/themes/default.png | Bin 52880 -> 57108 bytes doc/themes/haiku.png | Bin 53167 -> 54252 bytes doc/themes/nature.png | Bin 40786 -> 42295 bytes doc/themes/scrolls.png | Bin 40215 -> 44266 bytes doc/themes/sphinxdoc.png | Bin 41680 -> 42982 bytes doc/themes/traditional.png | Bin 46483 -> 45762 bytes 7 files changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/themes/agogo.png b/doc/themes/agogo.png index 7afc4a13..d29aa45c 100644 Binary files a/doc/themes/agogo.png and b/doc/themes/agogo.png differ diff --git a/doc/themes/default.png b/doc/themes/default.png index be821b67..93d8526c 100644 Binary files a/doc/themes/default.png and b/doc/themes/default.png differ diff --git a/doc/themes/haiku.png b/doc/themes/haiku.png index af4ef074..a8ae8557 100644 Binary files a/doc/themes/haiku.png and b/doc/themes/haiku.png differ diff --git a/doc/themes/nature.png b/doc/themes/nature.png index 053970d8..3d4f587f 100644 Binary files a/doc/themes/nature.png and b/doc/themes/nature.png differ diff --git a/doc/themes/scrolls.png b/doc/themes/scrolls.png index e4cb9aa9..8073c10e 100644 Binary files a/doc/themes/scrolls.png and b/doc/themes/scrolls.png differ diff --git a/doc/themes/sphinxdoc.png b/doc/themes/sphinxdoc.png index 2d2723a8..f4b59ecd 100644 Binary files a/doc/themes/sphinxdoc.png and b/doc/themes/sphinxdoc.png differ diff --git a/doc/themes/traditional.png b/doc/themes/traditional.png index 535213fe..4ad2b5ce 100644 Binary files a/doc/themes/traditional.png and b/doc/themes/traditional.png differ -- cgit v1.2.1 From 24c27f6520bb5cc97c6199c6dcff0728f2b205df Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 7 Jan 2010 15:14:26 +0100 Subject: Remove unused name. --- sphinx/builders/html.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index 719cc638..0bf180b8 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -22,7 +22,7 @@ except ImportError: from docutils import nodes from docutils.io import DocTreeInput, StringOutput -from docutils.core import Publisher, publish_parts +from docutils.core import Publisher from docutils.utils import new_document from docutils.frontend import OptionParser from docutils.readers.doctree import Reader as DoctreeReader -- cgit v1.2.1 From 34d2bc8c08362196e90716156255b408da3caee6 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 7 Jan 2010 16:20:52 +0100 Subject: ``html_static_path`` can now contain single file entries. Exclusion of static path files is not yet implemented. HG: Bitte gib eine Versions-Meldung ein. Zeilen beginnend mit 'HG:' werden entfernt. HG: Leere Versionsmeldung wird das ?bernehmen abbrechen. HG: -- HG: Benutzer: Georg Brandl HG: branch 'default' HG: added tests/root/_static/excluded.css HG: added tests/root/_static/subdir/foo.css HG: added tests/root/templated.css_t HG: Ge?ndert CHANGES HG: Ge?ndert doc/config.rst HG: Ge?ndert sphinx/builders/html.py HG: Ge?ndert sphinx/util/__init__.py HG: Ge?ndert tests/root/_static/README HG: Ge?ndert tests/root/conf.py HG: Ge?ndert tests/test_build_html.py --- CHANGES | 2 ++ doc/config.rst | 3 +++ sphinx/builders/changes.py | 5 ++--- sphinx/builders/html.py | 33 +++++++++++++++------------------ sphinx/util/__init__.py | 23 ++++++++++++++++------- tests/root/_static/README | 2 +- tests/root/_static/excluded.css | 1 + tests/root/_static/subdir/foo.css | 1 + tests/root/conf.py | 2 +- tests/root/templated.css_t | 2 ++ tests/test_build_html.py | 19 ++++++++++++++++--- 11 files changed, 60 insertions(+), 33 deletions(-) create mode 100644 tests/root/_static/excluded.css create mode 100644 tests/root/_static/subdir/foo.css create mode 100644 tests/root/templated.css_t diff --git a/CHANGES b/CHANGES index fc2a53c7..5de227f2 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,8 @@ Release 1.0 (in development) ============================ +* ``html_static_path`` can now contain single file entries. + * The new universal config value ``exclude_patterns`` makes the old ``unused_docs``, ``exclude_trees`` and ``exclude_dirnames`` obsolete. diff --git a/doc/config.rst b/doc/config.rst index be956ffa..745638c8 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -419,6 +419,9 @@ that use Sphinx' HTMLWriter class. .. versionchanged:: 0.4 The paths in :confval:`html_static_path` can now contain subdirectories. + .. versionchanged:: 1.0 + The entries in :confval:`html_static_path` can now be single files. + .. confval:: html_last_updated_fmt If this is not the empty string, a 'Last updated on:' timestamp is inserted diff --git a/sphinx/builders/changes.py b/sphinx/builders/changes.py index 92c48f16..3bc51d43 100644 --- a/sphinx/builders/changes.py +++ b/sphinx/builders/changes.py @@ -139,11 +139,10 @@ class ChangesBuilder(Builder): self.theme.get_options({}).iteritems()) copy_static_entry(path.join(package_dir, 'themes', 'default', 'static', 'default.css_t'), - path.join(self.outdir, 'default.css_t'), - self, themectx) + self.outdir, self, themectx) copy_static_entry(path.join(package_dir, 'themes', 'basic', 'static', 'basic.css'), - path.join(self.outdir, 'basic.css'), self) + self.outdir, self) def hl(self, text, version): text = escape(text) diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index 0bf180b8..46ed6991 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -556,26 +556,23 @@ class StandaloneHTMLBuilder(Builder): if path.isfile(jsfile): copyfile(jsfile, path.join(self.outdir, '_static', 'translations.js')) - # then, copy over all user-supplied static files + # then, copy over theme-supplied static files if self.theme: - staticdirnames = [path.join(themepath, 'static') - for themepath in self.theme.get_dirchain()[::-1]] - else: - staticdirnames = [] - staticdirnames += [path.join(self.confdir, spath) - for spath in self.config.html_static_path] - for staticdirname in staticdirnames: - if not path.isdir(staticdirname): - self.warn('static directory %r does not exist' % staticdirname) + themeentries = [path.join(themepath, 'static') + for themepath in self.theme.get_dirchain()[::-1]] + for entry in themeentries: + copy_static_entry(entry, path.join(self.outdir, '_static'), + self, self.globalcontext, exclude=False) + # then, copy over all user-supplied static files + staticentries = [path.join(self.confdir, spath) + for spath in self.config.html_static_path] + for entry in staticentries: + if not path.exists(entry): + self.warn('html_static_path entry %r does not exist' % entry) continue - for filename in os.listdir(staticdirname): - if filename.startswith('.'): - continue - fullname = path.join(staticdirname, filename) - targetname = path.join(self.outdir, '_static', filename) - copy_static_entry(fullname, targetname, self, - self.globalcontext) - # last, copy logo file (handled differently) + copy_static_entry(entry, path.join(self.outdir, '_static'), + self, self.globalcontext) + # last, copy logo file (handled differently XXX why?) if self.config.html_logo: logobase = path.basename(self.config.html_logo) copyfile(path.join(self.confdir, self.config.html_logo), diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index b641a8dc..2dbec72f 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -432,9 +432,12 @@ def copyfile(source, dest): pass -def copy_static_entry(source, target, builder, context={}): +def copy_static_entry(source, targetdir, builder, context={}, + exclude=True, level=0): + # XXX: exclusion if path.isfile(source): - if source.lower().endswith('_t'): + target = path.join(targetdir, path.basename(source)) + if source.lower().endswith('_t') and builder.templates: # templated! fsrc = open(source, 'rb') fdst = open(target[:-2], 'wb') @@ -444,11 +447,17 @@ def copy_static_entry(source, target, builder, context={}): else: copyfile(source, target) elif path.isdir(source): - if source in builder.config.exclude_dirnames: - return - if path.exists(target): - shutil.rmtree(target) - shutil.copytree(source, target) + if level == 0: + for entry in os.listdir(source): + if entry.startswith('.'): + continue + copy_static_entry(path.join(source, entry), targetdir, + builder, context, level=1) + else: + target = path.join(targetdir, path.basename(source)) + if path.exists(target): + shutil.rmtree(target) + shutil.copytree(source, target) def clean_astext(node): diff --git a/tests/root/_static/README b/tests/root/_static/README index d517219b..9e1ec356 100644 --- a/tests/root/_static/README +++ b/tests/root/_static/README @@ -1 +1 @@ -This placeholder file is there because Mercurial ignores empty directories. +This whole directory is there to test html_static_path. diff --git a/tests/root/_static/excluded.css b/tests/root/_static/excluded.css new file mode 100644 index 00000000..03c941a4 --- /dev/null +++ b/tests/root/_static/excluded.css @@ -0,0 +1 @@ +/* This file should be excluded from being copied over */ diff --git a/tests/root/_static/subdir/foo.css b/tests/root/_static/subdir/foo.css new file mode 100644 index 00000000..9427981d --- /dev/null +++ b/tests/root/_static/subdir/foo.css @@ -0,0 +1 @@ +/* Stub file */ diff --git a/tests/root/conf.py b/tests/root/conf.py index abd53dbe..88091877 100644 --- a/tests/root/conf.py +++ b/tests/root/conf.py @@ -33,7 +33,7 @@ html_theme_path = ['.'] html_theme_options = {'testopt': 'testoverride'} html_style = 'default.css' -html_static_path = ['_static'] +html_static_path = ['_static', 'templated.css_t'] html_last_updated_fmt = '%b %d, %Y' html_context = {'hckey': 'hcval', 'hckey_co': 'wrong_hcval_co'} diff --git a/tests/root/templated.css_t b/tests/root/templated.css_t new file mode 100644 index 00000000..72ddb807 --- /dev/null +++ b/tests/root/templated.css_t @@ -0,0 +1,2 @@ +/* Stub file, templated */ +{{ sphinx_version }} diff --git a/tests/test_build_html.py b/tests/test_build_html.py index 267950be..f4a4aa21 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -11,7 +11,6 @@ import os import re -import sys import difflib import htmlentitydefs from StringIO import StringIO @@ -21,8 +20,7 @@ try: except ImportError: pygments = None -from sphinx.builders.html import StandaloneHTMLBuilder - +from sphinx import __version__ from util import * from test_build import ENV_WARNINGS from etree13 import ElementTree as ET @@ -169,6 +167,19 @@ def check_xpath(etree, fname, path, check): 'path %s in %s: %r' % (check, path, fname, [node.text for node in nodes])) +def check_static_entries(outdir): + staticdir = outdir / '_static' + assert staticdir.isdir() + # a file from a directory entry in html_static_path + assert (staticdir / 'README').isfile() + # a directory from a directory entry in html_static_path + assert (staticdir / 'subdir' / 'foo.css').isfile() + # a file from a file entry in html_static_path + assert (staticdir / 'templated.css').isfile() + assert (staticdir / 'templated.css').text().splitlines()[1] == __version__ + # a file from _static, but matches exclude_patterns + ##assert not (staticdir / 'excluded.css').exists() + @gen_with_app(buildername='html', warning=html_warnfile, cleanenv=True, confoverrides={'html_context.hckey_co': 'hcval_co'}, tags=['testtag']) @@ -186,3 +197,5 @@ def test_html(app): etree = ET.parse(os.path.join(app.outdir, fname), parser) for path, check in paths.iteritems(): yield check_xpath, etree, fname, path, check + + check_static_entries(app.builder.outdir) -- cgit v1.2.1 From 6aaac900333341f58ca319ee0850e20af4214093 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 7 Jan 2010 16:39:59 +0100 Subject: Apply static path exclusion patterns again. Refactor exclusion stuff a bit, so that matchers only have to be compiled once. --- sphinx/builders/html.py | 12 ++++++++---- sphinx/environment.py | 16 +++++++++------- sphinx/util/__init__.py | 29 ++++++++++++++++++----------- tests/test_build_html.py | 2 +- 4 files changed, 36 insertions(+), 23 deletions(-) diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index 46ed6991..31af5903 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -29,7 +29,7 @@ from docutils.readers.doctree import Reader as DoctreeReader from sphinx import package_dir, __version__ from sphinx.util import SEP, os_path, relative_uri, ensuredir, \ - movefile, ustrftime, copy_static_entry, copyfile + movefile, ustrftime, copy_static_entry, copyfile, compile_matchers from sphinx.errors import SphinxError from sphinx.search import js_index from sphinx.theming import Theme @@ -562,16 +562,20 @@ class StandaloneHTMLBuilder(Builder): for themepath in self.theme.get_dirchain()[::-1]] for entry in themeentries: copy_static_entry(entry, path.join(self.outdir, '_static'), - self, self.globalcontext, exclude=False) + self, self.globalcontext) # then, copy over all user-supplied static files staticentries = [path.join(self.confdir, spath) for spath in self.config.html_static_path] + matchers = compile_matchers( + self.config.exclude_patterns + + ['**/' + d for d in self.config.exclude_dirnames] + ) for entry in staticentries: if not path.exists(entry): self.warn('html_static_path entry %r does not exist' % entry) continue - copy_static_entry(entry, path.join(self.outdir, '_static'), - self, self.globalcontext) + copy_static_entry(entry, path.join(self.outdir, '_static'), self, + self.globalcontext, exclude_matchers=matchers) # last, copy logo file (handled differently XXX why?) if self.config.html_logo: logobase = path.basename(self.config.html_logo) diff --git a/sphinx/environment.py b/sphinx/environment.py index 781d8b5c..1b89b8c0 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -35,7 +35,7 @@ from docutils.transforms.parts import ContentsFilter from sphinx import addnodes from sphinx.util import movefile, get_matching_docs, SEP, ustrftime, \ - docname_join, FilenameUniqDict, url_re, clean_astext + docname_join, FilenameUniqDict, url_re, clean_astext, compile_matchers from sphinx.errors import SphinxError from sphinx.directives import additional_xref_types @@ -393,13 +393,15 @@ class BuildEnvironment: """ Find all source files in the source dir and put them in self.found_docs. """ - patterns = config.exclude_patterns[:] - patterns += config.exclude_trees - patterns += [d + config.source_suffix for d in config.unused_docs] - patterns += ['**/' + d for d in config.exclude_dirnames] - patterns += ['**/_sources'] + matchers = compile_matchers( + config.exclude_patterns[:] + + config.exclude_trees + + [d + config.source_suffix for d in config.unused_docs] + + ['**/' + d for d in config.exclude_dirnames] + + ['**/_sources'] + ) self.found_docs = set(get_matching_docs( - self.srcdir, config.source_suffix, exclude_patterns=patterns)) + self.srcdir, config.source_suffix, exclude_matchers=matchers)) def get_outdated_files(self, config_changed): """ diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index 2dbec72f..ab277f4a 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -24,6 +24,8 @@ import traceback from os import path import docutils +from docutils.utils import relative_path + import sphinx # Errnos that we need. @@ -101,19 +103,16 @@ def walk(top, topdown=True, followlinks=False): yield top, dirs, nondirs -def get_matching_files(dirname, exclude_patterns=()): +def get_matching_files(dirname, exclude_matchers=()): """ Get all file names in a directory, recursively. - Exclude files and dirs matching a pattern in *exclude_patterns*. + Exclude files and dirs matching some matcher in *exclude_matchers*. """ # dirname is a normalized absolute path. dirname = path.normpath(path.abspath(dirname)) dirlen = len(dirname) + 1 # exclude final os.path.sep - matchers = [re.compile(_translate_pattern(pat)).match - for pat in exclude_patterns] - for root, dirs, files in walk(dirname, followlinks=True): relativeroot = root[dirlen:] @@ -121,7 +120,7 @@ def get_matching_files(dirname, exclude_patterns=()): for dn in dirs) qfiles = enumerate(path.join(relativeroot, fn).replace(os.path.sep, SEP) for fn in files) - for matcher in matchers: + for matcher in exclude_matchers: qdirs = [entry for entry in qdirs if not matcher(entry[1])] qfiles = [entry for entry in qfiles if not matcher(entry[1])] @@ -131,7 +130,7 @@ def get_matching_files(dirname, exclude_patterns=()): yield filename -def get_matching_docs(dirname, suffix, exclude_patterns=()): +def get_matching_docs(dirname, suffix, exclude_matchers=()): """ Get all file names (without suffix) matching a suffix in a directory, recursively. @@ -139,7 +138,7 @@ def get_matching_docs(dirname, suffix, exclude_patterns=()): Exclude files and dirs matching a pattern in *exclude_patterns*. """ suffixpattern = '*' + suffix - for filename in get_matching_files(dirname, exclude_patterns): + for filename in get_matching_files(dirname, exclude_matchers): if not fnmatch.fnmatch(filename, suffixpattern): continue yield filename[:-len(suffix)] @@ -280,6 +279,9 @@ def _translate_pattern(pat): res += re.escape(c) return res + '$' +def compile_matchers(patterns): + return [re.compile(_translate_pattern(pat)).match for pat in patterns] + _pat_cache = {} @@ -433,8 +435,12 @@ def copyfile(source, dest): def copy_static_entry(source, targetdir, builder, context={}, - exclude=True, level=0): - # XXX: exclusion + exclude_matchers=(), level=0): + if exclude_matchers: + relpath = relative_path(builder.srcdir, source) + for matcher in exclude_matchers: + if matcher(relpath): + return if path.isfile(source): target = path.join(targetdir, path.basename(source)) if source.lower().endswith('_t') and builder.templates: @@ -452,7 +458,8 @@ def copy_static_entry(source, targetdir, builder, context={}, if entry.startswith('.'): continue copy_static_entry(path.join(source, entry), targetdir, - builder, context, level=1) + builder, context, level=1, + exclude_matchers=exclude_matchers) else: target = path.join(targetdir, path.basename(source)) if path.exists(target): diff --git a/tests/test_build_html.py b/tests/test_build_html.py index f4a4aa21..37d57dd5 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -178,7 +178,7 @@ def check_static_entries(outdir): assert (staticdir / 'templated.css').isfile() assert (staticdir / 'templated.css').text().splitlines()[1] == __version__ # a file from _static, but matches exclude_patterns - ##assert not (staticdir / 'excluded.css').exists() + assert not (staticdir / 'excluded.css').exists() @gen_with_app(buildername='html', warning=html_warnfile, cleanenv=True, confoverrides={'html_context.hckey_co': 'hcval_co'}, -- cgit v1.2.1 From cbfa4e8acfa7ec1b81dbac4afca8b498ef96569a Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 7 Jan 2010 17:56:09 +0100 Subject: The ``html_sidebars`` config value can now contain patterns as keys, and the values can be lists that explicitly select which sidebar templates should be rendered. That means that the builtin sidebar contents can be included only selectively. --- CHANGES | 5 ++++ doc/conf.py | 2 +- doc/config.rst | 51 ++++++++++++++++++++++++++++++----- sphinx/builders/html.py | 40 ++++++++++++++++++++++----- sphinx/themes/basic/globaltoc.html | 15 +++++++++++ sphinx/themes/basic/layout.html | 50 +++------------------------------- sphinx/themes/basic/localtoc.html | 15 +++++++++++ sphinx/themes/basic/relations.html | 21 +++++++++++++++ sphinx/themes/basic/searchbox.html | 26 ++++++++++++++++++ sphinx/themes/basic/sourcelink.html | 18 +++++++++++++ sphinx/util/__init__.py | 18 ++++++++++++- tests/root/_templates/contentssb.html | 2 ++ tests/root/_templates/customsb.html | 2 ++ tests/root/conf.py | 2 +- tests/test_build_html.py | 4 +++ 15 files changed, 209 insertions(+), 62 deletions(-) create mode 100644 sphinx/themes/basic/globaltoc.html create mode 100644 sphinx/themes/basic/localtoc.html create mode 100644 sphinx/themes/basic/relations.html create mode 100644 sphinx/themes/basic/searchbox.html create mode 100644 sphinx/themes/basic/sourcelink.html create mode 100644 tests/root/_templates/contentssb.html create mode 100644 tests/root/_templates/customsb.html diff --git a/CHANGES b/CHANGES index 5de227f2..c6994224 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,11 @@ Release 1.0 (in development) ============================ +* The ``html_sidebars`` config value can now contain patterns as + keys, and the values can be lists that explicitly select which + sidebar templates should be rendered. That means that the builtin + sidebar contents can be included only selectively. + * ``html_static_path`` can now contain single file entries. * The new universal config value ``exclude_patterns`` makes the diff --git a/doc/conf.py b/doc/conf.py index feda5fd1..836ad52e 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -52,7 +52,7 @@ html_last_updated_fmt = '%b %d, %Y' html_index = 'index.html' # Custom sidebar templates, maps page names to templates. -html_sidebars = {'index': 'indexsidebar.html'} +html_sidebars = {'index': ['indexsidebar.html', 'searchbox.html']} # Additional templates that should be rendered to pages, maps page names to # templates. diff --git a/doc/config.rst b/doc/config.rst index 745638c8..e4693d84 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -90,7 +90,7 @@ General configuration .. confval:: exclude_patterns A list of glob-style patterns that should be excluded when looking for source - files. [#]_ They are matched against the source file names relative to the + files. [1]_ They are matched against the source file names relative to the source directory, using slashes as directory separators on all platforms. Example patterns: @@ -445,14 +445,53 @@ that use Sphinx' HTMLWriter class. .. confval:: html_sidebars Custom sidebar templates, must be a dictionary that maps document names to - template names. Example:: + template names. + + The keys can contain glob-style patterns [1]_, in which case all matching + documents will get the specified sidebars. (A warning is emitted when a + more than one glob-style pattern matches for any document.) + + The values can be either lists or single strings. + + * If a value is a list, it specifies the complete list of sidebar templates + to include. If all or some of the default sidebars are to be included, + they must be put into this list as well. + + The default sidebars (for documents that don't match any pattern) are: + ``['localtoc.html', 'relations.html', 'sourcelink.html', + 'searchbox.html']``. + + * If a value is a single string, it specifies a custom sidebar to be added + between the ``'sourcelink.html'`` and ``'searchbox.html'`` entries. This + is for compatibility with Sphinx versions before 1.0. + + Builtin sidebar templates that can be rendered are: + + * **localtoc.html** -- a fine-grained table of contents of the current document + * **globaltoc.html** -- a coarse-grained table of contents for the whole + documentation set, collapsed + * **relations.html** -- two links to the previous and next documents + * **sourcelink.html** -- a link to the source of the current document, if + enabled in :confval:`html_show_sourcelink` + * **searchbox.html** -- the "quick search" box + + Example:: html_sidebars = { - 'using/windows': 'windowssidebar.html' + '**': ['globaltoc.html', 'sourcelink.html', 'searchbox.html'], + 'using/windows': ['windowssidebar.html', 'searchbox.html'], } - This will render the template ``windowssidebar.html`` within the sidebar of - the given document. + This will render the custom template ``windowssidebar.html`` and the quick + search box within the sidebar of the given document, and render the default + sidebars for all other pages (except that the local TOC is replaced by the + global TOC). + + .. versionadded:: 1.0 + The ability to use globbing keys and to specify multiple sidebars. + + Note that this value only has no effect if the chosen theme does not possess + a sidebar, like the builtin **scrolls** and **haiku** themes. .. confval:: html_additional_pages @@ -835,7 +874,7 @@ These options influence LaTeX output. .. rubric:: Footnotes -.. [#] A note on available globbing syntax: you can use the standard shell +.. [1] A note on available globbing syntax: you can use the standard shell constructs ``*``, ``?``, ``[...]`` and ``[!...]`` with the feature that these all don't match slashes. A double star ``**`` can be used to match any sequence of characters *including* slashes. diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index 31af5903..09be64f2 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -28,8 +28,8 @@ from docutils.frontend import OptionParser from docutils.readers.doctree import Reader as DoctreeReader from sphinx import package_dir, __version__ -from sphinx.util import SEP, os_path, relative_uri, ensuredir, \ - movefile, ustrftime, copy_static_entry, copyfile, compile_matchers +from sphinx.util import SEP, os_path, relative_uri, ensuredir, patmatch, \ + movefile, ustrftime, copy_static_entry, copyfile, compile_matchers, any from sphinx.errors import SphinxError from sphinx.search import js_index from sphinx.theming import Theme @@ -74,6 +74,9 @@ class StandaloneHTMLBuilder(Builder): # Dito for this one. css_files = [] + default_sidebars = ['localtoc.html', 'relations.html', + 'sourcelink.html', 'searchbox.html'] + # cached publisher object for snippets _publisher = None @@ -654,6 +657,33 @@ class StandaloneHTMLBuilder(Builder): def get_outfilename(self, pagename): return path.join(self.outdir, os_path(pagename) + self.out_suffix) + def get_sidebars(self, pagename): + def has_wildcard(pattern): + return any(char in pattern for char in '*?[') + sidebars = None + matched = None + for pattern, patsidebars in self.config.html_sidebars.iteritems(): + if patmatch(pagename, pattern): + if matched: + if has_wildcard(pattern): + # warn if both patterns contain wildcards + if has_wildcard(matched): + self.warn('page %s matches two patterns in ' + 'html_sidebars: %r and %r' % + (pagename, matched, pattern)) + # else the already matched pattern is more specific + # than the present one, because it contains no wildcard + continue + matched = pattern + sidebars = patsidebars + if sidebars is None: + sidebars = self.default_sidebars + elif isinstance(sidebars, basestring): + # 0.x compatible mode: insert custom sidebar before searchbox + sidebars = self.default_sidebars[:-1] + [sidebars] + \ + self.default_sidebars[-1:] + return sidebars + # --------- these are overwritten by the serialization builder def get_target_uri(self, docname, typ=None): @@ -673,9 +703,9 @@ class StandaloneHTMLBuilder(Builder): return uri ctx['pathto'] = pathto ctx['hasdoc'] = lambda name: name in self.env.all_docs - ctx['customsidebar'] = self.config.html_sidebars.get(pagename) ctx['encoding'] = encoding = self.config.html_output_encoding ctx['toctree'] = lambda **kw: self._get_local_toctree(pagename, **kw) + ctx['sidebars'] = self.get_sidebars(pagename) ctx.update(addctx) self.app.emit('html-page-context', pagename, templatename, @@ -790,9 +820,7 @@ class SerializingHTMLBuilder(StandaloneHTMLBuilder): def handle_page(self, pagename, ctx, templatename='page.html', outfilename=None, event_arg=None): ctx['current_page_name'] = pagename - sidebarfile = self.config.html_sidebars.get(pagename) - if sidebarfile: - ctx['customsidebar'] = sidebarfile + ctx['sidebars'] = self.get_sidebars(pagename) if not outfilename: outfilename = path.join(self.outdir, diff --git a/sphinx/themes/basic/globaltoc.html b/sphinx/themes/basic/globaltoc.html new file mode 100644 index 00000000..472af34f --- /dev/null +++ b/sphinx/themes/basic/globaltoc.html @@ -0,0 +1,15 @@ +{# + basic/globaltoc.html + ~~~~~~~~~~~~~~~~~~~~ + + Sphinx sidebar template: global table of contents. + + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} +{%- block sidebartoc %} +{%- if display_toc %} +

{{ _('Table Of Contents') }}

+ {{ toctree() }} +{%- endif %} +{%- endblock %} diff --git a/sphinx/themes/basic/layout.html b/sphinx/themes/basic/layout.html index d86da2cb..97b68f8a 100644 --- a/sphinx/themes/basic/layout.html +++ b/sphinx/themes/basic/layout.html @@ -46,53 +46,9 @@

{%- endif %} {%- endblock %} - {%- block sidebartoc %} - {%- if display_toc %} -

{{ _('Table Of Contents') }}

- {{ toc }} - {%- endif %} - {%- endblock %} - {%- block sidebarrel %} - {%- if prev %} -

{{ _('Previous topic') }}

-

{{ prev.title }}

- {%- endif %} - {%- if next %} -

{{ _('Next topic') }}

-

{{ next.title }}

- {%- endif %} - {%- endblock %} - {%- block sidebarsourcelink %} - {%- if show_source and has_source and sourcename %} -

{{ _('This Page') }}

- - {%- endif %} - {%- endblock %} - {%- if customsidebar %} - {% include customsidebar %} - {%- endif %} - {%- block sidebarsearch %} - {%- if pagename != "search" %} - - - {%- endif %} - {%- endblock %} + {%- for sidebar in sidebars %} + {%- include sidebar %} + {%- endfor %} {%- endif %}{% endif %} diff --git a/sphinx/themes/basic/localtoc.html b/sphinx/themes/basic/localtoc.html new file mode 100644 index 00000000..dab70cd8 --- /dev/null +++ b/sphinx/themes/basic/localtoc.html @@ -0,0 +1,15 @@ +{# + basic/localtoc.html + ~~~~~~~~~~~~~~~~~~~ + + Sphinx sidebar template: local table of contents. + + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} +{%- block sidebartoc %} +{%- if display_toc %} +

{{ _('Table Of Contents') }}

+ {{ toc }} +{%- endif %} +{%- endblock %} diff --git a/sphinx/themes/basic/relations.html b/sphinx/themes/basic/relations.html new file mode 100644 index 00000000..18e7cc4c --- /dev/null +++ b/sphinx/themes/basic/relations.html @@ -0,0 +1,21 @@ +{# + basic/relations.html + ~~~~~~~~~~~~~~~~~~~~ + + Sphinx sidebar template: relation links. + + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} +{%- block sidebarrel %} +{%- if prev %} +

{{ _('Previous topic') }}

+

{{ prev.title }}

+{%- endif %} +{%- if next %} +

{{ _('Next topic') }}

+

{{ next.title }}

+{%- endif %} +{%- endblock %} diff --git a/sphinx/themes/basic/searchbox.html b/sphinx/themes/basic/searchbox.html new file mode 100644 index 00000000..fb5e0889 --- /dev/null +++ b/sphinx/themes/basic/searchbox.html @@ -0,0 +1,26 @@ +{# + basic/searchbox.html + ~~~~~~~~~~~~~~~~~~~~ + + Sphinx sidebar template: quick search box. + + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} +{%- block sidebarsearch %} +{%- if pagename != "search" %} + + +{%- endif %} +{%- endblock %} diff --git a/sphinx/themes/basic/sourcelink.html b/sphinx/themes/basic/sourcelink.html new file mode 100644 index 00000000..665e1272 --- /dev/null +++ b/sphinx/themes/basic/sourcelink.html @@ -0,0 +1,18 @@ +{# + basic/sourcelink.html + ~~~~~~~~~~~~~~~~~~~~~ + + Sphinx sidebar template: "show source" link. + + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +#} +{%- block sidebarsourcelink %} +{%- if show_source and has_source and sourcename %} +

{{ _('This Page') }}

+ +{%- endif %} +{%- endblock %} diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index ab277f4a..a7d9b67f 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -12,7 +12,6 @@ import os import re import sys -import stat import time import errno import types @@ -285,6 +284,14 @@ def compile_matchers(patterns): _pat_cache = {} +def patmatch(name, pat): + """ + Return if name matches pat. Adapted from fnmatch module. + """ + if pat not in _pat_cache: + _pat_cache[pat] = re.compile(_translate_pattern(pat)) + return _pat_cache[pat].match(name) + def patfilter(names, pat): """ Return the subset of the list NAMES that match PAT. @@ -483,6 +490,15 @@ def split_explicit_title(text): return False, text, text +try: + any = any +except NameError: + def any(gen): + for i in gen: + if i: + return True + return False + # monkey-patch Node.traverse to get more speed # traverse() is called so many times during a build that it saves # on average 20-25% overall build time! diff --git a/tests/root/_templates/contentssb.html b/tests/root/_templates/contentssb.html new file mode 100644 index 00000000..9951d3c3 --- /dev/null +++ b/tests/root/_templates/contentssb.html @@ -0,0 +1,2 @@ +{# sidebar only for contents document #} +

Contents sidebar

\ No newline at end of file diff --git a/tests/root/_templates/customsb.html b/tests/root/_templates/customsb.html new file mode 100644 index 00000000..cc88b8cf --- /dev/null +++ b/tests/root/_templates/customsb.html @@ -0,0 +1,2 @@ +{# custom sidebar template #} +

Custom sidebar

diff --git a/tests/root/conf.py b/tests/root/conf.py index 88091877..a3783511 100644 --- a/tests/root/conf.py +++ b/tests/root/conf.py @@ -31,7 +31,7 @@ rst_epilog = '.. |subst| replace:: global substitution' html_theme = 'testtheme' html_theme_path = ['.'] html_theme_options = {'testopt': 'testoverride'} - +html_sidebars = {'**': 'customsb.html', 'contents': 'contentssb.html'} html_style = 'default.css' html_static_path = ['_static', 'templated.css_t'] html_last_updated_fmt = '%b %d, %Y' diff --git a/tests/test_build_html.py b/tests/test_build_html.py index 37d57dd5..e8189e67 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -86,6 +86,8 @@ HTML_XPATH = { ".//dl[@class='userdesc']": '', ".//dt[@id='userdescrole-myobj']": '', ".//a[@href='#userdescrole-myobj']": '', + # custom sidebar + ".//h4": 'Custom sidebar', }, 'contents.html': { ".//meta[@name='hc'][@content='hcval']": '', @@ -98,6 +100,8 @@ HTML_XPATH = { ".//title": 'Sphinx ', ".//div[@class='footer']": 'Georg Brandl & Team', ".//a[@href='http://python.org/']": '', + # custom sidebar only for contents + ".//h4": 'Contents sidebar', }, 'bom.html': { ".//title": " File with UTF-8 BOM", -- cgit v1.2.1 From 44c418fefda194f17b9b4ad6a225187eeeaaff39 Mon Sep 17 00:00:00 2001 From: Roland Meister Date: Thu, 7 Jan 2010 18:01:11 +0100 Subject: Increased toctree level. Some documents are deeply nested. --- 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 e22b1520..07f13ffe 100644 --- a/sphinx/builders/epub.py +++ b/sphinx/builders/epub.py @@ -172,7 +172,7 @@ class EpubBuilder(StandaloneHTMLBuilder): if isinstance(doctree, nodes.reference): classes = doctree.parent.attributes['classes'] 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: level = l result.append({ -- cgit v1.2.1 From 0483cc8635b8968643afa985fb656a450ffe41bf Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 7 Jan 2010 18:07:22 +0100 Subject: Unify logo and favicon handling. --- sphinx/builders/html.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index 09be64f2..7cf5fdf6 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -579,11 +579,19 @@ class StandaloneHTMLBuilder(Builder): continue copy_static_entry(entry, path.join(self.outdir, '_static'), self, self.globalcontext, exclude_matchers=matchers) - # last, copy logo file (handled differently XXX why?) + # copy logo and favicon files if not already in static path if self.config.html_logo: logobase = path.basename(self.config.html_logo) - copyfile(path.join(self.confdir, self.config.html_logo), - path.join(self.outdir, '_static', logobase)) + logotarget = path.join(self.outdir, '_static', logobase) + if not path.isfile(logotarget): + copyfile(path.join(self.confdir, self.config.html_logo), + logotarget) + if self.config.html_favicon: + iconbase = path.basename(self.config.html_favicon) + icontarget = path.join(self.outdir, '_static', iconbase) + if not path.isfile(icontarget): + copyfile(path.join(self.confdir, self.config.html_favicon), + icontarget) # write build info file fp = open(path.join(self.outdir, '.buildinfo'), 'w') -- cgit v1.2.1 From 2166bd4f8fcf7831edb563389015f29aa1a73ed9 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 7 Jan 2010 21:49:31 +0100 Subject: Fix compatibility with blocks in the old sidebar scheme: blocks cannot be used in included templates. Deprecate the blocks accordingly. --- doc/templating.rst | 19 ++++++++++++++++--- sphinx/builders/html.py | 17 ++++++++++------- sphinx/themes/basic/globaltoc.html | 2 -- sphinx/themes/basic/layout.html | 26 +++++++++++++++++++++++--- sphinx/themes/basic/localtoc.html | 2 -- sphinx/themes/basic/relations.html | 2 -- sphinx/themes/basic/searchbox.html | 2 -- sphinx/themes/basic/sourcelink.html | 2 -- 8 files changed, 49 insertions(+), 23 deletions(-) diff --git a/doc/templating.rst b/doc/templating.rst index 0148ed32..abcc478a 100644 --- a/doc/templating.rst +++ b/doc/templating.rst @@ -139,23 +139,36 @@ The following blocks exist in the ``layout.html`` template: The logo location within the sidebar. Override this if you want to place some content at the top of the sidebar. +`footer` + The block for the footer div. If you want a custom footer or markup before + or after it, override this one. + +The following four blocks are *only* used for pages that do not have assigned a +list of custom sidebars in the :confval:`html_sidebars` config value. Their use +is deprecated in favor of separate sidebar templates, which can be included via +:confval:`html_sidebars`. + `sidebartoc` The table of contents within the sidebar. + .. deprecated:: 1.0 + `sidebarrel` The relation links (previous, next document) within the sidebar. + .. deprecated:: 1.0 + `sidebarsourcelink` The "Show source" link within the sidebar (normally only shown if this is enabled by :confval:`html_show_sourcelink`). + .. deprecated:: 1.0 + `sidebarsearch` The search box within the sidebar. Override this if you want to place some content at the bottom of the sidebar. -`footer` - The block for the footer div. If you want a custom footer or markup before - or after it, override this one. + .. deprecated:: 1.0 Configuration Variables diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index 7cf5fdf6..cdabcd65 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -665,11 +665,12 @@ class StandaloneHTMLBuilder(Builder): def get_outfilename(self, pagename): return path.join(self.outdir, os_path(pagename) + self.out_suffix) - def get_sidebars(self, pagename): + def add_sidebars(self, pagename, ctx): def has_wildcard(pattern): return any(char in pattern for char in '*?[') sidebars = None matched = None + customsidebar = None for pattern, patsidebars in self.config.html_sidebars.iteritems(): if patmatch(pagename, pattern): if matched: @@ -685,12 +686,14 @@ class StandaloneHTMLBuilder(Builder): matched = pattern sidebars = patsidebars if sidebars is None: - sidebars = self.default_sidebars + # keep defaults + pass elif isinstance(sidebars, basestring): # 0.x compatible mode: insert custom sidebar before searchbox - sidebars = self.default_sidebars[:-1] + [sidebars] + \ - self.default_sidebars[-1:] - return sidebars + customsidebar = sidebars + sidebars = None + ctx['sidebars'] = sidebars + ctx['customsidebar'] = customsidebar # --------- these are overwritten by the serialization builder @@ -713,7 +716,7 @@ class StandaloneHTMLBuilder(Builder): ctx['hasdoc'] = lambda name: name in self.env.all_docs ctx['encoding'] = encoding = self.config.html_output_encoding ctx['toctree'] = lambda **kw: self._get_local_toctree(pagename, **kw) - ctx['sidebars'] = self.get_sidebars(pagename) + self.add_sidebars(pagename, ctx) ctx.update(addctx) self.app.emit('html-page-context', pagename, templatename, @@ -828,7 +831,7 @@ class SerializingHTMLBuilder(StandaloneHTMLBuilder): def handle_page(self, pagename, ctx, templatename='page.html', outfilename=None, event_arg=None): ctx['current_page_name'] = pagename - ctx['sidebars'] = self.get_sidebars(pagename) + self.add_sidebars(pagename, ctx) if not outfilename: outfilename = path.join(self.outdir, diff --git a/sphinx/themes/basic/globaltoc.html b/sphinx/themes/basic/globaltoc.html index 472af34f..c5332471 100644 --- a/sphinx/themes/basic/globaltoc.html +++ b/sphinx/themes/basic/globaltoc.html @@ -7,9 +7,7 @@ :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} -{%- block sidebartoc %} {%- if display_toc %}

{{ _('Table Of Contents') }}

{{ toctree() }} {%- endif %} -{%- endblock %} diff --git a/sphinx/themes/basic/layout.html b/sphinx/themes/basic/layout.html index 97b68f8a..f489a656 100644 --- a/sphinx/themes/basic/layout.html +++ b/sphinx/themes/basic/layout.html @@ -46,9 +46,29 @@

{%- endif %} {%- endblock %} - {%- for sidebar in sidebars %} - {%- include sidebar %} - {%- endfor %} + {%- if sidebars %} + {#- new style sidebar: explicitly include/exclude templates #} + {%- for sidebar in sidebars %} + {%- include sidebar %} + {%- endfor %} + {%- else %} + {#- old style sidebars: using blocks -- should be deprecated #} + {%- block sidebartoc %} + {%- include "localtoc.html" %} + {%- endblock %} + {%- block sidebarrel %} + {%- include "relations.html" %} + {%- endblock %} + {%- block sidebarsourcelink %} + {%- include "sourcelink.html" %} + {%- endblock %} + {%- if customsidebar %} + {%- include customsidebar %} + {%- endif %} + {%- block sidebarsearch %} + {%- include "searchbox.html" %} + {%- endblock %} + {%- endif %} {%- endif %}{% endif %} diff --git a/sphinx/themes/basic/localtoc.html b/sphinx/themes/basic/localtoc.html index dab70cd8..896eda9c 100644 --- a/sphinx/themes/basic/localtoc.html +++ b/sphinx/themes/basic/localtoc.html @@ -7,9 +7,7 @@ :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} -{%- block sidebartoc %} {%- if display_toc %}

{{ _('Table Of Contents') }}

{{ toc }} {%- endif %} -{%- endblock %} diff --git a/sphinx/themes/basic/relations.html b/sphinx/themes/basic/relations.html index 18e7cc4c..b693daf8 100644 --- a/sphinx/themes/basic/relations.html +++ b/sphinx/themes/basic/relations.html @@ -7,7 +7,6 @@ :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} -{%- block sidebarrel %} {%- if prev %}

{{ _('Previous topic') }}

{{ next.title }}

{%- endif %} -{%- endblock %} diff --git a/sphinx/themes/basic/searchbox.html b/sphinx/themes/basic/searchbox.html index fb5e0889..56d6617c 100644 --- a/sphinx/themes/basic/searchbox.html +++ b/sphinx/themes/basic/searchbox.html @@ -7,7 +7,6 @@ :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} -{%- block sidebarsearch %} {%- if pagename != "search" %} {%- endif %} -{%- endblock %} diff --git a/sphinx/themes/basic/sourcelink.html b/sphinx/themes/basic/sourcelink.html index 665e1272..8fa7563b 100644 --- a/sphinx/themes/basic/sourcelink.html +++ b/sphinx/themes/basic/sourcelink.html @@ -7,7 +7,6 @@ :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} -{%- block sidebarsourcelink %} {%- if show_source and has_source and sourcename %}

{{ _('This Page') }}

    @@ -15,4 +14,3 @@ rel="nofollow">{{ _('Show Source') }}
{%- endif %} -{%- endblock %} -- cgit v1.2.1 From a1614c9b15a76e7218b00dacde6e11889b17648b Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Fri, 8 Jan 2010 18:09:16 +0100 Subject: Regroup examples list by theme used. --- EXAMPLES | 149 ++++++++++++++++++++++++++++++++++++++------------------------- 1 file changed, 90 insertions(+), 59 deletions(-) diff --git a/EXAMPLES b/EXAMPLES index 4c76d92e..06b9205d 100644 --- a/EXAMPLES +++ b/EXAMPLES @@ -1,102 +1,133 @@ Projects using Sphinx ===================== -This is an (incomplete) alphabetic list of projects that use Sphinx or are -experimenting with using it for their documentation. If you like to be -included, please mail to `the Google group +This is an (incomplete) alphabetic list of projects that use Sphinx or +are experimenting with using it for their documentation. If you like to +be included, please mail to `the Google group `_. -* Advanced Generic Widgets: http://xoomer.virgilio.it/infinity77/AGW_Docs/index.html -* Applied Mathematics at the Stellenbosch University: http://dip.sun.ac.za/ +I've grouped the list into sections to make it easier to find +interesting examples. + +Documentation using the default theme +------------------------------------- + * APSW: http://apsw.googlecode.com/svn/publish/index.html * ASE: https://wiki.fysik.dtu.dk/ase/ -* Bazaar: http://doc.bazaar.canonical.com/en/ * Blender: http://www.blender.org/documentation/250PythonDoc/ * boostmpi: http://documen.tician.de/boostmpi/ * Calibre: http://calibre.kovidgoyal.net/user_manual/ -* Chaco: http://code.enthought.com/projects/chaco/docs/html/ * CodePy: http://documen.tician.de/codepy/ * Cython: http://docs.cython.org/ * C\\C++ Python language binding project: http://language-binding.net/index.html * Director: http://packages.python.org/director/ -* Djagios: http://djagios.org/ -* Django: http://docs.djangoproject.com/ * F2py: http://www.f2py.org/html/ -* Fityk: http://www.unipress.waw.pl/fityk/ * GeoDjango: http://geodjango.org/docs/ -* GeoServer: http://docs.geoserver.org/ -* Glashammer: http://glashammer.org/ -* GPAW: https://wiki.fysik.dtu.dk/gpaw/ -* Grok: http://grok.zope.org/doc/current/ * GSL Shell: http://www.nongnu.org/gsl-shell/ * Hedge: http://documen.tician.de/hedge/ -* IFM: http://fluffybunny.memebot.com/ifm-docs/index.html -* Jinja: http://jinja.pocoo.org/2/documentation/ * Kaa: http://doc.freevo.org/api/kaa/ -* LEPL: http://www.acooke.org/lepl/ -* MapServer: http://mapserver.org/ -* Matplotlib: http://matplotlib.sourceforge.net/ -* Mayavi: http://code.enthought.com/projects/mayavi/docs/development/html/mayavi * MeshPy: http://documen.tician.de/meshpy/ -* MirrorBrain: http://mirrorbrain.org/docs/ -* Mixin.com: http://dev.mixin.com/ * mpmath: http://mpmath.googlecode.com/svn/trunk/doc/build/index.html -* MyHDL: http://www.myhdl.org/doc/0.6/ -* NetworkX: http://networkx.lanl.gov/ -* NOC: http://trac.nocproject.org/trac/wiki/NocGuide -* nose: http://somethingaboutorange.com/mrl/projects/nose/ -* NumPy: http://docs.scipy.org/doc/numpy/reference/ -* ObjectListView: http://objectlistview.sourceforge.net/python -* Open ERP: http://doc.openerp.com/ * OpenEXR: http://excamera.com/articles/26/doc/index.html * OpenGDA: http://www.opengda.org/gdadoc/html/ -* OpenLayers: http://docs.openlayers.org/ * openWNS: http://docs.openwns.org/ * Paste: http://pythonpaste.org/script/ * Paver: http://www.blueskyonmars.com/projects/paver/ -* Peach^3: http://peach3.nl/doc/latest/userdoc/ -* Py on Windows: http://timgolden.me.uk/python-on-windows/ * Pyccuracy: http://www.pyccuracy.org/ * PyCuda: http://documen.tician.de/pycuda/ -* PyEphem: http://rhodesmill.org/pyephem/ * Pyevolve: http://pyevolve.sourceforge.net/ -* PyLit: http://pylit.berlios.de/ * Pylo: http://documen.tician.de/pylo/ -* Pylons: http://docs.pylonshq.com/ -* PyMOTW: http://www.doughellmann.com/PyMOTW/ * PyPubSub: http://pubsub.sourceforge.net/ * pyrticle: http://documen.tician.de/pyrticle/ -* Pysparse: http://pysparse.sourceforge.net/ -* PyTango: http://www.tango-controls.org/static/PyTango/latest/doc/html/index.html * Python: http://docs.python.org/ -* python-apt: http://people.debian.org/~jak/python-apt-doc/ +* python-apt: http://apt.alioth.debian.org/python-apt-doc/ * PyUblas: http://documen.tician.de/pyublas/ -* Quex: http://quex.sourceforge.net/ -* Reteisi: http://docs.argolinux.org/reteisi/ -* Roundup: http://www.roundup-tracker.org/ -* Sage: http://sagemath.org/doc/ -* Satchmo: http://www.satchmoproject.com/docs/svn/ +* Quex: http://quex.sourceforge.net/doc/html/main.html * Scapy: http://www.secdev.org/projects/scapy/doc/ +* SimPy: http://simpy.sourceforge.net/SimPyDocs/index.html +* SymPy: http://docs.sympy.org/ +* WTForms: http://wtforms.simplecodes.com/docs/ +* z3c: http://docs.carduner.net/z3c-tutorial/ + + +Documentation using a customized version of the default theme +------------------------------------------------------------- + +* Advanced Generic Widgets: http://xoomer.virgilio.it/infinity77/AGW_Docs/index.html +* Bazaar: http://doc.bazaar.canonical.com/en/ +* Chaco: http://code.enthought.com/projects/chaco/docs/html/ +* Djagios: http://djagios.org/ +* GPAW: https://wiki.fysik.dtu.dk/gpaw/ +* Grok: http://grok.zope.org/doc/current/ +* IFM: http://fluffybunny.memebot.com/ifm-docs/index.html +* LEPL: http://www.acooke.org/lepl/ +* Mayavi: http://code.enthought.com/projects/mayavi/docs/development/html/mayavi +* NOC: http://trac.nocproject.org/trac/wiki/NocGuide +* NumPy: http://docs.scipy.org/doc/numpy/reference/ +* Peach^3: http://peach3.nl/doc/latest/userdoc/ +* Py on Windows: http://timgolden.me.uk/python-on-windows/ +* PyLit: http://pylit.berlios.de/ +* Sage: http://sagemath.org/doc/ * SciPy: http://docs.scipy.org/doc/scipy/reference/ -* Selenium: http://seleniumhq.org/docs/ -* Self: http://selflanguage.org/ -* SimPy: http://simpy.sourceforge.net/ -* Sphinx: http://sphinx.pocoo.org/ * Sprox: http://sprox.org/ -* SQLAlchemy: http://www.sqlalchemy.org/docs/ +* TurboGears: http://turbogears.org/2.0/docs/ +* Zope: http://docs.zope.org/zope2/index.html +* zc.async: http://packages.python.org/zc.async/1.5.0/ + + +Documentation using the sphinxdoc theme +--------------------------------------- + +* Fityk: http://www.unipress.waw.pl/fityk/ +* MapServer: http://mapserver.org/ +* Matplotlib: http://matplotlib.sourceforge.net/ +* MyHDL: http://www.myhdl.org/doc/0.6/ +* NetworkX: http://networkx.lanl.gov/ +* Pysparse: http://pysparse.sourceforge.net/ +* PyTango: http://www.tango-controls.org/static/PyTango/latest/doc/html/index.html +* Reteisi: http://docs.argolinux.org/reteisi/ +* Satchmo: http://www.satchmoproject.com/docs/svn/ +* Sphinx: http://sphinx.pocoo.org/ * Sqlkit: http://sqlkit.argolinux.org/ -* sqlparse: http://python-sqlparse.googlecode.com/svn/docs/api/index.html -* SymPy: http://docs.sympy.org/ -* tinyTiM: http://tinytim.sourceforge.net/docs/2.0/ -* The Wine Cellar Book: http://www.thewinecellarbook.com/doc/en/ * Total Open Station: http://tops.berlios.de/ -* TurboGears: http://turbogears.org/2.0/docs/ -* VOR: http://www.vor-cycling.be/ * WebFaction: http://docs.webfaction.com/ + + +Documentation using another builtin theme +----------------------------------------- + +* Jinja: http://jinja.pocoo.org/2/documentation/ (scrolls) +* sqlparse: http://python-sqlparse.googlecode.com/svn/docs/api/index.html (agogo) + + +Documentation using a custom theme/integrated in a site +------------------------------------------------------- + +* Django: http://docs.djangoproject.com/ +* GeoServer: http://docs.geoserver.org/ +* Glashammer: http://glashammer.org/ +* MirrorBrain: http://mirrorbrain.org/docs/ +* nose: http://somethingaboutorange.com/mrl/projects/nose/ +* ObjectListView: http://objectlistview.sourceforge.net/python +* Open ERP: http://doc.openerp.com/ +* OpenLayers: http://docs.openlayers.org/ +* PyEphem: http://rhodesmill.org/pyephem/ +* Pylons: http://pylonshq.com/docs/en/0.9.7/ +* PyMOTW: http://www.doughellmann.com/PyMOTW/ +* Roundup: http://www.roundup-tracker.org/ +* Selenium: http://seleniumhq.org/docs/ +* Self: http://selflanguage.org/ +* SQLAlchemy: http://www.sqlalchemy.org/docs/ +* tinyTiM: http://tinytim.sourceforge.net/docs/2.0/ * Werkzeug: http://werkzeug.pocoo.org/documentation/dev/ * WFront: http://discorporate.us/projects/WFront/ -* WTForms: http://wtforms.simplecodes.com/docs/ -* Zope 3: e.g. http://docs.carduner.net/z3c-tutorial/ -* zc.async: http://packages.python.org/zc.async/1.5.0/ -.. XXX maybe regroup by interesting features used? + +Homepages and other non-documentation sites +------------------------------------------- + +* Applied Mathematics at the Stellenbosch University: http://dip.sun.ac.za/ +* A personal page: http://www.dehlia.in/ +* lunarsite: http://lunaryorn.de/ +* The Wine Cellar Book: http://www.thewinecellarbook.com/doc/en/ +* VOR: http://www.vor-cycling.be/ -- cgit v1.2.1 From 1be1668cf717e78a1bca86df8d1f6493fe33dac4 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Fri, 8 Jan 2010 18:36:58 +0100 Subject: Added ``tab-width`` option to ``literalinclude`` directive. --- CHANGES | 2 ++ doc/markup/code.rst | 5 ++++- sphinx/directives/code.py | 3 +++ tests/root/includes.txt | 10 ++++++++++ tests/root/tabs.inc | 5 +++++ tests/test_build_html.py | 16 ++++++++++------ 6 files changed, 34 insertions(+), 7 deletions(-) create mode 100644 tests/root/tabs.inc diff --git a/CHANGES b/CHANGES index c6994224..aaac057c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,8 @@ Release 1.0 (in development) ============================ +* Added ``tab-width`` option to ``literalinclude`` directive. + * The ``html_sidebars`` config value can now contain patterns as keys, and the values can be lists that explicitly select which sidebar templates should be rendered. That means that the builtin diff --git a/doc/markup/code.rst b/doc/markup/code.rst index 8c223297..96871115 100644 --- a/doc/markup/code.rst +++ b/doc/markup/code.rst @@ -103,6 +103,9 @@ Includes is absolute (starting with ``/``), it is relative to the top source directory. + Tabs in the input are expanded if you give a ``tab-width`` option with the + desired tab width. + The directive also supports the ``linenos`` flag option to switch on line numbers, and a ``language`` option to select a language different from the current file's standard language. Example with options:: @@ -153,7 +156,7 @@ Includes The ``pyobject``, ``lines``, ``start-after`` and ``end-before`` options, as well as support for absolute filenames. .. versionadded:: 1.0 - The ``prepend`` and ``append`` options. + The ``prepend`` and ``append`` options, as well as ``tab-width``. .. rubric:: Footnotes diff --git a/sphinx/directives/code.py b/sphinx/directives/code.py index 1ae8b3a8..6535bdf5 100644 --- a/sphinx/directives/code.py +++ b/sphinx/directives/code.py @@ -81,6 +81,7 @@ class LiteralInclude(Directive): final_argument_whitespace = False option_spec = { 'linenos': directives.flag, + 'tab-width': int, 'language': directives.unchanged_required, 'encoding': directives.encoding, 'pyobject': directives.unchanged_required, @@ -174,6 +175,8 @@ class LiteralInclude(Directive): lines.append(append + '\n') text = ''.join(lines) + if self.options.get('tab-width'): + text = text.expandtabs(self.options['tab-width']) retnode = nodes.literal_block(text, text, source=fn) retnode.line = 1 if self.options.get('language', ''): diff --git a/tests/root/includes.txt b/tests/root/includes.txt index d6da137f..cb170840 100644 --- a/tests/root/includes.txt +++ b/tests/root/includes.txt @@ -49,6 +49,16 @@ Literalinclude options :prepend: START CODE :append: END CODE +.. cssclass:: inc-tab3 +.. literalinclude:: tabs.inc + :tab-width: 3 + :language: text + +.. cssclass:: inc-tab8 +.. literalinclude:: tabs.inc + :tab-width: 8 + :language: python + Test if dedenting before parsing works. .. highlight:: python diff --git a/tests/root/tabs.inc b/tests/root/tabs.inc new file mode 100644 index 00000000..20b51820 --- /dev/null +++ b/tests/root/tabs.inc @@ -0,0 +1,5 @@ +Tabs include file test +---------------------- + +The next line has a tab: +-| |- diff --git a/tests/test_build_html.py b/tests/test_build_html.py index e8189e67..25f733a4 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -119,18 +119,22 @@ HTML_XPATH = { if pygments: HTML_XPATH['includes.html'].update({ ".//pre/span[@class='s']": u'üöä', - ".//div[@class='inc-pyobj1 highlight-text']/div/pre": + ".//div[@class='inc-pyobj1 highlight-text']//pre": r'^class Foo:\n pass\n\s*$', - ".//div[@class='inc-pyobj2 highlight-text']/div/pre": + ".//div[@class='inc-pyobj2 highlight-text']//pre": r'^ def baz\(\):\n pass\n\s*$', - ".//div[@class='inc-lines highlight-text']/div/pre": + ".//div[@class='inc-lines highlight-text']//pre": r'^class Foo:\n pass\nclass Bar:\n$', - ".//div[@class='inc-startend highlight-text']/div/pre": + ".//div[@class='inc-startend highlight-text']//pre": ur'^foo = u"Including Unicode characters: üöä"\n$', - ".//div[@class='inc-preappend highlight-text']/div/pre": + ".//div[@class='inc-preappend highlight-text']//pre": r'(?m)^START CODE$', - ".//div[@class='inc-pyobj-dedent highlight-python']/div/pre/span": + ".//div[@class='inc-pyobj-dedent highlight-python']//span": r'def', + ".//div[@class='inc-tab3 highlight-text']//pre": + r'-| |-', + ".//div[@class='inc-tab8 highlight-python']//pre": + r'-| |-', }) HTML_XPATH['subdir/includes.html'].update({ ".//pre/span": 'line 1', -- cgit v1.2.1 From be635f561630f3c9384c5b5857fbaecc65da6769 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Fri, 8 Jan 2010 18:43:59 +0100 Subject: Record jinja2 version. --- sphinx/util/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index a7d9b67f..f5ca564d 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -25,6 +25,8 @@ from os import path import docutils from docutils.utils import relative_path +import jinja2 + import sphinx # Errnos that we need. @@ -227,6 +229,7 @@ def save_traceback(): os.write(fd, '# Sphinx version: %s\n' % sphinx.__version__) os.write(fd, '# Docutils version: %s %s\n' % (docutils.__version__, docutils.__version_details__)) + os.write(fd, '# Jinja2 version: %s\n' % jinja2.__version__) os.write(fd, exc) os.close(fd) return path -- cgit v1.2.1 From 99a2a2150ec0ed6a81e0e2ab0d4df62eefd00da0 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Fri, 8 Jan 2010 18:47:15 +0100 Subject: Don't name a variable like the macro it's in, Jinja 2.1 doesn't like it. --- sphinx/themes/basic/layout.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sphinx/themes/basic/layout.html b/sphinx/themes/basic/layout.html index f489a656..429cc9bb 100644 --- a/sphinx/themes/basic/layout.html +++ b/sphinx/themes/basic/layout.html @@ -48,8 +48,8 @@ {%- endblock %} {%- if sidebars %} {#- new style sidebar: explicitly include/exclude templates #} - {%- for sidebar in sidebars %} - {%- include sidebar %} + {%- for sidebartemplate in sidebars %} + {%- include sidebartemplate %} {%- endfor %} {%- else %} {#- old style sidebars: using blocks -- should be deprecated #} -- cgit v1.2.1 From 234bd9ca235231a3f0727f9f2c1abdfab205b06c Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Fri, 8 Jan 2010 18:52:36 +0100 Subject: Add another personal page. --- EXAMPLES | 1 + 1 file changed, 1 insertion(+) diff --git a/EXAMPLES b/EXAMPLES index 06b9205d..7380b2f3 100644 --- a/EXAMPLES +++ b/EXAMPLES @@ -128,6 +128,7 @@ Homepages and other non-documentation sites * Applied Mathematics at the Stellenbosch University: http://dip.sun.ac.za/ * A personal page: http://www.dehlia.in/ +* Benoit Boissinot: http://perso.ens-lyon.fr/benoit.boissinot/ * lunarsite: http://lunaryorn.de/ * The Wine Cellar Book: http://www.thewinecellarbook.com/doc/en/ * VOR: http://www.vor-cycling.be/ -- cgit v1.2.1 From 7df7eeb1918272ce1c395032baca55671b941bfe Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Fri, 8 Jan 2010 18:53:53 +0100 Subject: Add nature examples. --- EXAMPLES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/EXAMPLES b/EXAMPLES index 7380b2f3..8f89a068 100644 --- a/EXAMPLES +++ b/EXAMPLES @@ -96,7 +96,9 @@ Documentation using the sphinxdoc theme Documentation using another builtin theme ----------------------------------------- +* Distribute: http://packages.python.org/distribute/ (nature) * Jinja: http://jinja.pocoo.org/2/documentation/ (scrolls) +* pip: http://pip.openplans.org/ (nature) * sqlparse: http://python-sqlparse.googlecode.com/svn/docs/api/index.html (agogo) -- cgit v1.2.1 From 25d1dff2d40ebee23e4e00956e621f13208adf1d Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Fri, 8 Jan 2010 18:54:41 +0100 Subject: Add some XXXes... sigh. --- sphinx/directives/__init__.py | 2 ++ sphinx/domains/__init__.py | 2 +- sphinx/domains/python.py | 1 + sphinx/environment.py | 2 ++ sphinx/search.py | 14 +++++++------- sphinx/themes/basic/static/searchtools.js | 1 + 6 files changed, 14 insertions(+), 8 deletions(-) diff --git a/sphinx/directives/__init__.py b/sphinx/directives/__init__.py index 0ee18d22..5ffce16b 100644 --- a/sphinx/directives/__init__.py +++ b/sphinx/directives/__init__.py @@ -94,6 +94,8 @@ class ObjectDescription(Directive): 'rtype': l_('Return type'), } + # XXX refactor this + def handle_doc_fields(self, node): """ Convert field lists with known keys inside the description content into diff --git a/sphinx/domains/__init__.py b/sphinx/domains/__init__.py index 9e77cce8..516eb2d1 100644 --- a/sphinx/domains/__init__.py +++ b/sphinx/domains/__init__.py @@ -172,7 +172,7 @@ from sphinx.domains.c import CDomain from sphinx.domains.std import StandardDomain from sphinx.domains.python import PythonDomain -# this contains all registered domains +# this contains all registered domains (XXX global state) all_domains = { 'std': StandardDomain, 'py': PythonDomain, diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 1540f90b..46b93898 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -327,6 +327,7 @@ class PyModule(Directive): targetnode = nodes.target('', '', ids=['module-' + modname], ismod=True) self.state.document.note_explicit_target(targetnode) ret = [modulenode, targetnode] + # XXX this behavior of the module directive is a mess... if 'platform' in self.options: platform = self.options['platform'] modulenode['platform'] = platform diff --git a/sphinx/environment.py b/sphinx/environment.py index 09d15db3..9626924f 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -45,6 +45,7 @@ orig_directive_function = directives.directive class ElementLookupError(Exception): pass +# XXX why isn't this a method of env? def lookup_domain_element(env, type, name): """Lookup a markup element (directive or role), given its name which can be a full name (with domain). @@ -338,6 +339,7 @@ class BuildEnvironment: self.dlfiles = FilenameUniqDict() # temporary data storage while reading a document + # XXX find a better name self.doc_read_data = {} # Some magically present labels diff --git a/sphinx/search.py b/sphinx/search.py index 592f4cc6..17a8354d 100644 --- a/sphinx/search.py +++ b/sphinx/search.py @@ -150,8 +150,8 @@ class IndexBuilder(object): def get_objects(self, fn2index): rv = {} - ot = self._objtypes - on = self._objnames + otypes = self._objtypes + onames = self._objnames for domainname, domain in self.env.domains.iteritems(): for fullname, type, docname, anchor, prio in domain.get_objects(): if docname not in fn2index: @@ -162,15 +162,15 @@ class IndexBuilder(object): prefix, name = rpartition(fullname, '.') pdict = rv.setdefault(prefix, {}) try: - i = ot[domainname, type] + i = otypes[domainname, type] except KeyError: - i = len(ot) - ot[domainname, type] = i + i = len(otypes) + otypes[domainname, type] = i otype = domain.object_types.get(type) if otype: - on[i] = str(otype.lname) # fire translation proxies + onames[i] = str(otype.lname) # fire translation proxies else: - on[i] = type + onames[i] = type pdict[name] = (fn2index[docname], i, prio) return rv diff --git a/sphinx/themes/basic/static/searchtools.js b/sphinx/themes/basic/static/searchtools.js index 6b0000e8..faef4594 100644 --- a/sphinx/themes/basic/static/searchtools.js +++ b/sphinx/themes/basic/static/searchtools.js @@ -362,6 +362,7 @@ var Search = { match = objects[prefix][name]; descr = objnames[match[1]] + _(', in ') + titles[match[0]]; // XXX the generated anchors are not generally correct + // XXX there may be custom prefixes result = [filenames[match[0]], fullname, '#'+fullname, descr]; switch (match[2]) { case 1: objectResults.push(result); break; -- cgit v1.2.1 From 55a1f6949d7ceb5e7241237972db5801972b79d4 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Fri, 8 Jan 2010 20:40:28 +0100 Subject: Fix long line. --- sphinx/domains/std.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 38a18198..b291be20 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -369,8 +369,8 @@ class StandardDomain(Domain): docname, labelid = self.data['objects'].get((typ, target), ('', '')) if not docname: if typ == 'term': - env.warn(node['refdoc'], 'term not in glossary: %s' % target, - node.line) + env.warn(node['refdoc'], + 'term not in glossary: %s' % target, node.line) return None else: return make_refnode(builder, fromdocname, docname, -- cgit v1.2.1 From 7bdd7a78332a69ca1deec4f8493bc950f896b03f Mon Sep 17 00:00:00 2001 From: Roland Meister Date: Sat, 9 Jan 2010 11:12:36 +0100 Subject: Code cleanup: add space after comma. --- sphinx/builders/epub.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sphinx/builders/epub.py b/sphinx/builders/epub.py index e5157d2d..2d096a11 100644 --- a/sphinx/builders/epub.py +++ b/sphinx/builders/epub.py @@ -177,7 +177,7 @@ class EpubBuilder(StandaloneHTMLBuilder): if isinstance(doctree, nodes.reference): classes = doctree.parent.attributes['classes'] level = 1 - for l in range(8,0,-1): # or range(1,8)? + for l in range(8, 0, -1): # or range(1, 8)? if (_toctree_template % l) in classes: level = l result.append({ @@ -357,7 +357,7 @@ class EpubBuilder(StandaloneHTMLBuilder): if file in self.ignored_files: continue if node['level'] == level: - navlist.append(self.new_navpoint(node,level)) + navlist.append(self.new_navpoint(node, level)) elif node['level'] == level + 1: navstack.append(navlist) navlist = [] -- cgit v1.2.1 From c69928c56ea33852d8409c318920a22079af2584 Mon Sep 17 00:00:00 2001 From: Roland Meister Date: Sat, 9 Jan 2010 11:13:10 +0100 Subject: Removed comment about reversed. Reversed exists since Python 2.4 and is ok for sphinx. --- sphinx/builders/epub.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sphinx/builders/epub.py b/sphinx/builders/epub.py index 2d096a11..e78f807e 100644 --- a/sphinx/builders/epub.py +++ b/sphinx/builders/epub.py @@ -204,7 +204,6 @@ class EpubBuilder(StandaloneHTMLBuilder): 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, -- cgit v1.2.1 From 633fc33f08cd6fdf4b11a53ccd2549d18d20878f Mon Sep 17 00:00:00 2001 From: Roland Meister Date: Sat, 9 Jan 2010 11:19:41 +0100 Subject: Added the epub_tocdepth configuration option. --- doc/config.rst | 6 ++++++ sphinx/builders/epub.py | 2 ++ sphinx/config.py | 1 + sphinx/quickstart.py | 3 +++ 4 files changed, 12 insertions(+) diff --git a/doc/config.rst b/doc/config.rst index e4693d84..50aacc4e 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -701,6 +701,12 @@ the `Dublin Core metadata `_. 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 ``[]``. +.. 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: diff --git a/sphinx/builders/epub.py b/sphinx/builders/epub.py index e78f807e..0e4529e5 100644 --- a/sphinx/builders/epub.py +++ b/sphinx/builders/epub.py @@ -355,6 +355,8 @@ class EpubBuilder(StandaloneHTMLBuilder): file = node['refuri'].split('#')[0] if file in self.ignored_files: continue + if node['level'] > self.config.epub_tocdepth: + continue if node['level'] == level: navlist.append(self.new_navpoint(node, level)) elif node['level'] == level + 1: diff --git a/sphinx/config.py b/sphinx/config.py index 5fe3f63f..e7e05169 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -112,6 +112,7 @@ class Config(object): epub_pre_files = ([], 'env'), epub_post_files = ([], 'env'), epub_exclude_files = ([], 'env'), + epub_tocdepth = (3, 'env'), # LaTeX options latex_documents = ([], None), diff --git a/sphinx/quickstart.py b/sphinx/quickstart.py index faea2c2e..a7b80dd5 100644 --- a/sphinx/quickstart.py +++ b/sphinx/quickstart.py @@ -254,6 +254,9 @@ latex_documents = [ # A list of files that should not be packed into the epub file. #epub_exclude_files = [] + +# The depth of the table of contents in toc.ncx. +#epub_tocdepth = 3 ''' INTERSPHINX_CONFIG = ''' -- cgit v1.2.1 From 147c677614f33f2a8b9849445d9672a3639e6a4d Mon Sep 17 00:00:00 2001 From: Roland Meister Date: Sat, 9 Jan 2010 11:23:53 +0100 Subject: Removed the collapse_text method. Docutils astext method already does the right thing. --- sphinx/builders/epub.py | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/sphinx/builders/epub.py b/sphinx/builders/epub.py index 0e4529e5..5bcbb8b7 100644 --- a/sphinx/builders/epub.py +++ b/sphinx/builders/epub.py @@ -156,20 +156,6 @@ class EpubBuilder(StandaloneHTMLBuilder): 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 @@ -183,7 +169,7 @@ class EpubBuilder(StandaloneHTMLBuilder): result.append({ 'level': level, 'refuri': self.esc(doctree['refuri']), - 'text': self.esc(''.join(self.collapse_text(doctree, []))) + 'text': self.esc(doctree.astext()) }) else: for elem in doctree.children: @@ -200,9 +186,7 @@ class EpubBuilder(StandaloneHTMLBuilder): 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], [] - ))), + 'text': self.esc(self.env.titles[self.config.master_doc].astext()) }) for file, text in reversed(self.config.epub_pre_files): self.refnodes.insert(0, { -- cgit v1.2.1 From b1362739adc4aea11168e43087fd5f720735fa8b Mon Sep 17 00:00:00 2001 From: Roland Meister Date: Sat, 9 Jan 2010 11:25:13 +0100 Subject: Fixed comment for toctree depth. --- 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 5bcbb8b7..b3ac4242 100644 --- a/sphinx/builders/epub.py +++ b/sphinx/builders/epub.py @@ -159,7 +159,7 @@ class EpubBuilder(StandaloneHTMLBuilder): 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? + # toctree-l[1-8] on the parent node? if isinstance(doctree, nodes.reference): classes = doctree.parent.attributes['classes'] level = 1 -- cgit v1.2.1 From ac0ac6eabf2c6921a97e862c341e4be8c2e47bd5 Mon Sep 17 00:00:00 2001 From: Roland Meister Date: Sat, 9 Jan 2010 11:33:58 +0100 Subject: Use rsplit instead of split in insert_subnav. --- 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 b3ac4242..5104a895 100644 --- a/sphinx/builders/epub.py +++ b/sphinx/builders/epub.py @@ -321,7 +321,7 @@ class EpubBuilder(StandaloneHTMLBuilder): """Insert nested navpoints for given node. The node and subnav are already rendered to text. """ - nlist = node.split('\n') + nlist = node.rsplit('\n', 1) nlist.insert(-1, subnav) return '\n'.join(nlist) -- cgit v1.2.1 From fb8030a7462139d0178a56f7d30a83067b2e0c0b Mon Sep 17 00:00:00 2001 From: Roland Meister Date: Sat, 9 Jan 2010 12:17:16 +0100 Subject: Fix the dtb:depth meta parameter in toc.ncx. --- sphinx/builders/epub.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sphinx/builders/epub.py b/sphinx/builders/epub.py index 5104a895..77b9603b 100644 --- a/sphinx/builders/epub.py +++ b/sphinx/builders/epub.py @@ -383,6 +383,7 @@ class EpubBuilder(StandaloneHTMLBuilder): navpoints = self.build_navpoints(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') try: f.write(_toc_template % self.toc_metadata(level, navpoints)) -- cgit v1.2.1 From 55f11df7b4c85a111ecb1a582a97c0b7c9231063 Mon Sep 17 00:00:00 2001 From: Roland Meister Date: Sun, 10 Jan 2010 21:14:19 +0100 Subject: Added transform for visible links. --- doc/builders.rst | 5 +++++ sphinx/builders/epub.py | 25 +++++++++++++++++++++++++ sphinx/themes/epub/static/epub.css | 13 +++++++++++++ 3 files changed, 43 insertions(+) diff --git a/doc/builders.rst b/doc/builders.rst index e8dccc43..ff40783a 100644 --- a/doc/builders.rst +++ b/doc/builders.rst @@ -71,6 +71,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 ``_ or ``_. + 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``. .. module:: sphinx.builders.latex diff --git a/sphinx/builders/epub.py b/sphinx/builders/epub.py index 77b9603b..4d042353 100644 --- a/sphinx/builders/epub.py +++ b/sphinx/builders/epub.py @@ -16,6 +16,7 @@ from os import path import zipfile from docutils import nodes +from docutils.transforms import Transform from sphinx.builders.html import StandaloneHTMLBuilder @@ -111,6 +112,29 @@ _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 = ' [%s]' % uri + idx = ref.parent.index(ref) + 1 + link = nodes.inline(uri, uri) + link['classes'].append('link-target') + ref.parent.insert(idx, link) + + # The epub publisher class EpubBuilder(StandaloneHTMLBuilder): @@ -137,6 +161,7 @@ class EpubBuilder(StandaloneHTMLBuilder): # the output files for epub must be .html only self.out_suffix = '.html' self.playorder = 0 + self.app.add_transform(VisibleLinksTransform) def get_theme_config(self): return self.config.epub_theme, {} diff --git a/sphinx/themes/epub/static/epub.css b/sphinx/themes/epub/static/epub.css index f941b79a..c6320c8c 100644 --- a/sphinx/themes/epub/static/epub.css +++ b/sphinx/themes/epub/static/epub.css @@ -420,6 +420,19 @@ div.footer a { 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-family: "LiberationNarrow"; font-style: normal; -- cgit v1.2.1 From a921eb3e59d6a4d7b001109845adcd8e5b0d148b Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 12 Jan 2010 09:18:35 +0000 Subject: Make refdoc node attribute optional. --- sphinx/environment.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/sphinx/environment.py b/sphinx/environment.py index 1b89b8c0..5f0128dc 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -1172,6 +1172,7 @@ class BuildEnvironment: typ = node['reftype'] target = node['reftarget'] + refdoc = node.get('refdoc', fromdocname) try: if typ == 'ref': @@ -1181,7 +1182,7 @@ class BuildEnvironment: docname, labelid = self.anonlabels.get(target, ('','')) sectname = node.astext() if not docname: - self.warn(node['refdoc'], 'undefined label: %s' % + self.warn(refdoc, 'undefined label: %s' % target, node.line) else: # reference to the named label; the final node will @@ -1189,8 +1190,7 @@ class BuildEnvironment: docname, labelid, sectname = self.labels.get(target, ('','','')) if not docname: - self.warn( - node['refdoc'], + self.warn(refdoc, 'undefined label: %s' % target + ' -- if you ' 'don\'t give a link caption the label must ' 'precede a section header.', node.line) @@ -1216,10 +1216,10 @@ class BuildEnvironment: elif typ == 'doc': # directly reference to document by source name; # can be absolute or relative - docname = docname_join(node['refdoc'], target) + docname = docname_join(refdoc, target) if docname not in self.all_docs: - self.warn(node['refdoc'], - 'unknown document: %s' % docname, node.line) + self.warn(refdoc, 'unknown document: %s' % docname, + node.line) newnode = contnode else: if node['refcaption']: @@ -1236,8 +1236,7 @@ class BuildEnvironment: # keywords are referenced by named labels docname, labelid, _ = self.labels.get(target, ('','','')) if not docname: - #self.warn(node['refdoc'], - # 'unknown keyword: %s' % target) + #self.warn(refdoc, 'unknown keyword: %s' % target) newnode = contnode else: newnode = nodes.reference('', '') @@ -1266,12 +1265,11 @@ class BuildEnvironment: ('', '')) if not docname: if typ == 'term': - self.warn(node['refdoc'], + self.warn(refdoc, 'term not in glossary: %s' % target, node.line) elif typ == 'citation': - self.warn(node['refdoc'], - 'citation not found: %s' % target, + self.warn(refdoc, 'citation not found: %s' % target, node.line) newnode = contnode else: -- cgit v1.2.1 From 460d049918defeff87e6bc3d2e110f59c0ae77c1 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 12 Jan 2010 09:35:18 +0000 Subject: #313: fix search on Internet Explorer; add underscore.js to JavaScript libs. --- sphinx/builders/html.py | 3 ++- sphinx/themes/basic/static/doctools.js | 15 ++++++++++----- sphinx/themes/basic/static/searchtools.js | 2 +- sphinx/themes/basic/static/underscore.js | 16 ++++++++++++++++ 4 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 sphinx/themes/basic/static/underscore.js diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index cdabcd65..e6f078b0 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -70,7 +70,8 @@ class StandaloneHTMLBuilder(Builder): embedded = False # for things like HTML help or Qt help: suppresses sidebar # This is a class attribute because it is mutated by Sphinx.add_javascript. - script_files = ['_static/jquery.js', '_static/doctools.js'] + script_files = ['_static/jquery.js', '_static/underscore.js', + '_static/doctools.js'] # Dito for this one. css_files = [] diff --git a/sphinx/themes/basic/static/doctools.js b/sphinx/themes/basic/static/doctools.js index cb6f30e5..c2bd1ac5 100644 --- a/sphinx/themes/basic/static/doctools.js +++ b/sphinx/themes/basic/static/doctools.js @@ -9,6 +9,11 @@ * */ +/** + * select a different prefix for underscore + */ +$u = _.noConflict(); + /** * make the code below compatible with browsers without * an installed firebug like debugger @@ -18,7 +23,7 @@ if (!window.console || !console.firebug) { "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"]; window.console = {}; for (var i = 0; i < names.length; ++i) - window.console[names[i]] = function() {} + window.console[names[i]] = function() {}; } /** @@ -53,7 +58,7 @@ jQuery.getQueryParameters = function(s) { result[key] = [value]; } return result; -} +}; /** * small function to check if an array contains @@ -65,7 +70,7 @@ jQuery.contains = function(arr, item) { return true; } return false; -} +}; /** * highlight a given string on a jquery object by wrapping it in @@ -88,14 +93,14 @@ jQuery.fn.highlightText = function(text, className) { } else if (!jQuery(node).is("button, select, textarea")) { jQuery.each(node.childNodes, function() { - highlight(this) + highlight(this); }); } } return this.each(function() { highlight(this); }); -} +}; /** * Small JavaScript module for the documentation. diff --git a/sphinx/themes/basic/static/searchtools.js b/sphinx/themes/basic/static/searchtools.js index 2e9b5bee..ba91e30c 100644 --- a/sphinx/themes/basic/static/searchtools.js +++ b/sphinx/themes/basic/static/searchtools.js @@ -312,7 +312,7 @@ var Search = { var tmp = query.split(/\s+/); var object = (tmp.length == 1) ? tmp[0].toLowerCase() : null; for (var i = 0; i < tmp.length; i++) { - if (stopwords.indexOf(tmp[i]) != -1 || tmp[i].match(/^\d+$/)) { + if ($u.indexOf(stopwords, tmp[i]) != -1 || tmp[i].match(/^\d+$/)) { // skip this word continue; } diff --git a/sphinx/themes/basic/static/underscore.js b/sphinx/themes/basic/static/underscore.js new file mode 100644 index 00000000..9146e086 --- /dev/null +++ b/sphinx/themes/basic/static/underscore.js @@ -0,0 +1,16 @@ +(function(){var j=this,n=j._,i=function(a){this._wrapped=a},m=typeof StopIteration!=="undefined"?StopIteration:"__break__",b=j._=function(a){return new i(a)};if(typeof exports!=="undefined")exports._=b;var k=Array.prototype.slice,o=Array.prototype.unshift,p=Object.prototype.toString,q=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable;b.VERSION="0.5.5";b.each=function(a,c,d){try{if(a.forEach)a.forEach(c,d);else if(b.isArray(a)||b.isArguments(a))for(var e=0,f=a.length;e=e.computed&&(e={value:f,computed:g})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);var e={computed:Infinity};b.each(a,function(f,g,h){g=c?c.call(d,f,g,h):f;gf?1:0}),"value")};b.sortedIndex=function(a,c,d){d=d||b.identity;for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.zip=function(){for(var a=b.toArray(arguments),c=b.max(b.pluck(a,"length")),d=new Array(c),e=0;e0?f-c:c-f)>=0)return e;e[g++]=f}};b.bind=function(a,c){var d=b.rest(arguments,2);return function(){return a.apply(c||j,d.concat(b.toArray(arguments)))}};b.bindAll=function(a){var c=b.rest(arguments);if(c.length==0)c=b.functions(a);b.each(c,function(d){a[d]=b.bind(a[d],a)}); +return a};b.delay=function(a,c){var d=b.rest(arguments,2);return setTimeout(function(){return a.apply(a,d)},c)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(b.rest(arguments)))};b.wrap=function(a,c){return function(){var d=[a].concat(b.toArray(arguments));return c.apply(c,d)}};b.compose=function(){var a=b.toArray(arguments);return function(){for(var c=b.toArray(arguments),d=a.length-1;d>=0;d--)c=[a[d].apply(this,c)];return c[0]}};b.keys=function(a){if(b.isArray(a))return b.range(0,a.length); +var c=[];for(var d in a)q.call(a,d)&&c.push(d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=function(a){return b.select(b.keys(a),function(c){return b.isFunction(a[c])}).sort()};b.extend=function(a,c){for(var d in c)a[d]=c[d];return a};b.clone=function(a){if(b.isArray(a))return a.slice(0);return b.extend({},a)};b.tap=function(a,c){c(a);return a};b.isEqual=function(a,c){if(a===c)return true;var d=typeof a;if(d!=typeof c)return false;if(a==c)return true;if(!a&&c||a&&!c)return false; +if(a.isEqual)return a.isEqual(c);if(b.isDate(a)&&b.isDate(c))return a.getTime()===c.getTime();if(b.isNaN(a)&&b.isNaN(c))return true;if(b.isRegExp(a)&&b.isRegExp(c))return a.source===c.source&&a.global===c.global&&a.ignoreCase===c.ignoreCase&&a.multiline===c.multiline;if(d!=="object")return false;if(a.length&&a.length!==c.length)return false;d=b.keys(a);var e=b.keys(c);if(d.length!=e.length)return false;for(var f in a)if(!b.isEqual(a[f],c[f]))return false;return true};b.isEmpty=function(a){return b.keys(a).length== +0};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=function(a){return!!(a&&a.concat&&a.unshift)};b.isArguments=function(a){return a&&b.isNumber(a.length)&&!b.isArray(a)&&!r.call(a,"length")};b.isFunction=function(a){return!!(a&&a.constructor&&a.call&&a.apply)};b.isString=function(a){return!!(a===""||a&&a.charCodeAt&&a.substr)};b.isNumber=function(a){return p.call(a)==="[object Number]"};b.isDate=function(a){return!!(a&&a.getTimezoneOffset&&a.setUTCFullYear)};b.isRegExp=function(a){return!!(a&& +a.test&&a.exec&&(a.ignoreCase||a.ignoreCase===false))};b.isNaN=function(a){return b.isNumber(a)&&isNaN(a)};b.isNull=function(a){return a===null};b.isUndefined=function(a){return typeof a=="undefined"};b.noConflict=function(){j._=n;return this};b.identity=function(a){return a};b.breakLoop=function(){throw m;};var s=0;b.uniqueId=function(a){var c=s++;return a?a+c:c};b.template=function(a,c){a=new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+a.replace(/[\r\t\n]/g, +" ").replace(/'(?=[^%]*%>)/g,"\t").split("'").join("\\'").split("\t").join("'").replace(/<%=(.+?)%>/g,"',$1,'").split("<%").join("');").split("%>").join("p.push('")+"');}return p.join('');");return c?a(c):a};b.forEach=b.each;b.foldl=b.inject=b.reduce;b.foldr=b.reduceRight;b.filter=b.select;b.every=b.all;b.some=b.any;b.head=b.first;b.tail=b.rest;b.methods=b.functions;var l=function(a,c){return c?b(a).chain():a};b.each(b.functions(b),function(a){var c=b[a];i.prototype[a]=function(){var d=b.toArray(arguments); +o.call(d,this._wrapped);return l(c.apply(b,d),this._chain)}});b.each(["pop","push","reverse","shift","sort","splice","unshift"],function(a){var c=Array.prototype[a];i.prototype[a]=function(){c.apply(this._wrapped,arguments);return l(this._wrapped,this._chain)}});b.each(["concat","join","slice"],function(a){var c=Array.prototype[a];i.prototype[a]=function(){return l(c.apply(this._wrapped,arguments),this._chain)}});i.prototype.chain=function(){this._chain=true;return this};i.prototype.value=function(){return this._wrapped}})(); -- cgit v1.2.1 From e3e0d55614a20c12b26c3c52aff587d189f8b727 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 12 Jan 2010 09:57:57 +0000 Subject: #314: fix typo in autosummary template. --- sphinx/ext/autosummary/templates/autosummary/module.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/ext/autosummary/templates/autosummary/module.rst b/sphinx/ext/autosummary/templates/autosummary/module.rst index cc76c9e0..c14456ba 100644 --- a/sphinx/ext/autosummary/templates/autosummary/module.rst +++ b/sphinx/ext/autosummary/templates/autosummary/module.rst @@ -30,7 +30,7 @@ .. rubric:: Exceptions .. autosummary:: - {% for item in classes %} + {% for item in exceptions %} {{ item }} {%- endfor %} {% endif %} -- cgit v1.2.1 From 52c246e013366318c682d591d9978304bdf7ff4f Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 12 Jan 2010 21:01:49 +0000 Subject: Update long_description a bit. --- setup.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 981270c8..aedfb8e7 100644 --- a/setup.py +++ b/setup.py @@ -21,19 +21,20 @@ Sphinx uses reStructuredText as its markup language, and many of its strengths come from the power and straightforwardness of reStructuredText and its parsing and translating suite, the Docutils. -Although it is still under constant development, the following features -are already present, work fine and can be seen "in action" in the Python docs: +Among its features are the following: -* Output formats: HTML (including Windows HTML Help), plain text and LaTeX, - for printable PDF versions +* Output formats: HTML (including derivative formats such as HTML Help, Epub + and Qt Help), plain text and LaTeX or direct PDF output using rst2pdf * Extensive cross-references: semantic markup and automatic links for functions, classes, glossary terms and similar pieces of information * Hierarchical structure: easy definition of a document tree, with automatic links to siblings, parents and children * Automatic indices: general index as well as a module index * Code handling: automatic highlighting using the Pygments highlighter +* Flexible HTML output using the Jinja 2 templating engine * Various extensions are available, e.g. for automatic testing of snippets - and inclusion of appropriately formatted docstrings. + and inclusion of appropriately formatted docstrings +* Setuptools integration A development egg can be found `here `_. -- cgit v1.2.1 From 8c2ea23aa5b7f7c057a8ef86d8907b622824309c Mon Sep 17 00:00:00 2001 From: Roland Meister Date: Tue, 12 Jan 2010 22:09:02 +0100 Subject: Moved string constants to module level. --- sphinx/builders/epub.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/sphinx/builders/epub.py b/sphinx/builders/epub.py index 4d042353..44f5eb08 100644 --- a/sphinx/builders/epub.py +++ b/sphinx/builders/epub.py @@ -23,6 +23,9 @@ 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. +# 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! @@ -99,6 +102,10 @@ _spine_template = u'''\ _toctree_template = u'toctree-l%d' +_link_target_template = u' [%(uri)s]' + +_css_link_target_class = u'link-target' + _media_types = { '.html': 'application/xhtml+xml', '.css': 'text/css', @@ -128,11 +135,12 @@ class VisibleLinksTransform(Transform): uri = ref.get('refuri', '') if ( uri.startswith('http:') or uri.startswith('https:') or \ uri.startswith('ftp:') ) and uri not in ref.astext(): - uri = ' [%s]' % uri - idx = ref.parent.index(ref) + 1 - link = nodes.inline(uri, uri) - link['classes'].append('link-target') - ref.parent.insert(idx, link) + 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 -- cgit v1.2.1 From 69f011c1838929ba9e157b18bec22e644ab9356f Mon Sep 17 00:00:00 2001 From: Roland Meister Date: Tue, 12 Jan 2010 22:19:05 +0100 Subject: Code cleanup: removed comment; consistent use of single quote in string constants. --- sphinx/builders/epub.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sphinx/builders/epub.py b/sphinx/builders/epub.py index 44f5eb08..d17593df 100644 --- a/sphinx/builders/epub.py +++ b/sphinx/builders/epub.py @@ -306,11 +306,10 @@ class EpubBuilder(StandaloneHTMLBuilder): 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) + self.warn('unknown mimetype for %s, ignoring' % filename) continue projectfiles.append(_file_template % { 'href': self.esc(filename), -- cgit v1.2.1 From 15a2437628ac1803eeb62ff9033c78e17e3f6110 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 13 Jan 2010 20:45:34 +0100 Subject: Change naming convention in doc_read_data to "domain:key". --- sphinx/directives/other.py | 3 ++- sphinx/domains/python.py | 28 ++++++++++++++-------------- sphinx/domains/std.py | 8 ++++---- sphinx/environment.py | 4 ++-- sphinx/ext/autodoc.py | 20 ++++++++++---------- sphinx/ext/autosummary/__init__.py | 2 +- sphinx/ext/inheritance_diagram.py | 2 +- tests/test_autodoc.py | 24 ++++++++++++------------ 8 files changed, 46 insertions(+), 45 deletions(-) diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py index a347970c..1ba6e3bc 100644 --- a/sphinx/directives/other.py +++ b/sphinx/directives/other.py @@ -215,7 +215,8 @@ class VersionChange(Directive): env = self.state.document.settings.env env.versionchanges.setdefault(node['version'], []).append( (node['type'], env.doc_read_data['docname'], self.lineno, - env.doc_read_data.get('py_module'), + # XXX: python domain specific + env.doc_read_data.get('py:module'), env.doc_read_data.get('object'), node.astext())) return ret diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 46b93898..cec57188 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -67,7 +67,7 @@ class PyObject(ObjectDescription): raise ValueError classname, name, arglist, retann = m.groups() - currclass = self.env.doc_read_data.get('py_class') + currclass = self.env.doc_read_data.get('py:class') if currclass: add_module = False if classname and classname.startswith(currclass): @@ -95,7 +95,7 @@ class PyObject(ObjectDescription): # 'exceptions' module. elif add_module and self.env.config.add_module_names: modname = self.options.get( - 'module', self.env.doc_read_data.get('py_module')) + 'module', self.env.doc_read_data.get('py:module')) if modname and modname != 'exceptions': nodetext = modname + '.' signode += addnodes.desc_addname(nodetext, nodetext) @@ -140,7 +140,7 @@ class PyObject(ObjectDescription): def add_target_and_index(self, name_cls, sig, signode): modname = self.options.get( - 'module', self.env.doc_read_data.get('py_module')) + 'module', self.env.doc_read_data.get('py:module')) fullname = (modname and modname + '.' or '') + name_cls[0] # note target if fullname not in self.state.document.ids: @@ -169,7 +169,7 @@ class PyObject(ObjectDescription): def after_content(self): if self.clsname_set: - self.env.doc_read_data['py_class'] = None + self.env.doc_read_data['py:class'] = None class PyModulelevel(PyObject): @@ -214,7 +214,7 @@ class PyClasslike(PyObject): def before_content(self): PyObject.before_content(self) if self.names: - self.env.doc_read_data['py_class'] = self.names[0][0] + self.env.doc_read_data['py:class'] = self.names[0][0] self.clsname_set = True @@ -292,8 +292,8 @@ class PyClassmember(PyObject): def before_content(self): PyObject.before_content(self) lastname = self.names and self.names[-1][1] - if lastname and not self.env.doc_read_data.get('py_class'): - self.env.doc_read_data['py_class'] = lastname.strip('.') + if lastname and not self.env.doc_read_data.get('py:class'): + self.env.doc_read_data['py:class'] = lastname.strip('.') self.clsname_set = True @@ -317,7 +317,7 @@ class PyModule(Directive): env = self.state.document.settings.env modname = self.arguments[0].strip() noindex = 'noindex' in self.options - env.doc_read_data['py_module'] = modname + env.doc_read_data['py:module'] = modname env.domaindata['py']['modules'][modname] = \ (env.docname, self.options.get('synopsis', ''), self.options.get('platform', ''), 'deprecated' in self.options) @@ -361,16 +361,16 @@ class PyCurrentModule(Directive): env = self.state.document.settings.env modname = self.arguments[0].strip() if modname == 'None': - env.doc_read_data['py_module'] = None + env.doc_read_data['py:module'] = None else: - env.doc_read_data['py_module'] = modname + env.doc_read_data['py:module'] = modname return [] class PyXRefRole(XRefRole): def process_link(self, env, refnode, has_explicit_title, title, target): - refnode['py_module'] = env.doc_read_data.get('py_module') - refnode['py_class'] = env.doc_read_data.get('py_class') + refnode['py:module'] = env.doc_read_data.get('py:module') + refnode['py:class'] = env.doc_read_data.get('py:class') if not has_explicit_title: title = title.lstrip('.') # only has a meaning for the target target = target.lstrip('~') # only has a meaning for the title @@ -497,8 +497,8 @@ class PythonDomain(Domain): return make_refnode(builder, fromdocname, docname, 'module-' + target, contnode, title) else: - modname = node.get('py_module') - clsname = node.get('py_class') + modname = node.get('py:module') + clsname = node.get('py:class') searchorder = node.hasattr('refspecific') and 1 or 0 name, obj = self.find_obj(env, modname, clsname, target, typ, searchorder) diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 38a18198..16de4277 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -146,7 +146,7 @@ class Cmdoption(ObjectDescription): def add_target_and_index(self, name, sig, signode): targetname = name.replace('/', '-') - currprogram = self.env.doc_read_data.get('std_program') + currprogram = self.env.doc_read_data.get('std:program') if currprogram: targetname = '-' + currprogram + targetname targetname = 'cmdoption' + targetname @@ -175,9 +175,9 @@ class Program(Directive): env = self.state.document.settings.env program = ws_re.sub('-', self.arguments[0].strip()) if program == 'None': - env.doc_read_data['std_program'] = None + env.doc_read_data['std:program'] = None else: - env.doc_read_data['std_program'] = program + env.doc_read_data['std:program'] = program return [] @@ -185,7 +185,7 @@ class OptionXRefRole(XRefRole): innernodeclass = addnodes.literal_emphasis def process_link(self, env, refnode, has_explicit_title, title, target): - program = env.doc_read_data.get('std_program') + program = env.doc_read_data.get('std:program') if not has_explicit_title: if ' ' in title and not (title.startswith('/') or title.startswith('-')): diff --git a/sphinx/environment.py b/sphinx/environment.py index 9626924f..2ce3cc98 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -713,12 +713,12 @@ class BuildEnvironment: @property def currmodule(self): """Backwards compatible alias.""" - return self.doc_read_data.get('py_module') + return self.doc_read_data.get('py:module') @property def currclass(self): """Backwards compatible alias.""" - return self.doc_read_data.get('py_class') + return self.doc_read_data.get('py:class') def new_serialno(self, category=''): """Return a serial number, e.g. for index entry targets.""" diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc.py index b74b8f9b..53625dde 100644 --- a/sphinx/ext/autodoc.py +++ b/sphinx/ext/autodoc.py @@ -562,9 +562,9 @@ class Documenter(object): do all members, else those given by *self.options.members*. """ # set current namespace for finding members - self.env.doc_read_data['autodoc_module'] = self.modname + self.env.doc_read_data['autodoc:module'] = self.modname if self.objpath: - self.env.doc_read_data['autodoc_class'] = self.objpath[0] + self.env.doc_read_data['autodoc:class'] = self.objpath[0] want_all = all_members or self.options.inherited_members or \ self.options.members is ALL @@ -605,8 +605,8 @@ class Documenter(object): check_module=members_check_module) # reset current objects - self.env.doc_read_data['autodoc_module'] = None - self.env.doc_read_data['autodoc_class'] = None + self.env.doc_read_data['autodoc:module'] = None + self.env.doc_read_data['autodoc:class'] = None def generate(self, more_content=None, real_modname=None, check_module=False, all_members=False): @@ -764,10 +764,10 @@ class ModuleLevelDocumenter(Documenter): else: # if documenting a toplevel object without explicit module, # it can be contained in another auto directive ... - modname = self.env.doc_read_data.get('autodoc_module') + modname = self.env.doc_read_data.get('autodoc:module') # ... or in the scope of a module directive if not modname: - modname = self.env.doc_read_data.get('py_module') + modname = self.env.doc_read_data.get('py:module') # ... else, it stays None, which means invalid return modname, parents + [base] @@ -786,10 +786,10 @@ class ClassLevelDocumenter(Documenter): # if documenting a class-level object without path, # there must be a current class, either from a parent # auto directive ... - mod_cls = self.env.doc_read_data.get('autodoc_class') + mod_cls = self.env.doc_read_data.get('autodoc:class') # ... or from a class directive if mod_cls is None: - mod_cls = self.env.doc_read_data.get('py_class') + mod_cls = self.env.doc_read_data.get('py:class') # ... if still None, there's no way to know if mod_cls is None: return None, [] @@ -797,9 +797,9 @@ class ClassLevelDocumenter(Documenter): parents = [cls] # if the module name is still missing, get it like above if not modname: - modname = self.env.doc_read_data.get('autodoc_module') + modname = self.env.doc_read_data.get('autodoc:module') if not modname: - modname = self.env.doc_read_data.get('py_module') + modname = self.env.doc_read_data.get('py:module') # ... else, it stays None, which means invalid return modname, parents + [base] diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index 0e3bdf16..99d89587 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -228,7 +228,7 @@ class Autosummary(Directive): env = self.state.document.settings.env prefixes = [''] - currmodule = env.doc_read_data.get('py_module') + currmodule = env.doc_read_data.get('py:module') if currmodule: prefixes.insert(0, currmodule) diff --git a/sphinx/ext/inheritance_diagram.py b/sphinx/ext/inheritance_diagram.py index 236a7933..1fb8b0b2 100644 --- a/sphinx/ext/inheritance_diagram.py +++ b/sphinx/ext/inheritance_diagram.py @@ -284,7 +284,7 @@ class InheritanceDiagram(Directive): # Create a graph starting with the list of classes try: graph = InheritanceGraph(class_names, - env.doc_read_data.get('py_module')) + env.doc_read_data.get('py:module')) except InheritanceException, err: return [node.document.reporter.warning(err.args[0], line=self.lineno)] diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index c594b76a..b4dd2c44 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -97,28 +97,28 @@ def test_parse_name(): verify('function', 'util.raises', ('util', ['raises'], None, None)) verify('function', 'util.raises(exc) -> None', ('util', ['raises'], 'exc', 'None')) - directive.env.doc_read_data['autodoc_module'] = 'util' + directive.env.doc_read_data['autodoc:module'] = 'util' verify('function', 'raises', ('util', ['raises'], None, None)) - del directive.env.doc_read_data['autodoc_module'] - directive.env.doc_read_data['py_module'] = 'util' + del directive.env.doc_read_data['autodoc:module'] + directive.env.doc_read_data['py:module'] = 'util' verify('function', 'raises', ('util', ['raises'], None, None)) verify('class', 'TestApp', ('util', ['TestApp'], None, None)) # for members - directive.env.doc_read_data['py_module'] = 'foo' + directive.env.doc_read_data['py:module'] = 'foo' verify('method', 'util.TestApp.cleanup', ('util', ['TestApp', 'cleanup'], None, None)) - directive.env.doc_read_data['py_module'] = 'util' - directive.env.doc_read_data['py_class'] = 'Foo' - directive.env.doc_read_data['autodoc_class'] = 'TestApp' + directive.env.doc_read_data['py:module'] = 'util' + directive.env.doc_read_data['py:class'] = 'Foo' + directive.env.doc_read_data['autodoc:class'] = 'TestApp' verify('method', 'cleanup', ('util', ['TestApp', 'cleanup'], None, None)) verify('method', 'TestApp.cleanup', ('util', ['TestApp', 'cleanup'], None, None)) # and clean up - del directive.env.doc_read_data['py_module'] - del directive.env.doc_read_data['py_class'] - del directive.env.doc_read_data['autodoc_class'] + del directive.env.doc_read_data['py:module'] + del directive.env.doc_read_data['py:class'] + del directive.env.doc_read_data['autodoc:class'] def test_format_signature(): @@ -353,7 +353,7 @@ def test_generate(): 'function', 'util.foobar', more_content=None) # test auto and given content mixing - directive.env.doc_read_data['py_module'] = 'test_autodoc' + directive.env.doc_read_data['py:module'] = 'test_autodoc' assert_result_contains(' Function.', 'method', 'Class.meth') add_content = ViewList() add_content.append('Content.', '', 0) @@ -437,7 +437,7 @@ def test_generate(): 'attribute', 'test_autodoc.Class.descr') # test generation for C modules (which have no source file) - directive.env.doc_read_data['py_module'] = 'time' + directive.env.doc_read_data['py:module'] = 'time' assert_processes([('function', 'time.asctime')], 'function', 'asctime') assert_processes([('function', 'time.asctime')], 'function', 'asctime') -- cgit v1.2.1 From 74b451ccfed4818eac625ef08a2abb5fd0845c29 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 13 Jan 2010 20:58:44 +0100 Subject: Rename parse_signature() to handle_signature(); allows more to happen. Record module and class name in Python object descriptions. --- sphinx/directives/__init__.py | 8 +++++--- sphinx/domains/c.py | 2 +- sphinx/domains/python.py | 47 ++++++++++++++++++++++++++----------------- sphinx/domains/std.py | 2 +- 4 files changed, 36 insertions(+), 23 deletions(-) diff --git a/sphinx/directives/__init__.py b/sphinx/directives/__init__.py index 5ffce16b..80f62202 100644 --- a/sphinx/directives/__init__.py +++ b/sphinx/directives/__init__.py @@ -190,7 +190,7 @@ class ObjectDescription(Directive): return [strip_backslash_re.sub('', sig.strip()) for sig in self.arguments[0].split('\n')] - def parse_signature(self, sig, signode): + def handle_signature(self, sig, signode): """ Parse the signature *sig* into individual nodes and append them to *signode*. If ValueError is raised, parsing is aborted and the whole @@ -242,8 +242,10 @@ class ObjectDescription(Directive): signode['first'] = False node.append(signode) try: - # name can also be a tuple, e.g. (classname, objname) - name = self.parse_signature(sig, signode) + # name can also be a tuple, e.g. (classname, objname); + # this is strictly domain-specific (i.e. no assumptions may + # be made in this base class) + name = self.handle_signature(sig, signode) except ValueError, err: # signature parsing failed signode.clear() diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index 7a29ff68..fc564409 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -66,7 +66,7 @@ class CObject(ObjectDescription): else: node += tnode - def parse_signature(self, sig, signode): + def handle_signature(self, sig, signode): """Transform a C (or C++) signature into RST nodes.""" # first try the function pointer signature regex, it's more specific m = c_funcptr_sig_re.match(sig) diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index cec57188..e11ddee2 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -53,7 +53,7 @@ class PyObject(ObjectDescription): """ return False - def parse_signature(self, sig, signode): + def handle_signature(self, sig, signode): """ Transform a Python signature into RST nodes. Returns (fully qualified name of the thing, classname if any). @@ -65,32 +65,43 @@ class PyObject(ObjectDescription): m = py_sig_re.match(sig) if m is None: raise ValueError - classname, name, arglist, retann = m.groups() + name_prefix, name, arglist, retann = m.groups() - currclass = self.env.doc_read_data.get('py:class') - if currclass: + # determine module and class name (if applicable), as well as full name + modname = self.options.get( + 'module', self.env.doc_read_data.get('py:module')) + classname = self.env.doc_read_data.get('py:class') + if classname: add_module = False - if classname and classname.startswith(currclass): - fullname = classname + name + if name_prefix and name_prefix.startswith(classname): + fullname = name_prefix + name # class name is given again in the signature - classname = classname[len(currclass):].lstrip('.') - elif classname: + name_prefix = name_prefix[len(classname):].lstrip('.') + elif name_prefix: # class name is given in the signature, but different # (shouldn't happen) - fullname = currclass + '.' + classname + name + fullname = classname + '.' + name_prefix + name else: # class name is not given in the signature - fullname = currclass + '.' + name + fullname = classname + '.' + name else: add_module = True - fullname = classname and classname + name or name + if name_prefix: + classname = name_prefix.rstrip('.') + fullname = name_prefix + name + else: + classname = '' + fullname = name - prefix = self.get_signature_prefix(sig) - if prefix: - signode += addnodes.desc_annotation(prefix, prefix) + signode['module'] = modname + signode['class'] = classname - if classname: - signode += addnodes.desc_addname(classname, classname) + sig_prefix = self.get_signature_prefix(sig) + if sig_prefix: + signode += addnodes.desc_annotation(sig_prefix, sig_prefix) + + if name_prefix: + signode += addnodes.desc_addname(name_prefix, name_prefix) # exceptions are a special case, since they are documented in the # 'exceptions' module. elif add_module and self.env.config.add_module_names: @@ -107,7 +118,7 @@ class PyObject(ObjectDescription): signode += addnodes.desc_parameterlist() if retann: signode += addnodes.desc_returns(retann, retann) - return fullname, classname + return fullname, name_prefix signode += addnodes.desc_parameterlist() stack = [signode[-1]] @@ -130,7 +141,7 @@ class PyObject(ObjectDescription): raise ValueError if retann: signode += addnodes.desc_returns(retann, retann) - return fullname, classname + return fullname, name_prefix def get_index_text(self, modname, name): """ diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 16de4277..2a21f39a 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -35,7 +35,7 @@ class GenericObject(ObjectDescription): indextemplate = '' parse_node = None - def parse_signature(self, sig, signode): + def handle_signature(self, sig, signode): if self.parse_node: name = self.parse_node(self.env, sig, signode) else: -- cgit v1.2.1 From 05706159492a57568e615486e3c6032de521ea01 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 13 Jan 2010 23:42:58 +0100 Subject: Add html-collect-pages event. --- CHANGES | 2 ++ doc/ext/appapi.rst | 10 +++++++++- sphinx/application.py | 1 + sphinx/builders/html.py | 6 +++++- 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 2ddd8c7e..01eea2ab 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,8 @@ Release 1.0 (in development) * Support for docutils 0.4 has been removed. +* Added ``html-collect-pages`` event. + * Added ``tab-width`` option to ``literalinclude`` directive. * The ``html_sidebars`` config value can now contain patterns as diff --git a/doc/ext/appapi.rst b/doc/ext/appapi.rst index 7ed3d3d4..f3a4a061 100644 --- a/doc/ext/appapi.rst +++ b/doc/ext/appapi.rst @@ -360,7 +360,15 @@ registered event handlers. .. versionadded:: 0.5 -.. event:: page-context (app, pagename, templatename, context, doctree) +.. event:: html-collect-pages (app) + + Emitted when the HTML builder is starting to write non-document pages. You + can add pages to write by returning an iterable from this event consisting of + ``(pagename, context, templatename)``. + + .. versionadded:: 1.0 + +.. event:: html-page-context (app, pagename, templatename, context, doctree) Emitted when the HTML builder has created a context dictionary to render a template with -- this can be used to add custom elements to the context. diff --git a/sphinx/application.py b/sphinx/application.py index 99753b73..61c3b05a 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -47,6 +47,7 @@ events = { 'missing-reference': 'env, node, contnode', 'doctree-resolved': 'doctree, docname', 'env-updated': 'env', + 'html-collect-pages': 'builder', 'html-page-context': 'pagename, context, doctree or None', 'build-finished': 'exception', } diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index 2ef2e61f..cb800556 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -381,8 +381,12 @@ class StandaloneHTMLBuilder(Builder): def finish(self): self.info(bold('writing additional files...'), nonl=1) - # the global general index + # pages from extensions + for pagelist in self.app.emit('html-collect-pages'): + for pagename, context, template in pagelist: + self.handle_page(pagename, context, template) + # the global general index if self.config.html_use_index: # the total count of lines for each index letter, used to distribute # the entries into two columns -- cgit v1.2.1 From 3ce06dcb4abda7ec6402ab23c96c54b49e685006 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 13 Jan 2010 23:43:09 +0100 Subject: Note the full name on the signature node. --- sphinx/domains/python.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index e11ddee2..3a1424ba 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -95,6 +95,7 @@ class PyObject(ObjectDescription): signode['module'] = modname signode['class'] = classname + signode['fullname'] = fullname sig_prefix = self.get_signature_prefix(sig) if sig_prefix: -- cgit v1.2.1 From d32394401eae94f0ddb4818d983438ba9fb98efd Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 13 Jan 2010 23:43:43 +0100 Subject: Move getting module source to util. --- sphinx/errors.py | 8 ++++++++ sphinx/pycode/__init__.py | 45 +++++++++++---------------------------------- sphinx/util/__init__.py | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 34 deletions(-) diff --git a/sphinx/errors.py b/sphinx/errors.py index 684101c6..ca70fe4b 100644 --- a/sphinx/errors.py +++ b/sphinx/errors.py @@ -46,3 +46,11 @@ class ExtensionError(SphinxError): class ThemeError(SphinxError): category = 'Theme error' + + +class PycodeError(Exception): + def __str__(self): + res = self.args[0] + if len(self.args) > 1: + res += ' (exception was: %r)' % self.args[1] + return res diff --git a/sphinx/pycode/__init__.py b/sphinx/pycode/__init__.py index 19fd09e1..836cba80 100644 --- a/sphinx/pycode/__init__.py +++ b/sphinx/pycode/__init__.py @@ -14,8 +14,10 @@ import sys from os import path from cStringIO import StringIO +from sphinx.errors import PycodeError from sphinx.pycode import nodes from sphinx.pycode.pgen2 import driver, token, tokenize, parse, literals +from sphinx.util import get_module_source from sphinx.util.docstrings import prepare_docstring, prepare_commentdoc @@ -136,14 +138,6 @@ class AttrDocVisitor(nodes.NodeVisitor): self.collected[namespace, name] = docstring -class PycodeError(Exception): - def __str__(self): - res = self.args[0] - if len(self.args) > 1: - res += ' (exception was: %r)' % self.args[1] - return res - - class ModuleAnalyzer(object): # cache for analyzer objects -- caches both by module and file name cache = {} @@ -173,33 +167,11 @@ class ModuleAnalyzer(object): return entry try: - if modname not in sys.modules: - try: - __import__(modname) - except ImportError, err: - raise PycodeError('error importing %r' % modname, err) - mod = sys.modules[modname] - if hasattr(mod, '__loader__'): - try: - source = mod.__loader__.get_source(modname) - except Exception, err: - raise PycodeError('error getting source for %r' % modname, - err) + type, source = get_module_source(modname) + if type == 'string': obj = cls.for_string(source, modname) - cls.cache['module', modname] = obj - return obj - filename = getattr(mod, '__file__', None) - if filename is None: - raise PycodeError('no source found for module %r' % modname) - filename = path.normpath(path.abspath(filename)) - lfilename = filename.lower() - if lfilename.endswith('.pyo') or lfilename.endswith('.pyc'): - filename = filename[:-1] - elif not lfilename.endswith('.py'): - raise PycodeError('source is not a .py file: %r' % filename) - if not path.isfile(filename): - raise PycodeError('source file is not present: %r' % filename) - obj = cls.for_file(filename, modname) + else: + obj = cls.for_file(source, modname) except PycodeError, err: cls.cache['module', modname] = err raise @@ -214,6 +186,11 @@ class ModuleAnalyzer(object): # file-like object yielding source lines self.source = source + # cache the source code as well + pos = self.source.tell() + self.code = self.source.read() + self.source.seek(pos) + # will be filled by tokenize() self.tokens = None # will be filled by parse() diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index 012ca2ba..7381d5ea 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -508,6 +508,38 @@ def make_refnode(builder, fromdocname, todocname, targetid, child, title=None): return node +def get_module_source(modname): + """Try to find the source code for a module. + + Can return ('file', 'filename') in which case the source is in the given + file, or ('string', 'source') which which case the source is the string. + """ + if modname not in sys.modules: + try: + __import__(modname) + except Exception, err: + raise PycodeError('error importing %r' % modname, err) + mod = sys.modules[modname] + if hasattr(mod, '__loader__'): + try: + source = mod.__loader__.get_source(modname) + except Exception, err: + raise PycodeError('error getting source for %r' % modname, err) + return 'string', source + filename = getattr(mod, '__file__', None) + if filename is None: + raise PycodeError('no source found for module %r' % modname) + filename = path.normpath(path.abspath(filename)) + lfilename = filename.lower() + if lfilename.endswith('.pyo') or lfilename.endswith('.pyc'): + filename = filename[:-1] + elif not lfilename.endswith('.py'): + raise PycodeError('source is not a .py file: %r' % filename) + if not path.isfile(filename): + raise PycodeError('source file is not present: %r' % filename) + return 'file', filename + + try: any = any except NameError: -- cgit v1.2.1 From af1328ec3da2e64b226dd463c428046e1ab910e2 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 13 Jan 2010 23:53:01 +0100 Subject: Add the "viewcode" extension. --- CHANGES | 2 + doc/conf.py | 2 +- doc/ext/viewcode.rst | 19 ++++ doc/extensions.rst | 1 + sphinx/ext/viewcode.py | 162 +++++++++++++++++++++++++++++++++++ sphinx/themes/basic/static/basic.css | 16 ++++ 6 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 doc/ext/viewcode.rst create mode 100644 sphinx/ext/viewcode.py diff --git a/CHANGES b/CHANGES index 01eea2ab..8b605fa3 100644 --- a/CHANGES +++ b/CHANGES @@ -7,6 +7,8 @@ Release 1.0 (in development) * Support for docutils 0.4 has been removed. +* Added the ``viewcode`` extension. + * Added ``html-collect-pages`` event. * Added ``tab-width`` option to ``literalinclude`` directive. diff --git a/doc/conf.py b/doc/conf.py index afa33068..3ef3ff71 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -7,7 +7,7 @@ import sys, os, re # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.addons.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', - 'sphinx.ext.autosummary'] + 'sphinx.ext.autosummary', 'sphinx.ext.viewcode'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] diff --git a/doc/ext/viewcode.rst b/doc/ext/viewcode.rst new file mode 100644 index 00000000..ba6c8f86 --- /dev/null +++ b/doc/ext/viewcode.rst @@ -0,0 +1,19 @@ +:mod:`sphinx.ext.viewcode` -- Add links to highlighted source code +================================================================== + +.. module:: sphinx.ext.viewcode + :synopsis: Add links to a highlighted version of the source code. +.. moduleauthor:: Georg Brandl + +.. versionadded:: 1.0 + + +This extension looks at your Python object descriptions (``.. class::``, +``.. function::`` etc.) and tries to find the source files where the objects are +contained. When found, a separate HTML page will be output for each module with +a highlighted version of the source code, and a link will be added to all object +descriptions that leads to the source code of the described object. A link back +from the source to the description will also be inserted. + +There are currently no configuration values for this extension; you just need to +add ``'sphinx.ext.viewcode'`` to your :confval:`extensions` value for it to work. diff --git a/doc/extensions.rst b/doc/extensions.rst index 29c40ccd..38927520 100644 --- a/doc/extensions.rst +++ b/doc/extensions.rst @@ -52,6 +52,7 @@ These extensions are built in and can be activated by respective entries in the ext/coverage ext/todo ext/extlinks + ext/viewcode Third-party extensions diff --git a/sphinx/ext/viewcode.py b/sphinx/ext/viewcode.py new file mode 100644 index 00000000..6be03a46 --- /dev/null +++ b/sphinx/ext/viewcode.py @@ -0,0 +1,162 @@ +# -*- coding: utf-8 -*- +""" + sphinx.ext.viewcode + ~~~~~~~~~~~~~~~~~~~ + + Add links to module code in Python object descriptions. + + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from docutils import nodes + +from sphinx import addnodes +from sphinx.util import make_refnode +from sphinx.pycode import ModuleAnalyzer + + +def doctree_read(app, doctree): + env = app.builder.env + if not hasattr(env, '_viewcode_modules'): + env._viewcode_modules = {} + + def has_tag(modname, fullname, docname): + entry = env._viewcode_modules.get(modname, None) + if entry is None: + try: + analyzer = ModuleAnalyzer.for_module(modname) + except Exception: + env._viewcode_modules[modname] = False + return + analyzer.find_tags() + entry = analyzer.code, analyzer.tags, {} + env._viewcode_modules[modname] = entry + elif entry is False: + return + code, tags, used = entry + if fullname in tags: + used[fullname] = docname + return True + + for objnode in doctree.traverse(addnodes.desc): + if objnode['domain'] != 'py': + continue + names = set() + for signode in objnode.traverse(addnodes.desc_signature): + modname = signode['module'] + if not modname: + continue + fullname = signode['fullname'] + if not has_tag(modname, fullname, env.docname): + continue + if fullname in names: + # only one link per name, please + continue + names.add(fullname) + pagename = '_modules/' + modname.replace('.', '/') + onlynode = addnodes.only(expr='html') + onlynode += addnodes.pending_xref( + '', reftype='viewcode', refdomain='std', refexplicit=False, + reftarget=pagename, refid=fullname, + refdoc=env.docname) + onlynode[0] += nodes.inline('', _('[source]'), + classes=['viewcode-link']) + signode += onlynode + + +def missing_reference(app, env, node, contnode): + # resolve our "viewcode" reference nodes -- they need special treatment + if node['reftype'] == 'viewcode': + return make_refnode(app.builder, node['refdoc'], node['reftarget'], + node['refid'], contnode) + + +def collect_pages(app): + env = app.builder.env + if not hasattr(env, '_viewcode_modules'): + return + highlighter = app.builder.highlighter + urito = app.builder.get_relative_uri + + modnames = set(env._viewcode_modules) + + for modname, (code, tags, used) in env._viewcode_modules.iteritems(): + # construct a page name for the highlighted source + pagename = '_modules/' + modname.replace('.', '/') + # highlight the source using the builder's highlighter + highlighted = highlighter.highlight_block(code, 'python', False) + # split the code into lines + lines = highlighted.splitlines() + # split off wrap markup from the first line of the actual code + before, after = lines[0].split('
')
+        lines[0:1] = [before + '
', after]
+        # nothing to do for the last line; it always starts with 
anyway + # now that we have code lines (starting at index 1), insert anchors for + # the collected tags (HACK: this only works if the tag boundaries are + # properly nested!) + for name, docname in used.iteritems(): + type, start, end = tags[name] + backlink = urito(pagename, docname) + '#' + modname + '.' + name + lines[start] = ( + '
%s' % (name, backlink, _('[docs]')) + lines[start]) + lines[end - 1] += '
' + # try to find parents (for submodules) + parents = [] + parent = modname + while '.' in parent: + parent = parent.rsplit('.', 1)[0] + if parent in modnames: + parents.append({ + 'link': urito(pagename, '_modules/' + + parent.replace('.', '/')), + 'title': parent}) + parents.append({'link': urito(pagename, '_modules/index'), + 'title': _('Module code')}) + parents.reverse() + # putting it all together + context = { + 'parents': parents, + 'title': modname, + 'body': _('

Source code for %s

') % modname + '\n'.join(lines) + } + app.builder.info(' '+pagename, nonl=1) + yield (pagename, context, 'page.html') + + if not modnames: + return + + app.builder.info(' _modules/index') + html = ['\n'] + # the stack logic is needed for using nested lists for submodules + stack = [''] + for modname in sorted(modnames): + if modname.startswith(stack[-1]): + stack.append(modname + '.') + html.append('
    ') + else: + stack.pop() + while not modname.startswith(stack[-1]): + stack.pop() + html.append('
') + stack.append(modname + '.') + html.append('
  • %s
  • \n' % ( + urito('_modules/index', '_modules/' + modname.replace('.', '/')), + modname)) + html.append('' * (len(stack) - 1)) + context = { + 'title': _('Overview: module code'), + 'body': _('

    All modules for which code is available

    ') + \ + ''.join(html), + } + + yield ('_modules/index', context, 'page.html') + + +def setup(app): + app.connect('doctree-read', doctree_read) + app.connect('html-collect-pages', collect_pages) + app.connect('missing-reference', missing_reference) + #app.add_config_value('viewcode_include_modules', [], 'env') + #app.add_config_value('viewcode_exclude_modules', [], 'env') diff --git a/sphinx/themes/basic/static/basic.css b/sphinx/themes/basic/static/basic.css index bd0a8544..f04023bf 100644 --- a/sphinx/themes/basic/static/basic.css +++ b/sphinx/themes/basic/static/basic.css @@ -368,6 +368,22 @@ dl.glossary dt { margin-left: 1.5em; } +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + background-color: #f4debf; + border: 1px solid #D5BB73; + margin: -1px -10px; + padding: 0 10px; +} + /* -- code displays --------------------------------------------------------- */ pre { -- cgit v1.2.1 From 0d82900af94042533e516d07ad462b00b6c559de Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 13 Jan 2010 23:35:05 +0000 Subject: Fix the test suite. --- tests/test_autodoc.py | 2 +- tests/test_metadata.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index c594b76a..2d311cac 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -14,7 +14,7 @@ from util import * from docutils.statemachine import ViewList -from sphinx.ext.autodoc import AutoDirective, Documenter, add_documenter, \ +from sphinx.ext.autodoc import AutoDirective, add_documenter, \ ModuleLevelDocumenter, FunctionDocumenter, cut_lines, between, ALL diff --git a/tests/test_metadata.py b/tests/test_metadata.py index a6b0e7f5..1606deb4 100644 --- a/tests/test_metadata.py +++ b/tests/test_metadata.py @@ -25,9 +25,7 @@ def setup_module(): # Is there a better way of generating this doctree than manually iterating? global app, env app = TestApp(srcdir='(temp)') - env = BuildEnvironment(app.srcdir, app.doctreedir, app.config) - # Huh. Why do I need to do this? - env.set_warnfunc(lambda *args: warnings.append(args)) + env = app.env msg, num, it = env.update(app.config, app.srcdir, app.doctreedir, app) for docname in it: pass -- cgit v1.2.1 From 7ffc9a610f8fd37224cf611a617504c021d0e43f Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 13 Jan 2010 23:38:44 +0000 Subject: Cleanup make check. --- sphinx/ext/viewcode.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sphinx/ext/viewcode.py b/sphinx/ext/viewcode.py index 6be03a46..7f9df2da 100644 --- a/sphinx/ext/viewcode.py +++ b/sphinx/ext/viewcode.py @@ -100,7 +100,8 @@ def collect_pages(app): backlink = urito(pagename, docname) + '#' + modname + '.' + name lines[start] = ( '
    %s' % (name, backlink, _('[docs]')) + lines[start]) + 'href="%s">%s' % (name, backlink, _('[docs]')) + + lines[start]) lines[end - 1] += '
    ' # try to find parents (for submodules) parents = [] @@ -119,14 +120,15 @@ def collect_pages(app): context = { 'parents': parents, 'title': modname, - 'body': _('

    Source code for %s

    ') % modname + '\n'.join(lines) + 'body': _('

    Source code for %s

    ') % modname + \ + '\n'.join(lines) } app.builder.info(' '+pagename, nonl=1) yield (pagename, context, 'page.html') if not modnames: return - + app.builder.info(' _modules/index') html = ['\n'] # the stack logic is needed for using nested lists for submodules -- cgit v1.2.1 From 0403e0c5c144f170c35625c1b03d02b020eabf6d Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 13 Jan 2010 23:44:31 +0000 Subject: Fix test suite errors. --- sphinx/domains/std.py | 2 +- sphinx/util/__init__.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 49394172..4afbcdbf 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -127,7 +127,7 @@ class Cmdoption(ObjectDescription): Description of a command-line option (.. cmdoption). """ - def parse_signature(self, sig, signode): + def handle_signature(self, sig, signode): """Transform an option description into RST nodes.""" count = 0 firstname = '' diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index 7381d5ea..bd263545 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -29,6 +29,7 @@ from docutils.utils import relative_path import jinja2 import sphinx +from sphinx.errors import PycodeError # Errnos that we need. EEXIST = getattr(errno, 'EEXIST', 0) -- cgit v1.2.1 From 9589493115cdd7d2fd3efa5d19600441c9472cc1 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 14 Jan 2010 11:18:28 +0100 Subject: Fix docs of emit_firstresult(). --- doc/ext/appapi.rst | 3 +-- sphinx/application.py | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/doc/ext/appapi.rst b/doc/ext/appapi.rst index f3a4a061..77ced4ef 100644 --- a/doc/ext/appapi.rst +++ b/doc/ext/appapi.rst @@ -269,8 +269,7 @@ the following public API: .. method:: Sphinx.emit_firstresult(event, *arguments) Emit *event* and pass *arguments* to the callback functions. Return the - result of the first callback that doesn't return ``None`` (and don't call - the rest of the callbacks). + result of the first callback that doesn't return ``None``. .. versionadded:: 0.5 diff --git a/sphinx/application.py b/sphinx/application.py index 61c3b05a..758dd6ad 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -274,11 +274,11 @@ class Sphinx(object): event.pop(listener_id, None) def emit(self, event, *args): - result = [] + results = [] if event in self._listeners: for _, callback in self._listeners[event].iteritems(): - result.append(callback(self, *args)) - return result + results.append(callback(self, *args)) + return results def emit_firstresult(self, event, *args): for result in self.emit(event, *args): -- cgit v1.2.1 From c38856dc33f89843ff1ae490ab590b1dae9407aa Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 14 Jan 2010 19:53:00 +0100 Subject: Fix duplication of source links. --- sphinx/ext/viewcode.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sphinx/ext/viewcode.py b/sphinx/ext/viewcode.py index 7f9df2da..5aa87069 100644 --- a/sphinx/ext/viewcode.py +++ b/sphinx/ext/viewcode.py @@ -43,7 +43,9 @@ def doctree_read(app, doctree): if objnode['domain'] != 'py': continue names = set() - for signode in objnode.traverse(addnodes.desc_signature): + for signode in objnode: + if not isinstance(signode, addnodes.desc_signature): + continue modname = signode['module'] if not modname: continue -- cgit v1.2.1 From 05b64ba9052add8022826432890b7f44ba28777d Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 14 Jan 2010 20:42:10 +0100 Subject: Fix typo. --- doc/builders.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/builders.rst b/doc/builders.rst index e8dccc43..fe326c68 100644 --- a/doc/builders.rst +++ b/doc/builders.rst @@ -117,7 +117,7 @@ Note that a direct PDF builder using ReportLab is available in `rst2pdf (`pickle`, `simplejson`, `phpserialize`, and others) to dump the generated HTML documentation. The pickle builder is a subclass of it. - A concreate subclass of this builder serializing to the `PHP serialization`_ + A concrete subclass of this builder serializing to the `PHP serialization`_ format could look like this:: import phpserialize -- cgit v1.2.1 From 2fff5a30f797d70ed11b649d65ee51c9e9b31dc6 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 14 Jan 2010 21:33:30 +0100 Subject: Fix style inconsistency. --- doc/_templates/layout.html | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/_templates/layout.html b/doc/_templates/layout.html index 60d217df..6e609e1a 100644 --- a/doc/_templates/layout.html +++ b/doc/_templates/layout.html @@ -12,7 +12,8 @@ {% block rootrellink %}
  • Sphinx home | 
  • -
  • Documentation»
  • +
  • Documentation + »
  • {% endblock %} {% block header %} -- cgit v1.2.1 From 125ba24fced644c2d5189f1265a714b22ab1abfa Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 14 Jan 2010 21:33:54 +0100 Subject: Use

    , otherwise themes will break. --- sphinx/ext/viewcode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sphinx/ext/viewcode.py b/sphinx/ext/viewcode.py index 5aa87069..e1270875 100644 --- a/sphinx/ext/viewcode.py +++ b/sphinx/ext/viewcode.py @@ -122,7 +122,7 @@ def collect_pages(app): context = { 'parents': parents, 'title': modname, - 'body': _('

    Source code for %s

    ') % modname + \ + 'body': _('

    Source code for %s

    ') % modname + \ '\n'.join(lines) } app.builder.info(' '+pagename, nonl=1) @@ -151,7 +151,7 @@ def collect_pages(app): html.append('' * (len(stack) - 1)) context = { 'title': _('Overview: module code'), - 'body': _('

    All modules for which code is available

    ') + \ + 'body': _('

    All modules for which code is available

    ') + \ ''.join(html), } -- cgit v1.2.1 From 2ca01d8ee92ea953aa5a724dbfd903829db82fbd Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 14 Jan 2010 21:35:08 +0100 Subject: Adapt viewcode styles to theme. --- sphinx/themes/agogo/static/agogo.css_t | 18 ++++++++++++++ sphinx/themes/basic/static/basic.css | 30 +++++++++++------------- sphinx/themes/default/static/default.css_t | 10 ++++++++ sphinx/themes/haiku/static/haiku.css_t | 12 ++++++++++ sphinx/themes/nature/static/nature.css_t | 10 ++++++++ sphinx/themes/scrolls/static/scrolls.css_t | 17 ++++++++++++++ sphinx/themes/sphinxdoc/static/sphinxdoc.css | 11 +++++++++ sphinx/themes/traditional/static/traditional.css | 17 ++++++++++++++ 8 files changed, 109 insertions(+), 16 deletions(-) diff --git a/sphinx/themes/agogo/static/agogo.css_t b/sphinx/themes/agogo/static/agogo.css_t index 4dd03d92..993053d1 100644 --- a/sphinx/themes/agogo/static/agogo.css_t +++ b/sphinx/themes/agogo/static/agogo.css_t @@ -357,3 +357,21 @@ img.toggler { cursor: pointer; } +/* -- viewcode extension ---------------------------------------------------- */ + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family:: {{ theme_bodyfont }}; +} + +div.viewcode-block:target { + margin: -1px -3px; + padding: 0 3px; + background-color: #f4debf; + border-top: 1px solid #ac9; + border-bottom: 1px solid #ac9; +} diff --git a/sphinx/themes/basic/static/basic.css b/sphinx/themes/basic/static/basic.css index f04023bf..465b526e 100644 --- a/sphinx/themes/basic/static/basic.css +++ b/sphinx/themes/basic/static/basic.css @@ -368,22 +368,6 @@ dl.glossary dt { margin-left: 1.5em; } -.viewcode-link { - float: right; -} - -.viewcode-back { - float: right; - font-family: sans-serif; -} - -div.viewcode-block:target { - background-color: #f4debf; - border: 1px solid #D5BB73; - margin: -1px -10px; - padding: 0 10px; -} - /* -- code displays --------------------------------------------------------- */ pre { @@ -424,6 +408,20 @@ h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { background-color: transparent; } +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + /* -- math display ---------------------------------------------------------- */ img.math { diff --git a/sphinx/themes/default/static/default.css_t b/sphinx/themes/default/static/default.css_t index 04562d96..fb9fbe89 100644 --- a/sphinx/themes/default/static/default.css_t +++ b/sphinx/themes/default/static/default.css_t @@ -264,3 +264,13 @@ th { .note tt { background: #d6d6d6; } + +.viewcode-back { + font-family: {{ theme_bodyfont }}; +} + +div.viewcode-block:target { + background-color: #f4debf; + border-top: 1px solid #ac9; + border-bottom: 1px solid #ac9; +} diff --git a/sphinx/themes/haiku/static/haiku.css_t b/sphinx/themes/haiku/static/haiku.css_t index 7adfb0f3..93007dfb 100644 --- a/sphinx/themes/haiku/static/haiku.css_t +++ b/sphinx/themes/haiku/static/haiku.css_t @@ -357,3 +357,15 @@ hr { background: #FFF; } } + +.viewcode-back { + font-family: "DejaVu Sans", Arial, Helvetica, sans-serif; +} + +div.viewcode-block:target { + background-color: #f4debf; + border-top: 1px solid #ac9; + border-bottom: 1px solid #ac9; + margin: -1px -12px; + padding: 0 12px; +} diff --git a/sphinx/themes/nature/static/nature.css_t b/sphinx/themes/nature/static/nature.css_t index 5991e349..d0aa912b 100644 --- a/sphinx/themes/nature/static/nature.css_t +++ b/sphinx/themes/nature/static/nature.css_t @@ -233,3 +233,13 @@ tt { font-size: 1.1em; font-family: monospace; } + +.viewcode-back { + font-family: Arial, sans-serif; +} + +div.viewcode-block:target { + background-color: #f4debf; + border-top: 1px solid #ac9; + border-bottom: 1px solid #ac9; +} diff --git a/sphinx/themes/scrolls/static/scrolls.css_t b/sphinx/themes/scrolls/static/scrolls.css_t index 41a725a6..589f91ab 100644 --- a/sphinx/themes/scrolls/static/scrolls.css_t +++ b/sphinx/themes/scrolls/static/scrolls.css_t @@ -412,3 +412,20 @@ span.highlight { margin-bottom: 0; margin-left: 1.5em; } + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: 'Georgia', serif; +} + +div.viewcode-block:target { + background-color: #f4debf; + border-top: 1px solid #ac9; + border-bottom: 1px solid #ac9; + margin: -1px -5px; + padding: 0 5px; +} diff --git a/sphinx/themes/sphinxdoc/static/sphinxdoc.css b/sphinx/themes/sphinxdoc/static/sphinxdoc.css index 3f1e84e5..c7e6e335 100644 --- a/sphinx/themes/sphinxdoc/static/sphinxdoc.css +++ b/sphinx/themes/sphinxdoc/static/sphinxdoc.css @@ -326,3 +326,14 @@ div.versioninfo { line-height: 1.3em; font-size: 0.9em; } + +.viewcode-back { + font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', + 'Verdana', sans-serif; +} + +div.viewcode-block:target { + background-color: #f4debf; + border-top: 1px solid #ac9; + border-bottom: 1px solid #ac9; +} diff --git a/sphinx/themes/traditional/static/traditional.css b/sphinx/themes/traditional/static/traditional.css index 022e55ae..c9980fa5 100644 --- a/sphinx/themes/traditional/static/traditional.css +++ b/sphinx/themes/traditional/static/traditional.css @@ -717,3 +717,20 @@ form.comment textarea { display: none; } } + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: serif; +} + +div.viewcode-block:target { + background-color: #f4debf; + border-top: 1px solid #ac9; + border-bottom: 1px solid #ac9; + margin: -1px -10px; + padding: 0 10px; +} -- cgit v1.2.1 From ba163db555f11be32904acf421bae901f24a5cff Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 14 Jan 2010 21:49:06 +0100 Subject: Update to jQuery 1.4; fix a few incompatibilities. --- sphinx/themes/basic/static/doctools.js | 10 +- sphinx/themes/basic/static/jquery.js | 177 +++++++++++++++++++++++++----- sphinx/themes/basic/static/searchtools.js | 6 +- 3 files changed, 156 insertions(+), 37 deletions(-) diff --git a/sphinx/themes/basic/static/doctools.js b/sphinx/themes/basic/static/doctools.js index 31c780e8..bf3375ff 100644 --- a/sphinx/themes/basic/static/doctools.js +++ b/sphinx/themes/basic/static/doctools.js @@ -12,15 +12,15 @@ /** * make the code below compatible with browsers without * an installed firebug like debugger - */ if (!window.console || !console.firebug) { var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"]; window.console = {}; for (var i = 0; i < names.length; ++i) - window.console[names[i]] = function() {} + window.console[names[i]] = function() {}; } + */ /** * small helper function to urldecode strings @@ -77,7 +77,7 @@ jQuery.fn.highlightText = function(text, className) { if (node.nodeType == 3) { var val = node.nodeValue; var pos = val.toLowerCase().indexOf(text); - if (pos >= 0 && !jQuery.className.has(node.parentNode, className)) { + if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { var span = document.createElement("span"); span.className = className; span.appendChild(document.createTextNode(val.substr(pos, text.length))); @@ -89,7 +89,7 @@ jQuery.fn.highlightText = function(text, className) { } else if (!jQuery(node).is("button, select, textarea")) { jQuery.each(node.childNodes, function() { - highlight(this) + highlight(this); }); } } @@ -193,7 +193,7 @@ var Documentation = { var togglers = $('img.toggler').click(function() { var src = $(this).attr('src'); var idnum = $(this).attr('id').substr(7); - console.log($('tr.cg-' + idnum).toggle()); + $('tr.cg-' + idnum).toggle(); if (src.substr(-9) == 'minus.png') $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); else diff --git a/sphinx/themes/basic/static/jquery.js b/sphinx/themes/basic/static/jquery.js index 82b98e1d..5c70e4c5 100644 --- a/sphinx/themes/basic/static/jquery.js +++ b/sphinx/themes/basic/static/jquery.js @@ -1,32 +1,151 @@ -/* - * jQuery 1.2.6 - New Wave Javascript +/*! + * jQuery JavaScript Library v1.4 + * http://jquery.com/ * - * Copyright (c) 2008 John Resig (jquery.com) - * Dual licensed under the MIT (MIT-LICENSE.txt) - * and GPL (GPL-LICENSE.txt) licenses. + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://docs.jquery.com/License * - * $Date: 2008-05-24 14:22:17 -0400 (Sat, 24 May 2008) $ - * $Rev: 5685 $ + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2010, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Wed Jan 13 15:23:05 2010 -0500 */ -(function(){var _jQuery=window.jQuery,_$=window.$;var jQuery=window.jQuery=window.$=function(selector,context){return new jQuery.fn.init(selector,context);};var quickExpr=/^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/,isSimple=/^.[^:#\[\.]*$/,undefined;jQuery.fn=jQuery.prototype={init:function(selector,context){selector=selector||document;if(selector.nodeType){this[0]=selector;this.length=1;return this;}if(typeof selector=="string"){var match=quickExpr.exec(selector);if(match&&(match[1]||!context)){if(match[1])selector=jQuery.clean([match[1]],context);else{var elem=document.getElementById(match[3]);if(elem){if(elem.id!=match[3])return jQuery().find(selector);return jQuery(elem);}selector=[];}}else -return jQuery(context).find(selector);}else if(jQuery.isFunction(selector))return jQuery(document)[jQuery.fn.ready?"ready":"load"](selector);return this.setArray(jQuery.makeArray(selector));},jquery:"1.2.6",size:function(){return this.length;},length:0,get:function(num){return num==undefined?jQuery.makeArray(this):this[num];},pushStack:function(elems){var ret=jQuery(elems);ret.prevObject=this;return ret;},setArray:function(elems){this.length=0;Array.prototype.push.apply(this,elems);return this;},each:function(callback,args){return jQuery.each(this,callback,args);},index:function(elem){var ret=-1;return jQuery.inArray(elem&&elem.jquery?elem[0]:elem,this);},attr:function(name,value,type){var options=name;if(name.constructor==String)if(value===undefined)return this[0]&&jQuery[type||"attr"](this[0],name);else{options={};options[name]=value;}return this.each(function(i){for(name in options)jQuery.attr(type?this.style:this,name,jQuery.prop(this,options[name],type,i,name));});},css:function(key,value){if((key=='width'||key=='height')&&parseFloat(value)<0)value=undefined;return this.attr(key,value,"curCSS");},text:function(text){if(typeof text!="object"&&text!=null)return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(text));var ret="";jQuery.each(text||this,function(){jQuery.each(this.childNodes,function(){if(this.nodeType!=8)ret+=this.nodeType!=1?this.nodeValue:jQuery.fn.text([this]);});});return ret;},wrapAll:function(html){if(this[0])jQuery(html,this[0].ownerDocument).clone().insertBefore(this[0]).map(function(){var elem=this;while(elem.firstChild)elem=elem.firstChild;return elem;}).append(this);return this;},wrapInner:function(html){return this.each(function(){jQuery(this).contents().wrapAll(html);});},wrap:function(html){return this.each(function(){jQuery(this).wrapAll(html);});},append:function(){return this.domManip(arguments,true,false,function(elem){if(this.nodeType==1)this.appendChild(elem);});},prepend:function(){return this.domManip(arguments,true,true,function(elem){if(this.nodeType==1)this.insertBefore(elem,this.firstChild);});},before:function(){return this.domManip(arguments,false,false,function(elem){this.parentNode.insertBefore(elem,this);});},after:function(){return this.domManip(arguments,false,true,function(elem){this.parentNode.insertBefore(elem,this.nextSibling);});},end:function(){return this.prevObject||jQuery([]);},find:function(selector){var elems=jQuery.map(this,function(elem){return jQuery.find(selector,elem);});return this.pushStack(/[^+>] [^+>]/.test(selector)||selector.indexOf("..")>-1?jQuery.unique(elems):elems);},clone:function(events){var ret=this.map(function(){if(jQuery.browser.msie&&!jQuery.isXMLDoc(this)){var clone=this.cloneNode(true),container=document.createElement("div");container.appendChild(clone);return jQuery.clean([container.innerHTML])[0];}else -return this.cloneNode(true);});var clone=ret.find("*").andSelf().each(function(){if(this[expando]!=undefined)this[expando]=null;});if(events===true)this.find("*").andSelf().each(function(i){if(this.nodeType==3)return;var events=jQuery.data(this,"events");for(var type in events)for(var handler in events[type])jQuery.event.add(clone[i],type,events[type][handler],events[type][handler].data);});return ret;},filter:function(selector){return this.pushStack(jQuery.isFunction(selector)&&jQuery.grep(this,function(elem,i){return selector.call(elem,i);})||jQuery.multiFilter(selector,this));},not:function(selector){if(selector.constructor==String)if(isSimple.test(selector))return this.pushStack(jQuery.multiFilter(selector,this,true));else -selector=jQuery.multiFilter(selector,this);var isArrayLike=selector.length&&selector[selector.length-1]!==undefined&&!selector.nodeType;return this.filter(function(){return isArrayLike?jQuery.inArray(this,selector)<0:this!=selector;});},add:function(selector){return this.pushStack(jQuery.unique(jQuery.merge(this.get(),typeof selector=='string'?jQuery(selector):jQuery.makeArray(selector))));},is:function(selector){return!!selector&&jQuery.multiFilter(selector,this).length>0;},hasClass:function(selector){return this.is("."+selector);},val:function(value){if(value==undefined){if(this.length){var elem=this[0];if(jQuery.nodeName(elem,"select")){var index=elem.selectedIndex,values=[],options=elem.options,one=elem.type=="select-one";if(index<0)return null;for(var i=one?index:0,max=one?index+1:options.length;i=0||jQuery.inArray(this.name,value)>=0);else if(jQuery.nodeName(this,"select")){var values=jQuery.makeArray(value);jQuery("option",this).each(function(){this.selected=(jQuery.inArray(this.value,values)>=0||jQuery.inArray(this.text,values)>=0);});if(!values.length)this.selectedIndex=-1;}else -this.value=value;});},html:function(value){return value==undefined?(this[0]?this[0].innerHTML:null):this.empty().append(value);},replaceWith:function(value){return this.after(value).remove();},eq:function(i){return this.slice(i,i+1);},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments));},map:function(callback){return this.pushStack(jQuery.map(this,function(elem,i){return callback.call(elem,i,elem);}));},andSelf:function(){return this.add(this.prevObject);},data:function(key,value){var parts=key.split(".");parts[1]=parts[1]?"."+parts[1]:"";if(value===undefined){var data=this.triggerHandler("getData"+parts[1]+"!",[parts[0]]);if(data===undefined&&this.length)data=jQuery.data(this[0],key);return data===undefined&&parts[1]?this.data(parts[0]):data;}else -return this.trigger("setData"+parts[1]+"!",[parts[0],value]).each(function(){jQuery.data(this,key,value);});},removeData:function(key){return this.each(function(){jQuery.removeData(this,key);});},domManip:function(args,table,reverse,callback){var clone=this.length>1,elems;return this.each(function(){if(!elems){elems=jQuery.clean(args,this.ownerDocument);if(reverse)elems.reverse();}var obj=this;if(table&&jQuery.nodeName(this,"table")&&jQuery.nodeName(elems[0],"tr"))obj=this.getElementsByTagName("tbody")[0]||this.appendChild(this.ownerDocument.createElement("tbody"));var scripts=jQuery([]);jQuery.each(elems,function(){var elem=clone?jQuery(this).clone(true)[0]:this;if(jQuery.nodeName(elem,"script"))scripts=scripts.add(elem);else{if(elem.nodeType==1)scripts=scripts.add(jQuery("script",elem).remove());callback.call(obj,elem);}});scripts.each(evalScript);});}};jQuery.fn.init.prototype=jQuery.fn;function evalScript(i,elem){if(elem.src)jQuery.ajax({url:elem.src,async:false,dataType:"script"});else -jQuery.globalEval(elem.text||elem.textContent||elem.innerHTML||"");if(elem.parentNode)elem.parentNode.removeChild(elem);}function now(){return+new Date;}jQuery.extend=jQuery.fn.extend=function(){var target=arguments[0]||{},i=1,length=arguments.length,deep=false,options;if(target.constructor==Boolean){deep=target;target=arguments[1]||{};i=2;}if(typeof target!="object"&&typeof target!="function")target={};if(length==i){target=this;--i;}for(;i-1;}},swap:function(elem,options,callback){var old={};for(var name in options){old[name]=elem.style[name];elem.style[name]=options[name];}callback.call(elem);for(var name in options)elem.style[name]=old[name];},css:function(elem,name,force){if(name=="width"||name=="height"){var val,props={position:"absolute",visibility:"hidden",display:"block"},which=name=="width"?["Left","Right"]:["Top","Bottom"];function getWH(){val=name=="width"?elem.offsetWidth:elem.offsetHeight;var padding=0,border=0;jQuery.each(which,function(){padding+=parseFloat(jQuery.curCSS(elem,"padding"+this,true))||0;border+=parseFloat(jQuery.curCSS(elem,"border"+this+"Width",true))||0;});val-=Math.round(padding+border);}if(jQuery(elem).is(":visible"))getWH();else -jQuery.swap(elem,props,getWH);return Math.max(0,val);}return jQuery.curCSS(elem,name,force);},curCSS:function(elem,name,force){var ret,style=elem.style;function color(elem){if(!jQuery.browser.safari)return false;var ret=defaultView.getComputedStyle(elem,null);return!ret||ret.getPropertyValue("color")=="";}if(name=="opacity"&&jQuery.browser.msie){ret=jQuery.attr(style,"opacity");return ret==""?"1":ret;}if(jQuery.browser.opera&&name=="display"){var save=style.outline;style.outline="0 solid black";style.outline=save;}if(name.match(/float/i))name=styleFloat;if(!force&&style&&style[name])ret=style[name];else if(defaultView.getComputedStyle){if(name.match(/float/i))name="float";name=name.replace(/([A-Z])/g,"-$1").toLowerCase();var computedStyle=defaultView.getComputedStyle(elem,null);if(computedStyle&&!color(elem))ret=computedStyle.getPropertyValue(name);else{var swap=[],stack=[],a=elem,i=0;for(;a&&color(a);a=a.parentNode)stack.unshift(a);for(;i]*?)\/>/g,function(all,front,tag){return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?all:front+">";});var tags=jQuery.trim(elem).toLowerCase(),div=context.createElement("div");var wrap=!tags.indexOf("",""]||!tags.indexOf("",""]||tags.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
    "]||!tags.indexOf("
    "]||(!tags.indexOf("",""]||!tags.indexOf("",""]||jQuery.browser.msie&&[1,"div
    ","
    "]||[0,"",""];div.innerHTML=wrap[1]+elem+wrap[2];while(wrap[0]--)div=div.lastChild;if(jQuery.browser.msie){var tbody=!tags.indexOf(""&&tags.indexOf("=0;--j)if(jQuery.nodeName(tbody[j],"tbody")&&!tbody[j].childNodes.length)tbody[j].parentNode.removeChild(tbody[j]);if(/^\s/.test(elem))div.insertBefore(context.createTextNode(elem.match(/^\s*/)[0]),div.firstChild);}elem=jQuery.makeArray(div.childNodes);}if(elem.length===0&&(!jQuery.nodeName(elem,"form")&&!jQuery.nodeName(elem,"select")))return;if(elem[0]==undefined||jQuery.nodeName(elem,"form")||elem.options)ret.push(elem);else -ret=jQuery.merge(ret,elem);});return ret;},attr:function(elem,name,value){if(!elem||elem.nodeType==3||elem.nodeType==8)return undefined;var notxml=!jQuery.isXMLDoc(elem),set=value!==undefined,msie=jQuery.browser.msie;name=notxml&&jQuery.props[name]||name;if(elem.tagName){var special=/href|src|style/.test(name);if(name=="selected"&&jQuery.browser.safari)elem.parentNode.selectedIndex;if(name in elem&¬xml&&!special){if(set){if(name=="type"&&jQuery.nodeName(elem,"input")&&elem.parentNode)throw"type property can't be changed";elem[name]=value;}if(jQuery.nodeName(elem,"form")&&elem.getAttributeNode(name))return elem.getAttributeNode(name).nodeValue;return elem[name];}if(msie&¬xml&&name=="style")return jQuery.attr(elem.style,"cssText",value);if(set)elem.setAttribute(name,""+value);var attr=msie&¬xml&&special?elem.getAttribute(name,2):elem.getAttribute(name);return attr===null?undefined:attr;}if(msie&&name=="opacity"){if(set){elem.zoom=1;elem.filter=(elem.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(value)+''=="NaN"?"":"alpha(opacity="+value*100+")");}return elem.filter&&elem.filter.indexOf("opacity=")>=0?(parseFloat(elem.filter.match(/opacity=([^)]*)/)[1])/100)+'':"";}name=name.replace(/-([a-z])/ig,function(all,letter){return letter.toUpperCase();});if(set)elem[name]=value;return elem[name];},trim:function(text){return(text||"").replace(/^\s+|\s+$/g,"");},makeArray:function(array){var ret=[];if(array!=null){var i=array.length;if(i==null||array.split||array.setInterval||array.call)ret[0]=array;else -while(i)ret[--i]=array[i];}return ret;},inArray:function(elem,array){for(var i=0,length=array.length;i*",this).remove();while(this.firstChild)this.removeChild(this.firstChild);}},function(name,fn){jQuery.fn[name]=function(){return this.each(fn,arguments);};});jQuery.each(["Height","Width"],function(i,name){var type=name.toLowerCase();jQuery.fn[type]=function(size){return this[0]==window?jQuery.browser.opera&&document.body["client"+name]||jQuery.browser.safari&&window["inner"+name]||document.compatMode=="CSS1Compat"&&document.documentElement["client"+name]||document.body["client"+name]:this[0]==document?Math.max(Math.max(document.body["scroll"+name],document.documentElement["scroll"+name]),Math.max(document.body["offset"+name],document.documentElement["offset"+name])):size==undefined?(this.length?jQuery.css(this[0],type):null):this.css(type,size.constructor==String?size:size+"px");};});function num(elem,prop){return elem[0]&&parseInt(jQuery.curCSS(elem[0],prop,true),10)||0;}var chars=jQuery.browser.safari&&parseInt(jQuery.browser.version)<417?"(?:[\\w*_-]|\\\\.)":"(?:[\\w\u0128-\uFFFF*_-]|\\\\.)",quickChild=new RegExp("^>\\s*("+chars+"+)"),quickID=new RegExp("^("+chars+"+)(#)("+chars+"+)"),quickClass=new RegExp("^([#.]?)("+chars+"*)");jQuery.extend({expr:{"":function(a,i,m){return m[2]=="*"||jQuery.nodeName(a,m[2]);},"#":function(a,i,m){return a.getAttribute("id")==m[2];},":":{lt:function(a,i,m){return im[3]-0;},nth:function(a,i,m){return m[3]-0==i;},eq:function(a,i,m){return m[3]-0==i;},first:function(a,i){return i==0;},last:function(a,i,m,r){return i==r.length-1;},even:function(a,i){return i%2==0;},odd:function(a,i){return i%2;},"first-child":function(a){return a.parentNode.getElementsByTagName("*")[0]==a;},"last-child":function(a){return jQuery.nth(a.parentNode.lastChild,1,"previousSibling")==a;},"only-child":function(a){return!jQuery.nth(a.parentNode.lastChild,2,"previousSibling");},parent:function(a){return a.firstChild;},empty:function(a){return!a.firstChild;},contains:function(a,i,m){return(a.textContent||a.innerText||jQuery(a).text()||"").indexOf(m[3])>=0;},visible:function(a){return"hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden";},hidden:function(a){return"hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden";},enabled:function(a){return!a.disabled;},disabled:function(a){return a.disabled;},checked:function(a){return a.checked;},selected:function(a){return a.selected||jQuery.attr(a,"selected");},text:function(a){return"text"==a.type;},radio:function(a){return"radio"==a.type;},checkbox:function(a){return"checkbox"==a.type;},file:function(a){return"file"==a.type;},password:function(a){return"password"==a.type;},submit:function(a){return"submit"==a.type;},image:function(a){return"image"==a.type;},reset:function(a){return"reset"==a.type;},button:function(a){return"button"==a.type||jQuery.nodeName(a,"button");},input:function(a){return/input|select|textarea|button/i.test(a.nodeName);},has:function(a,i,m){return jQuery.find(m[3],a).length;},header:function(a){return/h\d/i.test(a.nodeName);},animated:function(a){return jQuery.grep(jQuery.timers,function(fn){return a==fn.elem;}).length;}}},parse:[/^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/,/^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/,new RegExp("^([:.#]*)("+chars+"+)")],multiFilter:function(expr,elems,not){var old,cur=[];while(expr&&expr!=old){old=expr;var f=jQuery.filter(expr,elems,not);expr=f.t.replace(/^\s*,\s*/,"");cur=not?elems=f.r:jQuery.merge(cur,f.r);}return cur;},find:function(t,context){if(typeof t!="string")return[t];if(context&&context.nodeType!=1&&context.nodeType!=9)return[];context=context||document;var ret=[context],done=[],last,nodeName;while(t&&last!=t){var r=[];last=t;t=jQuery.trim(t);var foundToken=false,re=quickChild,m=re.exec(t);if(m){nodeName=m[1].toUpperCase();for(var i=0;ret[i];i++)for(var c=ret[i].firstChild;c;c=c.nextSibling)if(c.nodeType==1&&(nodeName=="*"||c.nodeName.toUpperCase()==nodeName))r.push(c);ret=r;t=t.replace(re,"");if(t.indexOf(" ")==0)continue;foundToken=true;}else{re=/^([>+~])\s*(\w*)/i;if((m=re.exec(t))!=null){r=[];var merge={};nodeName=m[2].toUpperCase();m=m[1];for(var j=0,rl=ret.length;j=0;if(!not&&pass||not&&!pass)tmp.push(r[i]);}return tmp;},filter:function(t,r,not){var last;while(t&&t!=last){last=t;var p=jQuery.parse,m;for(var i=0;p[i];i++){m=p[i].exec(t);if(m){t=t.substring(m[0].length);m[2]=m[2].replace(/\\/g,"");break;}}if(!m)break;if(m[1]==":"&&m[2]=="not")r=isSimple.test(m[3])?jQuery.filter(m[3],r,true).r:jQuery(r).not(m[3]);else if(m[1]==".")r=jQuery.classFilter(r,m[2],not);else if(m[1]=="["){var tmp=[],type=m[3];for(var i=0,rl=r.length;i=0)^not)tmp.push(a);}r=tmp;}else if(m[1]==":"&&m[2]=="nth-child"){var merge={},tmp=[],test=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(m[3]=="even"&&"2n"||m[3]=="odd"&&"2n+1"||!/\D/.test(m[3])&&"0n+"+m[3]||m[3]),first=(test[1]+(test[2]||1))-0,last=test[3]-0;for(var i=0,rl=r.length;i=0)add=true;if(add^not)tmp.push(node);}r=tmp;}else{var fn=jQuery.expr[m[1]];if(typeof fn=="object")fn=fn[m[2]];if(typeof fn=="string")fn=eval("false||function(a,i){return "+fn+";}");r=jQuery.grep(r,function(elem,i){return fn(elem,i,m,r);},not);}}return{r:r,t:t};},dir:function(elem,dir){var matched=[],cur=elem[dir];while(cur&&cur!=document){if(cur.nodeType==1)matched.push(cur);cur=cur[dir];}return matched;},nth:function(cur,result,dir,elem){result=result||1;var num=0;for(;cur;cur=cur[dir])if(cur.nodeType==1&&++num==result)break;return cur;},sibling:function(n,elem){var r=[];for(;n;n=n.nextSibling){if(n.nodeType==1&&n!=elem)r.push(n);}return r;}});jQuery.event={add:function(elem,types,handler,data){if(elem.nodeType==3||elem.nodeType==8)return;if(jQuery.browser.msie&&elem.setInterval)elem=window;if(!handler.guid)handler.guid=this.guid++;if(data!=undefined){var fn=handler;handler=this.proxy(fn,function(){return fn.apply(this,arguments);});handler.data=data;}var events=jQuery.data(elem,"events")||jQuery.data(elem,"events",{}),handle=jQuery.data(elem,"handle")||jQuery.data(elem,"handle",function(){if(typeof jQuery!="undefined"&&!jQuery.event.triggered)return jQuery.event.handle.apply(arguments.callee.elem,arguments);});handle.elem=elem;jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];handler.type=parts[1];var handlers=events[type];if(!handlers){handlers=events[type]={};if(!jQuery.event.special[type]||jQuery.event.special[type].setup.call(elem)===false){if(elem.addEventListener)elem.addEventListener(type,handle,false);else if(elem.attachEvent)elem.attachEvent("on"+type,handle);}}handlers[handler.guid]=handler;jQuery.event.global[type]=true;});elem=null;},guid:1,global:{},remove:function(elem,types,handler){if(elem.nodeType==3||elem.nodeType==8)return;var events=jQuery.data(elem,"events"),ret,index;if(events){if(types==undefined||(typeof types=="string"&&types.charAt(0)=="."))for(var type in events)this.remove(elem,type+(types||""));else{if(types.type){handler=types.handler;types=types.type;}jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];if(events[type]){if(handler)delete events[type][handler.guid];else -for(handler in events[type])if(!parts[1]||events[type][handler].type==parts[1])delete events[type][handler];for(ret in events[type])break;if(!ret){if(!jQuery.event.special[type]||jQuery.event.special[type].teardown.call(elem)===false){if(elem.removeEventListener)elem.removeEventListener(type,jQuery.data(elem,"handle"),false);else if(elem.detachEvent)elem.detachEvent("on"+type,jQuery.data(elem,"handle"));}ret=null;delete events[type];}}});}for(ret in events)break;if(!ret){var handle=jQuery.data(elem,"handle");if(handle)handle.elem=null;jQuery.removeData(elem,"events");jQuery.removeData(elem,"handle");}}},trigger:function(type,data,elem,donative,extra){data=jQuery.makeArray(data);if(type.indexOf("!")>=0){type=type.slice(0,-1);var exclusive=true;}if(!elem){if(this.global[type])jQuery("*").add([window,document]).trigger(type,data);}else{if(elem.nodeType==3||elem.nodeType==8)return undefined;var val,ret,fn=jQuery.isFunction(elem[type]||null),event=!data[0]||!data[0].preventDefault;if(event){data.unshift({type:type,target:elem,preventDefault:function(){},stopPropagation:function(){},timeStamp:now()});data[0][expando]=true;}data[0].type=type;if(exclusive)data[0].exclusive=true;var handle=jQuery.data(elem,"handle");if(handle)val=handle.apply(elem,data);if((!fn||(jQuery.nodeName(elem,'a')&&type=="click"))&&elem["on"+type]&&elem["on"+type].apply(elem,data)===false)val=false;if(event)data.shift();if(extra&&jQuery.isFunction(extra)){ret=extra.apply(elem,val==null?data:data.concat(val));if(ret!==undefined)val=ret;}if(fn&&donative!==false&&val!==false&&!(jQuery.nodeName(elem,'a')&&type=="click")){this.triggered=true;try{elem[type]();}catch(e){}}this.triggered=false;}return val;},handle:function(event){var val,ret,namespace,all,handlers;event=arguments[0]=jQuery.event.fix(event||window.event);namespace=event.type.split(".");event.type=namespace[0];namespace=namespace[1];all=!namespace&&!event.exclusive;handlers=(jQuery.data(this,"events")||{})[event.type];for(var j in handlers){var handler=handlers[j];if(all||handler.type==namespace){event.handler=handler;event.data=handler.data;ret=handler.apply(this,arguments);if(val!==false)val=ret;if(ret===false){event.preventDefault();event.stopPropagation();}}}return val;},fix:function(event){if(event[expando]==true)return event;var originalEvent=event;event={originalEvent:originalEvent};var props="altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target timeStamp toElement type view wheelDelta which".split(" ");for(var i=props.length;i;i--)event[props[i]]=originalEvent[props[i]];event[expando]=true;event.preventDefault=function(){if(originalEvent.preventDefault)originalEvent.preventDefault();originalEvent.returnValue=false;};event.stopPropagation=function(){if(originalEvent.stopPropagation)originalEvent.stopPropagation();originalEvent.cancelBubble=true;};event.timeStamp=event.timeStamp||now();if(!event.target)event.target=event.srcElement||document;if(event.target.nodeType==3)event.target=event.target.parentNode;if(!event.relatedTarget&&event.fromElement)event.relatedTarget=event.fromElement==event.target?event.toElement:event.fromElement;if(event.pageX==null&&event.clientX!=null){var doc=document.documentElement,body=document.body;event.pageX=event.clientX+(doc&&doc.scrollLeft||body&&body.scrollLeft||0)-(doc.clientLeft||0);event.pageY=event.clientY+(doc&&doc.scrollTop||body&&body.scrollTop||0)-(doc.clientTop||0);}if(!event.which&&((event.charCode||event.charCode===0)?event.charCode:event.keyCode))event.which=event.charCode||event.keyCode;if(!event.metaKey&&event.ctrlKey)event.metaKey=event.ctrlKey;if(!event.which&&event.button)event.which=(event.button&1?1:(event.button&2?3:(event.button&4?2:0)));return event;},proxy:function(fn,proxy){proxy.guid=fn.guid=fn.guid||proxy.guid||this.guid++;return proxy;},special:{ready:{setup:function(){bindReady();return;},teardown:function(){return;}},mouseenter:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseover",jQuery.event.special.mouseenter.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseover",jQuery.event.special.mouseenter.handler);return true;},handler:function(event){if(withinElement(event,this))return true;event.type="mouseenter";return jQuery.event.handle.apply(this,arguments);}},mouseleave:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseout",jQuery.event.special.mouseleave.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseout",jQuery.event.special.mouseleave.handler);return true;},handler:function(event){if(withinElement(event,this))return true;event.type="mouseleave";return jQuery.event.handle.apply(this,arguments);}}}};jQuery.fn.extend({bind:function(type,data,fn){return type=="unload"?this.one(type,data,fn):this.each(function(){jQuery.event.add(this,type,fn||data,fn&&data);});},one:function(type,data,fn){var one=jQuery.event.proxy(fn||data,function(event){jQuery(this).unbind(event,one);return(fn||data).apply(this,arguments);});return this.each(function(){jQuery.event.add(this,type,one,fn&&data);});},unbind:function(type,fn){return this.each(function(){jQuery.event.remove(this,type,fn);});},trigger:function(type,data,fn){return this.each(function(){jQuery.event.trigger(type,data,this,true,fn);});},triggerHandler:function(type,data,fn){return this[0]&&jQuery.event.trigger(type,data,this[0],false,fn);},toggle:function(fn){var args=arguments,i=1;while(i=0){var selector=url.slice(off,url.length);url=url.slice(0,off);}callback=callback||function(){};var type="GET";if(params)if(jQuery.isFunction(params)){callback=params;params=null;}else{params=jQuery.param(params);type="POST";}var self=this;jQuery.ajax({url:url,type:type,dataType:"html",data:params,complete:function(res,status){if(status=="success"||status=="notmodified")self.html(selector?jQuery("
    ").append(res.responseText.replace(//g,"")).find(selector):res.responseText);self.each(callback,[res.responseText,status,res]);}});return this;},serialize:function(){return jQuery.param(this.serializeArray());},serializeArray:function(){return this.map(function(){return jQuery.nodeName(this,"form")?jQuery.makeArray(this.elements):this;}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password/i.test(this.type));}).map(function(i,elem){var val=jQuery(this).val();return val==null?null:val.constructor==Array?jQuery.map(val,function(val,i){return{name:elem.name,value:val};}):{name:elem.name,value:val};}).get();}});jQuery.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(i,o){jQuery.fn[o]=function(f){return this.bind(o,f);};});var jsc=now();jQuery.extend({get:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data=null;}return jQuery.ajax({type:"GET",url:url,data:data,success:callback,dataType:type});},getScript:function(url,callback){return jQuery.get(url,null,callback,"script");},getJSON:function(url,data,callback){return jQuery.get(url,data,callback,"json");},post:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data={};}return jQuery.ajax({type:"POST",url:url,data:data,success:callback,dataType:type});},ajaxSetup:function(settings){jQuery.extend(jQuery.ajaxSettings,settings);},ajaxSettings:{url:location.href,global:true,type:"GET",timeout:0,contentType:"application/x-www-form-urlencoded",processData:true,async:true,data:null,username:null,password:null,accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(s){s=jQuery.extend(true,s,jQuery.extend(true,{},jQuery.ajaxSettings,s));var jsonp,jsre=/=\?(&|$)/g,status,data,type=s.type.toUpperCase();if(s.data&&s.processData&&typeof s.data!="string")s.data=jQuery.param(s.data);if(s.dataType=="jsonp"){if(type=="GET"){if(!s.url.match(jsre))s.url+=(s.url.match(/\?/)?"&":"?")+(s.jsonp||"callback")+"=?";}else if(!s.data||!s.data.match(jsre))s.data=(s.data?s.data+"&":"")+(s.jsonp||"callback")+"=?";s.dataType="json";}if(s.dataType=="json"&&(s.data&&s.data.match(jsre)||s.url.match(jsre))){jsonp="jsonp"+jsc++;if(s.data)s.data=(s.data+"").replace(jsre,"="+jsonp+"$1");s.url=s.url.replace(jsre,"="+jsonp+"$1");s.dataType="script";window[jsonp]=function(tmp){data=tmp;success();complete();window[jsonp]=undefined;try{delete window[jsonp];}catch(e){}if(head)head.removeChild(script);};}if(s.dataType=="script"&&s.cache==null)s.cache=false;if(s.cache===false&&type=="GET"){var ts=now();var ret=s.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+ts+"$2");s.url=ret+((ret==s.url)?(s.url.match(/\?/)?"&":"?")+"_="+ts:"");}if(s.data&&type=="GET"){s.url+=(s.url.match(/\?/)?"&":"?")+s.data;s.data=null;}if(s.global&&!jQuery.active++)jQuery.event.trigger("ajaxStart");var remote=/^(?:\w+:)?\/\/([^\/?#]+)/;if(s.dataType=="script"&&type=="GET"&&remote.test(s.url)&&remote.exec(s.url)[1]!=location.host){var head=document.getElementsByTagName("head")[0];var script=document.createElement("script");script.src=s.url;if(s.scriptCharset)script.charset=s.scriptCharset;if(!jsonp){var done=false;script.onload=script.onreadystatechange=function(){if(!done&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){done=true;success();complete();head.removeChild(script);}};}head.appendChild(script);return undefined;}var requestDone=false;var xhr=window.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest();if(s.username)xhr.open(type,s.url,s.async,s.username,s.password);else -xhr.open(type,s.url,s.async);try{if(s.data)xhr.setRequestHeader("Content-Type",s.contentType);if(s.ifModified)xhr.setRequestHeader("If-Modified-Since",jQuery.lastModified[s.url]||"Thu, 01 Jan 1970 00:00:00 GMT");xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Accept",s.dataType&&s.accepts[s.dataType]?s.accepts[s.dataType]+", */*":s.accepts._default);}catch(e){}if(s.beforeSend&&s.beforeSend(xhr,s)===false){s.global&&jQuery.active--;xhr.abort();return false;}if(s.global)jQuery.event.trigger("ajaxSend",[xhr,s]);var onreadystatechange=function(isTimeout){if(!requestDone&&xhr&&(xhr.readyState==4||isTimeout=="timeout")){requestDone=true;if(ival){clearInterval(ival);ival=null;}status=isTimeout=="timeout"&&"timeout"||!jQuery.httpSuccess(xhr)&&"error"||s.ifModified&&jQuery.httpNotModified(xhr,s.url)&&"notmodified"||"success";if(status=="success"){try{data=jQuery.httpData(xhr,s.dataType,s.dataFilter);}catch(e){status="parsererror";}}if(status=="success"){var modRes;try{modRes=xhr.getResponseHeader("Last-Modified");}catch(e){}if(s.ifModified&&modRes)jQuery.lastModified[s.url]=modRes;if(!jsonp)success();}else -jQuery.handleError(s,xhr,status);complete();if(s.async)xhr=null;}};if(s.async){var ival=setInterval(onreadystatechange,13);if(s.timeout>0)setTimeout(function(){if(xhr){xhr.abort();if(!requestDone)onreadystatechange("timeout");}},s.timeout);}try{xhr.send(s.data);}catch(e){jQuery.handleError(s,xhr,null,e);}if(!s.async)onreadystatechange();function success(){if(s.success)s.success(data,status);if(s.global)jQuery.event.trigger("ajaxSuccess",[xhr,s]);}function complete(){if(s.complete)s.complete(xhr,status);if(s.global)jQuery.event.trigger("ajaxComplete",[xhr,s]);if(s.global&&!--jQuery.active)jQuery.event.trigger("ajaxStop");}return xhr;},handleError:function(s,xhr,status,e){if(s.error)s.error(xhr,status,e);if(s.global)jQuery.event.trigger("ajaxError",[xhr,s,e]);},active:0,httpSuccess:function(xhr){try{return!xhr.status&&location.protocol=="file:"||(xhr.status>=200&&xhr.status<300)||xhr.status==304||xhr.status==1223||jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return false;},httpNotModified:function(xhr,url){try{var xhrRes=xhr.getResponseHeader("Last-Modified");return xhr.status==304||xhrRes==jQuery.lastModified[url]||jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return false;},httpData:function(xhr,type,filter){var ct=xhr.getResponseHeader("content-type"),xml=type=="xml"||!type&&ct&&ct.indexOf("xml")>=0,data=xml?xhr.responseXML:xhr.responseText;if(xml&&data.documentElement.tagName=="parsererror")throw"parsererror";if(filter)data=filter(data,type);if(type=="script")jQuery.globalEval(data);if(type=="json")data=eval("("+data+")");return data;},param:function(a){var s=[];if(a.constructor==Array||a.jquery)jQuery.each(a,function(){s.push(encodeURIComponent(this.name)+"="+encodeURIComponent(this.value));});else -for(var j in a)if(a[j]&&a[j].constructor==Array)jQuery.each(a[j],function(){s.push(encodeURIComponent(j)+"="+encodeURIComponent(this));});else -s.push(encodeURIComponent(j)+"="+encodeURIComponent(jQuery.isFunction(a[j])?a[j]():a[j]));return s.join("&").replace(/%20/g,"+");}});jQuery.fn.extend({show:function(speed,callback){return speed?this.animate({height:"show",width:"show",opacity:"show"},speed,callback):this.filter(":hidden").each(function(){this.style.display=this.oldblock||"";if(jQuery.css(this,"display")=="none"){var elem=jQuery("<"+this.tagName+" />").appendTo("body");this.style.display=elem.css("display");if(this.style.display=="none")this.style.display="block";elem.remove();}}).end();},hide:function(speed,callback){return speed?this.animate({height:"hide",width:"hide",opacity:"hide"},speed,callback):this.filter(":visible").each(function(){this.oldblock=this.oldblock||jQuery.css(this,"display");this.style.display="none";}).end();},_toggle:jQuery.fn.toggle,toggle:function(fn,fn2){return jQuery.isFunction(fn)&&jQuery.isFunction(fn2)?this._toggle.apply(this,arguments):fn?this.animate({height:"toggle",width:"toggle",opacity:"toggle"},fn,fn2):this.each(function(){jQuery(this)[jQuery(this).is(":hidden")?"show":"hide"]();});},slideDown:function(speed,callback){return this.animate({height:"show"},speed,callback);},slideUp:function(speed,callback){return this.animate({height:"hide"},speed,callback);},slideToggle:function(speed,callback){return this.animate({height:"toggle"},speed,callback);},fadeIn:function(speed,callback){return this.animate({opacity:"show"},speed,callback);},fadeOut:function(speed,callback){return this.animate({opacity:"hide"},speed,callback);},fadeTo:function(speed,to,callback){return this.animate({opacity:to},speed,callback);},animate:function(prop,speed,easing,callback){var optall=jQuery.speed(speed,easing,callback);return this[optall.queue===false?"each":"queue"](function(){if(this.nodeType!=1)return false;var opt=jQuery.extend({},optall),p,hidden=jQuery(this).is(":hidden"),self=this;for(p in prop){if(prop[p]=="hide"&&hidden||prop[p]=="show"&&!hidden)return opt.complete.call(this);if(p=="height"||p=="width"){opt.display=jQuery.css(this,"display");opt.overflow=this.style.overflow;}}if(opt.overflow!=null)this.style.overflow="hidden";opt.curAnim=jQuery.extend({},prop);jQuery.each(prop,function(name,val){var e=new jQuery.fx(self,opt,name);if(/toggle|show|hide/.test(val))e[val=="toggle"?hidden?"show":"hide":val](prop);else{var parts=val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),start=e.cur(true)||0;if(parts){var end=parseFloat(parts[2]),unit=parts[3]||"px";if(unit!="px"){self.style[name]=(end||1)+unit;start=((end||1)/e.cur(true))*start;self.style[name]=start+unit;}if(parts[1])end=((parts[1]=="-="?-1:1)*end)+start;e.custom(start,end,unit);}else -e.custom(start,val,"");}});return true;});},queue:function(type,fn){if(jQuery.isFunction(type)||(type&&type.constructor==Array)){fn=type;type="fx";}if(!type||(typeof type=="string"&&!fn))return queue(this[0],type);return this.each(function(){if(fn.constructor==Array)queue(this,type,fn);else{queue(this,type).push(fn);if(queue(this,type).length==1)fn.call(this);}});},stop:function(clearQueue,gotoEnd){var timers=jQuery.timers;if(clearQueue)this.queue([]);this.each(function(){for(var i=timers.length-1;i>=0;i--)if(timers[i].elem==this){if(gotoEnd)timers[i](true);timers.splice(i,1);}});if(!gotoEnd)this.dequeue();return this;}});var queue=function(elem,type,array){if(elem){type=type||"fx";var q=jQuery.data(elem,type+"queue");if(!q||array)q=jQuery.data(elem,type+"queue",jQuery.makeArray(array));}return q;};jQuery.fn.dequeue=function(type){type=type||"fx";return this.each(function(){var q=queue(this,type);q.shift();if(q.length)q[0].call(this);});};jQuery.extend({speed:function(speed,easing,fn){var opt=speed&&speed.constructor==Object?speed:{complete:fn||!fn&&easing||jQuery.isFunction(speed)&&speed,duration:speed,easing:fn&&easing||easing&&easing.constructor!=Function&&easing};opt.duration=(opt.duration&&opt.duration.constructor==Number?opt.duration:jQuery.fx.speeds[opt.duration])||jQuery.fx.speeds.def;opt.old=opt.complete;opt.complete=function(){if(opt.queue!==false)jQuery(this).dequeue();if(jQuery.isFunction(opt.old))opt.old.call(this);};return opt;},easing:{linear:function(p,n,firstNum,diff){return firstNum+diff*p;},swing:function(p,n,firstNum,diff){return((-Math.cos(p*Math.PI)/2)+0.5)*diff+firstNum;}},timers:[],timerId:null,fx:function(elem,options,prop){this.options=options;this.elem=elem;this.prop=prop;if(!options.orig)options.orig={};}});jQuery.fx.prototype={update:function(){if(this.options.step)this.options.step.call(this.elem,this.now,this);(jQuery.fx.step[this.prop]||jQuery.fx.step._default)(this);if(this.prop=="height"||this.prop=="width")this.elem.style.display="block";},cur:function(force){if(this.elem[this.prop]!=null&&this.elem.style[this.prop]==null)return this.elem[this.prop];var r=parseFloat(jQuery.css(this.elem,this.prop,force));return r&&r>-10000?r:parseFloat(jQuery.curCSS(this.elem,this.prop))||0;},custom:function(from,to,unit){this.startTime=now();this.start=from;this.end=to;this.unit=unit||this.unit||"px";this.now=this.start;this.pos=this.state=0;this.update();var self=this;function t(gotoEnd){return self.step(gotoEnd);}t.elem=this.elem;jQuery.timers.push(t);if(jQuery.timerId==null){jQuery.timerId=setInterval(function(){var timers=jQuery.timers;for(var i=0;ithis.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var done=true;for(var i in this.options.curAnim)if(this.options.curAnim[i]!==true)done=false;if(done){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(jQuery.css(this.elem,"display")=="none")this.elem.style.display="block";}if(this.options.hide)this.elem.style.display="none";if(this.options.hide||this.options.show)for(var p in this.options.curAnim)jQuery.attr(this.elem.style,p,this.options.orig[p]);}if(done)this.options.complete.call(this.elem);return false;}else{var n=t-this.startTime;this.state=n/this.options.duration;this.pos=jQuery.easing[this.options.easing||(jQuery.easing.swing?"swing":"linear")](this.state,n,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update();}return true;}};jQuery.extend(jQuery.fx,{speeds:{slow:600,fast:200,def:400},step:{scrollLeft:function(fx){fx.elem.scrollLeft=fx.now;},scrollTop:function(fx){fx.elem.scrollTop=fx.now;},opacity:function(fx){jQuery.attr(fx.elem.style,"opacity",fx.now);},_default:function(fx){fx.elem.style[fx.prop]=fx.now+fx.unit;}}});jQuery.fn.offset=function(){var left=0,top=0,elem=this[0],results;if(elem)with(jQuery.browser){var parent=elem.parentNode,offsetChild=elem,offsetParent=elem.offsetParent,doc=elem.ownerDocument,safari2=safari&&parseInt(version)<522&&!/adobeair/i.test(userAgent),css=jQuery.curCSS,fixed=css(elem,"position")=="fixed";if(elem.getBoundingClientRect){var box=elem.getBoundingClientRect();add(box.left+Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),box.top+Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));add(-doc.documentElement.clientLeft,-doc.documentElement.clientTop);}else{add(elem.offsetLeft,elem.offsetTop);while(offsetParent){add(offsetParent.offsetLeft,offsetParent.offsetTop);if(mozilla&&!/^t(able|d|h)$/i.test(offsetParent.tagName)||safari&&!safari2)border(offsetParent);if(!fixed&&css(offsetParent,"position")=="fixed")fixed=true;offsetChild=/^body$/i.test(offsetParent.tagName)?offsetChild:offsetParent;offsetParent=offsetParent.offsetParent;}while(parent&&parent.tagName&&!/^body|html$/i.test(parent.tagName)){if(!/^inline|table.*$/i.test(css(parent,"display")))add(-parent.scrollLeft,-parent.scrollTop);if(mozilla&&css(parent,"overflow")!="visible")border(parent);parent=parent.parentNode;}if((safari2&&(fixed||css(offsetChild,"position")=="absolute"))||(mozilla&&css(offsetChild,"position")!="absolute"))add(-doc.body.offsetLeft,-doc.body.offsetTop);if(fixed)add(Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));}results={top:top,left:left};}function border(elem){add(jQuery.curCSS(elem,"borderLeftWidth",true),jQuery.curCSS(elem,"borderTopWidth",true));}function add(l,t){left+=parseInt(l,10)||0;top+=parseInt(t,10)||0;}return results;};jQuery.fn.extend({position:function(){var left=0,top=0,results;if(this[0]){var offsetParent=this.offsetParent(),offset=this.offset(),parentOffset=/^body|html$/i.test(offsetParent[0].tagName)?{top:0,left:0}:offsetParent.offset();offset.top-=num(this,'marginTop');offset.left-=num(this,'marginLeft');parentOffset.top+=num(offsetParent,'borderTopWidth');parentOffset.left+=num(offsetParent,'borderLeftWidth');results={top:offset.top-parentOffset.top,left:offset.left-parentOffset.left};}return results;},offsetParent:function(){var offsetParent=this[0].offsetParent;while(offsetParent&&(!/^body|html$/i.test(offsetParent.tagName)&&jQuery.css(offsetParent,'position')=='static'))offsetParent=offsetParent.offsetParent;return jQuery(offsetParent);}});jQuery.each(['Left','Top'],function(i,name){var method='scroll'+name;jQuery.fn[method]=function(val){if(!this[0])return;return val!=undefined?this.each(function(){this==window||this==document?window.scrollTo(!i?val:jQuery(window).scrollLeft(),i?val:jQuery(window).scrollTop()):this[method]=val;}):this[0]==window||this[0]==document?self[i?'pageYOffset':'pageXOffset']||jQuery.boxModel&&document.documentElement[method]||document.body[method]:this[0][method];};});jQuery.each(["Height","Width"],function(i,name){var tl=i?"Left":"Top",br=i?"Right":"Bottom";jQuery.fn["inner"+name]=function(){return this[name.toLowerCase()]()+num(this,"padding"+tl)+num(this,"padding"+br);};jQuery.fn["outer"+name]=function(margin){return this["inner"+name]()+num(this,"border"+tl+"Width")+num(this,"border"+br+"Width")+(margin?num(this,"margin"+tl)+num(this,"margin"+br):0);};});})(); \ No newline at end of file +(function(A,w){function oa(){if(!c.isReady){try{s.documentElement.doScroll("left")}catch(a){setTimeout(oa,1);return}c.ready()}}function La(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function $(a,b,d,f,e,i){var j=a.length;if(typeof b==="object"){for(var o in b)$(a,o,b[o],f,e,d);return a}if(d!==w){f=!i&&f&&c.isFunction(d);for(o=0;o-1){i=j.data;i.beforeFilter&&i.beforeFilter[a.type]&&!i.beforeFilter[a.type](a)||f.push(j.selector)}else delete t[p]}i=c(a.target).closest(f,a.currentTarget); +n=0;for(l=i.length;n)[^>]*$|^#([\w-]+)$/,Pa=/^.[^:#\[\.,]*$/,Qa=/\S/, +Ra=/^(\s|\u00A0)+|(\s|\u00A0)+$/g,Sa=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,P=navigator.userAgent,xa=false,Q=[],M,ca=Object.prototype.toString,da=Object.prototype.hasOwnProperty,ea=Array.prototype.push,R=Array.prototype.slice,V=Array.prototype.indexOf;c.fn=c.prototype={init:function(a,b){var d,f;if(!a)return this;if(a.nodeType){this.context=this[0]=a;this.length=1;return this}if(typeof a==="string")if((d=Oa.exec(a))&&(d[1]||!b))if(d[1]){f=b?b.ownerDocument||b:s;if(a=Sa.exec(a))if(c.isPlainObject(b)){a=[s.createElement(a[1])]; +c.fn.attr.call(a,b,true)}else a=[f.createElement(a[1])];else{a=ua([d[1]],[f]);a=(a.cacheable?a.fragment.cloneNode(true):a.fragment).childNodes}}else{if(b=s.getElementById(d[2])){if(b.id!==d[2])return U.find(a);this.length=1;this[0]=b}this.context=s;this.selector=a;return this}else if(!b&&/^\w+$/.test(a)){this.selector=a;this.context=s;a=s.getElementsByTagName(a)}else return!b||b.jquery?(b||U).find(a):c(b).find(a);else if(c.isFunction(a))return U.ready(a);if(a.selector!==w){this.selector=a.selector; +this.context=a.context}return c.isArray(a)?this.setArray(a):c.makeArray(a,this)},selector:"",jquery:"1.4",length:0,size:function(){return this.length},toArray:function(){return R.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this.slice(a)[0]:this[a]},pushStack:function(a,b,d){a=c(a||null);a.prevObject=this;a.context=this.context;if(b==="find")a.selector=this.selector+(this.selector?" ":"")+d;else if(b)a.selector=this.selector+"."+b+"("+d+")";return a},setArray:function(a){this.length= +0;ea.apply(this,a);return this},each:function(a,b){return c.each(this,a,b)},ready:function(a){c.bindReady();if(c.isReady)a.call(s,c);else Q&&Q.push(a);return this},eq:function(a){return a===-1?this.slice(a):this.slice(a,+a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(R.apply(this,arguments),"slice",R.call(arguments).join(","))},map:function(a){return this.pushStack(c.map(this,function(b,d){return a.call(b,d,b)}))},end:function(){return this.prevObject|| +c(null)},push:ea,sort:[].sort,splice:[].splice};c.fn.init.prototype=c.fn;c.extend=c.fn.extend=function(){var a=arguments[0]||{},b=1,d=arguments.length,f=false,e,i,j,o;if(typeof a==="boolean"){f=a;a=arguments[1]||{};b=2}if(typeof a!=="object"&&!c.isFunction(a))a={};if(d===b){a=this;--b}for(;b
    a";var e=d.getElementsByTagName("*"),i=d.getElementsByTagName("a")[0];if(!(!e||!e.length||!i)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length, +htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(i.getAttribute("style")),hrefNormalized:i.getAttribute("href")==="/a",opacity:/^0.55$/.test(i.style.opacity),cssFloat:!!i.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:s.createElement("select").appendChild(s.createElement("option")).selected,scriptEval:false,noCloneEvent:true,boxModel:null};b.type="text/javascript";try{b.appendChild(s.createTextNode("window."+f+"=1;"))}catch(j){}a.insertBefore(b, +a.firstChild);if(A[f]){c.support.scriptEval=true;delete A[f]}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function o(){c.support.noCloneEvent=false;d.detachEvent("onclick",o)});d.cloneNode(true).fireEvent("onclick")}c(function(){var o=s.createElement("div");o.style.width=o.style.paddingLeft="1px";s.body.appendChild(o);c.boxModel=c.support.boxModel=o.offsetWidth===2;s.body.removeChild(o).style.display="none"});a=function(o){var p=s.createElement("div");o="on"+o;var n=o in +p;if(!n){p.setAttribute(o,"return;");n=typeof p[o]==="function"}return n};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=e=i=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var H="jQuery"+K(),Ta=0,ya={},Ua={};c.extend({cache:{},expando:H,noData:{embed:true,object:true,applet:true},data:function(a, +b,d){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?ya:a;var f=a[H],e=c.cache;if(!b&&!f)return null;f||(f=++Ta);if(typeof b==="object"){a[H]=f;e=e[f]=c.extend(true,{},b)}else e=e[f]?e[f]:typeof d==="undefined"?Ua:(e[f]={});if(d!==w){a[H]=f;e[b]=d}return typeof b==="string"?e[b]:e}},removeData:function(a,b){if(!(a.nodeName&&c.noData[a.nodeName.toLowerCase()])){a=a==A?ya:a;var d=a[H],f=c.cache,e=f[d];if(b){if(e){delete e[b];c.isEmptyObject(e)&&c.removeData(a)}}else{try{delete a[H]}catch(i){a.removeAttribute&& +a.removeAttribute(H)}delete f[d]}}}});c.fn.extend({data:function(a,b){if(typeof a==="undefined"&&this.length)return c.data(this[0]);else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===w){var f=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(f===w&&this.length)f=c.data(this[0],a);return f===w&&d[1]?this.data(d[0]):f}else return this.trigger("setData"+d[1]+"!",[d[0],b]).each(function(){c.data(this,a,b)})},removeData:function(a){return this.each(function(){c.removeData(this, +a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var f=c.data(a,b);if(!d)return f||[];if(!f||c.isArray(d))f=c.data(a,b,c.makeArray(d));else f.push(d);return f}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),f=d.shift();if(f==="inprogress")f=d.shift();if(f){b==="fx"&&d.unshift("inprogress");f.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===w)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this, +a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var za=/[\n\t]/g,fa=/\s+/,Va=/\r/g,Wa=/href|src|style/,Xa=/(button|input)/i,Ya=/(button|input|object|select|textarea)/i,Za=/^(a|area)$/i,Aa=/radio|checkbox/;c.fn.extend({attr:function(a, +b){return $(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(p){var n=c(this);n.addClass(a.call(this,p,n.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(fa),d=0,f=this.length;d-1)return true;return false},val:function(a){if(a===w){var b=this[0];if(b){if(c.nodeName(b,"option"))return(b.attributes.value||{}).specified?b.value:b.text;if(c.nodeName(b,"select")){var d=b.selectedIndex,f=[],e=b.options;b=b.type==="select-one";if(d<0)return null;var i=b?d:0;for(d=b?d+1:e.length;i=0;else if(c.nodeName(this,"select")){var z=c.makeArray(t);c("option",this).each(function(){this.selected=c.inArray(c(this).val(),z)>=0});if(!z.length)this.selectedIndex= +-1}else this.value=t}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,f){if(!a||a.nodeType===3||a.nodeType===8)return w;if(f&&b in c.attrFn)return c(a)[b](d);f=a.nodeType!==1||!c.isXMLDoc(a);var e=d!==w;b=f&&c.props[b]||b;if(a.nodeType===1){var i=Wa.test(b);if(b in a&&f&&!i){if(e){if(b==="type"&&Xa.test(a.nodeName)&&a.parentNode)throw"type property can't be changed";a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue; +if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:Ya.test(a.nodeName)||Za.test(a.nodeName)&&a.href?0:w;return a[b]}if(!c.support.style&&f&&b==="style"){if(e)a.style.cssText=""+d;return a.style.cssText}e&&a.setAttribute(b,""+d);a=!c.support.hrefNormalized&&f&&i?a.getAttribute(b,2):a.getAttribute(b);return a===null?w:a}return c.style(a,b,d)}});var $a=function(a){return a.replace(/[^\w\s\.\|`]/g,function(b){return"\\"+b})};c.event={add:function(a,b,d,f){if(!(a.nodeType=== +3||a.nodeType===8)){if(a.setInterval&&a!==A&&!a.frameElement)a=A;if(!d.guid)d.guid=c.guid++;if(f!==w){d=c.proxy(d);d.data=f}var e=c.data(a,"events")||c.data(a,"events",{}),i=c.data(a,"handle"),j;if(!i){j=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(j.elem,arguments):w};i=c.data(a,"handle",j)}if(i){i.elem=a;b=b.split(/\s+/);for(var o,p=0;o=b[p++];){var n=o.split(".");o=n.shift();d.type=n.slice(0).sort().join(".");var t=e[o],z=this.special[o]||{};if(!t){t=e[o]={}; +if(!z.setup||z.setup.call(a,f,n,d)===false)if(a.addEventListener)a.addEventListener(o,i,false);else a.attachEvent&&a.attachEvent("on"+o,i)}if(z.add)if((n=z.add.call(a,d,f,n,t))&&c.isFunction(n)){n.guid=n.guid||d.guid;d=n}t[d.guid]=d;this.global[o]=true}a=null}}},global:{},remove:function(a,b,d){if(!(a.nodeType===3||a.nodeType===8)){var f=c.data(a,"events"),e,i,j;if(f){if(b===w||typeof b==="string"&&b.charAt(0)===".")for(i in f)this.remove(a,i+(b||""));else{if(b.type){d=b.handler;b=b.type}b=b.split(/\s+/); +for(var o=0;i=b[o++];){var p=i.split(".");i=p.shift();var n=!p.length,t=c.map(p.slice(0).sort(),$a);t=new RegExp("(^|\\.)"+t.join("\\.(?:.*\\.)?")+"(\\.|$)");var z=this.special[i]||{};if(f[i]){if(d){j=f[i][d.guid];delete f[i][d.guid]}else for(var B in f[i])if(n||t.test(f[i][B].type))delete f[i][B];z.remove&&z.remove.call(a,p,j);for(e in f[i])break;if(!e){if(!z.teardown||z.teardown.call(a,p)===false)if(a.removeEventListener)a.removeEventListener(i,c.data(a,"handle"),false);else a.detachEvent&&a.detachEvent("on"+ +i,c.data(a,"handle"));e=null;delete f[i]}}}}for(e in f)break;if(!e){if(B=c.data(a,"handle"))B.elem=null;c.removeData(a,"events");c.removeData(a,"handle")}}}},trigger:function(a,b,d,f){var e=a.type||a;if(!f){a=typeof a==="object"?a[H]?a:c.extend(c.Event(e),a):c.Event(e);if(e.indexOf("!")>=0){a.type=e=e.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();this.global[e]&&c.each(c.cache,function(){this.events&&this.events[e]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType=== +8)return w;a.result=w;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;var i=c.data(d,"handle");i&&i.apply(d,b);var j,o;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()])){j=d[e];o=d["on"+e]}}catch(p){}i=c.nodeName(d,"a")&&e==="click";if(!f&&j&&!a.isDefaultPrevented()&&!i){this.triggered=true;try{d[e]()}catch(n){}}else if(o&&d["on"+e].apply(d,b)===false)a.result=false;this.triggered=false;if(!a.isPropagationStopped())(d=d.parentNode||d.ownerDocument)&&c.event.trigger(a,b,d,true)}, +handle:function(a){var b,d;a=arguments[0]=c.event.fix(a||A.event);a.currentTarget=this;d=a.type.split(".");a.type=d.shift();b=!d.length&&!a.exclusive;var f=new RegExp("(^|\\.)"+d.slice(0).sort().join("\\.(?:.*\\.)?")+"(\\.|$)");d=(c.data(this,"events")||{})[a.type];for(var e in d){var i=d[e];if(b||f.test(i.type)){a.handler=i;a.data=i.data;i=i.apply(this,arguments);if(i!==w){a.result=i;if(i===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}return a.result}, +props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(a){if(a[H])return a;var b=a;a=c.Event(b);for(var d=this.props.length,f;d;){f=this.props[--d];a[f]=b[f]}if(!a.target)a.target=a.srcElement|| +s;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=s.documentElement;d=s.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(!a.which&&(a.charCode||a.charCode===0?a.charCode:a.keyCode))a.which=a.charCode||a.keyCode;if(!a.metaKey&& +a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==w)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a,b){c.extend(a,b||{});a.guid+=b.selector+b.live;c.event.add(this,b.live,qa,b)},remove:function(a){if(a.length){var b=0,d=new RegExp("(^|\\.)"+a[0]+"(\\.|$)");c.each(c.data(this,"events").live||{},function(){d.test(this.type)&&b++});b<1&&c.event.remove(this,a[0],qa)}},special:{}},beforeunload:{setup:function(a, +b,d){if(this.setInterval)this.onbeforeunload=d;return false},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=a;this.type=a.type}else this.type=a;this.timeStamp=K();this[H]=true};c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=ba;var a=this.originalEvent;if(a){a.preventDefault&&a.preventDefault();a.returnValue=false}},stopPropagation:function(){this.isPropagationStopped= +ba;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=ba;this.stopPropagation()},isDefaultPrevented:aa,isPropagationStopped:aa,isImmediatePropagationStopped:aa};var Ba=function(a){for(var b=a.relatedTarget;b&&b!==this;)try{b=b.parentNode}catch(d){break}if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}},Ca=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover", +mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?Ca:Ba,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?Ca:Ba)}}});if(!c.support.submitBubbles)c.event.special.submit={setup:function(a,b,d){if(this.nodeName.toLowerCase()!=="form"){c.event.add(this,"click.specialSubmit."+d.guid,function(f){var e=f.target,i=e.type;if((i==="submit"||i==="image")&&c(e).closest("form").length)return pa("submit",this,arguments)});c.event.add(this,"keypress.specialSubmit."+ +d.guid,function(f){var e=f.target,i=e.type;if((i==="text"||i==="password")&&c(e).closest("form").length&&f.keyCode===13)return pa("submit",this,arguments)})}else return false},remove:function(a,b){c.event.remove(this,"click.specialSubmit"+(b?"."+b.guid:""));c.event.remove(this,"keypress.specialSubmit"+(b?"."+b.guid:""))}};if(!c.support.changeBubbles){var ga=/textarea|input|select/i;function Da(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex> +-1?c.map(a.options,function(f){return f.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d}function ha(a,b){var d=a.target,f,e;if(!(!ga.test(d.nodeName)||d.readOnly)){f=c.data(d,"_change_data");e=Da(d);if(e!==f){if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",e);if(d.type!=="select"&&(f!=null||e)){a.type="change";return c.event.trigger(a,b,this)}}}}c.event.special.change={filters:{focusout:ha,click:function(a){var b=a.target,d=b.type;if(d=== +"radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return ha.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return ha.call(this,a)},beforeactivate:function(a){a=a.target;a.nodeName.toLowerCase()==="input"&&a.type==="radio"&&c.data(a,"_change_data",Da(a))}},setup:function(a,b,d){for(var f in W)c.event.add(this,f+".specialChange."+d.guid,W[f]);return ga.test(this.nodeName)}, +remove:function(a,b){for(var d in W)c.event.remove(this,d+".specialChange"+(b?"."+b.guid:""),W[d]);return ga.test(this.nodeName)}};var W=c.event.special.change.filters}s.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(f){f=c.event.fix(f);f.type=b;return c.event.handle.call(this,f)}c.event.special[b]={setup:function(){this.addEventListener(a,d,true)},teardown:function(){this.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d, +f,e){if(typeof d==="object"){for(var i in d)this[b](i,f,d[i],e);return this}if(c.isFunction(f)){thisObject=e;e=f;f=w}var j=b==="one"?c.proxy(e,function(o){c(this).unbind(o,j);return e.apply(this,arguments)}):e;return d==="unload"&&b!=="one"?this.one(d,f,e,thisObject):this.each(function(){c.event.add(this,d,j,f)})}});c.fn.extend({unbind:function(a,b){if(typeof a==="object"&&!a.preventDefault){for(var d in a)this.unbind(d,a[d]);return this}return this.each(function(){c.event.remove(this,a,b)})},trigger:function(a, +b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){a=c.Event(a);a.preventDefault();a.stopPropagation();c.event.trigger(a,b,this[0]);return a.result}},toggle:function(a){for(var b=arguments,d=1;d0){y=u;break}}u=u[g]}m[r]=y}}}var f=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, +e=0,i=Object.prototype.toString,j=false,o=true;[0,0].sort(function(){o=false;return 0});var p=function(g,h,k,m){k=k||[];var r=h=h||s;if(h.nodeType!==1&&h.nodeType!==9)return[];if(!g||typeof g!=="string")return k;for(var q=[],v,u,y,S,I=true,N=x(h),J=g;(f.exec(""),v=f.exec(J))!==null;){J=v[3];q.push(v[1]);if(v[2]){S=v[3];break}}if(q.length>1&&t.exec(g))if(q.length===2&&n.relative[q[0]])u=ia(q[0]+q[1],h);else for(u=n.relative[q[0]]?[h]:p(q.shift(),h);q.length;){g=q.shift();if(n.relative[g])g+=q.shift(); +u=ia(g,u)}else{if(!m&&q.length>1&&h.nodeType===9&&!N&&n.match.ID.test(q[0])&&!n.match.ID.test(q[q.length-1])){v=p.find(q.shift(),h,N);h=v.expr?p.filter(v.expr,v.set)[0]:v.set[0]}if(h){v=m?{expr:q.pop(),set:B(m)}:p.find(q.pop(),q.length===1&&(q[0]==="~"||q[0]==="+")&&h.parentNode?h.parentNode:h,N);u=v.expr?p.filter(v.expr,v.set):v.set;if(q.length>0)y=B(u);else I=false;for(;q.length;){var E=q.pop();v=E;if(n.relative[E])v=q.pop();else E="";if(v==null)v=h;n.relative[E](y,v,N)}}else y=[]}y||(y=u);if(!y)throw"Syntax error, unrecognized expression: "+ +(E||g);if(i.call(y)==="[object Array]")if(I)if(h&&h.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&F(h,y[g])))k.push(u[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&k.push(u[g]);else k.push.apply(k,y);else B(y,k);if(S){p(S,r,k,m);p.uniqueSort(k)}return k};p.uniqueSort=function(g){if(D){j=o;g.sort(D);if(j)for(var h=1;h":function(g,h){var k=typeof h==="string";if(k&&!/\W/.test(h)){h=h.toLowerCase();for(var m=0,r=g.length;m=0))k||m.push(v);else if(k)h[q]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()}, +CHILD:function(g){if(g[1]==="nth"){var h=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=h[1]+(h[2]||1)-0;g[3]=h[3]-0}g[0]=e++;return g},ATTR:function(g,h,k,m,r,q){h=g[1].replace(/\\/g,"");if(!q&&n.attrMap[h])g[1]=n.attrMap[h];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,h,k,m,r){if(g[1]==="not")if((f.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=p(g[3],null,null,h);else{g=p.filter(g[3],h,k,true^r);k||m.push.apply(m, +g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,h,k){return!!p(k[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)}, +text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}}, +setFilters:{first:function(g,h){return h===0},last:function(g,h,k,m){return h===m.length-1},even:function(g,h){return h%2===0},odd:function(g,h){return h%2===1},lt:function(g,h,k){return hk[3]-0},nth:function(g,h,k){return k[3]-0===h},eq:function(g,h,k){return k[3]-0===h}},filter:{PSEUDO:function(g,h,k,m){var r=h[1],q=n.filters[r];if(q)return q(g,k,h,m);else if(r==="contains")return(g.textContent||g.innerText||a([g])||"").indexOf(h[3])>=0;else if(r==="not"){h= +h[3];k=0;for(m=h.length;k=0}},ID:function(g,h){return g.nodeType===1&&g.getAttribute("id")===h},TAG:function(g,h){return h==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===h},CLASS:function(g,h){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(h)>-1},ATTR:function(g,h){var k=h[1];g=n.attrHandle[k]?n.attrHandle[k](g):g[k]!=null?g[k]:g.getAttribute(k);k=g+"";var m=h[2];h=h[4];return g==null?m==="!=":m=== +"="?k===h:m==="*="?k.indexOf(h)>=0:m==="~="?(" "+k+" ").indexOf(h)>=0:!h?k&&g!==false:m==="!="?k!==h:m==="^="?k.indexOf(h)===0:m==="$="?k.substr(k.length-h.length)===h:m==="|="?k===h||k.substr(0,h.length+1)===h+"-":false},POS:function(g,h,k,m){var r=n.setFilters[h[2]];if(r)return r(g,k,h,m)}}},t=n.match.POS;for(var z in n.match){n.match[z]=new RegExp(n.match[z].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[z]=new RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[z].source.replace(/\\(\d+)/g,function(g, +h){return"\\"+(h-0+1)}))}var B=function(g,h){g=Array.prototype.slice.call(g,0);if(h){h.push.apply(h,g);return h}return g};try{Array.prototype.slice.call(s.documentElement.childNodes,0)}catch(C){B=function(g,h){h=h||[];if(i.call(g)==="[object Array]")Array.prototype.push.apply(h,g);else if(typeof g.length==="number")for(var k=0,m=g.length;k";var k=s.documentElement;k.insertBefore(g,k.firstChild);if(s.getElementById(h)){n.find.ID=function(m,r,q){if(typeof r.getElementById!=="undefined"&&!q)return(r=r.getElementById(m[1]))?r.id===m[1]||typeof r.getAttributeNode!=="undefined"&& +r.getAttributeNode("id").nodeValue===m[1]?[r]:w:[]};n.filter.ID=function(m,r){var q=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&q&&q.nodeValue===r}}k.removeChild(g);k=g=null})();(function(){var g=s.createElement("div");g.appendChild(s.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(h,k){k=k.getElementsByTagName(h[1]);if(h[1]==="*"){h=[];for(var m=0;k[m];m++)k[m].nodeType===1&&h.push(k[m]);k=h}return k};g.innerHTML=""; +if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(h){return h.getAttribute("href",2)};g=null})();s.querySelectorAll&&function(){var g=p,h=s.createElement("div");h.innerHTML="

    ";if(!(h.querySelectorAll&&h.querySelectorAll(".TEST").length===0)){p=function(m,r,q,v){r=r||s;if(!v&&r.nodeType===9&&!x(r))try{return B(r.querySelectorAll(m),q)}catch(u){}return g(m,r,q,v)};for(var k in g)p[k]=g[k];h=null}}(); +(function(){var g=s.createElement("div");g.innerHTML="
    ";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(h,k,m){if(typeof k.getElementsByClassName!=="undefined"&&!m)return k.getElementsByClassName(h[1])};g=null}}})();var F=s.compareDocumentPosition?function(g,h){return g.compareDocumentPosition(h)&16}:function(g, +h){return g!==h&&(g.contains?g.contains(h):true)},x=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false},ia=function(g,h){var k=[],m="",r;for(h=h.nodeType?[h]:h;r=n.match.PSEUDO.exec(g);){m+=r[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;r=0;for(var q=h.length;r=0===d})};c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,f=0,e=this.length;f0)for(var i=d;i0},closest:function(a,b){if(c.isArray(a)){var d=[],f=this[0],e,i= +{},j;if(f&&a.length){e=0;for(var o=a.length;e-1:c(f).is(e)){d.push({selector:j,elem:f});delete i[j]}}f=f.parentNode}}return d}var p=c.expr.match.POS.test(a)?c(a,b||this.context):null;return this.map(function(n,t){for(;t&&t.ownerDocument&&t!==b;){if(p?p.index(t)>-1:c(t).is(a))return t;t=t.parentNode}return null})},index:function(a){if(!a||typeof a=== +"string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){a=typeof a==="string"?c(a,b||this.context):c.makeArray(a);b=c.merge(this.get(),a);return this.pushStack(sa(a[0])||sa(b[0])?b:c.unique(b))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode", +d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")? +a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,f){var e=c.map(this,b,d);ab.test(a)||(f=d);if(f&&typeof f==="string")e=c.filter(f,e);e=this.length>1?c.unique(e):e;if((this.length>1||cb.test(f))&&bb.test(a))e=e.reverse();return this.pushStack(e,a,R.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return c.find.matches(a,b)},dir:function(a,b,d){var f=[];for(a=a[b];a&&a.nodeType!==9&&(d===w||!c(a).is(d));){a.nodeType=== +1&&f.push(a);a=a[b]}return f},nth:function(a,b,d){b=b||1;for(var f=0;a;a=a[d])if(a.nodeType===1&&++f===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var Ga=/ jQuery\d+="(?:\d+|null)"/g,Y=/^\s+/,db=/(<([\w:]+)[^>]*?)\/>/g,eb=/^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i,Ha=/<([\w:]+)/,fb=/"},G={option:[1,""], +legend:[1,"
    ","
    "],thead:[1,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],col:[2,"","
    "],area:[1,"",""],_default:[0,"",""]};G.optgroup=G.option;G.tbody=G.tfoot=G.colgroup=G.caption=G.thead;G.th=G.td;if(!c.support.htmlSerialize)G._default=[1,"div
    ","
    "];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=c(this); +return d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==w)return this.empty().append((this[0]&&this[0].ownerDocument||s).createTextNode(a));return c.getText(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this}, +wrapInner:function(a){return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&& +this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this.nextSibling)});else if(arguments.length){var a=this.pushStack(this, +"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,f=this.ownerDocument;if(!d){d=f.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(Ga,"").replace(Y,"")],f)[0]}else return this.cloneNode(true)});if(a===true){ta(this,b);ta(this.find("*"),b.find("*"))}return b},html:function(a){if(a===w)return this[0]&&this[0].nodeType=== +1?this[0].innerHTML.replace(Ga,""):null;else if(typeof a==="string"&&!/ +{% endif %} +{% endblock %} +{% block body %} + +

    {{ indextitle }}

    + +
    + {%- for (letter, entries) in content %} + {{ letter }} + {%- if not loop.last %} | {% endif %} + {%- endfor %} +
    + + + {%- for letter, entries in content %} + + + {%- for (name, grouptype, group, page, anchor, extra, qualifier, description) + in entries %} + + + + {%- endfor %} + {%- endfor %} +
     
    + {{ letter }}
    {% if grouptype == 1 -%} + + {%- endif %}{% if grouptype == 2 %}   {% endif %} + {% if page %}{% endif -%} + {{ name|e }} + {%- if page %}{% endif %} + {%- if extra %} ({{ extra|e }}){% endif -%} + {% if qualifier %}{{ qualifier|e }}:{% endif %} + {{ description|e }}
    + +{% endblock %} diff --git a/sphinx/themes/basic/layout.html b/sphinx/themes/basic/layout.html index d3ef88ab..c844dabc 100644 --- a/sphinx/themes/basic/layout.html +++ b/sphinx/themes/basic/layout.html @@ -98,7 +98,7 @@ var DOCUMENTATION_OPTIONS = { URL_ROOT: '{{ pathto("", 1) }}', VERSION: '{{ release|e }}', - COLLAPSE_MODINDEX: false, + COLLAPSE_INDEX: false, FILE_SUFFIX: '{{ file_suffix }}', HAS_SOURCE: {{ has_source|lower }} }; diff --git a/sphinx/themes/basic/modindex.html b/sphinx/themes/basic/modindex.html deleted file mode 100644 index 96f8ac43..00000000 --- a/sphinx/themes/basic/modindex.html +++ /dev/null @@ -1,52 +0,0 @@ -{# - basic/modindex.html - ~~~~~~~~~~~~~~~~~~~ - - Template for the module index. - - :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -#} -{% extends "layout.html" %} -{% set title = _('Global Module Index') %} -{% block extrahead %} -{{ super() }} -{% if not embedded and collapse_modindex %} - -{% endif %} -{% endblock %} -{% block body %} - -

    {{ _('Global Module Index') }}

    - -
    - {%- for letter in letters %} - {{ letter }} {% if not loop.last %}| {% endif %} - {%- endfor %} -
    - - - {%- for modname, collapse, cgroup, indent, fname, synops, pform, dep, stripped in modindexentries %} - {%- if not modname -%} - - - {%- else -%} - - - - {%- endif -%} - {% endfor %} -
     
    {{ fname }}
    {% if collapse -%} - - {%- endif %}{% if indent %}   {% endif %} - {% if fname %}{% endif -%} - {{ stripped|e }}{{ modname|e }} - {%- if fname %}{% endif %} - {%- if pform and pform[0] %} ({{ pform|join(', ') }}){% endif -%} - {% if dep %}{{ _('Deprecated')}}:{% endif %} - {{ synops|e }}
    - -{% endblock %} diff --git a/sphinx/themes/basic/static/doctools.js b/sphinx/themes/basic/static/doctools.js index e91c4f3e..28c7a6da 100644 --- a/sphinx/themes/basic/static/doctools.js +++ b/sphinx/themes/basic/static/doctools.js @@ -111,7 +111,7 @@ var Documentation = { init : function() { this.fixFirefoxAnchorBug(); this.highlightSearchWords(); - this.initModIndex(); + this.initIndexTable(); }, /** @@ -192,9 +192,9 @@ var Documentation = { }, /** - * init the modindex toggle buttons + * init the domain index toggle buttons */ - initModIndex : function() { + initIndexTable : function() { var togglers = $('img.toggler').click(function() { var src = $(this).attr('src'); var idnum = $(this).attr('id').substr(7); @@ -204,7 +204,7 @@ var Documentation = { else $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); }).css('display', ''); - if (DOCUMENTATION_OPTIONS.COLLAPSE_MODINDEX) { + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { togglers.click(); } }, diff --git a/sphinx/util/texescape.py b/sphinx/util/texescape.py index 73790829..4cabd9b2 100644 --- a/sphinx/util/texescape.py +++ b/sphinx/util/texescape.py @@ -99,6 +99,7 @@ tex_replacements = [ ] tex_escape_map = {} +tex_replace_map = {} tex_hl_escape_map_old = {} # replacement map for Pygments <= 1.1 tex_hl_escape_map_new = {} # replacement map for Pygments >= 1.2 _old_cmd_chars = {ord(u'\\'): u'@', ord(u'{'): u'[', ord(u'}'): u']'} @@ -106,6 +107,7 @@ _old_cmd_chars = {ord(u'\\'): u'@', ord(u'{'): u'[', ord(u'}'): u']'} def init(): for a, b in tex_replacements: tex_escape_map[ord(a)] = b + tex_replace_map[ord(a)] = u'_' for a, b in tex_replacements: if a in u'[]{}\\': continue diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 74d2c8d2..c75424b2 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -24,7 +24,7 @@ from sphinx import highlighting from sphinx.errors import SphinxError from sphinx.locale import admonitionlabels, versionlabels, _ from sphinx.util.osutil import ustrftime -from sphinx.util.texescape import tex_escape_map +from sphinx.util.texescape import tex_escape_map, tex_replace_map from sphinx.util.smartypants import educateQuotesLatex HEADER = r'''%% Generated by Sphinx. @@ -47,7 +47,6 @@ HEADER = r'''%% Generated by Sphinx. \newcommand{\sphinxlogo}{%(logo)s} \renewcommand{\releasename}{%(releasename)s} %(makeindex)s -%(makemodindex)s ''' BEGIN_DOC = r''' @@ -58,9 +57,6 @@ BEGIN_DOC = r''' ''' FOOTER = r''' -%(footer)s -\renewcommand{\indexname}{%(modindexname)s} -%(printmodindex)s \renewcommand{\indexname}{%(indexname)s} %(printindex)s \end{document} @@ -146,12 +142,10 @@ class LaTeXTranslator(nodes.NodeVisitor): 'logo': '', 'releasename': 'Release', 'makeindex': '\\makeindex', - 'makemodindex': '\\makemodindex', 'shorthandoff': '', 'maketitle': '\\maketitle', 'tableofcontents': '\\tableofcontents', 'footer': '', - 'printmodindex': '\\printmodindex', 'printindex': '\\printindex', } @@ -176,7 +170,6 @@ class LaTeXTranslator(nodes.NodeVisitor): 'author': document.settings.author, 'releasename': _('Release'), 'preamble': builder.config.latex_preamble, - 'modindexname': _('Module Index'), 'indexname': _('Index'), }) if document.settings.docclass == 'howto': @@ -204,9 +197,6 @@ class LaTeXTranslator(nodes.NodeVisitor): self.elements['fncychap'] = '\\usepackage[Sonny]{fncychap}' else: self.elements['classoptions'] += ',english' - if not builder.config.latex_use_modindex: - self.elements['makemodindex'] = '' - self.elements['printmodindex'] = '' # allow the user to override them all self.elements.update(builder.config.latex_elements) @@ -243,11 +233,60 @@ class LaTeXTranslator(nodes.NodeVisitor): self.first_param = 0 def astext(self): - return (HEADER % self.elements + self.highlighter.get_stylesheet() + - u''.join(self.body) + FOOTER % self.elements) + return (HEADER % self.elements + + self.highlighter.get_stylesheet() + + u''.join(self.body) + + '\n' + self.elements['footer'] + '\n' + + self.generate_indices() + + FOOTER % self.elements) + + def hypertarget(self, target, text='', anchor=True): + #return '\\hypertarget{%s}{%s}' % (self.idescape(target), text) + return (anchor and '\\phantomsection' or '') + \ + '\\label{%s}%s' % (self.idescape(target), text) + + def hyperlink(self, target): + #return '\\hyperlink{%s}{' % (self.idescape(target)) + return '\\hyperref[%s]{' % (self.idescape(target)) def idescape(self, id): - return str(unicode(id).translate(tex_escape_map)) + return str(unicode(id).translate(tex_replace_map)) + + def generate_indices(self): + def generate(content, collapsed): + ret.append('\\begin{theindex}\n') + ret.append('\\def\\bigletter#1{{\\Large\\sffamily#1}' + '\\nopagebreak\\vspace{1mm}}\n') + for i, (letter, entries) in enumerate(content): + if i > 0: + ret.append('\\indexspace\n') + ret.append('\\bigletter{%s}\n' % letter) + for entry in entries: + if not entry[4]: + continue + ret.append('\\item {\\texttt{%s}}, \\pageref{%s}' % + (self.encode(entry[0]), entry[4])) + ret.append('\\end{theindex}\n') + + ret = [] + # latex_domain_indices can be False/True or a list of index names + indices_config = self.builder.config.latex_domain_indices + if indices_config: + for domain in self.builder.env.domains.itervalues(): + for indexinfo in domain.indices: + indexname = '%s-%s' % (domain.name, indexinfo[0]) + if isinstance(indices_config, list): + if indexname not in indices_config: + continue + if not domain.has_index_entries(indexinfo[0], + self.builder.docnames): + continue + ret.append('\\renewcommand{\\indexname}{%s}\n' % + indexinfo[1]) + generate(*domain.get_index(indexinfo[0], + self.builder.docnames)) + + return ''.join(ret) def visit_document(self, node): self.footnotestack.append(self.collect_footnotes(node)) @@ -261,8 +300,7 @@ class LaTeXTranslator(nodes.NodeVisitor): self.body.append('\n\\appendix\n') self.first_document = -1 if node.has_key('docname'): - self.body.append('\\hypertarget{--doc-%s}{}' % - self.idescape(node['docname'])) + self.body.append(self.hypertarget('--doc-' + node['docname'])) # "- 1" because the level is increased before the title is visited self.sectionlevel = self.top_sectionlevel - 1 def depart_document(self, node): @@ -275,21 +313,17 @@ class LaTeXTranslator(nodes.NodeVisitor): for bi in self.bibitems: # cite_key: underscores must not be escaped cite_key = bi[0].replace(r"\_", "_") - self.body.append('\\bibitem[%s]{%s}{\hypertarget{%s}{} %s}\n' % + self.body.append('\\bibitem[%s]{%s}{%s %s}\n' % (bi[0], cite_key, - self.idescape(cite_key.lower()), bi[1])) + self.hypertarget(cite_key.lower()), bi[1])) self.body.append('\\end{thebibliography}\n') self.bibitems = [] def visit_start_of_file(self, node): - # This marks the begin of a new file; therefore the current module and - # class must be reset - self.body.append('\n\\resetcurrentobjects\n') - # and also, new footnotes + # collect new footnotes self.footnotestack.append(self.collect_footnotes(node)) # also add a document target - self.body.append('\\hypertarget{--doc-%s}{}' % - self.idescape(node['docname'])) + self.body.append(self.hypertarget('--doc-' + node['docname'])) self.curfilestack.append(node['docname']) def collect_footnotes(self, node): @@ -321,14 +355,10 @@ class LaTeXTranslator(nodes.NodeVisitor): if not self.this_is_the_title: self.sectionlevel += 1 self.body.append('\n\n') - if self.next_section_target: - self.body.append(r'\hypertarget{%s}{}' % - self.idescape(self.next_section_target)) - self.next_section_target = None #if node.get('ids'): # for id in node['ids']: # if id not in self.written_ids: - # self.body.append(r'\hypertarget{%s}{}' % id) + # self.body.append(self.hypertarget(id)) # self.written_ids.add(id) def depart_section(self, node): self.sectionlevel = max(self.sectionlevel - 1, @@ -400,6 +430,12 @@ class LaTeXTranslator(nodes.NodeVisitor): # just use "subparagraph", it's not numbered anyway self.body.append(r'\%s{' % self.sectionnames[-1]) self.context.append('}\n') + + if self.next_section_target: + self.context[-1] += self.hypertarget(self.next_section_target, + anchor=False) + self.next_section_target = None + elif isinstance(parent, (nodes.topic, nodes.sidebar)): self.body.append(r'\textbf{') self.context.append('}\n\n\medskip\n\n') @@ -438,7 +474,7 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_desc_signature(self, node): if node.parent['objtype'] != 'describe' and node['ids']: - hyper = '\\hypertarget{%s}{}' % self.idescape(node['ids'][0]) + hyper = self.hypertarget(node['ids'][0]) else: hyper = '' self.body.append(hyper) @@ -700,7 +736,7 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_term(self, node): ctx = '] \\leavevmode' if node.has_key('ids') and node['ids']: - ctx += '\\hypertarget{%s}{}' % self.idescape(node['ids'][0]) + ctx += self.hypertarget(node['ids'][0]) self.body.append('\\item[') self.context.append(ctx) def depart_term(self, node): @@ -759,14 +795,14 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_module(self, node): modname = node['modname'] - self.body.append('\n\\hypertarget{module-%s}{}' % - self.idescape(modname.replace(' ',''))) - self.body.append('\n\\declaremodule[%s]{}{%s}' % ( - modname.replace('_', ''), self.encode(modname))) - self.body.append('\n\\modulesynopsis{%s}' % - self.encode(node['synopsis'])) - if node.has_key('platform'): - self.body.append('\\platform{%s}' % self.encode(node['platform'])) + self.body.append('\n' + + self.hypertarget('module-' + modname.replace(' ',''))) + #self.body.append('\n\\declaremodule[%s]{}{%s}' % ( + # modname.replace('_', ''), self.encode(modname))) + #self.body.append('\n\\modulesynopsis{%s}' % + # self.encode(node['synopsis'])) + #if node.has_key('platform'): + # self.body.append('\\platform{%s}' % self.encode(node['platform'])) def depart_module(self, node): pass @@ -925,7 +961,7 @@ class LaTeXTranslator(nodes.NodeVisitor): # indexing uses standard LaTeX index markup, so the targets # will be generated differently if not id.startswith('index-'): - self.body.append(r'\hypertarget{%s}{}' % self.idescape(id)) + self.body.append(self.hypertarget(id)) if node.has_key('refid') and node['refid'] not in self.written_ids: parindex = node.parent.index(node) @@ -986,14 +1022,14 @@ class LaTeXTranslator(nodes.NodeVisitor): self.context.append('}') elif uri.startswith('#'): # references to labels - self.body.append('\\hyperlink{%s}{' % self.idescape(uri[1:])) + self.body.append(self.hyperlink(uri[1:])) self.context.append('}') elif uri.startswith('%'): # references to documents or labels inside documents hashindex = uri.find('#') targetname = (hashindex == -1) and '--doc-' + uri[1:] \ or uri[hashindex+1:] - self.body.append('\\hyperlink{%s}{' % self.idescape(targetname)) + self.body.append(self.hyperlink(targetname)) self.context.append('}') elif uri.startswith('@token'): if self.in_production_list: diff --git a/tests/test_build_html.py b/tests/test_build_html.py index 5608960d..bfa0f1cf 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -170,7 +170,7 @@ HTML_XPATH = { ".//div[@class='footer']": 'Georg Brandl & Team', ".//a[@href='http://python.org/']": '', ".//li/a[@href='genindex.html']/em": 'Index', - ".//li/a[@href='modindex.html']/em": 'Module Index', + ".//li/a[@href='py-modindex.html']/em": 'Module Index', ".//li/a[@href='search.html']/em": 'Search Page', # custom sidebar only for contents ".//h4": 'Contents sidebar', -- cgit v1.2.1 From 413596916cafb9d7b64e1779267db2606aa3a7bb Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sat, 20 Feb 2010 14:18:48 +0100 Subject: Remove obsolete hyperref option. --- sphinx/texinputs/sphinx.sty | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sphinx/texinputs/sphinx.sty b/sphinx/texinputs/sphinx.sty index a0c14cb4..0d8c8445 100644 --- a/sphinx/texinputs/sphinx.sty +++ b/sphinx/texinputs/sphinx.sty @@ -440,8 +440,7 @@ % Include hyperref last. \RequirePackage[colorlinks,breaklinks, linkcolor=InnerLinkColor,filecolor=OuterLinkColor, - menucolor=OuterLinkColor,pagecolor=OuterLinkColor, - urlcolor=OuterLinkColor]{hyperref} + menucolor=OuterLinkColor,urlcolor=OuterLinkColor]{hyperref} % From docutils.writers.latex2e \providecommand{\DUspan}[2]{% -- cgit v1.2.1 From bf67038094392f9cbf2fe9403353d384c7b6a9c1 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sat, 20 Feb 2010 14:20:39 +0100 Subject: Do not throw away existing section ids. --- sphinx/environment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/environment.py b/sphinx/environment.py index c35d0e59..f03c4956 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -122,7 +122,7 @@ class MoveModuleTargets(Transform): if node['ids'][0].startswith('module-') and \ node.parent.__class__ is nodes.section and \ node.has_key('ismod'): - node.parent['ids'] = node['ids'] + node.parent['ids'][0:0] = node['ids'] node.parent.remove(node) -- cgit v1.2.1 From 976a208f3c7cebff362aaf609e0b2e2c8944ff57 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sat, 20 Feb 2010 14:21:03 +0100 Subject: Remove "group" identifier from get_index() entries; let the template figure that out. --- sphinx/domains/__init__.py | 24 +++++++++++++++++++++++- sphinx/domains/python.py | 16 ++++++---------- sphinx/themes/basic/domainindex.html | 9 ++++++--- sphinx/writers/latex.py | 10 +++++++--- tests/test_build_latex.py | 3 ++- 5 files changed, 44 insertions(+), 18 deletions(-) diff --git a/sphinx/domains/__init__.py b/sphinx/domains/__init__.py index e5d3b06f..b67d090c 100644 --- a/sphinx/domains/__init__.py +++ b/sphinx/domains/__init__.py @@ -189,7 +189,29 @@ class Domain(object): Return entries for the index given by *name*. If *docnames* is given, restrict to entries referring to these docnames. - XXX document return format + The return value is a tuple of ``(content, collapse)``, where *collapse* + is a boolean that determines if sub-entries should start collapsed (for + output formats that support collapsing sub-entries). + + *content* is a sequence of ``(letter, entries)`` tuples, where *letter* + is the "heading" for the given *entries*, usually the starting letter. + + *entries* is a sequence of single entries, where a single entry is a + sequence ``[name, subtype, docname, anchor, extra, qualifier, descr]``. + The items in this sequence have the following meaning: + + - `name` -- the name of the index entry to be displayed + - `subtype` -- sub-entry related type: + 0 -- normal entry + 1 -- entry with sub-entries + 2 -- sub-entry + - `docname` -- docname where the entry is located + - `anchor` -- anchor for the entry within `docname` + - `extra` -- extra info for the entry + - `qualifier` -- qualifier for the description + - `descr` -- description for the entry + + Qualifier and description are not rendered e.g. in LaTeX output. """ return [], False diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 995914c1..dfa40798 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -574,7 +574,6 @@ class PythonDomain(Domain): # sort out collapsable modules prev_modname = '' num_toplevels = 0 - current_group = 0 # collapse group for modname, (docname, synopsis, platforms, deprecated) in modules: if docnames and docname not in docnames: continue @@ -601,19 +600,16 @@ class PythonDomain(Domain): entries[-1][1] = 1 elif not prev_modname.startswith(package): # submodule without parent in list, add dummy entry - current_group += 1 - entries.append([stripped + package, 1, current_group, - '', '', '', '', '']) - grouptype = 2 + entries.append([stripped + package, 1, '', '', '', '', '']) + subtype = 2 else: num_toplevels += 1 - current_group += 1 - grouptype = 0 + subtype = 0 qualifier = deprecated and _('Deprecated') or '' - entries.append([stripped + modname, grouptype, current_group, - docname, 'module-' + stripped + modname, - platforms, qualifier, synopsis]) + entries.append([stripped + modname, subtype, docname, + 'module-' + stripped + modname, platforms, + qualifier, synopsis]) prev_modname = modname # apply heuristics when to collapse modindex at page load: diff --git a/sphinx/themes/basic/domainindex.html b/sphinx/themes/basic/domainindex.html index 9f4d4637..0aca7e69 100644 --- a/sphinx/themes/basic/domainindex.html +++ b/sphinx/themes/basic/domainindex.html @@ -19,6 +19,8 @@ {% endblock %} {% block body %} + {%- set curr_group = 0 %} +

    {{ indextitle }}

    @@ -33,11 +35,12 @@   {{ letter }} - {%- for (name, grouptype, group, page, anchor, extra, qualifier, description) + {%- for (name, grouptype, page, anchor, extra, qualifier, description) in entries %} - + {%- if grouptype == 1 %}{% set curr_group = curr_group + 1 %}{% endif %} + {% if grouptype == 1 -%} - {%- endif %} {% if grouptype == 2 %}   {% endif %} diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index c75424b2..45028e7d 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -262,10 +262,13 @@ class LaTeXTranslator(nodes.NodeVisitor): ret.append('\\indexspace\n') ret.append('\\bigletter{%s}\n' % letter) for entry in entries: - if not entry[4]: + if not entry[3]: continue - ret.append('\\item {\\texttt{%s}}, \\pageref{%s}' % - (self.encode(entry[0]), entry[4])) + ret.append('\\item {\\texttt{%s}}' % self.encode(entry[0])) + if entry[4]: + # add "extra" info + ret.append(' \\emph{(%s)}' % self.encode(entry[4])) + ret.append(', \\pageref{%s}' % self.idescape(entry[3])) ret.append('\\end{theindex}\n') ret = [] @@ -968,6 +971,7 @@ class LaTeXTranslator(nodes.NodeVisitor): try: next = node.parent[parindex+1] if isinstance(next, nodes.section): + # postpone the label until after the sectioning command self.next_section_target = node['refid'] return except IndexError: diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py index 96a7900b..4f40cc11 100644 --- a/tests/test_build_latex.py +++ b/tests/test_build_latex.py @@ -87,7 +87,8 @@ def test_latex(app): stdout, stderr = p.communicate() if p.returncode != 0: print stdout + print stderr del app.cleanup_trees[:] - assert False, 'latex exited with error' + assert False, 'latex exited with return code %s' % p.returncode finally: os.chdir(cwd) -- cgit v1.2.1 From 7338cf977e2a17e4d3730475a66f3161be14f3f4 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sat, 20 Feb 2010 14:28:58 +0100 Subject: Observe the deprecated *_use_modindex config values. --- sphinx/builders/html.py | 4 ++++ sphinx/writers/latex.py | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index c722a195..6dcdc27e 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -241,6 +241,10 @@ class StandaloneHTMLBuilder(Builder): if isinstance(indices_config, list): if indexname not in indices_config: continue + # deprecated config value + if indexname == 'py-modindex' and \ + not self.config.html_use_modindex: + continue if domain.has_index_entries(indexinfo[0]): self.domain_indices.append((domain.name,) + indexinfo) diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 45028e7d..c6dcf3bd 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -268,7 +268,7 @@ class LaTeXTranslator(nodes.NodeVisitor): if entry[4]: # add "extra" info ret.append(' \\emph{(%s)}' % self.encode(entry[4])) - ret.append(', \\pageref{%s}' % self.idescape(entry[3])) + ret.append(', \\pageref{%s}\n' % self.idescape(entry[3])) ret.append('\\end{theindex}\n') ret = [] @@ -281,6 +281,10 @@ class LaTeXTranslator(nodes.NodeVisitor): if isinstance(indices_config, list): if indexname not in indices_config: continue + # deprecated config value + if indexname == 'py-modindex' and \ + not self.builder.config.latex_use_modindex: + continue if not domain.has_index_entries(indexinfo[0], self.builder.docnames): continue -- cgit v1.2.1 From 2f2b2185f35a4c1ce7d849c4b98890096174679c Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sat, 20 Feb 2010 14:45:39 +0100 Subject: Get rid of the "module" node. --- sphinx/addnodes.py | 3 --- sphinx/domains/python.py | 8 ++------ sphinx/environment.py | 10 ++++++---- sphinx/writers/html.py | 5 ----- sphinx/writers/latex.py | 47 ++++++++++++++++++----------------------------- sphinx/writers/text.py | 7 ------- 6 files changed, 26 insertions(+), 54 deletions(-) diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py index 2b62633f..0a2f0f7f 100644 --- a/sphinx/addnodes.py +++ b/sphinx/addnodes.py @@ -90,9 +90,6 @@ class abbreviation(nodes.Inline, nodes.TextElement): pass # glossary class glossary(nodes.Element): pass -# module declaration -class module(nodes.Element): pass - # start of a file, used in the LaTeX builder only class start_of_file(nodes.Element): pass diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index dfa40798..fcd2216d 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -351,16 +351,12 @@ class PyModule(Directive): env.domaindata['py']['modules'][modname] = \ (env.docname, self.options.get('synopsis', ''), self.options.get('platform', ''), 'deprecated' in self.options) - modulenode = addnodes.module() - modulenode['modname'] = modname - modulenode['synopsis'] = self.options.get('synopsis', '') targetnode = nodes.target('', '', ids=['module-' + modname], ismod=True) self.state.document.note_explicit_target(targetnode) - ret = [modulenode, targetnode] + ret = [targetnode] # XXX this behavior of the module directive is a mess... if 'platform' in self.options: platform = self.options['platform'] - modulenode['platform'] = platform node = nodes.paragraph() node += nodes.emphasis('', _('Platforms: ')) node += nodes.Text(platform, platform) @@ -371,7 +367,7 @@ class PyModule(Directive): indextext = _('%s (module)') % modname inode = addnodes.index(entries=[('single', indextext, 'module-' + modname, modname)]) - ret.insert(0, inode) + ret.append(inode) return ret diff --git a/sphinx/environment.py b/sphinx/environment.py index f03c4956..c5668308 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -109,7 +109,8 @@ class DefaultSubstitutions(Transform): class MoveModuleTargets(Transform): """ - Move module targets to their nearest enclosing section title. + Move module targets that are the first thing in a section to the section + title. XXX Python specific """ @@ -119,9 +120,10 @@ class MoveModuleTargets(Transform): for node in self.document.traverse(nodes.target): if not node['ids']: continue - if node['ids'][0].startswith('module-') and \ - node.parent.__class__ is nodes.section and \ - node.has_key('ismod'): + if (node.has_key('ismod') and + node.parent.__class__ is nodes.section and + # index 0 is the section title node + node.parent.index(node) == 1): node.parent['ids'][0:0] = node['ids'] node.parent.remove(node) diff --git a/sphinx/writers/html.py b/sphinx/writers/html.py index 1ca61254..2db4aeaa 100644 --- a/sphinx/writers/html.py +++ b/sphinx/writers/html.py @@ -352,11 +352,6 @@ class HTMLTranslator(BaseTranslator): def depart_acks(self, node): pass - def visit_module(self, node): - pass - def depart_module(self, node): - pass - def visit_hlist(self, node): self.body.append('') def depart_hlist(self, node): diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index c6dcf3bd..4dc29bcf 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -220,7 +220,7 @@ class LaTeXTranslator(nodes.NodeVisitor): self.top_sectionlevel = 0 else: self.top_sectionlevel = 1 - self.next_section_target = None + self.next_section_ids = [] # flags self.verbatim = None self.in_title = 0 @@ -240,14 +240,14 @@ class LaTeXTranslator(nodes.NodeVisitor): self.generate_indices() + FOOTER % self.elements) - def hypertarget(self, target, text='', anchor=True): - #return '\\hypertarget{%s}{%s}' % (self.idescape(target), text) + def hypertarget(self, id, text='', anchor=True): + #return '\\hypertarget{%s}{%s}' % (self.idescape(id), text) return (anchor and '\\phantomsection' or '') + \ - '\\label{%s}%s' % (self.idescape(target), text) + '\\label{%s}%s' % (self.idescape(id), text) - def hyperlink(self, target): - #return '\\hyperlink{%s}{' % (self.idescape(target)) - return '\\hyperref[%s]{' % (self.idescape(target)) + def hyperlink(self, id): + #return '\\hyperlink{%s}{' % (self.idescape(id)) + return '\\hyperref[%s]{' % (self.idescape(id)) def idescape(self, id): return str(unicode(id).translate(tex_replace_map)) @@ -362,7 +362,8 @@ class LaTeXTranslator(nodes.NodeVisitor): if not self.this_is_the_title: self.sectionlevel += 1 self.body.append('\n\n') - #if node.get('ids'): + if node.get('ids'): + self.next_section_ids.extend(node['ids']) # for id in node['ids']: # if id not in self.written_ids: # self.body.append(self.hypertarget(id)) @@ -438,10 +439,12 @@ class LaTeXTranslator(nodes.NodeVisitor): self.body.append(r'\%s{' % self.sectionnames[-1]) self.context.append('}\n') - if self.next_section_target: - self.context[-1] += self.hypertarget(self.next_section_target, - anchor=False) - self.next_section_target = None + if self.next_section_ids: + for id in self.next_section_ids: + if id not in self.written_ids: + self.context[-1] += self.hypertarget(id, anchor=False) + self.written_ids.add(id) + self.next_section_ids = [] elif isinstance(parent, (nodes.topic, nodes.sidebar)): self.body.append(r'\textbf{') @@ -800,19 +803,6 @@ class LaTeXTranslator(nodes.NodeVisitor): def depart_hlistcol(self, node): pass - def visit_module(self, node): - modname = node['modname'] - self.body.append('\n' + - self.hypertarget('module-' + modname.replace(' ',''))) - #self.body.append('\n\\declaremodule[%s]{}{%s}' % ( - # modname.replace('_', ''), self.encode(modname))) - #self.body.append('\n\\modulesynopsis{%s}' % - # self.encode(node['synopsis'])) - #if node.has_key('platform'): - # self.body.append('\\platform{%s}' % self.encode(node['platform'])) - def depart_module(self, node): - pass - def latex_image_length(self, width_str): match = re.match('(\d*\.?\d*)\s*(\S*)', width_str) if not match: @@ -976,7 +966,7 @@ class LaTeXTranslator(nodes.NodeVisitor): next = node.parent[parindex+1] if isinstance(next, nodes.section): # postpone the label until after the sectioning command - self.next_section_target = node['refid'] + self.next_section_ids.append(node['refid']) return except IndexError: pass @@ -1035,9 +1025,8 @@ class LaTeXTranslator(nodes.NodeVisitor): elif uri.startswith('%'): # references to documents or labels inside documents hashindex = uri.find('#') - targetname = (hashindex == -1) and '--doc-' + uri[1:] \ - or uri[hashindex+1:] - self.body.append(self.hyperlink(targetname)) + id = (hashindex == -1) and '--doc-' + uri[1:] or uri[hashindex+1:] + self.body.append(self.hyperlink(id)) self.context.append('}') elif uri.startswith('@token'): if self.in_production_list: diff --git a/sphinx/writers/text.py b/sphinx/writers/text.py index a7e247ce..a60e821a 100644 --- a/sphinx/writers/text.py +++ b/sphinx/writers/text.py @@ -160,13 +160,6 @@ class TextTranslator(nodes.NodeVisitor): def depart_attribution(self, node): pass - def visit_module(self, node): - if node.has_key('platform'): - self.new_state(0) - self.add_text(_('Platform: %s') % node['platform']) - self.end_state() - raise nodes.SkipNode - def visit_desc(self, node): pass def depart_desc(self, node): -- cgit v1.2.1 From 15db9a93b9dff69893e34bbf66868ba409a97df6 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sat, 20 Feb 2010 19:47:22 +0100 Subject: Make id and reference handling in LaTeX output much more robust, by prefixing the source document name to label names. --- sphinx/environment.py | 3 +- sphinx/writers/latex.py | 85 ++++++++++++++++++++++++++++--------------------- 2 files changed, 50 insertions(+), 38 deletions(-) diff --git a/sphinx/environment.py b/sphinx/environment.py index c5668308..dacec2c1 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -155,7 +155,8 @@ class SortIds(Transform): class CitationReferences(Transform): """ - Handle citation references before the default docutils transform does. + Replace citation references by pending_xref nodes before the default + docutils transform tries to resolve them. """ default_priority = 619 diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 4dc29bcf..bc2bc14e 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -209,7 +209,6 @@ class LaTeXTranslator(nodes.NodeVisitor): self.next_table_colspec = None self.highlightlang = builder.config.highlight_language self.highlightlinenothreshold = sys.maxint - self.written_ids = set() self.footnotestack = [] self.curfilestack = [] self.handled_abbrs = set() @@ -220,7 +219,7 @@ class LaTeXTranslator(nodes.NodeVisitor): self.top_sectionlevel = 0 else: self.top_sectionlevel = 1 - self.next_section_ids = [] + self.next_section_ids = set() # flags self.verbatim = None self.in_title = 0 @@ -240,13 +239,13 @@ class LaTeXTranslator(nodes.NodeVisitor): self.generate_indices() + FOOTER % self.elements) - def hypertarget(self, id, text='', anchor=True): - #return '\\hypertarget{%s}{%s}' % (self.idescape(id), text) + def hypertarget(self, id, withdoc=True, anchor=True): + if withdoc: + id = self.curfilestack[-1] + ':' + id return (anchor and '\\phantomsection' or '') + \ - '\\label{%s}%s' % (self.idescape(id), text) + '\\label{%s}' % self.idescape(id) def hyperlink(self, id): - #return '\\hyperlink{%s}{' % (self.idescape(id)) return '\\hyperref[%s]{' % (self.idescape(id)) def idescape(self, id): @@ -268,7 +267,8 @@ class LaTeXTranslator(nodes.NodeVisitor): if entry[4]: # add "extra" info ret.append(' \\emph{(%s)}' % self.encode(entry[4])) - ret.append(', \\pageref{%s}\n' % self.idescape(entry[3])) + ret.append(', \\pageref{%s:%s}\n' % + (entry[2], self.idescape(entry[3]))) ret.append('\\end{theindex}\n') ret = [] @@ -307,7 +307,7 @@ class LaTeXTranslator(nodes.NodeVisitor): self.body.append('\n\\appendix\n') self.first_document = -1 if node.has_key('docname'): - self.body.append(self.hypertarget('--doc-' + node['docname'])) + self.body.append(self.hypertarget(':doc')) # "- 1" because the level is increased before the title is visited self.sectionlevel = self.top_sectionlevel - 1 def depart_document(self, node): @@ -318,11 +318,10 @@ class LaTeXTranslator(nodes.NodeVisitor): widest_label = bi[0] self.body.append('\n\\begin{thebibliography}{%s}\n' % widest_label) for bi in self.bibitems: - # cite_key: underscores must not be escaped - cite_key = bi[0].replace(r"\_", "_") + target = self.hypertarget(bi[2] + ':' + bi[0].lower(), + withdoc=False) self.body.append('\\bibitem[%s]{%s}{%s %s}\n' % - (bi[0], cite_key, - self.hypertarget(cite_key.lower()), bi[1])) + (bi[0], self.idescape(bi[0]), target, bi[1])) self.body.append('\\end{thebibliography}\n') self.bibitems = [] @@ -330,7 +329,7 @@ class LaTeXTranslator(nodes.NodeVisitor): # collect new footnotes self.footnotestack.append(self.collect_footnotes(node)) # also add a document target - self.body.append(self.hypertarget('--doc-' + node['docname'])) + self.next_section_ids.add(':doc') self.curfilestack.append(node['docname']) def collect_footnotes(self, node): @@ -363,11 +362,7 @@ class LaTeXTranslator(nodes.NodeVisitor): self.sectionlevel += 1 self.body.append('\n\n') if node.get('ids'): - self.next_section_ids.extend(node['ids']) - # for id in node['ids']: - # if id not in self.written_ids: - # self.body.append(self.hypertarget(id)) - # self.written_ids.add(id) + self.next_section_ids.update(node['ids']) def depart_section(self, node): self.sectionlevel = max(self.sectionlevel - 1, self.top_sectionlevel - 1) @@ -441,10 +436,8 @@ class LaTeXTranslator(nodes.NodeVisitor): if self.next_section_ids: for id in self.next_section_ids: - if id not in self.written_ids: - self.context[-1] += self.hypertarget(id, anchor=False) - self.written_ids.add(id) - self.next_section_ids = [] + self.context[-1] += self.hypertarget(id, anchor=False) + self.next_section_ids.clear() elif isinstance(parent, (nodes.topic, nodes.sidebar)): self.body.append(r'\textbf{') @@ -585,6 +578,7 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_label(self, node): if isinstance(node.parent, nodes.citation): self.bibitems[-1][0] = node.astext() + self.bibitems[-1][2] = self.curfilestack[-1] raise nodes.SkipNode def visit_tabular_col_spec(self, node): @@ -960,18 +954,28 @@ class LaTeXTranslator(nodes.NodeVisitor): if not id.startswith('index-'): self.body.append(self.hypertarget(id)) - if node.has_key('refid') and node['refid'] not in self.written_ids: - parindex = node.parent.index(node) + # postpone the labels until after the sectioning command + parindex = node.parent.index(node) + try: try: next = node.parent[parindex+1] - if isinstance(next, nodes.section): - # postpone the label until after the sectioning command - self.next_section_ids.append(node['refid']) - return except IndexError: - pass + # last node in parent, look at next after parent + # (for section of equal level) + next = node.parent.parent[node.parent.parent.index(node.parent)] + if isinstance(next, nodes.section): + if node.get('refid'): + self.next_section_ids.add(node['refid']) + self.next_section_ids.update(node['ids']) + return + except IndexError: + pass + if 'refuri' in node: + return + if node.get('refid'): add_target(node['refid']) - self.written_ids.add(node['refid']) + for id in node['ids']: + add_target(id) def depart_target(self, node): pass @@ -1019,13 +1023,19 @@ class LaTeXTranslator(nodes.NodeVisitor): self.body.append('\\href{%s}{' % self.encode_uri(uri)) self.context.append('}') elif uri.startswith('#'): - # references to labels - self.body.append(self.hyperlink(uri[1:])) + # references to labels in the same document + self.body.append(self.hyperlink(self.curfilestack[-1] + + ':' + uri[1:])) self.context.append('}') elif uri.startswith('%'): # references to documents or labels inside documents hashindex = uri.find('#') - id = (hashindex == -1) and '--doc-' + uri[1:] or uri[hashindex+1:] + if hashindex == -1: + # reference to the document + id = uri[1:] + '::doc' + else: + # reference to a label + id = uri[1:].replace('#', ':') self.body.append(self.hyperlink(id)) self.context.append('}') elif uri.startswith('@token'): @@ -1089,7 +1099,7 @@ class LaTeXTranslator(nodes.NodeVisitor): def visit_citation(self, node): # TODO maybe use cite bibitems - self.bibitems.append(['', '']) + self.bibitems.append(['', '', '']) # [citeid, citetext, docname] self.context.append(len(self.body)) def depart_citation(self, node): size = self.context.pop() @@ -1098,8 +1108,9 @@ class LaTeXTranslator(nodes.NodeVisitor): self.bibitems[-1][1] = text def visit_citation_reference(self, node): - citeid = node.astext() - self.body.append('\\cite{%s}' % citeid) + # This is currently never encountered, since citation_reference nodes + # are already replaced by pending_xref nodes in the environment. + self.body.append('\\cite{%s}' % self.idescape(node.astext())) raise nodes.SkipNode def visit_literal(self, node): @@ -1249,7 +1260,7 @@ class LaTeXTranslator(nodes.NodeVisitor): raise nodes.SkipNode def visit_description(self, node): - self.body.append( ' ' ) + self.body.append(' ') def depart_description(self, node): pass -- cgit v1.2.1 From 3ee9f0b01a567a9adf24eb28f75d0bca3706fa17 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sat, 20 Feb 2010 19:51:37 +0100 Subject: Make test work with docutils 0.5. --- tests/test_markup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_markup.py b/tests/test_markup.py index 1023528d..9883f83c 100644 --- a/tests/test_markup.py +++ b/tests/test_markup.py @@ -120,5 +120,5 @@ def test_latex_escaping(): u'@PYGZat[]@(@Gamma@)\\@(@infty@)@$@PYGZlb[]@PYGZrb[]\n' u'\\end{Verbatim}') # in URIs - yield (verify, u'`test `_', None, - u'\\href{http://example.com/~me/}{test}') + yield (verify_re, u'`test `_', None, + ur'\href{http://example.com/~me/}{test}.*') -- cgit v1.2.1 From fe1da9ed36f90eabd9f384b37a0eee4af247040f Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sat, 20 Feb 2010 19:53:44 +0100 Subject: Fix backslash escape. --- tests/test_markup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_markup.py b/tests/test_markup.py index 9883f83c..a7e60210 100644 --- a/tests/test_markup.py +++ b/tests/test_markup.py @@ -121,4 +121,4 @@ def test_latex_escaping(): u'\\end{Verbatim}') # in URIs yield (verify_re, u'`test `_', None, - ur'\href{http://example.com/~me/}{test}.*') + ur'\\href{http://example.com/~me/}{test}.*') -- cgit v1.2.1 From cdb65275eef02c7c3d248fa1316ddf8db1d43d0a Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sat, 20 Feb 2010 19:57:13 +0100 Subject: Add link to Qt help. --- doc/builders.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/builders.rst b/doc/builders.rst index e72941fa..9895fabc 100644 --- a/doc/builders.rst +++ b/doc/builders.rst @@ -59,11 +59,13 @@ The builder's "name" must be given to the **-b** command-line option of .. class:: QtHelpBuilder This builder produces the same output as the standalone HTML builder, but - also generates Qt help collection support files that allow + also generates `Qt help`_ collection support files that allow the Qt collection generator to compile them. Its name is ``qthelp``. + .. _Qt help: http://doc.trolltech.com/4.6/qthelp-framework.html + .. module:: sphinx.builders.devhelp .. class:: DevhelpBuilder -- cgit v1.2.1 From 7429ae935ec47c56c95998a8537236d4c1da7a2b Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 21 Feb 2010 11:50:08 +0100 Subject: Add manual page writer. --- CHANGES | 2 + doc/Makefile | 6 + doc/builders.rst | 16 ++ doc/conf.py | 5 + doc/config.rst | 29 ++++ doc/ext/appapi.rst | 10 +- sphinx/application.py | 2 + sphinx/builders/__init__.py | 1 + sphinx/builders/manpage.py | 93 +++++++++++ sphinx/config.py | 3 + sphinx/ext/autosummary/__init__.py | 6 +- sphinx/ext/todo.py | 3 +- sphinx/quickstart.py | 58 ++++++- sphinx/writers/manpage.py | 308 +++++++++++++++++++++++++++++++++++++ tests/test_build.py | 9 ++ tests/test_quickstart.py | 5 + 16 files changed, 543 insertions(+), 13 deletions(-) create mode 100644 sphinx/builders/manpage.py create mode 100644 sphinx/writers/manpage.py diff --git a/CHANGES b/CHANGES index 3129ca29..72a8b462 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,8 @@ Release 1.0 (in development) * Support for docutils 0.4 has been removed. +* Added a manual page builder. + * New more compact doc field syntax is now recognized: ``:param type name: description``. diff --git a/doc/Makefile b/doc/Makefile index bfd5ca83..153a93db 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -21,6 +21,7 @@ help: @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 " man to make manual pages" @echo " changes to make an overview over all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @@ -47,6 +48,11 @@ text: @echo @echo "Build finished." +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) _build/man + @echo + @echo "Build finished." + pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle diff --git a/doc/builders.rst b/doc/builders.rst index 9895fabc..5c61fba0 100644 --- a/doc/builders.rst +++ b/doc/builders.rst @@ -127,6 +127,22 @@ Note that a direct PDF builder using ReportLab is available in `rst2pdf .. versionadded:: 0.4 +.. module:: sphinx.builders.manpage +.. class:: ManualPageBuilder + + This builder produces manual pages in the groff format. You have to specify + which documents are to be included in which manual pages via the + :confval:`man_pages` configuration value. + + Its name is ``man``. + + .. note:: + + This builder requires the docutils manual page writer, which is only + available as of docutils 0.6. + + .. versionadded:: 1.0 + .. currentmodule:: sphinx.builders.html .. class:: SerializingHTMLBuilder diff --git a/doc/conf.py b/doc/conf.py index 57556990..b1a55b0e 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -49,6 +49,11 @@ latex_elements = { todo_include_todos = True +man_pages = [ + ('contents', 'sphinx-all', 'Sphinx documentation generator system manual', + 'Georg Brandl', 1), +] + # -- Extension interface ------------------------------------------------------- diff --git a/doc/config.rst b/doc/config.rst index 0793759a..6f9a7603 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -914,6 +914,35 @@ These options influence LaTeX output. Use the ``'pointsize'`` key in the :confval:`latex_elements` value. +.. _man-options: + +Options for manual page output +------------------------------ + +These options influence manual page output. + +.. confval:: man_pages + + This value determines how to group the document tree into manual pages. It + must be a list of tuples ``(startdocname, name, description, authors, + section)``, where the items are: + + * *startdocname*: document name that is the "root" of the manual page. All + documents referenced by it in TOC trees will be included in the manual file + too. (If you want one master manual page, use your :confval:`master_doc` + here.) + * *name*: name of the manual page. This should be a short string without + spaces or special characters. It is used to determine the file name as + well as the name of the manual page (in the NAME section). + * *description*: description of the manual page. This is used in the NAME + section. + * *authors*: A list of strings with authors, or a single string. + * *section*: The manual page section. Used for the output file name as well + as in the manual page header. + + .. versionadded:: 1.0 + + .. rubric:: Footnotes .. [1] A note on available globbing syntax: you can use the standard shell diff --git a/doc/ext/appapi.rst b/doc/ext/appapi.rst index a643a478..8df028e1 100644 --- a/doc/ext/appapi.rst +++ b/doc/ext/appapi.rst @@ -61,11 +61,11 @@ the following public API: Register a Docutils node class. This is necessary for Docutils internals. It may also be used in the future to validate nodes in the parsed documents. - Node visitor functions for the Sphinx HTML, LaTeX and text writers can be - given as keyword arguments: the keyword must be one or more of ``'html'``, - ``'latex'``, ``'text'``, the value a 2-tuple of ``(visit, depart)`` methods. - ``depart`` can be ``None`` if the ``visit`` function raises - :exc:`docutils.nodes.SkipNode`. Example: + Node visitor functions for the Sphinx HTML, LaTeX, text and manpage writers + can be given as keyword arguments: the keyword must be one or more of + ``'html'``, ``'latex'``, ``'text'``, ``'man'``, the value a 2-tuple of + ``(visit, depart)`` methods. ``depart`` can be ``None`` if the ``visit`` + function raises :exc:`docutils.nodes.SkipNode`. Example: .. code-block:: python diff --git a/sphinx/application.py b/sphinx/application.py index 2b26fa66..95ca6279 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -351,6 +351,8 @@ class Sphinx(object): from sphinx.writers.latex import LaTeXTranslator as translator elif key == 'text': from sphinx.writers.text import TextTranslator as translator + elif key == 'man': + from sphinx.writers.manpage import ManualPageTranslator as translator else: # ignore invalid keys for compatibility continue diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py index 6ab6af3e..e345d570 100644 --- a/sphinx/builders/__init__.py +++ b/sphinx/builders/__init__.py @@ -326,6 +326,7 @@ BUILTIN_BUILDERS = { 'epub': ('epub', 'EpubBuilder'), 'latex': ('latex', 'LaTeXBuilder'), 'text': ('text', 'TextBuilder'), + 'man': ('manpage', 'ManualPageBuilder'), 'changes': ('changes', 'ChangesBuilder'), 'linkcheck': ('linkcheck', 'CheckExternalLinksBuilder'), } diff --git a/sphinx/builders/manpage.py b/sphinx/builders/manpage.py new file mode 100644 index 00000000..7b3aef1d --- /dev/null +++ b/sphinx/builders/manpage.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +""" + sphinx.builders.manpage + ~~~~~~~~~~~~~~~~~~~~~~~ + + Manual pages builder. + + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from os import path + +from docutils.io import FileOutput +from docutils.frontend import OptionParser + +from sphinx import addnodes +from sphinx.errors import SphinxError +from sphinx.builders import Builder +from sphinx.environment import NoUri +from sphinx.util.nodes import inline_all_toctrees +from sphinx.util.console import bold, darkgreen +try: + from sphinx.writers.manpage import ManualPageWriter + has_manpage_writer = True +except ImportError: + has_manpage_writer = False + + +class ManualPageBuilder(Builder): + """ + Builds groff output in manual page format. + """ + name = 'man' + format = 'man' + supported_image_types = [] + + def init(self): + if not has_manpage_writer: + raise SphinxError('The docutils manual page writer can\'t be ' + 'found; it is only available as of docutils 0.6.') + if not self.config.man_pages: + self.warn('no "man_pages" config value found; no manual pages ' + 'will be written') + + def get_outdated_docs(self): + return 'all manpages' # for now + + def get_target_uri(self, docname, typ=None): + if typ == 'token': + return '' + raise NoUri + + def write(self, *ignored): + docwriter = ManualPageWriter(self) + docsettings = OptionParser( + defaults=self.env.settings, + components=(docwriter,)).get_default_values() + + self.info(bold('writing... '), nonl=True) + + for info in self.config.man_pages: + docname, name, description, authors, section = info + if isinstance(authors, basestring): + authors = [authors] + + targetname = '%s.%s' % (name, section) + self.info(darkgreen(targetname) + ' { ', nonl=True) + destination = FileOutput( + destination_path=path.join(self.outdir, targetname), + encoding='utf-8') + + tree = self.env.get_doctree(docname) + docnames = set() + largetree = inline_all_toctrees(self, docnames, docname, tree, + darkgreen) + self.info('} ', nonl=True) + self.env.resolve_references(largetree, docname, self) + # remove pending_xref nodes + for pendingnode in largetree.traverse(addnodes.pending_xref): + pendingnode.replace_self(pendingnode.children) + + largetree.settings = docsettings + largetree.settings.title = name + largetree.settings.subtitle = description + largetree.settings.authors = authors + largetree.settings.section = section + + docwriter.write(largetree, destination) + self.info() + + def finish(self): + pass diff --git a/sphinx/config.py b/sphinx/config.py index 1c262eb1..0de30a5a 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -141,6 +141,9 @@ class Config(object): # text options text_sectionchars = ('*=-~"+`', 'text'), text_windows_newlines = (False, 'text'), + + # manpage options + man_pages = ([], None), ) def __init__(self, dirname, filename, overrides, tags): diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index a4d46b1f..cf67c7fb 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -486,11 +486,13 @@ def setup(app): app.add_node(autosummary_toc, html=(autosummary_toc_visit_html, autosummary_noop), latex=(autosummary_noop, autosummary_noop), - text=(autosummary_noop, autosummary_noop)) + text=(autosummary_noop, autosummary_noop), + man=(autosummary_noop, autosummary_noop)) app.add_node(autosummary_table, html=(autosummary_table_visit_html, autosummary_noop), latex=(autosummary_noop, autosummary_noop), - text=(autosummary_noop, autosummary_noop)) + text=(autosummary_noop, autosummary_noop), + man=(autosummary_noop, autosummary_noop)) app.add_directive('autosummary', Autosummary) app.add_role('autolink', autolink_role) app.connect('doctree-read', process_autosummary_toc) diff --git a/sphinx/ext/todo.py b/sphinx/ext/todo.py index 96f9fdfc..ac362919 100644 --- a/sphinx/ext/todo.py +++ b/sphinx/ext/todo.py @@ -159,7 +159,8 @@ def setup(app): app.add_node(todo_node, html=(visit_todo_node, depart_todo_node), latex=(visit_todo_node, depart_todo_node), - text=(visit_todo_node, depart_todo_node)) + text=(visit_todo_node, depart_todo_node), + man=(visit_todo_node, depart_todo_node)) app.add_directive('todo', Todo) app.add_directive('todolist', TodoList) diff --git a/sphinx/quickstart.py b/sphinx/quickstart.py index daa88c36..4ad6e18d 100644 --- a/sphinx/quickstart.py +++ b/sphinx/quickstart.py @@ -226,13 +226,25 @@ latex_documents = [ #latex_domain_indices = True +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('%(master_str)s', '%(project_manpage)s', u'%(project_doc)s', + [u'%(author_str)s'], 1) +] +''' + +EPUB_CONFIG = ''' + # -- Options for Epub output --------------------------------------------------- # Bibliographic Dublin Core info. -#epub_title = '' -#epub_author = '' -#epub_publisher = '' -#epub_copyright = '' +epub_title = u'%(project_str)s' +epub_author = u'%(author_str)s' +epub_publisher = u'%(author_str)s' +epub_copyright = u'%(copyright_str)s' # The language of the text. It defaults to the language option # or en if the language is not set. @@ -324,6 +336,8 @@ help: \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 " text to make text files" +\t@echo " man to make manual pages" \t@echo " changes to make an overview of all changed/added/deprecated items" \t@echo " linkcheck to check all external links for integrity" \t@echo " doctest to run all doctests embedded in the documentation \ @@ -400,6 +414,16 @@ latexpdf: latex \tmake -C %(rbuilddir)s/latex all-pdf \t@echo "pdflatex finished; the PDF files are in %(rbuilddir)s/latex." +text: +\t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text +\t@echo +\t@echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: +\t$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man +\t@echo +\t@echo "Build finished. The manual pages are in $(BUILDDIR)/man." + changes: \t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes \t@echo @@ -444,6 +468,8 @@ if "%%1" == "help" ( \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. text to make text files +\techo. man to make manual pages \techo. changes to make an overview over all changed/added/deprecated items \techo. linkcheck to check all external links for integrity \techo. doctest to run all doctests embedded in the documentation if enabled @@ -531,6 +557,20 @@ if "%%1" == "latex" ( \tgoto end ) +if "%%1" == "text" ( +\t%%SPHINXBUILD%% -b text %%ALLSPHINXOPTS%% %%BUILDDIR%%/text +\techo. +\techo.Build finished. The text files are in %%BUILDDIR%%/text. +\tgoto end +) + +if "%%1" == "man" ( +\t%%SPHINXBUILD%% -b man %%ALLSPHINXOPTS%% %%BUILDDIR%%/man +\techo. +\techo.Build finished. The manual pages are in %%BUILDDIR%%/man. +\tgoto end +) + if "%%1" == "changes" ( \t%%SPHINXBUILD%% -b changes %%ALLSPHINXOPTS%% %%BUILDDIR%%/changes \techo. @@ -703,6 +743,11 @@ document is a custom template, you can also set this to another filename.''' do_prompt(d, 'master', 'Please enter a new file name, or rename the ' 'existing file and press Enter', d['master']) + print ''' +Sphinx can also add configuration for epub output:''' + do_prompt(d, 'epub', 'Do you want to use the epub builder (y/N)', + 'n', boolean) + print ''' Please indicate if you want to use one of the following Sphinx extensions:''' do_prompt(d, 'ext_autodoc', 'autodoc: automatically insert docstrings ' @@ -735,6 +780,7 @@ directly.''' 'y', boolean) d['project_fn'] = make_filename(d['project']) + d['project_manpage'] = d['project_fn'].lower() d['now'] = time.asctime() d['underline'] = len(d['project']) * '=' d['extensions'] = ', '.join( @@ -751,7 +797,7 @@ directly.''' # escape backslashes and single quotes in strings that are put into # a Python string literal - for key in ('project', 'copyright', 'author_texescaped', + for key in ('project', 'copyright', 'author', 'author_texescaped', 'project_doc_texescaped', 'version', 'release', 'master'): d[key + '_str'] = d[key].replace('\\', '\\\\').replace("'", "\\'") @@ -772,6 +818,8 @@ directly.''' mkdir_p(path.join(srcdir, d['dot'] + 'static')) conf_text = QUICKSTART_CONF % d + if d['epub']: + conf_text += EPUB_CONFIG % d if d['ext_intersphinx']: conf_text += INTERSPHINX_CONFIG diff --git a/sphinx/writers/manpage.py b/sphinx/writers/manpage.py new file mode 100644 index 00000000..6ba61a40 --- /dev/null +++ b/sphinx/writers/manpage.py @@ -0,0 +1,308 @@ +# -*- coding: utf-8 -*- +""" + sphinx.writers.manpage + ~~~~~~~~~~~~~~~~~~~~~~ + + Manual page writer, extended for Sphinx custom nodes. + + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from docutils import nodes +from docutils.writers.manpage import MACRO_DEF, Writer, \ + Translator as BaseTranslator + +from sphinx import addnodes +from sphinx.locale import admonitionlabels, versionlabels, _ +from sphinx.util.osutil import ustrftime + + +class ManualPageWriter(Writer): + def __init__(self, builder): + Writer.__init__(self) + self.builder = builder + + def translate(self): + visitor = ManualPageTranslator(self.builder, self.document) + self.visitor = visitor + self.document.walkabout(visitor) + self.output = visitor.astext() + + +class ManualPageTranslator(BaseTranslator): + """ + Custom translator. + """ + + def __init__(self, builder, *args, **kwds): + BaseTranslator.__init__(self, *args, **kwds) + self.builder = builder + + self.in_productionlist = 0 + + # first title is the manpage title + self.section_level = -1 + + # docinfo set by man_pages config value + self._docinfo['title'] = self.document.settings.title + self._docinfo['subtitle'] = self.document.settings.subtitle + self._docinfo['author'] = self.document.settings.authors + self._docinfo['manual_section'] = self.document.settings.section + + # docinfo set by other config values + self._docinfo['title_upper'] = self._docinfo['title'].upper() + if builder.config.today: + self._docinfo['date'] = builder.config.today + else: + self._docinfo['date'] = ustrftime(builder.config.today_fmt + or _('%B %d, %Y')) + self._docinfo['copyright'] = builder.config.copyright + self._docinfo['version'] = builder.config.version + self._docinfo['manual_group'] = builder.config.project + + # since self.append_header() is never called, need to do this here + self.body.append(MACRO_DEF) + + # overwritten -- added quotes around all .TH arguments + def header(self): + tmpl = (".TH \"%(title_upper)s\" \"%(manual_section)s\"" + " \"%(date)s\" \"%(version)s\" \"%(manual_group)s\"\n" + ".SH NAME\n" + "%(title)s \- %(subtitle)s\n") + return tmpl % self._docinfo + + def visit_start_of_file(self, node): + pass + def depart_start_of_file(self, node): + pass + + def visit_desc(self, node): + self.visit_definition_list(node) + def depart_desc(self, node): + self.depart_definition_list(node) + + def visit_desc_signature(self, node): + self.visit_definition_list_item(node) + self.visit_term(node) + def depart_desc_signature(self, node): + self.depart_term(node) + + def visit_desc_addname(self, node): + pass + def depart_desc_addname(self, node): + pass + + def visit_desc_type(self, node): + pass + def depart_desc_type(self, node): + pass + + def visit_desc_returns(self, node): + self.body.append(' -> ') + def depart_desc_returns(self, node): + pass + + def visit_desc_name(self, node): + pass + def depart_desc_name(self, node): + pass + + def visit_desc_parameterlist(self, node): + self.body.append('(') + self.first_param = 1 + def depart_desc_parameterlist(self, node): + self.body.append(')') + + def visit_desc_parameter(self, node): + if not self.first_param: + self.body.append(', ') + else: + self.first_param = 0 + def depart_desc_parameter(self, node): + pass + + def visit_desc_optional(self, node): + self.body.append('[') + def depart_desc_optional(self, node): + self.body.append(']') + + def visit_desc_annotation(self, node): + pass + def depart_desc_annotation(self, node): + pass + + def visit_desc_content(self, node): + self.visit_definition(node) + def depart_desc_content(self, node): + self.depart_definition(node) + + def visit_refcount(self, node): + self.body.append(self.defs['emphasis'][0]) + def depart_refcount(self, node): + self.body.append(self.defs['emphasis'][1]) + + def visit_versionmodified(self, node): + self.visit_paragraph(node) + text = versionlabels[node['type']] % node['version'] + if len(node): + text += ': ' + else: + text += '.' + self.body.append(text) + def depart_versionmodified(self, node): + self.depart_paragraph(node) + + # overwritten -- we don't want source comments to show up + def visit_comment(self, node): + raise nodes.SkipNode + + # overwritten -- added ensure_eol() + def visit_footnote(self, node): + self.ensure_eol() + BaseTranslator.visit_footnote(self, node) + + # overwritten -- handle footnotes rubric + def visit_rubric(self, node): + self.ensure_eol() + if len(node.children) == 1: + rubtitle = node.children[0].astext() + if rubtitle in ('Footnotes', _('Footnotes')): + self.body.append('.SH ' + self.deunicode(rubtitle).upper() + + '\n') + raise nodes.SkipNode + else: + self.body.append('.sp\n') + def depart_rubric(self, node): + pass + + def visit_seealso(self, node): + self.visit_admonition(node) + def depart_seealso(self, node): + self.depart_admonition(node) + + # overwritten -- use our own label translations + def visit_admonition(self, node, name=None): + if name: + self.body.append('.IP %s\n' % + admonitionlabels.get(name, name)) + + def visit_productionlist(self, node): + self.ensure_eol() + names = [] + self.in_productionlist += 1 + self.body.append('.sp\n.nf\n') + for production in node: + names.append(production['tokenname']) + maxlen = max(len(name) for name in names) + for production in node: + if production['tokenname']: + lastname = production['tokenname'].ljust(maxlen) + self.body.append(self.defs['strong'][0]) + self.body.append(self.deunicode(lastname)) + self.body.append(self.defs['strong'][1]) + self.body.append(' ::= ') + else: + self.body.append('%s ' % (' '*len(lastname))) + production.walkabout(self) + self.body.append('\n') + self.body.append('\n.fi\n') + self.in_productionlist -= 1 + raise nodes.SkipNode + + def visit_production(self, node): + pass + def depart_production(self, node): + pass + + # overwritten -- don't visit inner marked up nodes + def visit_reference(self, node): + self.body.append(self.defs['reference'][0]) + self.body.append(node.astext()) + self.body.append(self.defs['reference'][1]) + raise nodes.SkipNode + + def visit_centered(self, node): + self.ensure_eol() + self.body.append('.sp\n.ce\n') + def depart_centered(self, node): + self.body.append('\n.ce 0\n') + + def visit_compact_paragraph(self, node): + pass + def depart_compact_paragraph(self, node): + pass + + def visit_highlightlang(self, node): + pass + def depart_highlightlang(self, node): + pass + + def visit_download_reference(self, node): + pass + def depart_download_reference(self, node): + pass + + def visit_toctree(self, node): + raise nodes.SkipNode + + def visit_index(self, node): + raise nodes.SkipNode + + def visit_tabular_col_spec(self, node): + raise nodes.SkipNode + + def visit_glossary(self, node): + pass + def depart_glossary(self, node): + pass + + def visit_acks(self, node): + self.ensure_eol() + self.body.append(', '.join(n.astext() + for n in node.children[0].children) + '.') + self.body.append('\n') + raise nodes.SkipNode + + def visit_hlist(self, node): + self.visit_bullet_list(node) + def depart_hlist(self, node): + self.depart_bullet_list(node) + + def visit_hlistcol(self, node): + pass + def depart_hlistcol(self, node): + pass + + def visit_literal_emphasis(self, node): + return self.visit_emphasis(node) + def depart_literal_emphasis(self, node): + return self.depart_emphasis(node) + + def visit_abbreviation(self, node): + pass + def depart_abbreviation(self, node): + pass + + # overwritten: handle section titles better than in 0.6 release + def visit_title(self, node): + if isinstance(node.parent, addnodes.seealso): + self.body.append('.IP "') + return + elif isinstance(node.parent, nodes.section): + if self.section_level == 0: + # skip the document title + raise nodes.SkipNode + elif self.section_level == 1: + self.body.append('.SH %s\n' % + self.deunicode(node.astext().upper())) + raise nodes.SkipNode + return BaseTranslator.visit_title(self, node) + def depart_title(self, node): + if isinstance(node.parent, addnodes.seealso): + self.body.append('"\n') + return + return BaseTranslator.depart_title(self, node) + + def unknown_visit(self, node): + raise NotImplementedError('Unknown node: ' + node.__class__.__name__) diff --git a/tests/test_build.py b/tests/test_build.py index 82657fc0..f18ff175 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -45,6 +45,15 @@ def test_epub(app): def test_changes(app): app.builder.build_all() +try: + from docutils.writers.manpage import Writer +except ImportError: + pass +else: + @with_app(buildername='man') + def test_man(app): + app.builder.build_all() + @with_app(buildername='singlehtml', cleanenv=True) def test_singlehtml(app): app.builder.build_all() diff --git a/tests/test_quickstart.py b/tests/test_quickstart.py index 5138e2c6..cb40d27c 100644 --- a/tests/test_quickstart.py +++ b/tests/test_quickstart.py @@ -129,6 +129,7 @@ def test_quickstart_all_answers(tempdir): 'viewcode': 'no', 'Create Makefile': 'no', 'Create Windows command file': 'no', + 'Do you want to use the epub builder': 'yes', } qs.raw_input = mock_raw_input(answers, needanswer=True) qs.TERM_ENCODING = 'utf-8' @@ -151,6 +152,10 @@ def test_quickstart_all_answers(tempdir): assert ns['latex_documents'] == [ ('contents', 'STASI.tex', u'STASI™ Documentation', u'Wolfgang Schäuble \\& G\'Beckstein', 'manual')] + assert ns['epub_author'] == u'Wolfgang Schäuble & G\'Beckstein' + assert ns['man_pages'] == [ + ('contents', 'stasi', u'STASI™ Documentation', + [u'Wolfgang Schäuble & G\'Beckstein'], 1)] assert (tempdir / 'build').isdir() assert (tempdir / 'source' / '.static').isdir() -- cgit v1.2.1 From b5a0530bd05cd05c67193ba6f36a029b00f44c7c Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 21 Feb 2010 11:54:41 +0100 Subject: Fix long line. --- sphinx/application.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sphinx/application.py b/sphinx/application.py index 95ca6279..ba2940fa 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -352,7 +352,8 @@ class Sphinx(object): elif key == 'text': from sphinx.writers.text import TextTranslator as translator elif key == 'man': - from sphinx.writers.manpage import ManualPageTranslator as translator + from sphinx.writers.manpage import ManualPageTranslator \ + as translator else: # ignore invalid keys for compatibility continue -- cgit v1.2.1 From 18aa0e2f0436f2117bcb6090834123f80a279644 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 21 Feb 2010 12:01:27 +0100 Subject: Fix-up where the ImportError is caught if the manpage writer is missing. --- sphinx/builders/manpage.py | 6 +----- sphinx/writers/manpage.py | 10 ++++++++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/sphinx/builders/manpage.py b/sphinx/builders/manpage.py index 7b3aef1d..fa795a15 100644 --- a/sphinx/builders/manpage.py +++ b/sphinx/builders/manpage.py @@ -20,11 +20,7 @@ from sphinx.builders import Builder from sphinx.environment import NoUri from sphinx.util.nodes import inline_all_toctrees from sphinx.util.console import bold, darkgreen -try: - from sphinx.writers.manpage import ManualPageWriter - has_manpage_writer = True -except ImportError: - has_manpage_writer = False +from sphinx.writers.manpage import ManualPageWriter, has_manpage_writer class ManualPageBuilder(Builder): diff --git a/sphinx/writers/manpage.py b/sphinx/writers/manpage.py index 6ba61a40..325d5001 100644 --- a/sphinx/writers/manpage.py +++ b/sphinx/writers/manpage.py @@ -10,8 +10,14 @@ """ from docutils import nodes -from docutils.writers.manpage import MACRO_DEF, Writer, \ - Translator as BaseTranslator +try: + from docutils.writers.manpage import MACRO_DEF, Writer, \ + Translator as BaseTranslator + has_manpage_writer = True +except ImportError: + # define the classes in any case, sphinx.application needs it + Writer = BaseTranslator = object + has_manpage_writer = False from sphinx import addnodes from sphinx.locale import admonitionlabels, versionlabels, _ -- cgit v1.2.1 From fe8801e98f3537f396594f537c91243692d07954 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 21 Feb 2010 12:45:43 +0100 Subject: Use $(BUILDDIR) consistently. --- sphinx/quickstart.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sphinx/quickstart.py b/sphinx/quickstart.py index 4ad6e18d..d03ce920 100644 --- a/sphinx/quickstart.py +++ b/sphinx/quickstart.py @@ -387,12 +387,12 @@ qthelp: \t@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/%(project_fn)s.qhc" devhelp: -\t$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) %(rbuilddir)s/devhelp +\t$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp \t@echo \t@echo "Build finished." \t@echo "To view the help file:" \t@echo "# mkdir -p $$HOME/.local/share/devhelp/%(project_fn)s" -\t@echo "# ln -s %(rbuilddir)s/devhelp\ +\t@echo "# ln -s $(BUILDDIR)/devhelp\ $$HOME/.local/share/devhelp/%(project_fn)s" \t@echo "# devhelp" @@ -409,10 +409,10 @@ latex: \t "run these through (pdf)latex." latexpdf: latex -\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) %(rbuilddir)s/latex +\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex \t@echo "Running LaTeX files through pdflatex..." -\tmake -C %(rbuilddir)s/latex all-pdf -\t@echo "pdflatex finished; the PDF files are in %(rbuilddir)s/latex." +\tmake -C $(BUILDDIR)/latex all-pdf +\t@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: \t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @@ -537,7 +537,7 @@ if "%%1" == "qthelp" ( ) if "%%1" == "devhelp" ( -\t%%SPHINXBUILD%% -b devhelp %%ALLSPHINXOPTS%% %(rbuilddir)s/devhelp +\t%%SPHINXBUILD%% -b devhelp %%ALLSPHINXOPTS%% %%BUILDDIR%%/devhelp \techo. \techo.Build finished. \tgoto end -- cgit v1.2.1 From 78ae60e4534807505311b9d2c1673609a7b5f4c6 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 21 Feb 2010 15:36:26 +0100 Subject: Rename arguments and make a few more optional, for convenience. --- sphinx/application.py | 11 ++++++----- sphinx/cmdline.py | 8 ++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/sphinx/application.py b/sphinx/application.py index ba2940fa..cd9baf9c 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -61,8 +61,8 @@ ENV_PICKLE_FILENAME = 'environment.pickle' class Sphinx(object): def __init__(self, srcdir, confdir, outdir, doctreedir, buildername, - confoverrides, status, warning=sys.stderr, freshenv=False, - warningiserror=False, tags=None): + confoverrides=None, status=sys.stdout, warning=sys.stderr, + freshenv=False, warningiserror=False, tags=None): self.next_listener_id = 0 self._extensions = {} self._listeners = {} @@ -100,7 +100,8 @@ class Sphinx(object): # read config self.tags = Tags(tags) - self.config = Config(confdir, CONFIG_FILENAME, confoverrides, self.tags) + self.config = Config(confdir, CONFIG_FILENAME, + confoverrides or {}, self.tags) self.config.check_unicode(self.warn) # load all extension modules @@ -189,9 +190,9 @@ class Sphinx(object): self.builder = builderclass(self) self.emit('builder-inited') - def build(self, all_files, filenames): + def build(self, force_all=False, filenames=None): try: - if all_files: + if force_all: self.builder.build_all() elif filenames: self.builder.build_specific(filenames) diff --git a/sphinx/cmdline.py b/sphinx/cmdline.py index dc3269e2..bc5aff87 100644 --- a/sphinx/cmdline.py +++ b/sphinx/cmdline.py @@ -89,8 +89,8 @@ def main(argv): if err: return 1 - buildername = all_files = None - freshenv = warningiserror = use_pdb = False + buildername = None + force_all = freshenv = warningiserror = use_pdb = False status = sys.stdout warning = sys.stderr error = sys.stderr @@ -105,7 +105,7 @@ def main(argv): if filenames: usage(argv, 'Cannot combine -a option and filenames.') return 1 - all_files = True + force_all = True elif opt == '-t': tags.append(val) elif opt == '-d': @@ -167,7 +167,7 @@ def main(argv): app = Sphinx(srcdir, confdir, outdir, doctreedir, buildername, confoverrides, status, warning, freshenv, warningiserror, tags) - app.build(all_files, filenames) + app.build(force_all, filenames) return app.statuscode except KeyboardInterrupt: if use_pdb: -- cgit v1.2.1 From db8df49c09f49250cda9a90cd19005b82ffb54e8 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 21 Feb 2010 21:49:38 +0100 Subject: Add method for adding custom indexes. --- sphinx/application.py | 7 +++++++ sphinx/builders/html.py | 3 ++- sphinx/domains/__init__.py | 16 ++++++++++++++-- sphinx/domains/python.py | 11 +++-------- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/sphinx/application.py b/sphinx/application.py index cd9baf9c..7d4e3cac 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -405,6 +405,13 @@ class Sphinx(object): raise ExtensionError('domain %s not yet registered' % domain) self.domains[domain].roles[name] = role + # XXX needs documentation + def add_index_to_domain(self, domain, name, localname, shortname, func): + if domain not in self.domains: + raise ExtensionError('domain %s not yet registered' % domain) + self.domains[domain].indices.append((name, longname, shortname)) + setattr(self.domains[domain], '_get_%s_index' % name, func) + def add_object_type(self, directivename, rolename, indextemplate='', parse_node=None, ref_nodeclass=None, objname=''): StandardDomain.object_types[directivename] = \ diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index 6dcdc27e..cc3a5628 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -273,7 +273,8 @@ class StandaloneHTMLBuilder(Builder): if self.config.html_use_index: rellinks.append(('genindex', _('General Index'), 'I', _('index'))) for index in self.domain_indices: - rellinks.append(('%s-%s' % index[0:2], index[2], '', index[3])) + if index[3]: + rellinks.append(('%s-%s' % index[0:2], index[2], '', index[3])) if self.config.html_style is not None: stylename = self.config.html_style diff --git a/sphinx/domains/__init__.py b/sphinx/domains/__init__.py index b67d090c..a03a74db 100644 --- a/sphinx/domains/__init__.py +++ b/sphinx/domains/__init__.py @@ -181,8 +181,14 @@ class Domain(object): """ Return True if there are entries for the index given by *name*. If *docnames* is given, restrict to entries referring to these docnames. + + Do not overwrite this method, add a method ``has__entries(self, + docnames=None)`` method for every index. """ - return False + func = getattr(self, 'has_%s_entries' % name, None) + if not func: + return bool(self.get_index(name, docnames)) + return func(docnames) def get_index(self, name, docnames=None): """ @@ -212,8 +218,14 @@ class Domain(object): - `descr` -- description for the entry Qualifier and description are not rendered e.g. in LaTeX output. + + Do not overwrite this method, add a method ``get__index(self, + docnames=None)`` method for every index. """ - return [], False + func = getattr(self, 'get_%s_index' % name, None) + if not func: + return [] + return func(docnames) from sphinx.domains.c import CDomain diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index fcd2216d..1678929c 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -459,7 +459,7 @@ class PythonDomain(Domain): 'modules': {}, # modname -> docname, synopsis, platform, deprecated } indices = [ - ('modindex', l_('Global Module Index'), l_('modules')), + ('modindex', l_('Python Module Index'), l_('modules')), ] def clear_doc(self, docname): @@ -545,9 +545,7 @@ class PythonDomain(Domain): for refname, (docname, type) in self.data['objects'].iteritems(): yield (refname, type, docname, refname, 1) - def has_index_entries(self, name, docnames=None): - if name != 'modindex': - return False + def has_modindex_entries(self, docnames=None): if not docnames: return bool(self.data['modules']) else: @@ -556,10 +554,7 @@ class PythonDomain(Domain): return True return False - def get_index(self, name, docnames=None): - if name != 'modindex': - return None, None - + def get_modindex_index(self, docnames=None): content = {} # list of prefixes to ignore ignores = self.env.config['modindex_common_prefix'] -- cgit v1.2.1 From 107be941e719505cfdb3ca6587a1ac5cef961abf Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 21 Feb 2010 21:57:58 +0100 Subject: Add some versionadded tags. --- doc/ext/autosummary.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/ext/autosummary.rst b/doc/ext/autosummary.rst index 20b688c1..8193bc52 100644 --- a/doc/ext/autosummary.rst +++ b/doc/ext/autosummary.rst @@ -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 listed. See `Customizing templates`_ below. + .. versionadded:: 1.0 + :program:`sphinx-autogen` -- generate autodoc stub pages -------------------------------------------------------- @@ -142,6 +144,8 @@ also use this new config value: Customizing templates --------------------- +.. versionadded:: 1.0 + You can customize the stub page templates, in a similar way as the HTML Jinja templates, see :ref:`templating`. (:class:`~sphinx.application.TemplateBridge` is not supported.) -- cgit v1.2.1 From 27157952713cbab8e45d01dfa0bf57cd8c067c4e Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 21 Feb 2010 22:16:06 +0100 Subject: Added ``autodoc_default_flags`` config value, which can be used to select default flags for all autodoc directives. --- CHANGES | 3 +++ doc/ext/autodoc.rst | 18 ++++++++++++++++++ sphinx/ext/autodoc.py | 13 +++++++++++++ 3 files changed, 34 insertions(+) diff --git a/CHANGES b/CHANGES index 671d71b3..2b122032 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,9 @@ Release 1.0 (in development) * 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. * The ``html_sidebars`` config value can now contain patterns as diff --git a/doc/ext/autodoc.rst b/doc/ext/autodoc.rst index b4b4b3f3..4098d711 100644 --- a/doc/ext/autodoc.rst +++ b/doc/ext/autodoc.rst @@ -228,6 +228,24 @@ There are also new config values that you can set: .. 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 ----------------------- diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc.py index d3d19dcf..81c3f6a4 100644 --- a/sphinx/ext/autodoc.py +++ b/sphinx/ext/autodoc.py @@ -1098,6 +1098,10 @@ class AutoDirective(Directive): # a registry of type -> getattr function _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 has_content = True required_arguments = 1 @@ -1120,6 +1124,14 @@ class AutoDirective(Directive): # find out what documenter to call objtype = self.name[4:] 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 self.genopt = Options(assemble_option_dict( self.options.items(), doc_class.option_spec)) @@ -1177,6 +1189,7 @@ def setup(app): app.add_config_value('autoclass_content', 'class', 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-signature') app.add_event('autodoc-skip-member') -- cgit v1.2.1 From 260585b5da3fb55f1b8e2584a1494ca64074cffe Mon Sep 17 00:00:00 2001 From: anatoly techtonik Date: Tue, 23 Feb 2010 16:59:52 +0200 Subject: allow SPHINXBUILD override from parent environment --- sphinx/quickstart.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sphinx/quickstart.py b/sphinx/quickstart.py index 4e5b72e7..6bc19e7a 100644 --- a/sphinx/quickstart.py +++ b/sphinx/quickstart.py @@ -419,7 +419,9 @@ BATCHFILE = '''\ REM Command file for Sphinx documentation -set SPHINXBUILD=sphinx-build +if "%%SPHINXBUILD%%" == "" ( + set SPHINXBUILD=sphinx-build +) set BUILDDIR=%(rbuilddir)s set ALLSPHINXOPTS=-d %%BUILDDIR%%/doctrees %%SPHINXOPTS%% %(rsrcdir)s if NOT "%%PAPER%%" == "" ( -- cgit v1.2.1 From 01dfc2c167c2cdc4502ceb55141177afa54cd86a Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 23 Feb 2010 21:12:18 +0100 Subject: Remove tab. --- sphinx/quickstart.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/quickstart.py b/sphinx/quickstart.py index 6bc19e7a..d3f67bb1 100644 --- a/sphinx/quickstart.py +++ b/sphinx/quickstart.py @@ -420,7 +420,7 @@ BATCHFILE = '''\ REM Command file for Sphinx documentation if "%%SPHINXBUILD%%" == "" ( - set SPHINXBUILD=sphinx-build +\tset SPHINXBUILD=sphinx-build ) set BUILDDIR=%(rbuilddir)s set ALLSPHINXOPTS=-d %%BUILDDIR%%/doctrees %%SPHINXOPTS%% %(rsrcdir)s -- cgit v1.2.1 From 580a01ca1a1ee8b34b0b324a2f8e76b6545682f7 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 25 Feb 2010 22:03:27 +0100 Subject: Update phony target lists. --- doc/Makefile | 2 +- sphinx/quickstart.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/Makefile b/doc/Makefile index 153a93db..a8897d2b 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -11,7 +11,7 @@ PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) \ $(SPHINXOPTS) . -.PHONY: help clean html dirhtml pickle htmlhelp qthelp latex changes linkcheck doctest +.PHONY: help clean html dirhtml pickle htmlhelp qthelp latex changes linkcheck doctest man help: @echo "Please use \`make ' where is one of" diff --git a/sphinx/quickstart.py b/sphinx/quickstart.py index d03ce920..a7f5f74f 100644 --- a/sphinx/quickstart.py +++ b/sphinx/quickstart.py @@ -320,8 +320,8 @@ PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) \ $(SPHINXOPTS) %(rsrcdir)s -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp epub \ -latex changes linkcheck doctest +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp \ +epub latex latexpdf text man changes linkcheck doctest help: \t@echo "Please use \\`make ' where is one of" -- cgit v1.2.1 From cec38fed058dabd298157fd4e668ee9b9ecdb736 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 25 Feb 2010 22:21:41 +0100 Subject: Allow giving no authors. --- doc/config.rst | 4 +++- sphinx/builders/manpage.py | 5 ++++- sphinx/writers/manpage.py | 4 +++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/doc/config.rst b/doc/config.rst index 917bdf14..21b0134d 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -935,7 +935,9 @@ These options influence manual page output. well as the name of the manual page (in the NAME section). * *description*: description of the manual page. This is used in the NAME section. - * *authors*: A list of strings with authors, or a single string. + * *authors*: A list of strings with authors, or a single string. Can be + an empty string or list if you do not want to automatically generate + an AUTHORS section in the manual page. * *section*: The manual page section. Used for the output file name as well as in the manual page header. diff --git a/sphinx/builders/manpage.py b/sphinx/builders/manpage.py index fa795a15..756e4732 100644 --- a/sphinx/builders/manpage.py +++ b/sphinx/builders/manpage.py @@ -58,7 +58,10 @@ class ManualPageBuilder(Builder): for info in self.config.man_pages: docname, name, description, authors, section = info if isinstance(authors, basestring): - authors = [authors] + if authors: + authors = [authors] + else: + authors = [] targetname = '%s.%s' % (name, section) self.info(darkgreen(targetname) + ' { ', nonl=True) diff --git a/sphinx/writers/manpage.py b/sphinx/writers/manpage.py index 325d5001..3d4f18cb 100644 --- a/sphinx/writers/manpage.py +++ b/sphinx/writers/manpage.py @@ -53,7 +53,9 @@ class ManualPageTranslator(BaseTranslator): # docinfo set by man_pages config value self._docinfo['title'] = self.document.settings.title self._docinfo['subtitle'] = self.document.settings.subtitle - self._docinfo['author'] = self.document.settings.authors + if self.document.settings.authors: + # don't set it if no author given + self._docinfo['author'] = self.document.settings.authors self._docinfo['manual_section'] = self.document.settings.section # docinfo set by other config values -- cgit v1.2.1 From 6ae33f0123e31b7cfe14eccba563c510f08c50ab Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 25 Feb 2010 22:22:01 +0100 Subject: Remove hand-written manpages, add rst documents to be built with the manpage builder. --- doc/conf.py | 4 ++ doc/man/sphinx-build.rst | 101 ++++++++++++++++++++++++++++++++++++++++ doc/man/sphinx-quickstart.rst | 30 ++++++++++++ doc/sphinx-build.1 | 105 ------------------------------------------ doc/sphinx-quickstart.1 | 17 ------- 5 files changed, 135 insertions(+), 122 deletions(-) create mode 100644 doc/man/sphinx-build.rst create mode 100644 doc/man/sphinx-quickstart.rst delete mode 100644 doc/sphinx-build.1 delete mode 100644 doc/sphinx-quickstart.1 diff --git a/doc/conf.py b/doc/conf.py index b1a55b0e..24cf5f3c 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -52,6 +52,10 @@ todo_include_todos = True man_pages = [ ('contents', 'sphinx-all', 'Sphinx documentation generator system manual', 'Georg Brandl', 1), + ('man/sphinx-build', 'sphinx-build', 'Sphinx documentation generator tool', + '', 1), + ('man/sphinx-quickstart', 'sphinx-quickstart', 'Sphinx documentation ' + 'template generator', '', 1), ] diff --git a/doc/man/sphinx-build.rst b/doc/man/sphinx-build.rst new file mode 100644 index 00000000..b8e5b272 --- /dev/null +++ b/doc/man/sphinx-build.rst @@ -0,0 +1,101 @@ +sphinx-build manual page +======================== + +Synopsis +-------- + +**sphinx-build** [*options*] <*sourcedir*> <*outdir*> [*filenames* ...] + + +Description +----------- + +:program:`sphinx-build` generates documentation from the files in +```` and places it in the ````. + +:program:`sphinx-build` looks for ``/conf.py`` for the configuration +settings. :manpage:`sphinx-quickstart(1)` may be used to generate template +files, including ``conf.py``. + +:program:`sphinx-build` can create documentation in different formats. A format +is selected by specifying the builder name on the command line; it defaults to +HTML. Builders can also perform other tasks related to documentation +processing. + +By default, everything that is outdated is built. Output only for selected +files can be built by specifying individual filenames. + +List of available builders: + +html + HTML file generation. This is the default builder. + +htmlhelp + Generates files for CHM (compiled help files) generation. + +qthelp + Generates files for Qt help collection generation. + +devhelp + Generates files for the GNOME Devhelp help viewer. + +latex + Generates LaTeX output that can be compiled to a PDF document. + +man + Generates manual pages. + +text + Generates a plain-text version of the documentation. + +changes + Generates HTML files listing changed/added/deprecated items for + the current version of the documented project. + +linkcheck + Checks the integrity of all external links in the source. + +pickle / json + Generates serialized HTML files for use in web applications. + + +Options +------- + +-b Builder to use; defaults to html. See the full list + of builders above. +-a Generates output for all files; without this option only + output for new and changed files is generated. +-E Ignores cached files, forces to re-read all source files + from disk. +-c Locates the conf.py file in the specified path instead of + . +-C Specifies that no conf.py file at all is to be used. + Configuration can only be set with the -D option. +-D = Overrides a setting from the configuration file. +-d Path to cached files; defaults to /.doctrees. +-A = Passes a value into the HTML templates (only for HTML + builders). +-N Prevents colored output. +-q Quiet operation, just prints warnings and errors on stderr. +-Q Very quiet operation, doesn't print anything except for + errors. +-w Write warnings and errors into the given file, in addition + to stderr. +-W Turn warnings into errors. +-P Runs Pdb on exception. + + +See also +-------- + +:manpage:`sphinx-quickstart(1)` + +Author +------ + +Georg Brandl , Armin Ronacher et +al. + +This manual page was initially written by Mikhail Gusarov +, for the Debian project. diff --git a/doc/man/sphinx-quickstart.rst b/doc/man/sphinx-quickstart.rst new file mode 100644 index 00000000..2464bcf7 --- /dev/null +++ b/doc/man/sphinx-quickstart.rst @@ -0,0 +1,30 @@ +sphinx-quickstart manual page +============================= + +Synopsis +-------- + +**sphinx-quickstart** + + +Description +----------- + +:program:`sphinx-quickstart` is an interactive tool that asks some questions +about your project and then generates a complete documentation directory and +sample Makefile to be used with :manpage:`sphinx-build(1)`. + + +See also +-------- + +:manpage:`sphinx-build(1)` + +Author +------ + +Georg Brandl , Armin Ronacher et +al. + +This manual page was initially written by Mikhail Gusarov +, for the Debian project. diff --git a/doc/sphinx-build.1 b/doc/sphinx-build.1 deleted file mode 100644 index a3df16d0..00000000 --- a/doc/sphinx-build.1 +++ /dev/null @@ -1,105 +0,0 @@ -.TH sphinx-build 1 "Jan 2009" "Sphinx 0.6" "User Commands" -.SH NAME -sphinx-build \- Sphinx documentation generator tool -.SH SYNOPSIS -.B sphinx-build -[\fIoptions\fR] <\fIsourcedir\fR> <\fIoutdir\fR> [\fIfilenames\fR...] -.SH DESCRIPTION -sphinx-build generates documentation from the files in and places it -in the . - -sphinx-build looks for /conf.py for the configuration settings. -.B sphinx-quickstart(1) -may be used to generate template files, including conf.py. - -sphinx-build can create documentation in different formats. A format is -selected by specifying the builder name on the command line; it defaults to -HTML. Builders can also perform other tasks related to documentation -processing. - -By default, everything that is outdated is built. Output only for selected -files can be built by specifying individual filenames. - -List of available builders: -.TP -\fBhtml\fR -HTML files generation. This is default builder. -.TP -\fBhtmlhelp\fR -Generates files for CHM generation. -.TP -\fBqthelp\fR -Generates files for Qt help collection generation. -.TP -\fBdevhelp\fR -Generates files for GNOME Devhelp help viewer. -.TP -\fBlatex\fR -Generates a LaTeX version of the documentation. -.TP -\fBtext\fR -Generates a plain-text version of the documentation. -.TP -\fBchanges\fR -Generates HTML files listing changed/added/deprecated items for the -current version. -.TP -\fBlinkcheck\fR -Checks the integrity of all external links in the documentation. -.TP -\fBpickle / json\fR -Generates serialized HTML files in the selected format. - -.SH OPTIONS -.TP -\fB-b\fR -Builder to use; defaults to html. See the full list of builders above. -.TP -\fB-a\fR -Generates output for all files; without this option only output for -new and changed files is generated. -.TP -\fB-E\fR -Ignores cached files, forces to re-read all source files from disk. -.TP -\fB-c\fR -Locates the conf.py file in the specified path instead of . -.TP -\fB-C\fR -Specifies that no conf.py file at all is to be used. Configuration can -only be set with the -D option. -.TP -\fB-D\fR = -Overrides a setting from the configuration file. -.TP -\fB-d\fR -Path to cached files; defaults to /.doctrees. -.TP -\fB-A\fR = -Passes a value into the HTML templates (only for html builders). -.TP -\fB-N\fR -Prevents colored output. -.TP -\fB-q\fR -Quiet operation, just prints warnings and errors on stderr. -.TP -\fB-Q\fR -Very quiet operation, doesn't print anything except for errors. -.TP -\fB-w\fR -Write warnings and errors into the given file, in addition to stderr. -.TP -\fB-W\fR -Turn warnings into errors. -.TP -\fB-P\fR -Runs Pdb on exception. -.SH "SEE ALSO" -.BR sphinx-quickstart(1) -.SH AUTHOR -Georg Brandl , Armin Ronacher et -al. -.PP -This manual page was initially written by Mikhail Gusarov -, for the Debian project. diff --git a/doc/sphinx-quickstart.1 b/doc/sphinx-quickstart.1 deleted file mode 100644 index 93b0a4a5..00000000 --- a/doc/sphinx-quickstart.1 +++ /dev/null @@ -1,17 +0,0 @@ -.TH sphinx-quickstart 1 "Jan 2009" "Sphinx 0.6" "User Commands" -.SH NAME -sphinx-quickstart \- Sphinx documentation template generator -.SH SYNOPSIS -.B sphinx-quickstart -.SH DESCRIPTION -sphinx-quickstart is an interactive tool that asks some questions about your -project and then generates a complete documentation directory and sample -Makefile to be used with \fBsphinx-build(1)\fR. -.SH "SEE ALSO" -.BR sphinx-build(1) -.SH AUTHOR -Georg Brandl , Armin Ronacher et -al. -.PP -This manual page was initially written by Mikhail Gusarov - for the Debian project. -- cgit v1.2.1 From 3c74907fed821ce9d33dda88d43d8aa3587c79d6 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 25 Feb 2010 22:49:22 +0100 Subject: Do not emit warnings for images. --- sphinx/writers/manpage.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sphinx/writers/manpage.py b/sphinx/writers/manpage.py index 3d4f18cb..eb35c5a5 100644 --- a/sphinx/writers/manpage.py +++ b/sphinx/writers/manpage.py @@ -223,6 +223,13 @@ class ManualPageTranslator(BaseTranslator): def depart_production(self, node): pass + # overwritten -- don't emit a warning for images + def visit_image(self, node): + if 'alt' in node.attributes: + self.body.append('[image: %s]\n' % node['alt']) + self.body.append('[image]\n') + raise nodes.SkipNode + # overwritten -- don't visit inner marked up nodes def visit_reference(self, node): self.body.append(self.defs['reference'][0]) -- cgit v1.2.1 From cef7879fa4dc1dc2c379216a42741db28712e6c3 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 25 Feb 2010 22:49:54 +0100 Subject: Add "orphan" metadata entry to suppress non-toctree-inclusion warnings. --- doc/man/sphinx-build.rst | 7 ++++--- doc/man/sphinx-quickstart.rst | 3 +++ doc/markup/misc.rst | 6 ++++++ sphinx/environment.py | 2 ++ 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/doc/man/sphinx-build.rst b/doc/man/sphinx-build.rst index b8e5b272..ca493ec0 100644 --- a/doc/man/sphinx-build.rst +++ b/doc/man/sphinx-build.rst @@ -1,3 +1,5 @@ +:orphan: + sphinx-build manual page ======================== @@ -72,10 +74,9 @@ Options . -C Specifies that no conf.py file at all is to be used. Configuration can only be set with the -D option. --D = Overrides a setting from the configuration file. +-D Overrides a setting from the configuration file. -d Path to cached files; defaults to /.doctrees. --A = Passes a value into the HTML templates (only for HTML - builders). +-A Passes a value into the HTML templates (only for HTML builders). -N Prevents colored output. -q Quiet operation, just prints warnings and errors on stderr. -Q Very quiet operation, doesn't print anything except for diff --git a/doc/man/sphinx-quickstart.rst b/doc/man/sphinx-quickstart.rst index 2464bcf7..17277261 100644 --- a/doc/man/sphinx-quickstart.rst +++ b/doc/man/sphinx-quickstart.rst @@ -1,3 +1,5 @@ +:orphan: + sphinx-quickstart manual page ============================= @@ -20,6 +22,7 @@ See also :manpage:`sphinx-build(1)` + Author ------ diff --git a/doc/markup/misc.rst b/doc/markup/misc.rst index 01e5a3f1..7db403ab 100644 --- a/doc/markup/misc.rst +++ b/doc/markup/misc.rst @@ -29,6 +29,12 @@ At the moment, these metadata fields are recognized: If set, the web application won't display a comment form for a page generated from this source file. +``orphan`` + If set, warnings about this file not being included in any toctree will be + suppressed. + + .. versionadded:: 1.0 + Meta-information markup ----------------------- diff --git a/sphinx/environment.py b/sphinx/environment.py index dacec2c1..ad6d6ea8 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -1549,4 +1549,6 @@ class BuildEnvironment: if docname == self.config.master_doc: # the master file is not included anywhere ;) continue + if 'orphan' in self.metadata[docname]: + continue self.warn(docname, 'document isn\'t included in any toctree') -- cgit v1.2.1 From 0b6666904b8535b1c80d75eb27fe6ca6f0ab1e89 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sat, 27 Feb 2010 23:13:31 +0100 Subject: Remove Python-specific option from general directive. --- sphinx/directives/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sphinx/directives/__init__.py b/sphinx/directives/__init__.py index ecb4ce57..01305469 100644 --- a/sphinx/directives/__init__.py +++ b/sphinx/directives/__init__.py @@ -47,7 +47,6 @@ class ObjectDescription(Directive): final_argument_whitespace = True option_spec = { 'noindex': directives.flag, - 'module': directives.unchanged, } # types of doc fields that this directive handles, see sphinx.util.docfields -- cgit v1.2.1 From 42fc484adb89d951066b49143ce62c0264bca3f2 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sat, 27 Feb 2010 23:13:51 +0100 Subject: Add app.override_domain(). --- doc/ext/appapi.rst | 12 ++++++++++-- sphinx/application.py | 9 ++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/doc/ext/appapi.rst b/doc/ext/appapi.rst index 8df028e1..b536fd98 100644 --- a/doc/ext/appapi.rst +++ b/doc/ext/appapi.rst @@ -45,16 +45,24 @@ the following public API: .. method:: Sphinx.add_domain(domain) - Make the given *domain* (which must be a subclass of + Make the given *domain* (which must be a class; more precisely, a subclass of :class:`sphinx.domains.Domain`) known to Sphinx. .. XXX where is Domain documented? .. versionadded:: 1.0 +.. method:: Sphinx.override_domain(domain) + + Make the given *domain* known to Sphinx, assuming that there is already a + domain with its ``.name``. The new domain must be a subclass of the existing + one. + + .. versionadded:: 1.0 + .. method:: Sphinx.add_event(name) - Register an event called *name*. + Register an event called *name*. This is needed to be able to emit it. .. method:: Sphinx.add_node(node, **kwds) diff --git a/sphinx/application.py b/sphinx/application.py index 7d4e3cac..860affbb 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -388,11 +388,18 @@ class Sphinx(object): roles.register_local_role(name, role) def add_domain(self, domain): - # XXX what about subclassing and overriding? if domain.name in self.domains: raise ExtensionError('domain %s already registered' % domain.name) self.domains[domain.name] = domain + def override_domain(self, domain): + if domain.name not in self.domains: + raise ExtensionError('domain %s not yet registered' % domain.name) + if not issubclass(domain, self.domains[domain.name]): + raise ExtensionError('new domain not a subclass of registered ' + 'domain' % domain.name) + self.domains[domain.name] = domain + def add_directive_to_domain(self, domain, name, obj, content=None, arguments=None, **options): if domain not in self.domains: -- cgit v1.2.1 From 2c28d53b342a21f2f851eaf91ba713304df06ee6 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sat, 27 Feb 2010 23:14:01 +0100 Subject: Add some more docstring content. --- sphinx/directives/__init__.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/sphinx/directives/__init__.py b/sphinx/directives/__init__.py index 01305469..62e0a7d8 100644 --- a/sphinx/directives/__init__.py +++ b/sphinx/directives/__init__.py @@ -38,7 +38,8 @@ strip_backslash_re = re.compile(r'\\(?=[^\\])') class ObjectDescription(Directive): """ Directive to describe a class, function or similar object. Not used - directly, but subclassed to add custom behavior. + directly, but subclassed (in domain-specific directives) to add custom + behavior. """ has_content = True @@ -54,7 +55,8 @@ class ObjectDescription(Directive): def get_signatures(self): """ - Retrieve the signatures to document from the directive arguments. + Retrieve the signatures to document from the directive arguments. By + default, signatures are given as arguments, one per line. """ # remove backslashes to support (dummy) escapes; helps Vim highlighting return [strip_backslash_re.sub('', sig.strip()) @@ -89,6 +91,23 @@ class ObjectDescription(Directive): pass def run(self): + """ + Main directive entry function, called by docutils upon encountering the + directive. + + This directive is meant to be quite easily subclassable, so it delegates + to several additional methods. What it does: + + * find out if called as a domain-specific directive, set self.domain + * create a `desc` node to fit all description inside + * parse standard options, currently `noindex` + * create an index node if needed as self.indexnode + * parse all given signatures (as returned by self.get_signatures()) + using self.handle_signature(), which should either return a name + or raise ValueError + * add index entries using self.add_target_and_index() + * parse the content and handle doc fields in it + """ if ':' in self.name: self.domain, self.objtype = self.name.split(':', 1) else: -- cgit v1.2.1 From 6e1e5babd8669fd5ac358d8be187b2dfe0ed6805 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 28 Feb 2010 09:19:00 +0100 Subject: Add Blinker. --- EXAMPLES | 1 + 1 file changed, 1 insertion(+) diff --git a/EXAMPLES b/EXAMPLES index 5b405f51..e089cd46 100644 --- a/EXAMPLES +++ b/EXAMPLES @@ -111,6 +111,7 @@ Documentation using another builtin theme Documentation using a custom theme/integrated in a site ------------------------------------------------------- +* Blinker: http://discorporate.us/projects/Blinker/docs/ * Django: http://docs.djangoproject.com/ * GeoServer: http://docs.geoserver.org/ * Glashammer: http://glashammer.org/ -- cgit v1.2.1 From 3ec1bfb75e97099c509f958895677c2856a957ce Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 28 Feb 2010 09:21:24 +0100 Subject: Add missing option_spec for Python domain. --- sphinx/domains/python.py | 4 ++++ tests/root/objects.txt | 2 ++ 2 files changed, 6 insertions(+) diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index 1678929c..af1392a6 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -40,6 +40,10 @@ class PyObject(ObjectDescription): """ Description of a general Python object. """ + option_spec = { + 'noindex': directives.flag, + 'module': directives.unchanged, + } doc_field_types = [ TypedField('parameter', label=l_('Parameters'), diff --git a/tests/root/objects.txt b/tests/root/objects.txt index 4e44a097..7a93aeed 100644 --- a/tests/root/objects.txt +++ b/tests/root/objects.txt @@ -10,6 +10,8 @@ Testing object descriptions .. function:: func_noindex :noindex: +.. function:: func_with_module + :module: foolib .. module:: mod :synopsis: Module synopsis. -- cgit v1.2.1 From 59964efbfbcbeed5e0cd19bfae3d230bd3cb40fc Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 28 Feb 2010 09:36:27 +0100 Subject: Apply some changes to agogo, contributed by Florian Diesch. --- doc/theming.rst | 2 ++ sphinx/themes/agogo/static/agogo.css_t | 37 ++++++++++++++++++++++++++++++++-- sphinx/themes/agogo/theme.conf | 3 ++- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/doc/theming.rst b/doc/theming.rst index 6c650e4a..e4a6b9a5 100644 --- a/doc/theming.rst +++ b/doc/theming.rst @@ -162,6 +162,8 @@ These themes are: headings. - **headerlinkcolor** (CSS color): Color for the backreference link in headings. + - **textalign** (CSS *text-align* value): Text alignment for the body, default + is ``justify``. * **nature** -- A greenish theme. There are currently no options beyond *nosidebar*. diff --git a/sphinx/themes/agogo/static/agogo.css_t b/sphinx/themes/agogo/static/agogo.css_t index 4dd03d92..cfe21612 100644 --- a/sphinx/themes/agogo/static/agogo.css_t +++ b/sphinx/themes/agogo/static/agogo.css_t @@ -39,10 +39,13 @@ div.header-wrapper { /* Default body styles */ a { - text-decoration: none; color: {{ theme_linkcolor }}; } +div.bodywrapper a, div.footer a { + text-decoration: underline; +} + .clearer { clear: both; } @@ -99,7 +102,21 @@ dt:hover > a.headerlink { visibility: visible; } +img { + border: 0; +} +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 2px 7px 1px 7px; + border-left: 0.2em solid black; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} /* Header */ @@ -152,7 +169,7 @@ div.document { div.body { padding-right: 2em; - text-align: justify; + text-align: {{ theme_textalign }}; } div.document ul { @@ -217,6 +234,14 @@ div.sidebar { font-size: .9em; } +div.sidebar a, div.header a { + text-decoration: none; +} + +div.sidebar a:hover, div.header a:hover { + text-decoration: underline; +} + div.sidebar h3 { color: #2e3436; text-transform: uppercase; @@ -241,6 +266,14 @@ div.sidebar li.toctree-l1 a { div.sidebar li.toctree-l2 a { background-color: transparent; border: none; + margin-left: 1em; + border-bottom: 1px solid #dddddd; +} + +div.sidebar li.toctree-l3 a { + background-color: transparent; + border: none; + margin-left: 2em; border-bottom: 1px solid #dddddd; } diff --git a/sphinx/themes/agogo/theme.conf b/sphinx/themes/agogo/theme.conf index 9cdac5d7..3fc88580 100644 --- a/sphinx/themes/agogo/theme.conf +++ b/sphinx/themes/agogo/theme.conf @@ -15,4 +15,5 @@ footerbg = url(bgfooter.png) top left repeat-x linkcolor = #ce5c00 headercolor1 = #204a87 headercolor2 = #3465a4 -headerlinkcolor = #fcaf3e \ No newline at end of file +headerlinkcolor = #fcaf3e +textalign = justify \ No newline at end of file -- cgit v1.2.1 From 74aab2f9e245ff44c1eb6d5f658836c0493df56e Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 28 Feb 2010 10:17:59 +0100 Subject: In HTML output, inline roles now get a CSS class with their name, allowing styles to customize their appearance. Domain-specific roles get two classes, ``domain`` and ``domain-rolename``. --- CHANGES | 4 ++++ sphinx/roles.py | 18 ++++++++++-------- tests/test_markup.py | 17 +++++++++++------ 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/CHANGES b/CHANGES index 47b6c4e6..31a2a99b 100644 --- a/CHANGES +++ b/CHANGES @@ -10,6 +10,10 @@ Release 1.0 (in development) * Added a manual page builder. +* In HTML output, inline roles now get a CSS class with their name, + allowing styles to customize their appearance. Domain-specific + roles get two classes, ``domain`` and ``domain-rolename``. + * New more compact doc field syntax is now recognized: ``:param type name: description``. diff --git a/sphinx/roles.py b/sphinx/roles.py index ce30d139..ae035436 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -36,7 +36,8 @@ generic_docroles = { } for rolename, nodeclass in generic_docroles.iteritems(): - role = roles.GenericRole(rolename, nodeclass) + generic = roles.GenericRole(rolename, nodeclass) + role = roles.CustomRole(rolename, generic, {'classes': [rolename]}) roles.register_local_role(rolename, role) @@ -102,12 +103,13 @@ class XRefRole(object): domain, role = '', typ else: domain, role = typ.split(':', 1) + classes = ['xref', domain, '%s-%s' % (domain, role)] text = utils.unescape(text) # if the first character is a bang, don't cross-reference at all if text[0:1] == '!': if self.fix_parens: text, tgt = self._fix_parens(env, False, text[1:], "") - innernode = self.innernodeclass(rawtext, text, classes=['xref']) + innernode = self.innernodeclass(rawtext, text, classes=classes) return self.result_nodes(inliner.document, env, innernode, is_ref=False) # split title and target in role content @@ -127,7 +129,7 @@ class XRefRole(object): env, refnode, has_explicit_title, title, target) # now that the target and title are finally determined, set them refnode['reftarget'] = target - refnode += self.innernodeclass(rawtext, title, classes=['xref']) + refnode += self.innernodeclass(rawtext, title, classes=classes) # we also need the source document refnode['refdoc'] = env.docname # result_nodes allow further modification of return values @@ -179,7 +181,7 @@ def indexmarkup_role(typ, rawtext, etext, lineno, inliner, return [prb], [msg] ref = inliner.document.settings.pep_base_url + 'pep-%04d' % pepnum sn = nodes.strong('PEP '+text, 'PEP '+text) - rn = nodes.reference('', '', refuri=ref) + rn = nodes.reference('', '', refuri=ref, classes=[typ]) rn += sn return [indexnode, targetnode, rn], [] elif typ == 'rfc': @@ -194,15 +196,15 @@ def indexmarkup_role(typ, rawtext, etext, lineno, inliner, return [prb], [msg] ref = inliner.document.settings.rfc_base_url + inliner.rfc_url % rfcnum sn = nodes.strong('RFC '+text, 'RFC '+text) - rn = nodes.reference('', '', refuri=ref) + rn = nodes.reference('', '', refuri=ref, classes=[typ]) rn += sn return [indexnode, targetnode, rn], [] def menusel_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): return [nodes.emphasis( - rawtext, - utils.unescape(text).replace('-->', u'\N{TRIANGULAR BULLET}'))], [] + rawtext, utils.unescape(text).replace('-->', u'\N{TRIANGULAR BULLET}'), + classes=[typ])], [] return role @@ -212,7 +214,7 @@ def emph_literal_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): text = utils.unescape(text) pos = 0 - retnode = nodes.literal(role=typ.lower()) + retnode = nodes.literal(role=typ.lower(), classes=[typ]) for m in _litvar_re.finditer(text): if m.start() > pos: txt = text[pos:m.start()] diff --git a/tests/test_markup.py b/tests/test_markup.py index a7e60210..a8ced747 100644 --- a/tests/test_markup.py +++ b/tests/test_markup.py @@ -80,26 +80,26 @@ def verify(rst, html_expected, latex_expected): def test_inline(): # correct interpretation of code with whitespace - _html = ('

    ' + _html = ('

    ' 'code   sample

    ') - yield verify, '``code sample``', _html, '\\code{code sample}' - yield verify, ':samp:`code sample`', _html, '\\samp{code sample}' + yield verify_re, '``code sample``', _html, r'\\code{code sample}' + yield verify_re, ':samp:`code sample`', _html, r'\\samp{code sample}' # interpolation of braces in samp and file roles (HTML only) yield (verify, ':samp:`a{b}c`', - '

    a' + '

    a' 'b' 'c

    ', '\\samp{abc}') # interpolation of arrows in menuselection yield (verify, ':menuselection:`a --> b`', - u'

    a \N{TRIANGULAR BULLET} b

    ', + u'

    a \N{TRIANGULAR BULLET} b

    ', '\\emph{a \\(\\rightarrow\\) b}') # non-interpolation of dashes in option role yield (verify_re, ':option:`--with-option`', - '

    --with-option

    $', + '

    --with-option

    $', r'\\emph{\\texttt{-{-}with-option}}$') # verify smarty-pants quotes @@ -110,6 +110,11 @@ def test_inline(): '"John"

    ', '\\code{"John"}') + # verify classes for inline roles + yield (verify, ':manpage:`mp(1)`', + '

    mp(1)

    ', + '\\emph{\\texttt{mp(1)}}') + def test_latex_escaping(): # correct escaping in normal mode yield (verify, u'Γ\\\\∞$', None, -- cgit v1.2.1 From a8cca36b28a33b22e7d79600cfbbd33f5ec4f09a Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 28 Feb 2010 10:19:36 +0100 Subject: Fix-up classes for roles without domain. --- sphinx/roles.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sphinx/roles.py b/sphinx/roles.py index ae035436..d20898ff 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -101,9 +101,10 @@ class XRefRole(object): typ = typ.lower() if ':' not in typ: domain, role = '', typ + classes = ['xref', role] else: domain, role = typ.split(':', 1) - classes = ['xref', domain, '%s-%s' % (domain, role)] + classes = ['xref', domain, '%s-%s' % (domain, role)] text = utils.unescape(text) # if the first character is a bang, don't cross-reference at all if text[0:1] == '!': -- cgit v1.2.1 From ffe393b1b0a47d28c770668378ccff750fa82be3 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 28 Feb 2010 11:39:13 +0100 Subject: #187: Added support for source ordering of members in autodoc, with ``autodoc_member_order = 'bysource'``. --- CHANGES | 3 +++ doc/ext/autodoc.rst | 10 ++++++++-- sphinx/ext/autodoc.py | 13 ++++++++++--- sphinx/pycode/__init__.py | 22 +++++++++++++++++----- tests/test_autodoc.py | 36 ++++++++++++++++++++++++++++++++++++ 5 files changed, 74 insertions(+), 10 deletions(-) diff --git a/CHANGES b/CHANGES index 31458763..5fc36540 100644 --- a/CHANGES +++ b/CHANGES @@ -10,6 +10,9 @@ Release 1.0 (in development) * Added a manual page builder. +* Added support for source ordering of members in autodoc, with + ``autodoc_member_order = 'bysource'``. + * In HTML output, inline roles now get a CSS class with their name, allowing styles to customize their appearance. Domain-specific roles get two classes, ``domain`` and ``domain-rolename``. diff --git a/doc/ext/autodoc.rst b/doc/ext/autodoc.rst index 4098d711..08a54082 100644 --- a/doc/ext/autodoc.rst +++ b/doc/ext/autodoc.rst @@ -223,10 +223,16 @@ There are also new config values that you can set: .. confval:: autodoc_member_order This value selects if automatically documented members are sorted - alphabetical (value ``'alphabetical'``) or by member type (value - ``'groupwise'``). The default is alphabetical. + alphabetical (value ``'alphabetical'``), by member type (value + ``'groupwise'``) or by source order (value ``'bysource'``). The default is + alphabetical. + + Note that for source order, the module must be a Python module with the + source code available. .. versionadded:: 0.6 + .. versionchanged:: 1.0 + Support for ``'bysource'``. .. confval:: autodoc_default_flags diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc.py index 7e42002e..2597c03b 100644 --- a/sphinx/ext/autodoc.py +++ b/sphinx/ext/autodoc.py @@ -599,12 +599,19 @@ class Documenter(object): '.'.join(self.objpath + [mname]) memberdocumenters.append( classes[-1](self.directive, full_mname, self.indent)) - - if (self.options.member_order or self.env.config.autodoc_member_order) \ - == 'groupwise': + member_order = self.options.member_order or \ + self.env.config.autodoc_member_order + if member_order == 'groupwise': # sort by group; relies on stable sort to keep items in the # same group sorted alphabetically memberdocumenters.sort(key=lambda d: d.member_order) + elif member_order == 'bysource' and self.analyzer: + # sort by source order, by virtue of the module analyzer + tagorder = self.analyzer.tagorder + def keyfunc(documenter): + fullname = documenter.name.split('::')[1] + return tagorder.get(fullname, len(tagorder)) + memberdocumenters.sort(key=keyfunc) for documenter in memberdocumenters: documenter.generate(all_members=True, diff --git a/sphinx/pycode/__init__.py b/sphinx/pycode/__init__.py index 73c2042f..63303a85 100644 --- a/sphinx/pycode/__init__.py +++ b/sphinx/pycode/__init__.py @@ -58,9 +58,17 @@ class AttrDocVisitor(nodes.NodeVisitor): self.encoding = encoding self.namespace = [] self.collected = {} + self.tagnumber = 0 + self.tagorder = {} + + def add_tag(self, name): + name = '.'.join(self.namespace + [name]) + self.tagorder[name] = self.tagnumber + self.tagnumber += 1 def visit_classdef(self, node): """Visit a class.""" + self.add_tag(node[1].value) self.namespace.append(node[1].value) self.generic_visit(node) self.namespace.pop() @@ -68,6 +76,7 @@ class AttrDocVisitor(nodes.NodeVisitor): def visit_funcdef(self, node): """Visit a function (or method).""" # usually, don't descend into functions -- nothing interesting there + self.add_tag(node[1].value) if node[1].value == '__init__': # however, collect attributes set in __init__ methods self.in_init += 1 @@ -91,8 +100,7 @@ class AttrDocVisitor(nodes.NodeVisitor): prefix = pnode.get_prefix() prefix = prefix.decode(self.encoding) docstring = prepare_commentdoc(prefix) - if docstring: - self.add_docstring(node, docstring) + self.add_docstring(node, docstring) def visit_simple_stmt(self, node): """Visit a docstring statement which may have an assignment before.""" @@ -133,9 +141,11 @@ class AttrDocVisitor(nodes.NodeVisitor): continue else: name = target.value - namespace = '.'.join(self.namespace) - if namespace.startswith(self.scope): - self.collected[namespace, name] = docstring + self.add_tag(name) + if docstring: + namespace = '.'.join(self.namespace) + if namespace.startswith(self.scope): + self.collected[namespace, name] = docstring class ModuleAnalyzer(object): @@ -197,6 +207,7 @@ class ModuleAnalyzer(object): self.parsetree = None # will be filled by find_attr_docs() self.attr_docs = None + self.tagorder = None # will be filled by find_tags() self.tags = None @@ -234,6 +245,7 @@ class ModuleAnalyzer(object): attr_visitor = AttrDocVisitor(number2name, scope, self.encoding) attr_visitor.visit(self.parsetree) self.attr_docs = attr_visitor.collected + self.tagorder = attr_visitor.tagorder # now that we found everything we could in the tree, throw it away # (it takes quite a bit of memory for large modules) self.parsetree = None diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index ea41dcaa..62768b20 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -341,6 +341,26 @@ def test_generate(): assert item in directive.result del directive.result[:] + def assert_order(items, objtype, name, member_order, **kw): + inst = AutoDirective._registry[objtype](directive, name) + inst.options.member_order = member_order + inst.generate(**kw) + assert len(_warnings) == 0, _warnings + items = list(reversed(items)) + lineiter = iter(directive.result) + #for line in directive.result: + # if line.strip(): + # print repr(line) + while items: + item = items.pop() + for line in lineiter: + if line == item: + break + else: # ran out of items! + assert False, 'item %r not found in result or not in the ' \ + ' correct order' % item + del directive.result[:] + options.members = [] # no module found? @@ -442,6 +462,22 @@ def test_generate(): assert_processes([('function', 'time.asctime')], 'function', 'asctime') assert_processes([('function', 'time.asctime')], 'function', 'asctime') + # test autodoc_member_order == 'source' + directive.env.temp_data['py:module'] = 'test_autodoc' + assert_order(['.. py:class:: Class(arg)', + ' .. py:attribute:: Class.descr', + ' .. py:method:: Class.meth()', + ' .. py:method:: Class.undocmeth()', + ' .. py:attribute:: Class.attr', + ' .. py:attribute:: Class.prop', + ' .. py:attribute:: Class.docattr', + ' .. py:attribute:: Class.udocattr', + ' .. py:attribute:: Class.inst_attr_comment', + ' .. py:attribute:: Class.inst_attr_string', + ' .. py:method:: Class.inheritedmeth()', + ], + 'class', 'Class', member_order='bysource', all_members=True) + # --- generate fodder ------------ -- cgit v1.2.1 From f060980ceee1883ecaf5704c2b3b8c3b819bcf72 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 28 Feb 2010 12:08:06 +0100 Subject: Fix test_markup. --- tests/test_markup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_markup.py b/tests/test_markup.py index a8ced747..0277d327 100644 --- a/tests/test_markup.py +++ b/tests/test_markup.py @@ -99,7 +99,7 @@ def test_inline(): # non-interpolation of dashes in option role yield (verify_re, ':option:`--with-option`', - '

    --with-option

    $', + '

    --with-option

    $', r'\\emph{\\texttt{-{-}with-option}}$') # verify smarty-pants quotes -- cgit v1.2.1 From 06c5940646d823a28bf550150b206a9674f6c98f Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 28 Feb 2010 12:13:55 +0100 Subject: #282: Added a "nitpicky" mode that emits warnings for all missing references. It is activated by the ``-n`` command-line switch or the ``nitpicky`` config value. --- CHANGES | 4 ++++ doc/config.rst | 45 +++++++++++++++++++++++++++------------------ doc/intro.rst | 4 ++++ doc/man/sphinx-build.rst | 22 +++++++++++----------- sphinx/cmdline.py | 5 ++++- sphinx/config.py | 1 + sphinx/environment.py | 14 +++++++++++++- 7 files changed, 64 insertions(+), 31 deletions(-) diff --git a/CHANGES b/CHANGES index 5fc36540..e64ef2e6 100644 --- a/CHANGES +++ b/CHANGES @@ -20,6 +20,10 @@ Release 1.0 (in development) * New more compact doc field syntax is now recognized: ``:param type name: description``. +* Added a "nitpicky" mode that emits warnings for all missing + references. It is activated by the ``-n`` command-line switch + or the ``nitpicky`` config value. + * Added the ``viewcode`` extension. * Added ``html-collect-pages`` event. diff --git a/doc/config.rst b/doc/config.rst index 21b0134d..5dbccb4e 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -206,29 +206,19 @@ General configuration .. versionadded:: 0.5 -.. confval:: modindex_common_prefix - - A list of prefixes that are ignored for sorting the Python module index - (e.g., if this is set to ``['foo.']``, then ``foo.bar`` is shown under ``B``, - not ``F``). This can be handy if you document a project that consists of a - single package. Works only for the HTML builder currently. Default is - ``[]``. - - .. versionadded:: 0.6 - -.. confval:: trim_doctest_flags +.. confval:: needs_sphinx - If true, doctest flags (comments looking like ``# doctest: FLAG, ...``) at - the ends of lines are removed for all code blocks showing interactive Python - sessions (i.e. doctests). Default is true. See the extension - :mod:`~sphinx.ext.doctest` for more possibilities of including doctests. + If set to a ``major.minor`` version string like ``'1.1'``, Sphinx will + compare it with its version and refuse to build if it is too old. Default is + no requirement. .. versionadded:: 1.0 -.. confval:: needs_sphinx +.. confval:: nitpicky - If set to a ``major.minor`` version string like ``'1.1'``, Sphinx will - compare it with its version and refuse to build if it is too old. + If true, Sphinx will warn about *all* references where the target cannot be + found. Default is ``False``. You can activate this mode temporarily using + the :option:`-n` command-line switch. .. versionadded:: 1.0 @@ -334,6 +324,16 @@ Project information A boolean that decides whether :dir:`moduleauthor` and :dir:`sectionauthor` directives produce any output in the built files. +.. confval:: modindex_common_prefix + + A list of prefixes that are ignored for sorting the Python module index + (e.g., if this is set to ``['foo.']``, then ``foo.bar`` is shown under ``B``, + not ``F``). This can be handy if you document a project that consists of a + single package. Works only for the HTML builder currently. Default is + ``[]``. + + .. versionadded:: 0.6 + .. confval:: trim_footnote_reference_space Trim spaces before footnote references that are necessary for the reST parser @@ -341,6 +341,15 @@ Project information .. versionadded:: 0.6 +.. confval:: trim_doctest_flags + + If true, doctest flags (comments looking like ``# doctest: FLAG, ...``) at + the ends of lines are removed for all code blocks showing interactive Python + sessions (i.e. doctests). Default is true. See the extension + :mod:`~sphinx.ext.doctest` for more possibilities of including doctests. + + .. versionadded:: 1.0 + .. _html-options: diff --git a/doc/intro.rst b/doc/intro.rst index 773bc74e..37346b6f 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -130,6 +130,10 @@ The :program:`sphinx-build` script has several more options: **-A** *name=value* Make the *name* assigned to *value* in the HTML templates. +**-n** + Run in nit-picky mode. Currently, this generates warnings for all missing + references. + **-N** Do not do colored output. (On Windows, colored output is disabled in any case.) diff --git a/doc/man/sphinx-build.rst b/doc/man/sphinx-build.rst index ca493ec0..30f824e8 100644 --- a/doc/man/sphinx-build.rst +++ b/doc/man/sphinx-build.rst @@ -66,25 +66,25 @@ Options -b Builder to use; defaults to html. See the full list of builders above. --a Generates output for all files; without this option only +-a Generate output for all files; without this option only output for new and changed files is generated. --E Ignores cached files, forces to re-read all source files +-E Ignore cached files, forces to re-read all source files from disk. --c Locates the conf.py file in the specified path instead of +-c Locate the conf.py file in the specified path instead of . --C Specifies that no conf.py file at all is to be used. +-C Specify that no conf.py file at all is to be used. Configuration can only be set with the -D option. --D Overrides a setting from the configuration file. +-D Override a setting from the configuration file. -d Path to cached files; defaults to /.doctrees. --A Passes a value into the HTML templates (only for HTML builders). --N Prevents colored output. --q Quiet operation, just prints warnings and errors on stderr. --Q Very quiet operation, doesn't print anything except for - errors. +-A Pass a value into the HTML templates (only for HTML builders). +-n Run in nit-picky mode, warn about all missing references. +-N Prevent colored output. +-q Quiet operation, just print warnings and errors on stderr. +-Q Very quiet operation, don't print anything except for errors. -w Write warnings and errors into the given file, in addition to stderr. -W Turn warnings into errors. --P Runs Pdb on exception. +-P Run Pdb on exception. See also diff --git a/sphinx/cmdline.py b/sphinx/cmdline.py index bc5aff87..e3e94465 100644 --- a/sphinx/cmdline.py +++ b/sphinx/cmdline.py @@ -43,6 +43,7 @@ new and changed files -C -- use no config file at all, only -D options -D -- override a setting in configuration -A -- pass a value into the templates, for HTML builder + -n -- nit-picky mode, warn about all missing references -N -- do not do colored output -q -- no output on stdout, just warnings on stderr -Q -- no output at all, not even warnings @@ -61,7 +62,7 @@ def main(argv): nocolor() try: - opts, args = getopt.getopt(argv[1:], 'ab:t:d:c:CD:A:g:NEqQWw:P') + opts, args = getopt.getopt(argv[1:], 'ab:t:d:c:CD:A:ng:NEqQWw:P') allopts = set(opt[0] for opt in opts) srcdir = confdir = path.abspath(args[0]) if not path.isdir(srcdir): @@ -142,6 +143,8 @@ def main(argv): except ValueError: pass confoverrides['html_context.%s' % key] = val + elif opt == '-n': + confoverrides['nitpicky'] = True elif opt == '-N': nocolor() elif opt == '-E': diff --git a/sphinx/config.py b/sphinx/config.py index 0de30a5a..24082b6c 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -62,6 +62,7 @@ class Config(object): trim_doctest_flags = (True, 'env'), default_domain = ('py', 'env'), needs_sphinx = (None, None), + nitpicky = (False, 'env'), # HTML options html_theme = ('default', 'html'), diff --git a/sphinx/environment.py b/sphinx/environment.py index c6ca8b7e..b4720ae7 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -1227,6 +1227,7 @@ class BuildEnvironment: typ = node['reftype'] target = node['reftarget'] refdoc = node.get('refdoc', fromdocname) + warned = False try: if node.has_key('refdomain') and node['refdomain']: @@ -1247,6 +1248,7 @@ class BuildEnvironment: if not docname: self.warn(refdoc, 'undefined label: %s' % target, node.line) + warned = True else: # reference to named label; the final node will # contain the section name after the label @@ -1257,6 +1259,7 @@ class BuildEnvironment: 'undefined label: %s' % target + ' -- if you ' 'don\'t give a link caption the label must ' 'precede a section header.', node.line) + warned = True if docname: newnode = nodes.reference('', '') innernode = nodes.emphasis(sectname, sectname) @@ -1281,6 +1284,7 @@ class BuildEnvironment: if docname not in self.all_docs: self.warn(refdoc, 'unknown document: %s' % docname, node.line) + warned = True else: if node['refexplicit']: # reference with explicit title @@ -1295,8 +1299,9 @@ class BuildEnvironment: elif typ == 'citation': docname, labelid = self.citations.get(target, ('', '')) if not docname: - self.warn(node['refdoc'], + self.warn(refdoc, 'citation not found: %s' % target, node.line) + warned = True else: newnode = make_refnode(builder, fromdocname, docname, labelid, contnode) @@ -1317,6 +1322,13 @@ class BuildEnvironment: if newnode is None: newnode = builder.app.emit_firstresult( 'missing-reference', self, node, contnode) + # still not found? warn if in nit-picky mode + if newnode is None and not warned and self.config.nitpicky: + self.warn(refdoc, + 'reference target not found: %stype %s, target %s' + % (node.get('refdomain') and + 'domain %s, ' % node['refdomain'] or '', + typ, target)) except NoUri: newnode = contnode node.replace_self(newnode or contnode) -- cgit v1.2.1 From 06fd1678117bc2a1c61b4d2de130d2480160e91d Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 28 Feb 2010 14:43:44 +0100 Subject: Fix handling of the default role: it needs to be looked up after patching the role/directive lookup functions. --- sphinx/environment.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sphinx/environment.py b/sphinx/environment.py index b4720ae7..c545d760 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -608,15 +608,6 @@ class BuildEnvironment: if src_path is None: src_path = self.doc2path(docname) - if self.config.default_role: - role_fn, messages = roles.role(self.config.default_role, english, - 0, dummy_reporter) - if role_fn: - roles._roles[''] = role_fn - else: - self.warn(docname, 'default role %s not found' % - self.config.default_role) - self.temp_data['docname'] = docname # defaults to the global default, but can be re-set in a document self.temp_data['default_domain'] = \ @@ -628,6 +619,15 @@ class BuildEnvironment: self.patch_lookup_functions() + if self.config.default_role: + role_fn, messages = roles.role(self.config.default_role, english, + 0, dummy_reporter) + if role_fn: + roles._roles[''] = role_fn + else: + self.warn(docname, 'default role %s not found' % + self.config.default_role) + codecs.register_error('sphinx', self.warn_and_replace) class SphinxSourceClass(FileInput): -- cgit v1.2.1 From 4d77d3d6fb8859f859ecd46a7aa3a03923b9cfa2 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 28 Feb 2010 14:45:43 +0100 Subject: Change domain-index API: introduce a class. --- doc/conf.py | 1 + doc/config.rst | 10 ++-- doc/ext/appapi.rst | 33 +++++++++-- sphinx/application.py | 5 +- sphinx/builders/html.py | 24 ++++---- sphinx/domains/__init__.py | 137 +++++++++++++++++++++++-------------------- sphinx/domains/python.py | 141 +++++++++++++++++++++++---------------------- sphinx/writers/latex.py | 14 ++--- 8 files changed, 199 insertions(+), 166 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index 24cf5f3c..c487f486 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -47,6 +47,7 @@ latex_elements = { 'fontpkg': '\\usepackage{palatino}', } +autodoc_member_order = 'groupwise' todo_include_todos = True man_pages = [ diff --git a/doc/config.rst b/doc/config.rst index 5dbccb4e..089026d3 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -147,9 +147,9 @@ General configuration Directories in which to search for additional Sphinx message catalogs (see :confval:`language`), relative to the source directory. The directories on - this path are searched by the standard :mod:`gettext` module for a domain of - ``sphinx``; so if you add the directory :file:`./locale` to this settting, - the message catalogs must be in + this path are searched by the standard :mod:`gettext` module for a text + domain of ``sphinx``; so if you add the directory :file:`./locale` to this + settting, the message catalogs must be in :file:`./locale/{language}/LC_MESSAGES/sphinx.mo`. The default is ``[]``. @@ -188,8 +188,8 @@ General configuration The name of a reST role (builtin or Sphinx extension) to use as the default role, that is, for text marked up ```like this```. This can be set to - ``'obj'`` to make ```filter``` a cross-reference to the function "filter". - The default is ``None``, which doesn't reassign the default role. + ``'py:obj'`` to make ```filter``` a cross-reference to the Python function + "filter". The default is ``None``, which doesn't reassign the default role. The default role can always be set within individual documents using the standard reST :dir:`default-role` directive. diff --git a/doc/ext/appapi.rst b/doc/ext/appapi.rst index b536fd98..e301cd84 100644 --- a/doc/ext/appapi.rst +++ b/doc/ext/appapi.rst @@ -46,17 +46,22 @@ the following public API: .. method:: Sphinx.add_domain(domain) Make the given *domain* (which must be a class; more precisely, a subclass of - :class:`sphinx.domains.Domain`) known to Sphinx. - - .. XXX where is Domain documented? + :class:`~sphinx.domains.Domain`) known to Sphinx. .. versionadded:: 1.0 .. method:: Sphinx.override_domain(domain) - Make the given *domain* known to Sphinx, assuming that there is already a - domain with its ``.name``. The new domain must be a subclass of the existing - one. + Make the given *domain* class known to Sphinx, assuming that there is already + a domain with its ``.name``. The new domain must be a subclass of the + existing one. + + .. versionadded:: 1.0 + +.. method:: Sphinx.add_index_to_domain(domain, index) + + Add a custom *index* class to the domain named *domain*. *index* must be a + subclass of :class:`~sphinx.domains.Index`. .. versionadded:: 1.0 @@ -445,3 +450,19 @@ The template bridge .. autoclass:: TemplateBridge :members: + + +.. _domain-api: + +Domain API +---------- + +.. module:: sphinx.domains + +.. autoclass:: Domain + :members: + +.. autoclass:: ObjType + +.. autoclass:: Index + :members: diff --git a/sphinx/application.py b/sphinx/application.py index 22ca54d4..b5ba514c 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -417,12 +417,11 @@ class Sphinx(object): raise ExtensionError('domain %s not yet registered' % domain) self.domains[domain].roles[name] = role - # XXX needs documentation def add_index_to_domain(self, domain, name, localname, shortname, func): if domain not in self.domains: raise ExtensionError('domain %s not yet registered' % domain) - self.domains[domain].indices.append((name, longname, shortname)) - setattr(self.domains[domain], '_get_%s_index' % name, func) + self.domains[domain].indices.append((name, localname, shortname)) + setattr(self.domains[domain], 'get_%s_index' % name, func) def add_object_type(self, directivename, rolename, indextemplate='', parse_node=None, ref_nodeclass=None, objname=''): diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index cc3a5628..cd5f4f4a 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -236,8 +236,8 @@ class StandaloneHTMLBuilder(Builder): indices_config = self.config.html_domain_indices if indices_config: for domain in self.env.domains.itervalues(): - for indexinfo in domain.indices: - indexname = '%s-%s' % (domain.name, indexinfo[0]) + for indexcls in domain.indices: + indexname = '%s-%s' % (domain.name, indexcls.name) if isinstance(indices_config, list): if indexname not in indices_config: continue @@ -245,8 +245,10 @@ class StandaloneHTMLBuilder(Builder): if indexname == 'py-modindex' and \ not self.config.html_use_modindex: continue - if domain.has_index_entries(indexinfo[0]): - self.domain_indices.append((domain.name,) + indexinfo) + content, collapse = indexcls(domain).generate() + if content: + self.domain_indices.append( + (indexname, indexcls, content, collapse)) # format the "last updated on" string, only once is enough since it # typically doesn't include the time of day @@ -272,9 +274,11 @@ class StandaloneHTMLBuilder(Builder): rellinks = [] if self.config.html_use_index: rellinks.append(('genindex', _('General Index'), 'I', _('index'))) - for index in self.domain_indices: - if index[3]: - rellinks.append(('%s-%s' % index[0:2], index[2], '', index[3])) + for indexname, indexcls, content, collapse in self.domain_indices: + # if it has a short name + if indexcls.shortname: + rellinks.append((indexname, indexcls.localname, + '', indexcls.shortname)) if self.config.html_style is not None: stylename = self.config.html_style @@ -470,14 +474,12 @@ class StandaloneHTMLBuilder(Builder): self.handle_page('genindex', genindexcontext, 'genindex.html') def write_domain_indices(self): - for index in self.domain_indices: - content, collapse = self.env.domains[index[0]].get_index(index[1]) + for indexname, indexcls, content, collapse in self.domain_indices: indexcontext = dict( - indextitle = index[2], + indextitle = indexcls.localname, content = content, collapse_index = collapse, ) - indexname = '%s-%s' % index[0:2] self.info(' ' + indexname, nonl=1) self.handle_page(indexname, indexcontext, 'domainindex.html') diff --git a/sphinx/domains/__init__.py b/sphinx/domains/__init__.py index a03a74db..f1e6e8af 100644 --- a/sphinx/domains/__init__.py +++ b/sphinx/domains/__init__.py @@ -10,6 +10,8 @@ :license: BSD, see LICENSE for details. """ +from sphinx.errors import SphinxError + class ObjType(object): """ @@ -23,7 +25,7 @@ class ObjType(object): - *roles*: all the roles that can refer to an object of this type - *attrs*: object attributes -- currently only "searchprio" is known, which defines the object's priority in the full-text search index, - see `Domain.get_objects`. + see :meth:`Domain.get_objects()`. """ known_attrs = { @@ -37,6 +39,63 @@ class ObjType(object): self.attrs.update(attrs) +class Index(object): + """ + An Index is the description for a domain-specific index. To add an index to + a domain, subclass Index, overriding the three name attributes: + + * `name` is an identifier used for generating file names. + * `localname` is the section title for the index. + * `shortname` is a short name for the index, for use in the relation bar in + HTML output. Can be empty to disable entries in the relation bar. + + and providing a :meth:`generate()` method. Then, add the index class to + your domain's `indices` list. Extensions can add indices to existing + domains using :meth:`~sphinx.application.Sphinx.add_index_to_domain()`. + """ + + name = None + localname = None + shortname = None + + def __init__(self, domain): + if self.name is None or self.localname is None: + raise SphinxError('Index subclass %s has no valid name or localname' + % self.__class__.__name__) + self.domain = domain + + def generate(self, docnames=None): + """ + Return entries for the index given by *name*. If *docnames* is given, + restrict to entries referring to these docnames. + + The return value is a tuple of ``(content, collapse)``, where *collapse* + is a boolean that determines if sub-entries should start collapsed (for + output formats that support collapsing sub-entries). + + *content* is a sequence of ``(letter, entries)`` tuples, where *letter* + is the "heading" for the given *entries*, usually the starting letter. + + *entries* is a sequence of single entries, where a single entry is a + sequence ``[name, subtype, docname, anchor, extra, qualifier, descr]``. + The items in this sequence have the following meaning: + + - `name` -- the name of the index entry to be displayed + - `subtype` -- sub-entry related type: + 0 -- normal entry + 1 -- entry with sub-entries + 2 -- sub-entry + - `docname` -- docname where the entry is located + - `anchor` -- anchor for the entry within `docname` + - `extra` -- extra info for the entry + - `qualifier` -- qualifier for the description + - `descr` -- description for the entry + + Qualifier and description are not rendered e.g. in LaTeX output. + """ + return [] + + class Domain(object): """ A Domain is meant to be a group of "object" description directives for @@ -45,9 +104,9 @@ class Domain(object): of a templating language, Sphinx roles and directives, etc. Each domain has a separate storage for information about existing objects - and how to reference them in `data`, which must be a dictionary. It also - must implement several functions that expose the object information in a - uniform way to parts of Sphinx that allow the user to reference or search + and how to reference them in `self.data`, which must be a dictionary. It + also must implement several functions that expose the object information in + a uniform way to parts of Sphinx that allow the user to reference or search for objects in a domain-agnostic way. About `self.data`: since all object and cross-referencing information is @@ -56,8 +115,8 @@ class Domain(object): build process starts, every active domain is instantiated and given the environment object; the `domaindata` dict must then either be nonexistent or a dictionary whose 'version' key is equal to the domain class' - `data_version` attribute. Otherwise, `IOError` is raised and the pickled - environment is discarded. + :attr:`data_version` attribute. Otherwise, `IOError` is raised and the + pickled environment is discarded. """ #: domain name: should be short, but unique @@ -70,12 +129,12 @@ class Domain(object): directives = {} #: role name -> role callable roles = {} - #: (index identifier, localized index name, localized short name) tuples + #: a list of Index subclasses indices = [] #: data value for a fresh environment initial_data = {} - #: data version + #: data version, bump this when the format of `self.data` changes data_version = 0 def __init__(self, env): @@ -153,8 +212,8 @@ class Domain(object): then given to the 'missing-reference' event, and if that yields no resolution, replaced by *contnode*. - The method can also raise `sphinx.environment.NoUri` to suppress the - 'missing-reference' event being emitted. + The method can also raise :exc:`sphinx.environment.NoUri` to suppress + the 'missing-reference' event being emitted. """ pass @@ -170,63 +229,13 @@ class Domain(object): * `priority` -- how "important" the object is (determines placement in search results) - 1: default priority (placed before full-text matches) - 0: object is important (placed before default-priority objects) - 2: object is unimportant (placed after full-text matches) - -1: object should not show up in search at all + - 1: default priority (placed before full-text matches) + - 0: object is important (placed before default-priority objects) + - 2: object is unimportant (placed after full-text matches) + - -1: object should not show up in search at all """ return [] - def has_index_entries(self, name, docnames=None): - """ - Return True if there are entries for the index given by *name*. If - *docnames* is given, restrict to entries referring to these docnames. - - Do not overwrite this method, add a method ``has__entries(self, - docnames=None)`` method for every index. - """ - func = getattr(self, 'has_%s_entries' % name, None) - if not func: - return bool(self.get_index(name, docnames)) - return func(docnames) - - def get_index(self, name, docnames=None): - """ - Return entries for the index given by *name*. If *docnames* is given, - restrict to entries referring to these docnames. - - The return value is a tuple of ``(content, collapse)``, where *collapse* - is a boolean that determines if sub-entries should start collapsed (for - output formats that support collapsing sub-entries). - - *content* is a sequence of ``(letter, entries)`` tuples, where *letter* - is the "heading" for the given *entries*, usually the starting letter. - - *entries* is a sequence of single entries, where a single entry is a - sequence ``[name, subtype, docname, anchor, extra, qualifier, descr]``. - The items in this sequence have the following meaning: - - - `name` -- the name of the index entry to be displayed - - `subtype` -- sub-entry related type: - 0 -- normal entry - 1 -- entry with sub-entries - 2 -- sub-entry - - `docname` -- docname where the entry is located - - `anchor` -- anchor for the entry within `docname` - - `extra` -- extra info for the entry - - `qualifier` -- qualifier for the description - - `descr` -- description for the entry - - Qualifier and description are not rendered e.g. in LaTeX output. - - Do not overwrite this method, add a method ``get__index(self, - docnames=None)`` method for every index. - """ - func = getattr(self, 'get_%s_index' % name, None) - if not func: - return [] - return func(docnames) - from sphinx.domains.c import CDomain from sphinx.domains.std import StandardDomain diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index af1392a6..af8b2eda 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -17,7 +17,7 @@ from docutils.parsers.rst import directives from sphinx import addnodes from sphinx.roles import XRefRole from sphinx.locale import l_, _ -from sphinx.domains import Domain, ObjType +from sphinx.domains import Domain, ObjType, Index from sphinx.directives import ObjectDescription from sphinx.util.nodes import make_refnode from sphinx.util.compat import Directive @@ -419,6 +419,75 @@ class PyXRefRole(XRefRole): return title, target +class PythonModuleIndex(Index): + """ + Index subclass to provide the Python module index. + """ + + name = 'modindex' + localname = l_('Python Module Index') + shortname = l_('modules') + + def generate(self, docnames=None): + content = {} + # list of prefixes to ignore + ignores = self.domain.env.config['modindex_common_prefix'] + ignores = sorted(ignores, key=len, reverse=True) + # list of all modules, sorted by module name + modules = sorted(self.domain.data['modules'].iteritems(), + key=lambda x: x[0].lower()) + # sort out collapsable modules + prev_modname = '' + num_toplevels = 0 + for modname, (docname, synopsis, platforms, deprecated) in modules: + if docnames and docname not in docnames: + continue + + for ignore in ignores: + if modname.startswith(ignore): + modname = modname[len(ignore):] + stripped = ignore + break + else: + stripped = '' + + # we stripped the whole module name? + if not modname: + modname, stripped = stripped, '' + + entries = content.setdefault(modname[0].lower(), []) + + package = modname.split('.')[0] + if package != modname: + # it's a submodule + if prev_modname == package: + # first submodule - make parent a group head + entries[-1][1] = 1 + elif not prev_modname.startswith(package): + # submodule without parent in list, add dummy entry + entries.append([stripped + package, 1, '', '', '', '', '']) + subtype = 2 + else: + num_toplevels += 1 + subtype = 0 + + qualifier = deprecated and _('Deprecated') or '' + entries.append([stripped + modname, subtype, docname, + 'module-' + stripped + modname, platforms, + qualifier, synopsis]) + prev_modname = modname + + # apply heuristics when to collapse modindex at page load: + # only collapse if number of toplevel modules is larger than + # number of submodules + collapse = len(modules) - num_toplevels < num_toplevels + + # sort by first letter + content = sorted(content.iteritems()) + + return content, collapse + + class PythonDomain(Domain): """Python language domain.""" name = 'py' @@ -463,7 +532,7 @@ class PythonDomain(Domain): 'modules': {}, # modname -> docname, synopsis, platform, deprecated } indices = [ - ('modindex', l_('Python Module Index'), l_('modules')), + PythonModuleIndex, ] def clear_doc(self, docname): @@ -548,71 +617,3 @@ class PythonDomain(Domain): yield (modname, 'module', info[0], 'module-' + modname, 0) for refname, (docname, type) in self.data['objects'].iteritems(): yield (refname, type, docname, refname, 1) - - def has_modindex_entries(self, docnames=None): - if not docnames: - return bool(self.data['modules']) - else: - for modname, info in self.data['modules'].iteritems(): - if info[0] in docnames: - return True - return False - - def get_modindex_index(self, docnames=None): - content = {} - # list of prefixes to ignore - ignores = self.env.config['modindex_common_prefix'] - ignores = sorted(ignores, key=len, reverse=True) - # list of all modules, sorted by module name - modules = sorted(self.data['modules'].iteritems(), - key=lambda x: x[0].lower()) - # sort out collapsable modules - prev_modname = '' - num_toplevels = 0 - for modname, (docname, synopsis, platforms, deprecated) in modules: - if docnames and docname not in docnames: - continue - - for ignore in ignores: - if modname.startswith(ignore): - modname = modname[len(ignore):] - stripped = ignore - break - else: - stripped = '' - - # we stripped the whole module name? - if not modname: - modname, stripped = stripped, '' - - entries = content.setdefault(modname[0].lower(), []) - - package = modname.split('.')[0] - if package != modname: - # it's a submodule - if prev_modname == package: - # first submodule - make parent a group head - entries[-1][1] = 1 - elif not prev_modname.startswith(package): - # submodule without parent in list, add dummy entry - entries.append([stripped + package, 1, '', '', '', '', '']) - subtype = 2 - else: - num_toplevels += 1 - subtype = 0 - - qualifier = deprecated and _('Deprecated') or '' - entries.append([stripped + modname, subtype, docname, - 'module-' + stripped + modname, platforms, - qualifier, synopsis]) - prev_modname = modname - - # apply heuristics when to collapse modindex at page load: - # only collapse if number of toplevel modules is larger than - # number of submodules - collapse = len(modules) - num_toplevels < num_toplevels - - # sort by first letter - content = sorted(content.iteritems()) - - return content, collapse diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index fd33caec..0343687b 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -276,8 +276,8 @@ class LaTeXTranslator(nodes.NodeVisitor): indices_config = self.builder.config.latex_domain_indices if indices_config: for domain in self.builder.env.domains.itervalues(): - for indexinfo in domain.indices: - indexname = '%s-%s' % (domain.name, indexinfo[0]) + for indexcls in domain.indices: + indexname = '%s-%s' % (domain.name, indexcls.name) if isinstance(indices_config, list): if indexname not in indices_config: continue @@ -285,13 +285,13 @@ class LaTeXTranslator(nodes.NodeVisitor): if indexname == 'py-modindex' and \ not self.builder.config.latex_use_modindex: continue - if not domain.has_index_entries(indexinfo[0], - self.builder.docnames): + content, collapsed = indexcls(domain).generate( + self.builder.docnames) + if not content: continue ret.append('\\renewcommand{\\indexname}{%s}\n' % - indexinfo[1]) - generate(*domain.get_index(indexinfo[0], - self.builder.docnames)) + indexcls.localname) + generate(content, collapsed) return ''.join(ret) -- cgit v1.2.1 From 90304c85ec2de2c22b0c3d226a6522b784eb8471 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 28 Feb 2010 14:54:56 +0100 Subject: Port 0f8b5391a6c2 to domains: Fix references to methods without a module. --- sphinx/domains/python.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index af8b2eda..de1ab84c 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -569,6 +569,8 @@ class PythonDomain(Domain): else: if name in objects: newname = name + elif classname and classname + '.' + name in objects: + newname = classname + '.' + name elif modname and modname + '.' + name in objects: newname = modname + '.' + name elif modname and classname and \ -- cgit v1.2.1 From 304289a891e6357fe6af4b83a23125b3e908f6a9 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 28 Feb 2010 17:24:55 +0100 Subject: Add a bit more content to the reST primer, and add links to the reST reference docs for all constructs. --- doc/conf.py | 6 ++- doc/markup/index.rst | 2 + doc/rest.rst | 146 ++++++++++++++++++++++++++++++++++++--------------- 3 files changed, 112 insertions(+), 42 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index c487f486..7e220848 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -6,7 +6,7 @@ import re import sphinx extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', - 'sphinx.ext.autosummary'] + 'sphinx.ext.autosummary', 'sphinx.ext.extlinks'] master_doc = 'contents' templates_path = ['_templates'] @@ -49,6 +49,10 @@ latex_elements = { autodoc_member_order = 'groupwise' todo_include_todos = True +extlinks = {'rstref': ('http://docutils.sourceforge.net/docs/ref/rst/' + 'restructuredtext.html#%s', ''), + 'rstdir': ('http://docutils.sourceforge.net/docs/ref/rst/' + 'directives.html#%s', '')} man_pages = [ ('contents', 'sphinx-all', 'Sphinx documentation generator system manual', diff --git a/doc/markup/index.rst b/doc/markup/index.rst index 1127fa73..a39a71a8 100644 --- a/doc/markup/index.rst +++ b/doc/markup/index.rst @@ -1,3 +1,5 @@ +.. _sphinxmarkup: + Sphinx Markup Constructs ======================== diff --git a/doc/rest.rst b/doc/rest.rst index 87c9f80c..76fc78d6 100644 --- a/doc/rest.rst +++ b/doc/rest.rst @@ -10,17 +10,19 @@ language, this will not take too long. .. seealso:: - The authoritative `reStructuredText User - Documentation `_. + The authoritative `reStructuredText User Documentation + `_. The "ref" links in this + document link to the description of the individual constructs in the reST + reference. Paragraphs ---------- -The paragraph is the most basic block in a reST document. Paragraphs are simply -chunks of text separated by one or more blank lines. As in Python, indentation -is significant in reST, so all lines of the same paragraph must be left-aligned -to the same level of indentation. +The paragraph (:rstref:`ref `) is the most basic block in a reST +document. Paragraphs are simply chunks of text separated by one or more blank +lines. As in Python, indentation is significant in reST, so all lines of the +same paragraph must be left-aligned to the same level of indentation. .. _inlinemarkup: @@ -52,12 +54,12 @@ provide semantic markup and cross-referencing of identifiers, as described in the appropriate section. The general syntax is ``:rolename:`content```. -Lists and Quotes ----------------- +Lists and Quote-like blocks +--------------------------- -List markup is natural: just place an asterisk at the start of a paragraph and -indent properly. The same goes for numbered lists; they can also be -autonumbered using a ``#`` sign:: +List markup (:rstref:`ref `) is natural: just place an asterisk at +the start of a paragraph and indent properly. The same goes for numbered lists; +they can also be autonumbered using a ``#`` sign:: * This is a bulleted list. * It has two items, the second @@ -81,7 +83,7 @@ parent list items by blank lines:: * and here the parent list continues -Definition lists are created as follows:: +Definition lists (:rstref:`ref `) are created as follows:: term (up to a line of text) Definition of the term, which must be indented @@ -91,17 +93,31 @@ Definition lists are created as follows:: next term Description. +Note that the term cannot have more than one line of text. -Paragraphs are quoted by just indenting them more than the surrounding -paragraphs. +Quoted paragraphs (:rstref:`ref `) are created by just indenting +them more than the surrounding paragraphs. + +Line blocks (:rstref:`ref `) are a way of preserving line breaks:: + + | These lines are + | broken exactly like in + | the source file. + +There are also several more special blocks available: + +* field lists (:rstref:`ref `) +* option lists (:rstref:`ref `) +* quoted literal blocks (:rstref:`ref `) +* doctest blocks (:rstref:`ref `) Source Code ----------- -Literal code blocks are introduced by ending a paragraph with the special marker -``::``. The literal block must be indented (and, like all paragraphs, separated -from the surrounding ones by blank lines):: +Literal code blocks (:rstref:`ref `) are introduced by ending a +paragraph with the special marker ``::``. The literal block must be indented +(and, like all paragraphs, separated from the surrounding ones by blank lines):: This is a normal text paragraph. The next paragraph is a code sample:: @@ -124,28 +140,67 @@ That way, the second sentence in the above example's first paragraph would be rendered as "The next paragraph is a code sample:". +Tables +------ + +Two forms of tables are supported. For *grid tables* (:rstref:`ref +`), you have to "paint" the cell grid yourself. They look like +this:: + + +------------------------+------------+----------+----------+ + | Header row, column 1 | Header 2 | Header 3 | Header 4 | + | (header rows optional) | | | | + +========================+============+==========+==========+ + | body row 1, column 1 | column 2 | column 3 | column 4 | + +------------------------+------------+----------+----------+ + | body row 2 | ... | ... | | + +------------------------+------------+---------------------+ + +*Simple tables* (:rstref:`ref `) are easier to write, but +limited: they must contain more than one row, and the first column cannot +contain multiple lines. They look like this:: + + ===== ===== ======= + A B A and B + ===== ===== ======= + False False False + True False False + False True False + True True True + ===== ===== ======= + + Hyperlinks ---------- External links ^^^^^^^^^^^^^^ -Use ```Link text `_`` for inline web links. If the link text -should be the web address, you don't need special markup at all, the parser +Use ```Link text `_`` for inline web links. If the link +text should be the web address, you don't need special markup at all, the parser finds links and mail addresses in ordinary text. +You can also separate the link and the target definition (:rstref:`ref +`), like this:: + + This is a paragraph that contains `a link`_. + + .. _a link: http://example.com/ + + Internal links ^^^^^^^^^^^^^^ -Internal linking is done via a special reST role, see the section on specific -markup, :ref:`ref-role`. +Internal linking is done via a special reST role provided by Sphinx, see the +section on specific markup, :ref:`ref-role`. Sections -------- -Section headers are created by underlining (and optionally overlining) the -section title with a punctuation character, at least as long as the text:: +Section headers (:rstref:`ref `) are created by underlining (and +optionally overlining) the section title with a punctuation character, at least +as long as the text:: ================= This is a heading @@ -170,9 +225,9 @@ target formats (HTML, LaTeX) have a limited supported nesting depth. Explicit Markup --------------- -"Explicit markup" is used in reST for most constructs that need special -handling, such as footnotes, specially-highlighted paragraphs, comments, and -generic directives. +"Explicit markup" (:rstref:`ref `) is used in reST for +most constructs that need special handling, such as footnotes, +specially-highlighted paragraphs, comments, and generic directives. An explicit markup block begins with a line starting with ``..`` followed by whitespace and is terminated by the next paragraph at the same level of @@ -186,8 +241,17 @@ when you write it.) Directives ---------- -A directive is a generic block of explicit markup. Besides roles, it is one of -the extension mechanisms of reST, and Sphinx makes heavy use of it. +A directive (:rstref:`ref `) is a generic block of explicit markup. +Besides roles, it is one of the extension mechanisms of reST, and Sphinx makes +heavy use of it. + +Docutils supports the following directives: + +.. hlist:: + + * XXX + +Directives added by Sphinx are described in :ref:`sphinxmarkup`. Basically, a directive consists of a name, arguments, options and content. (Keep this terminology in mind, it is used in the next chapter describing custom @@ -211,7 +275,7 @@ directive start. Images ------ -reST supports an image directive, used like so:: +reST supports an image directive (:rstdir:`ref `), used like so:: .. image:: gnu.png (options) @@ -251,9 +315,9 @@ the former, while the HTML builder would prefer the latter. Footnotes --------- -For footnotes, use ``[#name]_`` to mark the footnote location, and add the -footnote body at the bottom of the document after a "Footnotes" rubric heading, -like so:: +For footnotes (:rstref:`ref `), use ``[#name]_`` to mark the footnote +location, and add the footnote body at the bottom of the document after a +"Footnotes" rubric heading, like so:: Lorem ipsum [#f1]_ dolor sit amet ... [#f2]_ @@ -269,9 +333,9 @@ footnotes without names (``[#]_``). Citations --------- -Standard reST citations are supported, with the additional feature that they are -"global", i.e. all citations can be referenced from all files. Use them like -so:: +Standard reST citations (:rstref:`ref `) are supported, with the +additional feature that they are "global", i.e. all citations can be referenced +from all files. Use them like so:: Lorem ipsum [Ref]_ dolor sit amet. @@ -284,14 +348,13 @@ numeric or begins with ``#``. Substitutions ------------- -reST supports "substitutions", which are pieces of text and/or markup referred -to in the text by ``|name|``. They are defined like footnotes with explicit -markup blocks, like this:: +reST supports "substitutions" (:rstref:`ref `), which +are pieces of text and/or markup referred to in the text by ``|name|``. They +are defined like footnotes with explicit markup blocks, like this:: .. |name| replace:: replacement *text* -See the `reST reference for substitutions -`_ +See the :rstref:`reST reference for substitutions ` for details. If you want to use some substitutions for all documents, put them into a @@ -307,7 +370,8 @@ Comments -------- Every explicit markup block which isn't a valid markup construct (like the -footnotes above) is regarded as a comment. For example:: +footnotes above) is regarded as a comment (:rstref:`ref `). For +example:: .. This is a comment. -- cgit v1.2.1 From c091fb455490a7a00854a30f410f1a824bae232c Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 28 Feb 2010 17:42:41 +0100 Subject: List all docutils directives with short descriptions and links to the reference. --- doc/rest.rst | 51 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/doc/rest.rst b/doc/rest.rst index 76fc78d6..aa294391 100644 --- a/doc/rest.rst +++ b/doc/rest.rst @@ -247,9 +247,56 @@ heavy use of it. Docutils supports the following directives: -.. hlist:: +* Admonitions: :rstdir:`attention`, :rstdir:`caution`, :rstdir:`danger`, + :rstdir:`error`, :rstdir:`hint`, :rstdir:`important`, :rstdir:`note`, + :rstdir:`tip`, :rstdir:`warning` and the generic :rstdir:`admonition`. + (Most themes style only "note" and "warning" specially.) - * XXX +* Images: + + - :rstdir:`image` (see also Images_ below) + - :rstdir:`figure` (an image with caption and optional legend) + +* Additional body elements: + + - :rstdir:`contents` (a local, i.e. for the current file only, table of + contents) + - :rstdir:`container` (a container with a custom class, useful to generate an + outer ``
    `` in HTML) + - :rstdir:`rubric` (a heading without relation to the document sectioning) + - :rstdir:`topic`, :rstdir:`sidebar` (special highlighted body elements) + - :rstdir:`parsed-literal` (literal block that supports inline markup) + - :rstdir:`epigraph` (a block quote with optional attribution line) + - :rstdir:`highlights`, :rstdir:`pull-quote` (block quotes with their own + class attribute) + - :rstdir:`compound` (a compound paragraph) + +* Special tables: + + - :rstdir:`table` (a table with title) + - :rstdir:`csv-table` (a table generated from comma-separated values) + - :rstdir:`list-table` (a table generated from a list of lists) + +* Special directives: + + - :rstdir:`raw` (include raw target-format markup) + - :rstdir:`include` (include reStructuredText from another file) + +* HTML specifics: + + - :rstdir:`meta` (generation of HTML ```` tags) + - :rstdir:`title` (override document title) + +* Influencing markup: + + - :rstdir:`default-role` (set a new default role) + - :rstdir:`role` (create a new role) + + Since these are only per-file, better use Sphinx' facilities for setting the + :confval:`default_role`. + +Do *not* use the directives :rstdir:`sectnum`, :rstdir:`header` and +:rstdir:`footer`. Directives added by Sphinx are described in :ref:`sphinxmarkup`. -- cgit v1.2.1 From 19eabfa2a8d4ef42f4b4a1560d49e2c0a7b1a85e Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 28 Feb 2010 19:30:30 +0100 Subject: Re-export docutils .. class:: as .. rst-class:: and un-document .. cssclass::. --- doc/markup/desc.rst | 4 ---- doc/rest.rst | 7 +++++++ sphinx/directives/other.py | 4 +++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/doc/markup/desc.rst b/doc/markup/desc.rst index c8dec77c..eb7de7c3 100644 --- a/doc/markup/desc.rst +++ b/doc/markup/desc.rst @@ -187,10 +187,6 @@ The directives are: The first way is the preferred one. - .. versionadded:: 0.4 - The standard reST directive ``class`` is now provided by Sphinx under - the name ``cssclass``. - .. directive:: .. attribute:: name Describes an object data attribute. The description should include diff --git a/doc/rest.rst b/doc/rest.rst index aa294391..2dff0996 100644 --- a/doc/rest.rst +++ b/doc/rest.rst @@ -281,6 +281,7 @@ Docutils supports the following directives: - :rstdir:`raw` (include raw target-format markup) - :rstdir:`include` (include reStructuredText from another file) + - :rstdir:`class` (assign a class attribute to the next element) [1]_ * HTML specifics: @@ -451,3 +452,9 @@ There are some problems one commonly runs into while authoring reST documents: * **No nested inline markup:** Something like ``*see :func:`foo`*`` is not possible. + + +.. rubric:: Footnotes + +.. [1] When the default domain contains a :dir:`class` directive, this directive + will be shadowed. Therefore, Sphinx re-exports it as :dir:`rst-class`. diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py index c71f9afe..448498f8 100644 --- a/sphinx/directives/other.py +++ b/sphinx/directives/other.py @@ -380,6 +380,8 @@ directives.register_directive('hlist', HList) directives.register_directive('only', Only) # register the standard rst class directive under a different name -# only for backwards compatibility now from docutils.parsers.rst.directives.misc import Class +# only for backwards compatibility now directives.register_directive('cssclass', Class) +# new standard name when default-domain with "class" is in effect +directives.register_directive('rst-class', Class) -- cgit v1.2.1 From 9c43b32493858e2cbc6aae716c66119b2e851774 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 28 Feb 2010 22:00:22 +0100 Subject: Begin the Great Refactoring of the docs. * Move sphinx-build option description from intro to a new document. * Move toctree information to a new document in markup/. * Add a tutorial document placed after intro. Begin filling it in. --- doc/concepts.rst | 195 ------------------------------------------------- doc/conf.py | 1 - doc/contents.rst | 3 +- doc/faq.rst | 2 + doc/glossary.rst | 16 ++++ doc/intro.rst | 123 +++---------------------------- doc/invocation.rst | 177 ++++++++++++++++++++++++++++++++++++++++++++ doc/markup/index.rst | 1 + doc/markup/toctree.rst | 176 ++++++++++++++++++++++++++++++++++++++++++++ doc/more.png | Bin 0 -> 1502 bytes doc/tutorial.rst | 129 ++++++++++++++++++++++++++++++++ 11 files changed, 513 insertions(+), 310 deletions(-) delete mode 100644 doc/concepts.rst create mode 100644 doc/invocation.rst create mode 100644 doc/markup/toctree.rst create mode 100644 doc/more.png create mode 100644 doc/tutorial.rst diff --git a/doc/concepts.rst b/doc/concepts.rst deleted file mode 100644 index b0040d8f..00000000 --- a/doc/concepts.rst +++ /dev/null @@ -1,195 +0,0 @@ -.. highlight:: rest - -.. _concepts: - -Sphinx concepts -=============== - -Document names --------------- - -Since the reST source files can have different extensions (some people like -``.txt``, some like ``.rst`` -- the extension can be configured with -:confval:`source_suffix`) and different OSes have different path separators, -Sphinx abstracts them: all "document names" are relative to the :term:`source -directory`, the extension is stripped, and path separators are converted to -slashes. All values, parameters and suchlike referring to "documents" expect -such a document name. - -Examples for document names are ``index``, ``library/zipfile``, or -``reference/datamodel/types``. Note that there is no leading slash. - - -The TOC tree ------------- - -.. index:: pair: table of; contents - -Since reST does not have facilities to interconnect several documents, or split -documents into multiple output files, Sphinx uses a custom directive to add -relations between the single files the documentation is made of, as well as -tables of contents. The ``toctree`` directive is the central element. - -.. directive:: toctree - - This directive inserts a "TOC tree" at the current location, using the - individual TOCs (including "sub-TOC trees") of the documents given in the - directive body (whose path is relative to the document the directive occurs - in). A numeric ``maxdepth`` option may be given to indicate the depth of the - tree; by default, all levels are included. [#]_ - - Consider this example (taken from the Python docs' library reference index):: - - .. toctree:: - :maxdepth: 2 - - intro - strings - datatypes - numeric - (many more documents listed here) - - This accomplishes two things: - - * Tables of contents from all those documents are inserted, with a maximum - depth of two, that means one nested heading. ``toctree`` directives in - those documents are also taken into account. - * Sphinx knows that the relative order of the documents ``intro``, - ``strings`` and so forth, and it knows that they are children of the shown - document, the library index. From this information it generates "next - chapter", "previous chapter" and "parent chapter" links. - - Document titles in the :dir:`toctree` will be automatically read from the - title of the referenced document. If that isn't what you want, you can - specify an explicit title and target using a similar syntax to reST - hyperlinks (and Sphinx's :ref:`cross-referencing syntax `). This - looks like:: - - .. toctree:: - - intro - All about strings - datatypes - - The second line above will link to the ``strings`` document, but will use the - title "All about strings" instead of the title of the ``strings`` document. - - You can also add external links, by giving an HTTP URL instead of a document - name. - - If you want to have section numbers even in HTML output, give the toctree a - ``numbered`` flag option. For example:: - - .. toctree:: - :numbered: - - foo - bar - - Numbering then starts at the heading of ``foo``. Sub-toctrees are - automatically numbered (don't give the ``numbered`` flag to those). - - If you want only the titles of documents in the tree to show up, not other - headings of the same level, you can use the ``titlesonly`` option:: - - .. toctree:: - :titlesonly: - - foo - bar - - You can use "globbing" in toctree directives, by giving the ``glob`` flag - option. All entries are then matched against the list of available - documents, and matches are inserted into the list alphabetically. Example:: - - .. toctree:: - :glob: - - intro* - recipe/* - * - - This includes first all documents whose names start with ``intro``, then all - documents in the ``recipe`` folder, then all remaining documents (except the - one containing the directive, of course.) [#]_ - - The special entry name ``self`` stands for the document containing the - toctree directive. This is useful if you want to generate a "sitemap" from - the toctree. - - You can also give a "hidden" option to the directive, like this:: - - .. toctree:: - :hidden: - - doc_1 - doc_2 - - This will still notify Sphinx of the document hierarchy, but not insert links - into the document at the location of the directive -- this makes sense if you - intend to insert these links yourself, in a different style, or in the HTML - sidebar. - - In the end, all documents in the :term:`source directory` (or subdirectories) - must occur in some ``toctree`` directive; Sphinx will emit a warning if it - finds a file that is not included, because that means that this file will not - be reachable through standard navigation. Use :confval:`unused_docs` to - explicitly exclude documents from building, and :confval:`exclude_trees` to - exclude whole directories. - - The "master document" (selected by :confval:`master_doc`) is the "root" of - the TOC tree hierarchy. It can be used as the documentation's main page, or - as a "full table of contents" if you don't give a ``maxdepth`` option. - - .. versionchanged:: 0.3 - Added "globbing" option. - - .. versionchanged:: 0.6 - Added "numbered" and "hidden" options as well as external links and - support for "self" references. - - .. versionchanged:: 1.0 - Added "titlesonly" option. - - -Special names -------------- - -Sphinx reserves some document names for its own use; you should not try to -create documents with these names -- it will cause problems. - -The special document names (and pages generated for them) are: - -* ``genindex``, ``modindex``, ``search`` - - These are used for the general index, the Python module index, and the search - page, respectively. - - The general index is populated with entries from modules, all index-generating - :ref:`object descriptions `, and from :dir:`index` directives. - - The module index contains one entry per :dir:`module` directive. - - The search page contains a form that uses the generated JSON search index and - JavaScript to full-text search the generated documents for search words; it - should work on every major browser that supports modern JavaScript. - -* every name beginning with ``_`` - - Though only few such names are currently used by Sphinx, you should not create - documents or document-containing directories with such names. (Using ``_`` as - a prefix for a custom template directory is fine.) - - -.. rubric:: Footnotes - -.. [#] The ``maxdepth`` option does not apply to the LaTeX writer, where the - whole table of contents will always be presented at the begin of the - document, and its depth is controlled by the ``tocdepth`` counter, which - you can reset in your :confval:`latex_preamble` config value using - e.g. ``\setcounter{tocdepth}{2}``. - -.. [#] A note on available globbing syntax: you can use the standard shell - constructs ``*``, ``?``, ``[...]`` and ``[!...]`` with the feature that - these all don't match slashes. A double star ``**`` can be used to match - any sequence of characters *including* slashes. diff --git a/doc/conf.py b/doc/conf.py index 7e220848..b0e847e7 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -16,7 +16,6 @@ project = 'Sphinx' copyright = '2007-2010, Georg Brandl' version = sphinx.__released__ release = version - show_authors = True html_theme = 'sphinxdoc' diff --git a/doc/contents.rst b/doc/contents.rst index 3393f4a8..079f93f2 100644 --- a/doc/contents.rst +++ b/doc/contents.rst @@ -7,7 +7,8 @@ Sphinx documentation contents :maxdepth: 2 intro - concepts + tutorial + invocation rest markup/index domains diff --git a/doc/faq.rst b/doc/faq.rst index 81204c08..1a993204 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -37,6 +37,8 @@ How do I... come through cleanly. +.. _usingwith: + Using Sphinx with... -------------------- diff --git a/doc/glossary.rst b/doc/glossary.rst index 0c7725b8..aa71b2b8 100644 --- a/doc/glossary.rst +++ b/doc/glossary.rst @@ -32,6 +32,19 @@ Glossary See :ref:`directives` for more information. + document name + Since reST source files can have different extensions (some people like + ``.txt``, some like ``.rst`` -- the extension can be configured with + :confval:`source_suffix`) and different OSes have different path separators, + Sphinx abstracts them: :dfn:`document names` are always relative to the + :term:`source directory`, the extension is stripped, and path separators + are converted to slashes. All values, parameters and such referring to + "documents" expect such document names. + + Examples for document names are ``index``, ``library/zipfile``, or + ``reference/datamodel/types``. Note that there is no leading or trailing + slash. + domain A domain is a collection of markup (reStructuredText :term:`directive`\ s and :term:`role`\ s) to describe and link to :term:`object`\ s belonging @@ -50,6 +63,9 @@ Glossary parsing stage, so that successive runs only need to read and parse new and changed documents. + master document + The document that contains the root :dir:`toctree` directive. + object The basic building block of Sphinx documentation. Every "object directive" (e.g. :dir:`function` or :dir:`object`) creates such a block; diff --git a/doc/intro.rst b/doc/intro.rst index 37346b6f..d72cf360 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -35,6 +35,12 @@ to reStructuredText/Sphinx from other documentation systems. markup; it is at `Google Code `_. +Use with other systems +---------------------- + +See the :ref:`pertinent section in the FAQ list `. + + Prerequisites ------------- @@ -47,117 +53,8 @@ or some (not broken) SVN trunk snapshot. .. _Pygments: http://pygments.org -Setting up the documentation sources ------------------------------------- - -The root directory of a documentation collection is called the :dfn:`source -directory`. Normally, this directory also contains the Sphinx configuration -file :file:`conf.py`, but that file can also live in another directory, the -:dfn:`configuration directory`. - -.. versionadded:: 0.3 - Support for a different configuration directory. - -Sphinx comes with a script called :program:`sphinx-quickstart` that sets up a -source directory and creates a default :file:`conf.py` from a few questions it -asks you. Just run :: - - $ sphinx-quickstart - -and answer the questions. - - -Running a build ---------------- - -A build is started with the :program:`sphinx-build` script. It is called -like this:: - - $ sphinx-build -b latex sourcedir builddir - -where *sourcedir* is the :term:`source directory`, and *builddir* is the -directory in which you want to place the built documentation (it must be an -existing directory). The :option:`-b` option selects a builder; in this example -Sphinx will build LaTeX files. - -The :program:`sphinx-build` script has several more options: - -**-a** - If given, always write all output files. The default is to only write output - files for new and changed source files. (This may not apply to all - builders.) - -**-E** - Don't use a saved :term:`environment` (the structure caching all - cross-references), but rebuild it completely. The default is to only read - and parse source files that are new or have changed since the last run. - -**-t** *tag* - Define the tag *tag*. This is relevant for :dir:`only` directives that only - include their content if this tag is set. - - .. versionadded:: 0.6 - -**-d** *path* - Since Sphinx has to read and parse all source files before it can write an - output file, the parsed source files are cached as "doctree pickles". - Normally, these files are put in a directory called :file:`.doctrees` under - the build directory; with this option you can select a different cache - directory (the doctrees can be shared between all builders). - -**-c** *path* - Don't look for the :file:`conf.py` in the source directory, but use the given - configuration directory instead. Note that various other files and paths - given by configuration values are expected to be relative to the - configuration directory, so they will have to be present at this location - too. - - .. versionadded:: 0.3 - -**-C** - Don't look for a configuration file; only take options via the ``-D`` option. - - .. versionadded:: 0.5 - -**-D** *setting=value* - Override a configuration value set in the :file:`conf.py` file. The value - must be a string or dictionary value. For the latter, supply the setting - name and key like this: ``-D latex_elements.docclass=scrartcl``. - - .. versionchanged:: 0.6 - The value can now be a dictionary value. - -**-A** *name=value* - Make the *name* assigned to *value* in the HTML templates. - -**-n** - Run in nit-picky mode. Currently, this generates warnings for all missing - references. - -**-N** - Do not do colored output. (On Windows, colored output is disabled in any - case.) - -**-q** - Do not output anything on standard output, only write warnings and errors to - standard error. - -**-Q** - Do not output anything on standard output, also suppress warnings. Only - errors are written to standard error. - -**-w** *file* - Write warnings (and errors) to the given file, in addition to standard error. - -**-W** - Turn warnings into errors. This means that the build stops at the first - warning and ``sphinx-build`` exits with exit status 1. - -**-P** - (Useful for debugging only.) Run the Python debugger, :mod:`pdb`, if an - unhandled exception occurs while building. - +Usage +----- -You can also give one or more filenames on the command line after the source and -build directories. Sphinx will then try to build only these output files (and -their dependencies). +See :doc:`the tutorial ` for an introduction. It also contains links +to more advanced sections in this manual for the topics it discusses. diff --git a/doc/invocation.rst b/doc/invocation.rst new file mode 100644 index 00000000..10e2d04c --- /dev/null +++ b/doc/invocation.rst @@ -0,0 +1,177 @@ +.. _invocation: + +sphinx-build Invocation +======================= + +A build is started with the :program:`sphinx-build` script. It is called like +this:: + + $ sphinx-build [options] sourcedir builddir [filenames] + +where *sourcedir* is the :term:`source directory`, and *builddir* is the +directory in which you want to place the built documentation (it must be an +existing directory). + +The :program:`sphinx-build` script has several options: + +.. cmdoption:: -b buildername + + Select a builder. The most common builders are: + + **html** + Build HTML pages. This is the default builder. + + **dirhtml** + Build HTML pages, but with a single directory per document. Makes for + prettier URLs (no ``.html``) if served from a webserver. + + **singlehtml** + Build a single HTML with the whole content. + + **htmlhelp**, **qthelp**, **devhelp**, **epub** + Build HTML files with additional information for building a documentation + collection in one of these formats. + + **latex** + Build LaTeX sources that can be compiled to a PDF document using + :program:`pdflatex`. + + **man** + Build manual pages in groff format for UNIX systems. + + **text** + Build plain text files. + + **doctest** + Run all doctests in the documentation, if the :mod:`~sphinx.ext.doctest` + extension is enabled. + + **linkcheck** + Check the integrity of all external links. + + See :ref:`builders` for a list of all builders shipped with Sphinx. + Extensions can add their own builders. + +.. cmdoption:: -a + + If given, always write all output files. The default is to only write output + files for new and changed source files. (This may not apply to all + builders.) + +.. cmdoption:: -E + + Don't use a saved :term:`environment` (the structure caching all + cross-references), but rebuild it completely. The default is to only read + and parse source files that are new or have changed since the last run. + +.. cmdoption:: -t tag + + Define the tag *tag*. This is relevant for :dir:`only` directives that only + include their content if this tag is set. + + .. versionadded:: 0.6 + +.. cmdoption:: -d path + + Since Sphinx has to read and parse all source files before it can write an + output file, the parsed source files are cached as "doctree pickles". + Normally, these files are put in a directory called :file:`.doctrees` under + the build directory; with this option you can select a different cache + directory (the doctrees can be shared between all builders). + +.. cmdoption:: -c path + + Don't look for the :file:`conf.py` in the source directory, but use the given + configuration directory instead. Note that various other files and paths + given by configuration values are expected to be relative to the + configuration directory, so they will have to be present at this location + too. + + .. versionadded:: 0.3 + +.. cmdoption:: -C + + Don't look for a configuration file; only take options via the ``-D`` option. + + .. versionadded:: 0.5 + +.. cmdoption:: -D setting=value + + Override a configuration value set in the :file:`conf.py` file. The value + must be a string or dictionary value. For the latter, supply the setting + name and key like this: ``-D latex_elements.docclass=scrartcl``. For boolean + values, use ``0`` or ``1`` as the value. + + .. versionchanged:: 0.6 + The value can now be a dictionary value. + +.. cmdoption:: -A name=value + + Make the *name* assigned to *value* in the HTML templates. + + .. versionadded:: 0.5 + +.. cmdoption:: -n + + Run in nit-picky mode. Currently, this generates warnings for all missing + references. + +.. cmdoption:: -N + + Do not emit colored output. (On Windows, colored output is disabled in any + case.) + +.. cmdoption:: -q + + Do not output anything on standard output, only write warnings and errors to + standard error. + +.. cmdoption:: -Q + + Do not output anything on standard output, also suppress warnings. Only + errors are written to standard error. + +.. cmdoption:: -w file + + Write warnings (and errors) to the given file, in addition to standard error. + +.. cmdoption:: -W + + Turn warnings into errors. This means that the build stops at the first + warning and ``sphinx-build`` exits with exit status 1. + +.. cmdoption:: -P + + (Useful for debugging only.) Run the Python debugger, :mod:`pdb`, if an + unhandled exception occurs while building. + + +You can also give one or more filenames on the command line after the source and +build directories. Sphinx will then try to build only these output files (and +their dependencies). + + +Makefile options +---------------- + +The :file:`Makefile` and :file:`make.bat` files created by +:program:`sphinx-quickstart` usually run :program:`sphinx-build` only with the +:option:`-b` and :option:`-d` options. However, they support the following +variables to customize behavior: + +.. describe:: PAPER + + The value for :confval:`latex_paper_size`. + +.. describe:: SPHINXBUILD + + The command to use instead of ``sphinx-build``. + +.. describe:: BUILDDIR + + The build directory to use instead of the one chosen in + :program:`sphinx-quickstart`. + +.. describe:: SPHINXOPTS + + Additional options for :program:`sphinx-build`. diff --git a/doc/markup/index.rst b/doc/markup/index.rst index a39a71a8..f1bc27d1 100644 --- a/doc/markup/index.rst +++ b/doc/markup/index.rst @@ -8,6 +8,7 @@ markup. This section contains the reference material for these facilities. .. toctree:: + toctree desc para code diff --git a/doc/markup/toctree.rst b/doc/markup/toctree.rst new file mode 100644 index 00000000..d85a0de0 --- /dev/null +++ b/doc/markup/toctree.rst @@ -0,0 +1,176 @@ +.. highlight:: rst +.. _toctree-directive: + +The TOC tree +============ + +.. index:: pair: table of; contents + +Since reST does not have facilities to interconnect several documents, or split +documents into multiple output files, Sphinx uses a custom directive to add +relations between the single files the documentation is made of, as well as +tables of contents. The ``toctree`` directive is the central element. + +.. directive:: toctree + + This directive inserts a "TOC tree" at the current location, using the + individual TOCs (including "sub-TOC trees") of the documents given in the + directive body (whose path is relative to the document the directive occurs + in). A numeric ``maxdepth`` option may be given to indicate the depth of the + tree; by default, all levels are included. [#]_ + + Consider this example (taken from the Python docs' library reference index):: + + .. toctree:: + :maxdepth: 2 + + intro + strings + datatypes + numeric + (many more documents listed here) + + This accomplishes two things: + + * Tables of contents from all those documents are inserted, with a maximum + depth of two, that means one nested heading. ``toctree`` directives in + those documents are also taken into account. + * Sphinx knows that the relative order of the documents ``intro``, + ``strings`` and so forth, and it knows that they are children of the shown + document, the library index. From this information it generates "next + chapter", "previous chapter" and "parent chapter" links. + + Document titles in the :dir:`toctree` will be automatically read from the + title of the referenced document. If that isn't what you want, you can + specify an explicit title and target using a similar syntax to reST + hyperlinks (and Sphinx's :ref:`cross-referencing syntax `). This + looks like:: + + .. toctree:: + + intro + All about strings + datatypes + + The second line above will link to the ``strings`` document, but will use the + title "All about strings" instead of the title of the ``strings`` document. + + You can also add external links, by giving an HTTP URL instead of a document + name. + + If you want to have section numbers even in HTML output, give the toctree a + ``numbered`` flag option. For example:: + + .. toctree:: + :numbered: + + foo + bar + + Numbering then starts at the heading of ``foo``. Sub-toctrees are + automatically numbered (don't give the ``numbered`` flag to those). + + If you want only the titles of documents in the tree to show up, not other + headings of the same level, you can use the ``titlesonly`` option:: + + .. toctree:: + :titlesonly: + + foo + bar + + You can use "globbing" in toctree directives, by giving the ``glob`` flag + option. All entries are then matched against the list of available + documents, and matches are inserted into the list alphabetically. Example:: + + .. toctree:: + :glob: + + intro* + recipe/* + * + + This includes first all documents whose names start with ``intro``, then all + documents in the ``recipe`` folder, then all remaining documents (except the + one containing the directive, of course.) [#]_ + + The special entry name ``self`` stands for the document containing the + toctree directive. This is useful if you want to generate a "sitemap" from + the toctree. + + You can also give a "hidden" option to the directive, like this:: + + .. toctree:: + :hidden: + + doc_1 + doc_2 + + This will still notify Sphinx of the document hierarchy, but not insert links + into the document at the location of the directive -- this makes sense if you + intend to insert these links yourself, in a different style, or in the HTML + sidebar. + + In the end, all documents in the :term:`source directory` (or subdirectories) + must occur in some ``toctree`` directive; Sphinx will emit a warning if it + finds a file that is not included, because that means that this file will not + be reachable through standard navigation. Use :confval:`unused_docs` to + explicitly exclude documents from building, and :confval:`exclude_trees` to + exclude whole directories. + + The "master document" (selected by :confval:`master_doc`) is the "root" of + the TOC tree hierarchy. It can be used as the documentation's main page, or + as a "full table of contents" if you don't give a ``maxdepth`` option. + + .. versionchanged:: 0.3 + Added "globbing" option. + + .. versionchanged:: 0.6 + Added "numbered" and "hidden" options as well as external links and + support for "self" references. + + .. versionchanged:: 1.0 + Added "titlesonly" option. + + +Special names +------------- + +Sphinx reserves some document names for its own use; you should not try to +create documents with these names -- it will cause problems. + +The special document names (and pages generated for them) are: + +* ``genindex``, ``modindex``, ``search`` + + These are used for the general index, the Python module index, and the search + page, respectively. + + The general index is populated with entries from modules, all index-generating + :ref:`object descriptions `, and from :dir:`index` directives. + + The module index contains one entry per :dir:`module` directive. + + The search page contains a form that uses the generated JSON search index and + JavaScript to full-text search the generated documents for search words; it + should work on every major browser that supports modern JavaScript. + +* every name beginning with ``_`` + + Though only few such names are currently used by Sphinx, you should not create + documents or document-containing directories with such names. (Using ``_`` as + a prefix for a custom template directory is fine.) + + +.. rubric:: Footnotes + +.. [#] The ``maxdepth`` option does not apply to the LaTeX writer, where the + whole table of contents will always be presented at the begin of the + document, and its depth is controlled by the ``tocdepth`` counter, which + you can reset in your :confval:`latex_preamble` config value using + e.g. ``\setcounter{tocdepth}{2}``. + +.. [#] A note on available globbing syntax: you can use the standard shell + constructs ``*``, ``?``, ``[...]`` and ``[!...]`` with the feature that + these all don't match slashes. A double star ``**`` can be used to match + any sequence of characters *including* slashes. diff --git a/doc/more.png b/doc/more.png new file mode 100644 index 00000000..3eb7b05c Binary files /dev/null and b/doc/more.png differ diff --git a/doc/tutorial.rst b/doc/tutorial.rst new file mode 100644 index 00000000..a24d88e4 --- /dev/null +++ b/doc/tutorial.rst @@ -0,0 +1,129 @@ +.. highlight:: rst + +Sphinx Tutorial -- your first documentation +=========================================== + +This document is meant to give an overview of all common tasks while using +Sphinx. The green arrows designate "more info" links leading to advanced +sections about the described task. + + +Setting up the documentation sources +------------------------------------ + +The root directory of a documentation collection is called the :term:`source +directory`. This directory also contains the Sphinx configuration file +:file:`conf.py`, where you can configure all aspects of how Sphinx reads your +sources and builds your documentation. [#]_ + +Sphinx comes with a script called :program:`sphinx-quickstart` that sets up a +source directory and creates a default :file:`conf.py` with the most useful +configuration values from a few questions it asks you. Just run :: + + $ sphinx-quickstart + +and answer its questions. + + +Adding some content +------------------- + +Let's assume you've run :program:`sphinx-quickstart`. It created a source +directory with :file:`conf.py` and a master document, :file:`index.rst` (if you +accepted the defaults). The main function of the :term:`master document` is to +serve as a welcome page, and to contain the root of the "table of contents tree" +(or *toctree*). This is one of the main things that Sphinx adds to +reStructuredText, a way to connect multiple files to a single hierarchy of +documents. + +.. sidebar:: reStructuredText directives + + ``toctree`` is a reStructuredText :dfn:`directive`, a very versatile piece of + markup. Directives can have arguments, options and content. + + *Arguments* are given directly after the double colon following the + directive's name. Each directive decides whether it can have arguments, and + how many. + + *Options* are given after the arguments, in form of a "field list". The + ``maxdepth`` is such an option for the ``toctree`` directive. + + *Content* follows the options or arguments after a blank line. Each + directive decides whether to allow content, and what to do with it. + + A common gotcha with directives is that **the first line of the content must + be indented to the same level as the options are**. + + +The toctree directive initially is empty, and looks like this:: + + .. toctree:: + :maxdepth: 2 + +You add documents listing them in the *content* of the directive:: + + .. toctree:: + :maxdepth: 2 + + intro + tutorial + ... + +This is exactly how the toctree for this documentation looks. The documents to +include are given as :term:`document name`\ s, which in short means that you +leave off the file name extension and use slashes as directory separators. + +|more| Read more about :ref:`the toctree directive `. + +You can now create the files you listed in the toctree, and their section titles +will be inserted (up to the "maxdepth" level) at the place where the toctree +directive is placed. Also, Sphinx now knows about the order and hierarchy of +your documents. (They may contain ``toctree`` directives themselves, which +means you can create deeply nested hierarchies if necessary.) + + +Running the build +----------------- + +A build is started with the :program:`sphinx-build` script. It is called +like this:: + + $ sphinx-build -b html sourcedir builddir + +where *sourcedir* is the :term:`source directory`, and *builddir* is the +directory in which you want to place the built documentation. The :option:`-b` +option selects a builder; in this example Sphinx will build LaTeX files. + +However, :program:`sphinx-quickstart` script creates a :file:`Makefile` and a +:file:`make.bat` which make life even easier for you: with them you only need +to run :: + + $ make html + +to build HTML docs in the build directory you chose. + +|more| See :ref:`invocation` for all options that :program:`sphinx-build` +supports. + + +Topics to be covered +-------------------- + +- Autodoc +- Domains +- Basic configuration +- Selecting a theme +- Templating +- Using extensions +- Writing extensions + + +.. rubric:: Footnotes + +.. [#] This is the usual lay-out. However, :file:`conf.py` can also live in + another directory, the :term:`configuration directory`. See + :ref:`invocation`. + +.. |more| image:: more.png + :align: middle + :alt: more info -- cgit v1.2.1 From 70c9b5d464d3342b91a51dd825a5bc68c8f148b3 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 28 Feb 2010 22:05:19 +0100 Subject: s/highlight/highlighted/ for the search word highlighting class: "highlight" is already used by pygments. --- sphinx/themes/agogo/static/agogo.css_t | 4 ++++ sphinx/themes/basic/static/basic.css | 2 +- sphinx/themes/basic/static/doctools.js | 4 ++-- sphinx/themes/basic/static/searchtools.js | 2 +- sphinx/themes/epub/static/epub.css | 2 +- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/sphinx/themes/agogo/static/agogo.css_t b/sphinx/themes/agogo/static/agogo.css_t index ddc8385f..a39b4d74 100644 --- a/sphinx/themes/agogo/static/agogo.css_t +++ b/sphinx/themes/agogo/static/agogo.css_t @@ -118,6 +118,10 @@ p.admonition-title { font-weight: bold; } +dt:target, .highlighted { + background-color: #fbe54e; +} + /* Header */ div.header { diff --git a/sphinx/themes/basic/static/basic.css b/sphinx/themes/basic/static/basic.css index 465b526e..7cfacd5b 100644 --- a/sphinx/themes/basic/static/basic.css +++ b/sphinx/themes/basic/static/basic.css @@ -316,7 +316,7 @@ dd { margin-left: 30px; } -dt:target, .highlight { +dt:target, .highlighted { background-color: #fbe54e; } diff --git a/sphinx/themes/basic/static/doctools.js b/sphinx/themes/basic/static/doctools.js index 28c7a6da..23e217c3 100644 --- a/sphinx/themes/basic/static/doctools.js +++ b/sphinx/themes/basic/static/doctools.js @@ -182,7 +182,7 @@ var Documentation = { var body = $('div.body'); window.setTimeout(function() { $.each(terms, function() { - body.highlightText(this.toLowerCase(), 'highlight'); + body.highlightText(this.toLowerCase(), 'highlighted'); }); }, 10); $('
    ').text(excerpt); $.each(hlwords, function() { - rv = rv.highlightText(this, 'highlight'); + rv = rv.highlightText(this, 'highlighted'); }); return rv; } diff --git a/sphinx/themes/epub/static/epub.css b/sphinx/themes/epub/static/epub.css index c6320c8c..de21c462 100644 --- a/sphinx/themes/epub/static/epub.css +++ b/sphinx/themes/epub/static/epub.css @@ -298,7 +298,7 @@ dd { margin-left: 30px; } -dt:target, .highlight { +dt:target, .highlighted { background-color: #ddd; } -- cgit v1.2.1 From 332fc0a4ab4217ad0891ffdac7b365210b9c9bd0 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 28 Feb 2010 23:03:16 +0100 Subject: Rename tutorial, add example for image substitution. --- doc/rest.rst | 7 ++++++- doc/tutorial.rst | 32 ++++++++++++++++++++------------ 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/doc/rest.rst b/doc/rest.rst index 2dff0996..37fc1a4e 100644 --- a/doc/rest.rst +++ b/doc/rest.rst @@ -154,7 +154,7 @@ this:: | body row 1, column 1 | column 2 | column 3 | column 4 | +------------------------+------------+----------+----------+ | body row 2 | ... | ... | | - +------------------------+------------+---------------------+ + +------------------------+------------+----------+----------+ *Simple tables* (:rstref:`ref `) are easier to write, but limited: they must contain more than one row, and the first column cannot @@ -402,6 +402,11 @@ are defined like footnotes with explicit markup blocks, like this:: .. |name| replace:: replacement *text* +or this:: + + .. |caution| image:: warning.png + :alt: Warning! + See the :rstref:`reST reference for substitutions ` for details. diff --git a/doc/tutorial.rst b/doc/tutorial.rst index a24d88e4..8aa1574d 100644 --- a/doc/tutorial.rst +++ b/doc/tutorial.rst @@ -1,11 +1,13 @@ .. highlight:: rst -Sphinx Tutorial -- your first documentation -=========================================== +First Steps with Sphinx +======================= -This document is meant to give an overview of all common tasks while using -Sphinx. The green arrows designate "more info" links leading to advanced -sections about the described task. +This document is meant to give a tutorial-like overview of all common tasks +while using Sphinx. + +The green arrows designate "more info" links leading to advanced sections about +the described task. Setting up the documentation sources @@ -22,7 +24,7 @@ configuration values from a few questions it asks you. Just run :: $ sphinx-quickstart -and answer its questions. +and answer its questions. (Be sure to say yes to the "autodoc" extension.) Adding some content @@ -85,14 +87,17 @@ means you can create deeply nested hierarchies if necessary.) Running the build ----------------- -A build is started with the :program:`sphinx-build` script. It is called -like this:: +Now that you have added some files and content, let's build the docs. A build +is started with the :program:`sphinx-build` script, called like this:: $ sphinx-build -b html sourcedir builddir where *sourcedir* is the :term:`source directory`, and *builddir* is the directory in which you want to place the built documentation. The :option:`-b` -option selects a builder; in this example Sphinx will build LaTeX files. +option selects a builder; in this example Sphinx will build HTML files. + +|more| See :ref:`invocation` for all options that :program:`sphinx-build` +supports. However, :program:`sphinx-quickstart` script creates a :file:`Makefile` and a :file:`make.bat` which make life even easier for you: with them you only need @@ -100,10 +105,12 @@ to run :: $ make html -to build HTML docs in the build directory you chose. +to build HTML docs in the build directory you chose. Execute ``make`` without +an argument to see which targets are available. -|more| See :ref:`invocation` for all options that :program:`sphinx-build` -supports. + +Documenting objects +------------------- Topics to be covered @@ -112,6 +119,7 @@ Topics to be covered - Autodoc - Domains - Basic configuration +- Static files - Selecting a theme - Templating - Using extensions -- cgit v1.2.1 From 81c09bed619f2ad9f5d1bf09cb34b1c25d226f5e Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 28 Feb 2010 23:03:38 +0100 Subject: Add a bit more info. --- doc/invocation.rst | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/doc/invocation.rst b/doc/invocation.rst index 10e2d04c..e3ddc980 100644 --- a/doc/invocation.rst +++ b/doc/invocation.rst @@ -1,22 +1,23 @@ .. _invocation: -sphinx-build Invocation -======================= +Invocation of sphinx-build +========================== -A build is started with the :program:`sphinx-build` script. It is called like -this:: +The :program:`sphinx-build` script builds a Sphinx documentation set. It is +called like this:: $ sphinx-build [options] sourcedir builddir [filenames] where *sourcedir* is the :term:`source directory`, and *builddir* is the -directory in which you want to place the built documentation (it must be an -existing directory). +directory in which you want to place the built documentation. Most of the time, +you don't need to specify any *filenames*. The :program:`sphinx-build` script has several options: .. cmdoption:: -b buildername - Select a builder. The most common builders are: + The most important option: it selects a builder. The most common builders + are: **html** Build HTML pages. This is the default builder. -- cgit v1.2.1 From 5c16aebcc7736f7e66d14ba78133a93e912a46a8 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 28 Feb 2010 23:12:00 +0100 Subject: Update blurb in setup.py and on the index page, link directly to tutorial.rst from index. --- doc/_templates/index.html | 71 ++++++++++++++++++++++++----------------------- doc/intro.rst | 6 ++-- setup.py | 17 ++++++------ 3 files changed, 49 insertions(+), 45 deletions(-) diff --git a/doc/_templates/index.html b/doc/_templates/index.html index a68a6251..082a1f87 100644 --- a/doc/_templates/index.html +++ b/doc/_templates/index.html @@ -4,7 +4,7 @@

    Welcome

    -

    What users say:

    +

    What users say:

    “Cheers for a great tool that actually makes programmers want to write documentation!”

    @@ -12,28 +12,30 @@

    Sphinx is a tool that makes it easy to create intelligent and beautiful documentation, written by Georg Brandl and licensed under the BSD license.

    -

    It was originally created to translate the - new Python documentation, and it has excellent support for the documentation - of Python projects, but other documents can be written with it too. Of course, - this site is also created from reStructuredText sources using Sphinx! +

    It was originally created for the + new Python documentation, and it has excellent facilities for the + documentation of Python projects, but C/C++ is already supported as well, + and it is planned to add special support for other languages as well. Of + course, this site is also created from reStructuredText sources using + Sphinx!

    - It is still under constant development, and the following features are - already present, work fine and can be seen “in action” in the - Python docs: + Sphinx is under constant development. The following features are present, + work fine and can be seen “in action” in the Python docs:

      -
    • Output formats: HTML (including Windows HTML Help) and LaTeX, for - printable PDF versions
    • +
    • Output formats: HTML (including Windows HTML Help), LaTeX (for + printable PDF versions), manual pages, plain text
    • Extensive cross-references: semantic markup and automatic links - for functions, classes, glossary terms and similar pieces of information
    • + for functions, classes, citations, glossary terms and similar pieces of + information
    • Hierarchical structure: easy definition of a document tree, with - automatic links to siblings, parents and children
    • + automatic links to siblings, parents and children
    • Automatic indices: general index as well as a module index
    • Code handling: automatic highlighting using the Pygments highlighter
    • + href="http://pygments.org">Pygments highlighter
    • Extensions: automatic testing of code snippets, inclusion of - docstrings from Python modules, and more
    • + docstrings from Python modules (API docs), and more

    Sphinx uses reStructuredText @@ -42,39 +44,40 @@ suite, the Docutils.

    -

    Examples

    -

    - The Python documentation and - this page are different examples of Sphinx in use. - You can also download PDF versions of the Sphinx documentation: - a version generated from - the LaTeX Sphinx produces, and a - version generated by rst2pdf. -

    -

    - For examples of how Sphinx source files look, use the “Show source” - links on all pages of the documentation apart from this welcome page. -

    -

    Links to more documentation generated with Sphinx can be found on the - Projects using Sphinx page. -

    -

    Documentation

    + + - -
    +

    + You can also download PDF versions of the Sphinx documentation: + a version generated from + the LaTeX Sphinx produces, and + a version generated + by rst2pdf. +

    + +

    Examples

    +

    Links to documentation generated with Sphinx can be found on the + Projects using Sphinx page. +

    +

    + For examples of how Sphinx source files look, use the “Show + source” links on all pages of the documentation apart from this + welcome page. +

    +

    You may also be interested in the very nice tutorial on how to create a customized documentation using Sphinx written by the matplotlib diff --git a/doc/intro.rst b/doc/intro.rst index d72cf360..585d04f0 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -11,9 +11,9 @@ browsing and navigation. But from the same source, it can also generate a LaTeX file that you can compile into a PDF version of the documents. The focus is on hand-written documentation, rather than auto-generated API docs. -Though there is limited support for that kind of docs as well (which is intended -to be freely mixed with hand-written content), if you need pure API docs have a -look at `Epydoc `_, which also understands reST. +Though there is support for that kind of docs as well (which is intended to be +freely mixed with hand-written content), if you need pure API docs have a look +at `Epydoc `_, which also understands reST. Conversion from other systems diff --git a/setup.py b/setup.py index 6a46e6d6..b2c96f48 100644 --- a/setup.py +++ b/setup.py @@ -11,20 +11,21 @@ import sphinx long_desc = ''' Sphinx is a tool that makes it easy to create intelligent and beautiful -documentation for Python projects (or other documents consisting of -multiple reStructuredText sources), written by Georg Brandl. -It was originally created to translate the new Python documentation, -but has now been cleaned up in the hope that it will be useful to many -other projects. +documentation for Python projects (or other documents consisting of multiple +reStructuredText sources), written by Georg Brandl. It was originally created +for the new Python documentation, and has excellent facilities for Python +project documentation, but C/C++ is supported as well, and more languages are +planned. Sphinx uses reStructuredText as its markup language, and many of its strengths -come from the power and straightforwardness of reStructuredText and its -parsing and translating suite, the Docutils. +come from the power and straightforwardness of reStructuredText and its parsing +and translating suite, the Docutils. Among its features are the following: * Output formats: HTML (including derivative formats such as HTML Help, Epub - and Qt Help), plain text and LaTeX or direct PDF output using rst2pdf + and Qt Help), plain text, manual pages and LaTeX or direct PDF output + using rst2pdf * Extensive cross-references: semantic markup and automatic links for functions, classes, glossary terms and similar pieces of information * Hierarchical structure: easy definition of a document tree, with automatic -- cgit v1.2.1 -- cgit v1.2.1 From 029e89e50216d7e0e8945f9773cf7c476ac3e663 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 1 Mar 2010 10:09:14 +0100 Subject: Add more visitors for manpage writer. --- sphinx/ext/inheritance_diagram.py | 3 ++- sphinx/ext/mathbase.py | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/sphinx/ext/inheritance_diagram.py b/sphinx/ext/inheritance_diagram.py index 62a3e560..b930d8ca 100644 --- a/sphinx/ext/inheritance_diagram.py +++ b/sphinx/ext/inheritance_diagram.py @@ -361,7 +361,8 @@ def setup(app): inheritance_diagram, latex=(latex_visit_inheritance_diagram, None), html=(html_visit_inheritance_diagram, None), - text=(skip, None)) + text=(skip, None), + man=(skip, None)) app.add_directive('inheritance-diagram', InheritanceDiagram) app.add_config_value('inheritance_graph_attrs', {}, False), app.add_config_value('inheritance_node_attrs', {}, False), diff --git a/sphinx/ext/mathbase.py b/sphinx/ext/mathbase.py index 774f5608..8a5b75d3 100644 --- a/sphinx/ext/mathbase.py +++ b/sphinx/ext/mathbase.py @@ -108,6 +108,20 @@ def text_visit_eqref(self, node): raise nodes.SkipNode +def man_visit_math(self, node): + self.body.append(node['latex']) + raise nodes.SkipNode + +def man_visit_displaymath(self, node): + self.visit_centered(node) +def man_depart_displaymath(self, node): + self.depart_centered(node) + +def man_visit_eqref(self, node): + self.body.append(node['target']) + raise nodes.SkipNode + + def html_visit_eqref(self, node): self.body.append('' % node['target']) @@ -136,14 +150,17 @@ def setup_math(app, htmlinlinevisitors, htmldisplayvisitors): app.add_node(math, latex=(latex_visit_math, None), text=(text_visit_math, None), + man=(man_visit_math, None), html=htmlinlinevisitors) app.add_node(displaymath, latex=(latex_visit_displaymath, None), text=(text_visit_displaymath, None), + man=(man_visit_displaymath, man_depart_displaymath), html=htmldisplayvisitors) app.add_node(eqref, latex=(latex_visit_eqref, None), text=(text_visit_eqref, None), + man=(man_visit_eqref, None), html=(html_visit_eqref, html_depart_eqref)) app.add_role('math', math_role) app.add_role('eq', eq_role) -- cgit v1.2.1 From ddad244565bea419c1a9f9d420d11161b8850b2d Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 1 Mar 2010 11:13:33 +0100 Subject: More doc. --- sphinx/directives/__init__.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sphinx/directives/__init__.py b/sphinx/directives/__init__.py index 62e0a7d8..be603768 100644 --- a/sphinx/directives/__init__.py +++ b/sphinx/directives/__init__.py @@ -67,12 +67,18 @@ class ObjectDescription(Directive): Parse the signature *sig* into individual nodes and append them to *signode*. If ValueError is raised, parsing is aborted and the whole *sig* is put into a single desc_name node. + + The return value should be a value that identifies the object. It is + passed to :meth:`add_target_and_index()` unchanged, and otherwise only + used to skip duplicates. """ raise ValueError def add_target_and_index(self, name, sig, signode): """ Add cross-reference IDs and entries to self.indexnode, if applicable. + + *name* is whatever :meth:`handle_signature()` returned. """ return # do nothing by default -- cgit v1.2.1 From e18251d30ac297a9c87647553d76f35b55a4f4e6 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 1 Mar 2010 14:22:14 +0100 Subject: Add some domain documentation. --- doc/conf.py | 2 +- doc/config.rst | 9 + doc/domains.rst | 400 ++++++++++++++++++++++++++++++++++++++++++++- doc/invocation.rst | 32 ++-- doc/markup/desc.rst | 364 ----------------------------------------- doc/markup/index.rst | 3 +- doc/markup/misc.rst | 10 +- doc/markup/toctree.rst | 3 +- doc/tutorial.rst | 4 +- sphinx/directives/other.py | 3 + sphinx/domains/std.py | 3 +- 11 files changed, 446 insertions(+), 387 deletions(-) delete mode 100644 doc/markup/desc.rst diff --git a/doc/conf.py b/doc/conf.py index b0e847e7..03dac676 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -67,7 +67,7 @@ man_pages = [ from sphinx import addnodes -dir_sig_re = re.compile(r'\.\. ([^:]+)::(.*)$') +dir_sig_re = re.compile(r'\.\. (.+?)::(.*)$') def parse_directive(env, sig, signode): if not sig.startswith('.'): diff --git a/doc/config.rst b/doc/config.rst index 089026d3..fae85fc4 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -182,6 +182,15 @@ General configuration .. versionadded:: 0.6 +.. confval:: default_domain + + .. index:: default; domain + + The name of the default :ref:`domain `. Can also be ``None`` to + disable a default domain. The default is ``'py'``. + + .. versionadded:: 1.0 + .. confval:: default_role .. index:: default; role diff --git a/doc/domains.rst b/doc/domains.rst index 990a3ca4..50854746 100644 --- a/doc/domains.rst +++ b/doc/domains.rst @@ -1,6 +1,404 @@ +.. highlight:: rst + .. _domains: Sphinx Domains ============== -.. todo:: Write this section. +.. versionadded:: 1.0 + +What is a Domain? +----------------- + +Originally, Sphinx was conceived for a single project, the documentation of the +Python language. Shortly afterwards, it was made available for everyone as a +documentation tool, but the documentation of Python modules remained deeply +built in -- the most fundamental directives, like ``function``, were designed +for Python objects. Since Sphinx has become somewhat popular, interest +developed in using it for many different purposes: C/C++ projects, JavaScript, +or even reStructuredText markup (like in this documentation). + +While this was always possible, it is now much easier to easily support +documentation of projects using different programming languages or even ones not +supported by the main Sphinx distribution, by providing a **domain** for every +such purpose. + +A domain is a collection of markup (reStructuredText :term:`directive`\ s and +:term:`role`\ s) to describe and link to :term:`object`\ s belonging together, +e.g. elements of a programming language. Directive and role names in a domain +have names like ``domain:name``, e.g. ``py:function``. Domains can also provide +custom indices (like the Python Module Index). + +Having domains means that there are no naming problems when one set of +documentation wants to refer to e.g. C++ and Python classes. It also means that +extensions that support the documentation of whole new languages are much easier +to write. + +This section describes what the domains that come with Sphinx provide. The +domain API is documented as well, in the section :ref:`domain-api`. + + +.. _basic-domain-markup: + +Basic Markup +------------ + +Most domains provide a number of :dfn:`object description directives`, used to +describe specific objects provided by modules. Each directive requires one or +more signatures to provide basic information about what is being described, and +the content should be the description. The basic version makes entries in the +general index; if no index entry is desired, you can give the directive option +flag ``:noindex:``. An example using a Python domain directive:: + + .. py:function:: spam(eggs) + ham(eggs) + :noindex: + + Spam or ham the foo. + +The domains also provide roles that link back to these object descriptions. For +example, to link to one of the functions described in the example above, you +could say :: + + The function :py:func:`spam` does a similar thing. + +As you can see, both directive and role names contain the domain name and the +directive name. + +.. rubric:: Default Domain + +To avoid having to writing the domain name all the time when you e.g. only +describe Python objects, a default domain can be selected with either the config +value :confval:`default_domain` or this directive: + +.. directive:: .. default-domain:: name + + Select a new default domain. While the :confval:`default_domain` selects a + global default, this only has an effect within the same file. + +If no other default is selected, the Python domain (named ``py``) is the default +one, mostly for compatibility with documentation written for older versions of +Sphinx. + +Directives and roles that belong to the default domain can be mentioned without +giving the domain name, i.e. :: + + .. function:: pyfunc() + + Describes a Python function. + + Reference to :func:`pyfunc`. + + +The Python Domain +----------------- + +The Python domain (name **py**) provides the following directives for module +declarations: + +.. directive:: .. py:module:: name + + This directive marks the beginning of the description of a module (or package + submodule, in which case the name should be fully qualified, including the + package name). It does not create content (like e.g. :dir:`py:class` does). + + This directive will also cause an entry in the global module index. + + The ``platform`` option, if present, is a comma-separated list of the + platforms on which the module is available (if it is available on all + platforms, the option should be omitted). The keys are short identifiers; + examples that are in use include "IRIX", "Mac", "Windows", and "Unix". It is + important to use a key which has already been used when applicable. + + The ``synopsis`` option should consist of one sentence describing the + module's purpose -- it is currently only used in the Global Module Index. + + The ``deprecated`` option can be given (with no value) to mark a module as + deprecated; it will be designated as such in various locations then. + + +.. directive:: .. py:currentmodule:: name + + This directive tells Sphinx that the classes, functions etc. documented from + here are in the given module (like :dir:`py:module`), but it will not create + index entries, an entry in the Global Module Index, or a link target for + :role:`mod`. This is helpful in situations where documentation for things in + a module is spread over multiple files or sections -- one location has the + :dir:`py:module` directive, the others only :dir:`py:currentmodule`. + + +The following directives are provided for module and class contents: + +.. directive:: .. py:data:: name + + Describes global data in a module, including both variables and values used + as "defined constants." Class and object attributes are not documented + using this environment. + +.. directive:: .. py:exception:: name + + Describes an exception class. The signature can, but need not include + parentheses with constructor arguments. + +.. directive:: .. py:function:: name(signature) + + Describes a module-level function. The signature should include the + parameters, enclosing optional parameters in brackets. Default values can be + given if it enhances clarity; see :ref:`signatures`. For example:: + + .. py:function:: Timer.repeat([repeat=3[, number=1000000]]) + + Object methods are not documented using this directive. Bound object methods + placed in the module namespace as part of the public interface of the module + are documented using this, as they are equivalent to normal functions for + most purposes. + + The description should include information about the parameters required and + how they are used (especially whether mutable objects passed as parameters + are modified), side effects, and possible exceptions. A small example may be + provided. + +.. directive:: .. py:class:: name[(signature)] + + Describes a class. The signature can include parentheses with parameters + which will be shown as the constructor arguments. See also + :ref:`signatures`. + + Methods and attributes belonging to the class should be placed in this + directive's body. If they are placed outside, the supplied name should + contain the class name so that cross-references still work. Example:: + + .. py:class:: Foo + .. py:method:: quux() + + -- or -- + + .. py:class:: Bar + + .. py:method:: Bar.quux() + + The first way is the preferred one. + +.. directive:: .. py:attribute:: name + + Describes an object data attribute. The description should include + information about the type of the data to be expected and whether it may be + changed directly. + +.. directive:: .. py:method:: name(signature) + + Describes an object method. The parameters should not include the ``self`` + parameter. The description should include similar information to that + described for ``function``. See also :ref:`signatures`. + +.. directive:: .. py:staticmethod:: name(signature) + + Like :dir:`py:method`, but indicates that the method is a static method. + + .. versionadded:: 0.4 + +.. directive:: .. py:classmethod:: name(signature) + + Like :dir:`py:method`, but indicates that the method is a class method. + + .. versionadded:: 0.6 + + +.. _signatures: + +Python Signatures +~~~~~~~~~~~~~~~~~ + +Signatures of functions, methods and class constructors can be given like they +would be written in Python, with the exception that optional parameters can be +indicated by brackets:: + + .. py:function:: compile(source[, filename[, symbol]]) + +It is customary to put the opening bracket before the comma. In addition to +this "nested" bracket style, a "flat" style can also be used, due to the fact +that most optional parameters can be given independently:: + + .. py:function:: compile(source[, filename, symbol]) + +Default values for optional arguments can be given (but if they contain commas, +they will confuse the signature parser). Python 3-style argument annotations +can also be given as well as return type annotations:: + + .. py:function:: compile(source : string[, filename, symbol]) -> ast object + + +Info field lists +~~~~~~~~~~~~~~~~ + +.. versionadded:: 0.4 + +Inside Python object description directives, reST field lists with these fields +are recognized and formatted nicely: + +* ``param``, ``parameter``, ``arg``, ``argument``, ``key``, ``keyword``: + Description of a parameter. +* ``type``: Type of a parameter. +* ``raises``, ``raise``, ``except``, ``exception``: That (and when) a specific + exception is raised. +* ``var``, ``ivar``, ``cvar``: Description of a variable. +* ``returns``, ``return``: Description of the return value. +* ``rtype``: Return type. + +The field names must consist of one of these keywords and an argument (except +for ``returns`` and ``rtype``, which do not need an argument). This is best +explained by an example:: + + .. py:function:: format_exception(etype, value, tb[, limit=None]) + + Format the exception with a traceback. + + :param etype: exception type + :param value: exception value + :param tb: traceback object + :param limit: maximum number of stack frames to show + :type limit: integer or None + :rtype: list of strings + +It is also possible to combine parameter type and description, if the type is a +single word, like this:: + + :param integer limit: maximum number of stack frames to show + +This will render like this: + + .. py:function:: format_exception(etype, value, tb[, limit=None]) + :noindex: + + Format the exception with a traceback. + + :param etype: exception type + :param value: exception value + :param tb: traceback object + :param limit: maximum number of stack frames to show + :type limit: integer or None + :rtype: list of strings + + +The C Domain +------------ + +The C domain (name **c**) is suited for documentation of C API. + +.. directive:: .. c:function:: type name(signature) + + Describes a C function. The signature should be given as in C, e.g.:: + + .. c:function:: PyObject* PyType_GenericAlloc(PyTypeObject *type, Py_ssize_t nitems) + + This is also used to describe function-like preprocessor macros. The names + of the arguments should be given so they may be used in the description. + + Note that you don't have to backslash-escape asterisks in the signature, as + it is not parsed by the reST inliner. + +.. directive:: .. c:member:: type name + + Describes a C struct member. Example signature:: + + .. c:member:: PyObject* PyTypeObject.tp_bases + + The text of the description should include the range of values allowed, how + the value should be interpreted, and whether the value can be changed. + References to structure members in text should use the ``member`` role. + +.. directive:: .. c:macro:: name + + Describes a "simple" C macro. Simple macros are macros which are used for + code expansion, but which do not take arguments so cannot be described as + functions. This is not to be used for simple constant definitions. Examples + of its use in the Python documentation include :c:macro:`PyObject_HEAD` and + :c:macro:`Py_BEGIN_ALLOW_THREADS`. + +.. directive:: .. c:type:: name + + Describes a C type (whether defined by a typedef or struct). The signature + should just be the type name. + +.. directive:: .. c:var:: type name + + Describes a global C variable. The signature should include the type, such + as:: + + .. c:var:: PyObject* PyClass_Type + + +The Standard Domain +------------------- + +The so-called "standard" domain collects all markup that doesn't warrant a +domain of its own. Its directives and roles are not prefixed with a domain +name. + +The standard domain is also where custom object descriptions, added using the +:func:`~sphinx.application.Sphinx.add_object_type` API, are placed. + +There is a set of directives allowing documenting command-line programs: + +.. directive:: .. option:: name args, name args, ... + + Describes a command line option or switch. Option argument names should be + enclosed in angle brackets. Example:: + + .. option:: -m , --module + + Run a module as a script. + + The directive will create a cross-reference target named after the *first* + option, referencable by :role:`option` (in the example case, you'd use + something like ``:option:`-m```). + +.. directive:: .. envvar:: name + + Describes an environment variable that the documented code or program uses or + defines. Referencable by :role:`envvar`. + +.. directive:: .. program:: name + + Like :dir:`py:currentmodule`, this directive produces no output. Instead, it + serves to notify Sphinx that all following :dir:`option` directives + document options for the program called *name*. + + If you use :dir:`program`, you have to qualify the references in your + :role:`option` roles by the program name, so if you have the following + situation :: + + .. program:: rm + + .. option:: -r + + Work recursively. + + .. program:: svn + + .. option:: -r revision + + Specify the revision to work upon. + + then ``:option:`rm -r``` would refer to the first option, while + ``:option:`svn -r``` would refer to the second one. + + The program name may contain spaces (in case you want to document subcommands + like ``svn add`` and ``svn commit`` separately). + + .. versionadded:: 0.5 + + +There is also a very generic object description directive, which is not tied to +any domain: + +.. directive:: .. describe:: text + .. object:: text + + This directive produces the same formatting as the specific ones provided by + domains, but does not create index entries or cross-referencing targets. + Example:: + + .. describe:: PAPER + + You can set this variable to select a paper size. diff --git a/doc/invocation.rst b/doc/invocation.rst index e3ddc980..94b5db2e 100644 --- a/doc/invocation.rst +++ b/doc/invocation.rst @@ -14,7 +14,7 @@ you don't need to specify any *filenames*. The :program:`sphinx-build` script has several options: -.. cmdoption:: -b buildername +.. option:: -b buildername The most important option: it selects a builder. The most common builders are: @@ -53,26 +53,26 @@ The :program:`sphinx-build` script has several options: See :ref:`builders` for a list of all builders shipped with Sphinx. Extensions can add their own builders. -.. cmdoption:: -a +.. option:: -a If given, always write all output files. The default is to only write output files for new and changed source files. (This may not apply to all builders.) -.. cmdoption:: -E +.. option:: -E Don't use a saved :term:`environment` (the structure caching all cross-references), but rebuild it completely. The default is to only read and parse source files that are new or have changed since the last run. -.. cmdoption:: -t tag +.. option:: -t tag Define the tag *tag*. This is relevant for :dir:`only` directives that only include their content if this tag is set. .. versionadded:: 0.6 -.. cmdoption:: -d path +.. option:: -d path Since Sphinx has to read and parse all source files before it can write an output file, the parsed source files are cached as "doctree pickles". @@ -80,7 +80,7 @@ The :program:`sphinx-build` script has several options: the build directory; with this option you can select a different cache directory (the doctrees can be shared between all builders). -.. cmdoption:: -c path +.. option:: -c path Don't look for the :file:`conf.py` in the source directory, but use the given configuration directory instead. Note that various other files and paths @@ -90,13 +90,13 @@ The :program:`sphinx-build` script has several options: .. versionadded:: 0.3 -.. cmdoption:: -C +.. option:: -C Don't look for a configuration file; only take options via the ``-D`` option. .. versionadded:: 0.5 -.. cmdoption:: -D setting=value +.. option:: -D setting=value Override a configuration value set in the :file:`conf.py` file. The value must be a string or dictionary value. For the latter, supply the setting @@ -106,42 +106,42 @@ The :program:`sphinx-build` script has several options: .. versionchanged:: 0.6 The value can now be a dictionary value. -.. cmdoption:: -A name=value +.. option:: -A name=value Make the *name* assigned to *value* in the HTML templates. .. versionadded:: 0.5 -.. cmdoption:: -n +.. option:: -n Run in nit-picky mode. Currently, this generates warnings for all missing references. -.. cmdoption:: -N +.. option:: -N Do not emit colored output. (On Windows, colored output is disabled in any case.) -.. cmdoption:: -q +.. option:: -q Do not output anything on standard output, only write warnings and errors to standard error. -.. cmdoption:: -Q +.. option:: -Q Do not output anything on standard output, also suppress warnings. Only errors are written to standard error. -.. cmdoption:: -w file +.. option:: -w file Write warnings (and errors) to the given file, in addition to standard error. -.. cmdoption:: -W +.. option:: -W Turn warnings into errors. This means that the build stops at the first warning and ``sphinx-build`` exits with exit status 1. -.. cmdoption:: -P +.. option:: -P (Useful for debugging only.) Run the Python debugger, :mod:`pdb`, if an unhandled exception occurs while building. diff --git a/doc/markup/desc.rst b/doc/markup/desc.rst deleted file mode 100644 index eb7de7c3..00000000 --- a/doc/markup/desc.rst +++ /dev/null @@ -1,364 +0,0 @@ -.. highlight:: rest - -Module-specific markup ----------------------- - -The markup described in this section is used to provide information about a -module being documented. Normally this markup appears after a title heading; a -typical module section might start like this:: - - :mod:`parrot` -- Dead parrot access - =================================== - - .. module:: parrot - :platform: Unix, Windows - :synopsis: Analyze and reanimate dead parrots. - .. moduleauthor:: Eric Cleese - .. moduleauthor:: John Idle - - -The directives you can use for module declarations are: - -.. directive:: .. module:: name - - This directive marks the beginning of the description of a module (or package - submodule, in which case the name should be fully qualified, including the - package name). It does not create content (like e.g. :dir:`class` does). - - This directive will also cause an entry in the global module index. - - The ``platform`` option, if present, is a comma-separated list of the - platforms on which the module is available (if it is available on all - platforms, the option should be omitted). The keys are short identifiers; - examples that are in use include "IRIX", "Mac", "Windows", and "Unix". It is - important to use a key which has already been used when applicable. - - The ``synopsis`` option should consist of one sentence describing the - module's purpose -- it is currently only used in the Global Module Index. - - The ``deprecated`` option can be given (with no value) to mark a module as - deprecated; it will be designated as such in various locations then. - - -.. directive:: .. currentmodule:: name - - This directive tells Sphinx that the classes, functions etc. documented from - here are in the given module (like :dir:`module`), but it will not create - index entries, an entry in the Global Module Index, or a link target for - :role:`mod`. This is helpful in situations where documentation for things in - a module is spread over multiple files or sections -- one location has the - :dir:`module` directive, the others only :dir:`currentmodule`. - - -.. directive:: .. moduleauthor:: name - - The ``moduleauthor`` directive, which can appear multiple times, names the - authors of the module code, just like ``sectionauthor`` names the author(s) - of a piece of documentation. It too only produces output if the - :confval:`show_authors` configuration value is True. - - -.. note:: - - It is important to make the section title of a module-describing file - meaningful since that value will be inserted in the table-of-contents trees - in overview files. - - -.. _desc-units: - -Object descriptions -------------------- - -.. XXX generalize for domains - -There are a number of directives used to describe specific objects provided by -modules. Each directive requires one or more signatures to provide basic -information about what is being described, and the content should be the -description. The basic version makes entries in the general index; if no index -entry is desired, you can give the directive option flag ``:noindex:``. The -following example shows all of the features of this directive type:: - - .. function:: spam(eggs) - ham(eggs) - :noindex: - - Spam or ham the foo. - -The signatures of object methods or data attributes should always include the -type name (``.. method:: FileInput.input(...)``), even if it is obvious from the -context which type they belong to; this is to enable consistent -cross-references. If you describe methods belonging to an abstract protocol, -such as "context managers", include a (pseudo-)type name too to make the -index entries more informative. - -The directives are: - -.. XXX update this - -.. directive:: .. cfunction:: type name(signature) - - Describes a C function. The signature should be given as in C, e.g.:: - - .. cfunction:: PyObject* PyType_GenericAlloc(PyTypeObject *type, Py_ssize_t nitems) - - This is also used to describe function-like preprocessor macros. The names - of the arguments should be given so they may be used in the description. - - Note that you don't have to backslash-escape asterisks in the signature, - as it is not parsed by the reST inliner. - -.. directive:: .. cmember:: type name - - Describes a C struct member. Example signature:: - - .. cmember:: PyObject* PyTypeObject.tp_bases - - The text of the description should include the range of values allowed, how - the value should be interpreted, and whether the value can be changed. - References to structure members in text should use the ``member`` role. - -.. directive:: .. cmacro:: name - - Describes a "simple" C macro. Simple macros are macros which are used - for code expansion, but which do not take arguments so cannot be described as - functions. This is not to be used for simple constant definitions. Examples - of its use in the Python documentation include :c:macro:`PyObject_HEAD` and - :c:macro:`Py_BEGIN_ALLOW_THREADS`. - -.. directive:: .. ctype:: name - - Describes a C type. The signature should just be the type name. - -.. directive:: .. cvar:: type name - - Describes a global C variable. The signature should include the type, such - as:: - - .. cvar:: PyObject* PyClass_Type - -.. directive:: .. data:: name - - Describes global data in a module, including both variables and values used - as "defined constants." Class and object attributes are not documented - using this environment. - -.. directive:: .. exception:: name - - Describes an exception class. The signature can, but need not include - parentheses with constructor arguments. - -.. directive:: .. function:: name(signature) - - Describes a module-level function. The signature should include the - parameters, enclosing optional parameters in brackets. Default values can be - given if it enhances clarity; see :ref:`signatures`. For example:: - - .. function:: Timer.repeat([repeat=3[, number=1000000]]) - - Object methods are not documented using this directive. Bound object methods - placed in the module namespace as part of the public interface of the module - are documented using this, as they are equivalent to normal functions for - most purposes. - - The description should include information about the parameters required and - how they are used (especially whether mutable objects passed as parameters - are modified), side effects, and possible exceptions. A small example may be - provided. - -.. directive:: .. class:: name[(signature)] - - Describes a class. The signature can include parentheses with parameters - which will be shown as the constructor arguments. See also - :ref:`signatures`. - - Methods and attributes belonging to the class should be placed in this - directive's body. If they are placed outside, the supplied name should - contain the class name so that cross-references still work. Example:: - - .. class:: Foo - .. method:: quux() - - -- or -- - - .. class:: Bar - - .. method:: Bar.quux() - - The first way is the preferred one. - -.. directive:: .. attribute:: name - - Describes an object data attribute. The description should include - information about the type of the data to be expected and whether it may be - changed directly. - -.. directive:: .. method:: name(signature) - - Describes an object method. The parameters should not include the ``self`` - parameter. The description should include similar information to that - described for ``function``. See also :ref:`signatures`. - -.. directive:: .. staticmethod:: name(signature) - - Like :dir:`method`, but indicates that the method is a static method. - - .. versionadded:: 0.4 - -.. directive:: .. classmethod:: name(signature) - - Like :dir:`method`, but indicates that the method is a class method. - - .. versionadded:: 0.6 - - -.. _signatures: - -Signatures -~~~~~~~~~~ - -Signatures of functions, methods and class constructors can be given like they -would be written in Python, with the exception that optional parameters can be -indicated by brackets:: - - .. function:: compile(source[, filename[, symbol]]) - -It is customary to put the opening bracket before the comma. In addition to -this "nested" bracket style, a "flat" style can also be used, due to the fact -that most optional parameters can be given independently:: - - .. function:: compile(source[, filename, symbol]) - -Default values for optional arguments can be given (but if they contain commas, -they will confuse the signature parser). Python 3-style argument annotations -can also be given as well as return type annotations:: - - .. function:: compile(source : string[, filename, symbol]) -> ast object - - -Info field lists -~~~~~~~~~~~~~~~~ - -.. versionadded:: 0.4 - -.. XXX this is only correct for Python - -Inside object description directives, reST field lists with these fields are -recognized and formatted nicely: - -* ``param``, ``parameter``, ``arg``, ``argument``, ``key``, ``keyword``: - Description of a parameter. -* ``type``: Type of a parameter. -* ``raises``, ``raise``, ``except``, ``exception``: That (and when) a specific - exception is raised. -* ``var``, ``ivar``, ``cvar``: Description of a variable. -* ``returns``, ``return``: Description of the return value. -* ``rtype``: Return type. - -The field names must consist of one of these keywords and an argument (except -for ``returns`` and ``rtype``, which do not need an argument). This is best -explained by an example:: - - .. function:: format_exception(etype, value, tb[, limit=None]) - - Format the exception with a traceback. - - :param etype: exception type - :param value: exception value - :param tb: traceback object - :param limit: maximum number of stack frames to show - :type limit: integer or None - :rtype: list of strings - -It is also possible to combine parameter type and description, if the type is a -single word, like this:: - - :param integer limit: maximum number of stack frames to show - -This will render like this: - - .. function:: format_exception(etype, value, tb[, limit=None]) - :noindex: - - Format the exception with a traceback. - - :param etype: exception type - :param value: exception value - :param tb: traceback object - :param limit: maximum number of stack frames to show - :type limit: integer or None - :rtype: list of strings - - -Command-line program markup -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -There is a set of directives allowing documenting command-line programs: - -.. directive:: .. cmdoption:: name args, name args, ... - - Describes a command line option or switch. Option argument names should be - enclosed in angle brackets. Example:: - - .. cmdoption:: -m , --module - - Run a module as a script. - - The directive will create a cross-reference target named after the *first* - option, referencable by :role:`option` (in the example case, you'd use - something like ``:option:`-m```). - -.. directive:: .. envvar:: name - - Describes an environment variable that the documented code or program uses or - defines. - - -.. directive:: .. program:: name - - Like :dir:`currentmodule`, this directive produces no output. Instead, it - serves to notify Sphinx that all following :dir:`cmdoption` directives - document options for the program called *name*. - - If you use :dir:`program`, you have to qualify the references in your - :role:`option` roles by the program name, so if you have the following - situation :: - - .. program:: rm - - .. cmdoption:: -r - - Work recursively. - - .. program:: svn - - .. cmdoption:: -r revision - - Specify the revision to work upon. - - then ``:option:`rm -r``` would refer to the first option, while - ``:option:`svn -r``` would refer to the second one. - - The program name may contain spaces (in case you want to document subcommands - like ``svn add`` and ``svn commit`` separately). - - .. versionadded:: 0.5 - - -Custom object types -~~~~~~~~~~~~~~~~~~~ - -There is also a generic version of these directives: - -.. directive:: .. describe:: text - - This directive produces the same formatting as the specific ones explained - above but does not create index entries or cross-referencing targets. It is - used, for example, to describe the directives in this document. Example:: - - .. describe:: opcode - - Describes a Python bytecode instruction. - -Extensions may add more directives like that, using the -:func:`~sphinx.application.Sphinx.add_object_type` method. diff --git a/doc/markup/index.rst b/doc/markup/index.rst index f1bc27d1..9492456d 100644 --- a/doc/markup/index.rst +++ b/doc/markup/index.rst @@ -9,8 +9,9 @@ markup. This section contains the reference material for these facilities. .. toctree:: toctree - desc para code inline misc + +More markup is added by :ref:`domains`. diff --git a/doc/markup/misc.rst b/doc/markup/misc.rst index 7db403ab..b0e78dc2 100644 --- a/doc/markup/misc.rst +++ b/doc/markup/misc.rst @@ -39,7 +39,7 @@ At the moment, these metadata fields are recognized: Meta-information markup ----------------------- -.. directive:: sectionauthor +.. directive:: .. sectionauthor:: name Identifies the author of the current section. The argument should include the author's name such that it can be used for presentation and email @@ -54,6 +54,14 @@ Meta-information markup output. +.. directive:: .. codeauthor:: name + + The :dir:`codeauthor` directive, which can appear multiple times, names the + authors of the described code, just like :dir:`sectionauthor` names the + author(s) of a piece of documentation. It too only produces output if the + :confval:`show_authors` configuration value is True. + + .. _tags: Including content based on tags diff --git a/doc/markup/toctree.rst b/doc/markup/toctree.rst index d85a0de0..af2fc223 100644 --- a/doc/markup/toctree.rst +++ b/doc/markup/toctree.rst @@ -147,7 +147,8 @@ The special document names (and pages generated for them) are: page, respectively. The general index is populated with entries from modules, all index-generating - :ref:`object descriptions `, and from :dir:`index` directives. + :ref:`object descriptions `, and from :dir:`index` + directives. The module index contains one entry per :dir:`module` directive. diff --git a/doc/tutorial.rst b/doc/tutorial.rst index 8aa1574d..d3e40dee 100644 --- a/doc/tutorial.rst +++ b/doc/tutorial.rst @@ -113,11 +113,13 @@ Documenting objects ------------------- + + Topics to be covered -------------------- - Autodoc -- Domains +- Other Domains - Basic configuration - Static files - Selecting a theme diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py index 448498f8..138d10c8 100644 --- a/sphinx/directives/other.py +++ b/sphinx/directives/other.py @@ -127,6 +127,8 @@ class Author(Directive): text = _('Section author: ') elif self.name == 'moduleauthor': text = _('Module author: ') + elif self.name == 'codeauthor': + text = _('Code author: ') else: text = _('Author: ') emph += nodes.Text(text, text) @@ -368,6 +370,7 @@ class Only(Directive): directives.register_directive('toctree', TocTree) directives.register_directive('sectionauthor', Author) directives.register_directive('moduleauthor', Author) +directives.register_directive('codeauthor', Author) directives.register_directive('index', Index) directives.register_directive('deprecated', VersionChange) directives.register_directive('versionadded', VersionChange) diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index da28f956..8b254e15 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -330,7 +330,8 @@ class StandardDomain(Domain): directives = { 'program': Program, - 'cmdoption': Cmdoption, + 'cmdoption': Cmdoption, # old name for backwards compatibility + 'option': Cmdoption, 'envvar': EnvVar, 'glossary': Glossary, 'productionlist': ProductionList, -- cgit v1.2.1 From d7bbf7502699ad780e17cda2482bfc2ac05e9543 Mon Sep 17 00:00:00 2001 From: mitsuhiko Date: Sun, 28 Feb 2010 17:44:40 +0100 Subject: Added first version of the C++ signature parser --- sphinx/domains/__init__.py | 2 + sphinx/domains/cpp.py | 340 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 342 insertions(+) create mode 100644 sphinx/domains/cpp.py diff --git a/sphinx/domains/__init__.py b/sphinx/domains/__init__.py index f1e6e8af..66e1f681 100644 --- a/sphinx/domains/__init__.py +++ b/sphinx/domains/__init__.py @@ -238,6 +238,7 @@ class Domain(object): from sphinx.domains.c import CDomain +from sphinx.domains.cpp import CPPDomain from sphinx.domains.std import StandardDomain from sphinx.domains.python import PythonDomain @@ -245,4 +246,5 @@ BUILTIN_DOMAINS = { 'std': StandardDomain, 'py': PythonDomain, 'c': CDomain, + 'cpp': CPPDomain } diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py new file mode 100644 index 00000000..9c5c185c --- /dev/null +++ b/sphinx/domains/cpp.py @@ -0,0 +1,340 @@ +# -*- coding: utf-8 -*- +""" + sphinx.domains.cpp + ~~~~~~~~~~~~~~~~~~ + + The C++ language domain. + + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re +import string + +from docutils import nodes + +from sphinx import addnodes +from sphinx.roles import XRefRole +from sphinx.locale import l_, _ +from sphinx.domains import Domain, ObjType +from sphinx.directives import ObjectDescription +from sphinx.util.nodes import make_refnode +from sphinx.util.docfields import Field, TypedField + + +_identifier_re = re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*') +_whitespace_re = re.compile(r'\s+(?u)') +_string_re = re.compile(r"[LuU8]?('([^'\\]*(?:\\.[^'\\]*)*)'" + r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S) + + +class DefinitionError(ValueError): + pass + + +class _DefExpr(object): + + def __unicode__(self): + raise NotImplementedError() + + def split_owner(self): + return None, self + + def __str__(self): + return unicode(self).encode('utf-8') + + def __repr__(self): + return '' % self + + +class _NameDefExpr(_DefExpr): + + def __init__(self, name): + self.name = name + + def __unicode__(self): + return unicode(self.name) + + +class _PathDefExpr(_DefExpr): + + def __init__(self, parts): + self.path = parts + + def split_owner(self): + if len(self.path) > 1: + return _PathDefExpr(self.path[:-1]), self.path[-1] + return _DefExpr.split_owner(self) + + def __unicode__(self): + return u'::'.join(map(unicode, self.path)) + + +class _TemplateDefExpr(_DefExpr): + + def __init__(self, typename, args): + self.typename = typename + self.args = args + + def __unicode__(self): + return u'%s<%s>' % (self.typename, u', '.join(map(unicode, self.args))) + + +class _ArgumentDefExpr(_DefExpr): + + def __init__(self, type, name, default=None): + self.type = type + self.name = name + self.default = default + + def __unicode__(self): + return (self.type is not None and u'%s %s' % (self.type, self.name) + or unicode(self.name)) + (self.default is not None and + u'=%s' % self.default or u'') + + +class _FunctionDefExpr(_DefExpr): + + def __init__(self, name, signature, const, pure_virtual): + self.name = name + self.signature = signature + self.const = const + self.pure_virtual = pure_virtual + + def __unicode__(self): + return u'%s(%s)%s%s' % ( + self.name, + u', '.join(map(unicode, self.signature)), + self.const and u' const' or u'', + self.pure_virtual and ' = 0' or '' + ) + + +class DefinitionParser(object): + + def __init__(self, definition): + self.definition = definition.strip() + self.pos = 0 + self.end = len(self.definition) + self.last_match = None + self._previous_state = (0, None) + + def fail(self, msg): + raise DefinitionError('Invalid definition: "%s", %s [error at %d]' % + (self.definition, msg, self.pos)) + + def match(self, regex): + match = regex.match(self.definition, self.pos) + if match is not None: + self._previous_state = (self.pos, self.last_match) + self.pos = match.end() + self.last_match = match + return True + return False + + def backout(self): + self.pos, self.last_match = self._previous_state + + def skip_string(self, string): + strlen = len(string) + if self.definition[self.pos:self.pos + strlen] == string: + self.pos += strlen + return True + return False + + def skip_ws(self): + return self.match(_whitespace_re) is not None + + @property + def eof(self): + return self.pos >= self.end + + @property + def current_char(self): + try: + return self.definition[self.pos] + except IndexError: + return 'EOF' + + @property + def matched_text(self): + if self.last_match is not None: + return self.last_match.group() + + def _parse_name(self): + if not self.match(_identifier_re): + self.fail('expected name') + return _NameDefExpr(self.matched_text) + + def _parse_type_expr(self): + typename = self._parse_name() + self.skip_ws() + if not self.skip_string('<'): + return typename + + args = [] + while 1: + self.skip_ws() + if self.skip_string('>'): + break + if args: + if not self.skip_string(','): + self.fail('"," or ">" in template expected') + self.skip_ws() + args.append(self._parse_type(True)) + return _TemplateDefExpr(typename, args) + + def _parse_type(self, in_template=False): + result = [] + + # if there is a leading :: or not, we don't care because we + # treat them exactly the same + self.skip_string('::') + + while 1: + self.skip_ws() + if (in_template and self.current_char in ',>') or \ + (result and not self.skip_string('::')) or \ + self.eof: + break + result.append(self._parse_type_expr()) + + if not result: + self.fail('expected type') + if len(result) == 1: + return result[0] + return _PathDefExpr(result) + + def _parse_default_expr(self): + self.skip_ws() + if self.match(_string_re): + return self.matched_text + idx1 = self.definition.find(',', self.pos) + idx2 = self.definition.find(')', self.pos) + if idx1 < 0: + idx = idx2 + elif idx2 < 0: + idx = idx1 + else: + idx = min(idx1, idx2) + if idx < 0: + self.fail('unexpected end in default expression') + rv = self.definition[self.pos:idx] + self.pos = idx + return rv + + def _parse_signature(self): + self.skip_ws() + if not self.skip_string('('): + self.fail('expected parentheses for function') + + args = [] + while 1: + self.skip_ws() + if self.eof: + self.fail('missing closing parentheses') + if self.skip_string(')'): + break + if args: + if not self.skip_string(','): + self.fail('expected comma between arguments') + self.skip_ws() + + argname = self._parse_type() + argtype = default = None + self.skip_ws() + if self.skip_string('='): + self.pos += 1 + default = self._parse_default_expr() + elif self.current_char not in ',)': + argtype = argname + argname = self._parse_name() + self.skip_ws() + if self.skip_string('='): + default = self._parse_default_expr() + + args.append(_ArgumentDefExpr(argtype, argname, default)) + self.skip_ws() + const = self.skip_string('const') + if const: + self.skip_ws() + if self.skip_string('='): + self.skip_ws() + if not (self.skip_string('0') or \ + self.skip_string('NULL') or \ + self.skip_string('nullptr')): + self.fail('pure virtual functions must be defined with ' + 'either 0, NULL or nullptr, other macros are ' + 'not allowed') + pure_virtual = True + else: + pure_virtual = False + return args, const, pure_virtual + + def parse_variable(self): + type = self._parse_type() + name = self._parse_type() + return type, name + + def parse_function(self): + rv = self._parse_type() + name = self._parse_type() + return rv, _FunctionDefExpr(name, *self._parse_signature()) + + +class CPPObject(ObjectDescription): + """Description of a C++ language object.""" + + +class CPPDomain(Domain): + """C++ language domain.""" + name = 'cpp' + label = 'C++' + object_types = { + 'class': ObjType(l_('C++ class'), 'class'), + 'function': ObjType(l_('C++ function'), 'func'), + 'member': ObjType(l_('C++ member'), 'member'), + 'macro': ObjType(l_('C++ macro'), 'macro'), + 'type': ObjType(l_('C++ type'), 'type'), + 'var': ObjType(l_('C++ variable'), 'data'), + } + + directives = { + 'class': CPPObject, + 'function': CPPObject, + 'member': CPPObject, + 'macro': CPPObject, + 'type': CPPObject, + 'var': CPPObject, + } + roles = { + 'class': XRefRole(), + 'func' : XRefRole(fix_parens=True), + 'member': XRefRole(), + 'macro': XRefRole(), + 'data': XRefRole(), + 'type': XRefRole(), + } + initial_data = { + 'objects': {}, # fullname -> docname, objtype + } + + def clear_doc(self, docname): + for fullname, (fn, _) in self.data['objects'].items(): + if fn == docname: + del self.data['objects'][fullname] + + def resolve_xref(self, env, fromdocname, builder, + typ, target, node, contnode): + # strip pointer asterisk + target = target.rstrip(' *') + if target not in self.data['objects']: + return None + obj = self.data['objects'][target] + return make_refnode(builder, fromdocname, obj[0], target, + contnode, target) + + def get_objects(self): + for refname, (docname, type) in self.data['objects'].iteritems(): + yield (refname, type, docname, refname, 1) -- cgit v1.2.1 From fa64a90ff5cf57a9a65a33bb4e483bc71e2fd59f Mon Sep 17 00:00:00 2001 From: mitsuhiko Date: Sun, 28 Feb 2010 19:07:32 +0100 Subject: C++ function signatures are now properly converted to rst nodes. --- sphinx/domains/c.py | 4 +- sphinx/domains/cpp.py | 166 +++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 161 insertions(+), 9 deletions(-) diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index d4de0fa6..1edec525 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -29,7 +29,7 @@ wsplit_re = re.compile(r'(\W+)') # REs for C signatures c_sig_re = re.compile( r'''^([^(]*?) # return type - ([\w:.]+) \s* # thing name (colon allowed for C++ class names) + ([\w:.]+) \s* # thing name (colon allowed for C++) (?: \((.*)\) )? # optionally arguments (\s+const)? $ # const specifier ''', re.VERBOSE) @@ -76,7 +76,7 @@ class CObject(ObjectDescription): node += tnode def handle_signature(self, sig, signode): - """Transform a C (or C++) signature into RST nodes.""" + """Transform a C signature into RST nodes.""" # first try the function pointer signature regex, it's more specific m = c_funcptr_sig_re.match(sig) if m is None: diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 9c5c185c..e946dbe5 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -29,7 +29,7 @@ _string_re = re.compile(r"[LuU8]?('([^'\\]*(?:\\.[^'\\]*)*)'" r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S) -class DefinitionError(ValueError): +class DefinitionError(Exception): pass @@ -71,6 +71,34 @@ class _PathDefExpr(_DefExpr): return u'::'.join(map(unicode, self.path)) +class _ModifierDefExpr(_DefExpr): + + def __init__(self, modifiers, typename): + self.modifiers = modifiers + self.typename = typename + + def __unicode__(self): + return u' '.join(map(unicode, list(self.modifiers) + [self.typename])) + + +class _PtrDefExpr(_DefExpr): + + def __init__(self, typename): + self.typename = typename + + def __unicode__(self): + return u'%s*' % self.typename + + +class _RefDefExpr(_DefExpr): + + def __init__(self, typename): + self.typename = typename + + def __unicode__(self): + return u'%s&' % self.typename + + class _TemplateDefExpr(_DefExpr): def __init__(self, typename, args): @@ -113,6 +141,22 @@ class _FunctionDefExpr(_DefExpr): class DefinitionParser(object): + # mapping of valid type modifiers. if the set is None it means + # the modifier can prefix all types, otherwise only the types + # (actually more keywords) in the set. Also check + # _guess_typename when changing this + _modifiers = { + 'volatile': None, + 'register': None, + 'const': None, + 'mutable': None, + 'typename': None, + 'unsigned': set(('char', 'int', 'long')), + 'signed': set(('char', 'int', 'long')), + 'short': set(('int',)), + 'long': set(('int', 'long')) + } + def __init__(self, definition): self.definition = definition.strip() self.pos = 0 @@ -121,8 +165,8 @@ class DefinitionParser(object): self._previous_state = (0, None) def fail(self, msg): - raise DefinitionError('Invalid definition: "%s", %s [error at %d]' % - (self.definition, msg, self.pos)) + raise DefinitionError('Invalid definition: %s [error at %d]\n %s' % + (msg, self.pos, self.definition)) def match(self, regex): match = regex.match(self.definition, self.pos) @@ -185,12 +229,69 @@ class DefinitionParser(object): args.append(self._parse_type(True)) return _TemplateDefExpr(typename, args) + def _guess_typename(self, path): + if not path: + return [], 'int' + # for the long type, we don't want the int in there + if 'long' in path: + path = [x for x in path if x != 'int'] + # remove one long + path.remove('long') + return path, 'long' + if path[-1] in ('int', 'char'): + return path[:-1], path[-1] + return path, 'int' + + def _attach_refptr(self, expr): + self.skip_ws() + if self.skip_string('*'): + return _PtrDefExpr(expr) + elif self.skip_string('&'): + return _RefDefExpr(expr) + return expr + + def _parse_builtin(self, modifier): + path = [modifier] + following = self._modifiers[modifier] + while 1: + self.skip_ws() + if not self.match(_identifier_re): + break + identifier = self.matched_text + if identifier in following: + path.append(identifier) + following = self._modifiers[modifier] + assert following + else: + self.backout() + break + modifiers, typename = self._guess_typename(path) + rv = _ModifierDefExpr(modifiers, _NameDefExpr(typename)) + return self._attach_refptr(rv) + def _parse_type(self, in_template=False): result = [] + modifiers = [] # if there is a leading :: or not, we don't care because we - # treat them exactly the same - self.skip_string('::') + # treat them exactly the same. Buf *if* there is one, we + # don't have to check for type modifiers + if not self.skip_string('::'): + self.skip_ws() + if self.match(_identifier_re): + modifier = self.matched_text + if modifier in self._modifiers: + following = self._modifiers[modifier] + # if the set is not none, there is a limited set + # of types that might follow. It is technically + # impossible for a template to follow, so what + # we do is go to a different function that just + # eats types + if following is not None: + return self._parse_builtin(modifier) + modifiers.append(modifier) + else: + self.backout() while 1: self.skip_ws() @@ -203,8 +304,12 @@ class DefinitionParser(object): if not result: self.fail('expected type') if len(result) == 1: - return result[0] - return _PathDefExpr(result) + rv = result[0] + else: + rv = _PathDefExpr(result) + if modifiers: + rv = _ModifierDefExpr(modifiers, rv) + return self._attach_refptr(rv) def _parse_default_expr(self): self.skip_ws() @@ -282,10 +387,57 @@ class DefinitionParser(object): name = self._parse_type() return rv, _FunctionDefExpr(name, *self._parse_signature()) + def assert_end(self): + self.skip_ws() + if not self.eof: + self.fail('expected end of definition, got %r' % + self.definition[self.pos:]) + class CPPObject(ObjectDescription): """Description of a C++ language object.""" + def _attach_return_type(self, node, return_type): + # XXX: link? how could we do that + text = unicode(return_type) + u' ' + tnode = nodes.Text(text, text) + node += tnode + + def _attach_function(self, node, func): + owner, name = func.name.split_owner() + funcname = unicode(name) + if owner is not None: + owner = unicode(owner) + '::' + node += addnodes.desc_addname(owner, owner) + node += addnodes.desc_name(funcname, funcname) + else: + node += addnodes.desc_name(funcname, funcname) + + paramlist = addnodes.desc_parameterlist() + for arg in func.signature: + # XXX: is that okay? maybe more semantic here + strarg = unicode(arg) + param = addnodes.desc_parameter('', '', noemph=True) + param += nodes.emphasis(strarg, strarg) + paramlist += param + + node += paramlist + if func.const: + node += addnodes.desc_addname(' const', ' const') + if func.pure_virtual: + node += addnodes.desc_addname(' = 0', ' = 0') + + def handle_signature(self, sig, signode): + """Transform a C (or C++) signature into RST nodes.""" + parser = DefinitionParser(sig) + rv, func = parser.parse_function() + parser.assert_end() + + signode += addnodes.desc_type('', '') + self._attach_return_type(signode, rv) + self._attach_function(signode, func) + return str(func.name) + class CPPDomain(Domain): """C++ language domain.""" -- cgit v1.2.1 From af0695a8ba5eea721c09fe65a9aa3979c12fe0d2 Mon Sep 17 00:00:00 2001 From: mitsuhiko Date: Sun, 28 Feb 2010 21:04:35 +0100 Subject: Properly parses operator definitions now. 'std:string module::class::operator std::string()' is picked up properly. --- sphinx/domains/cpp.py | 117 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 96 insertions(+), 21 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index e946dbe5..e2181aec 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -23,10 +23,16 @@ from sphinx.util.nodes import make_refnode from sphinx.util.docfields import Field, TypedField -_identifier_re = re.compile(r'[a-zA-Z_][a-zA-Z0-9_]*') +_identifier_re = re.compile(r'(~?[a-zA-Z_][a-zA-Z0-9_]*)') _whitespace_re = re.compile(r'\s+(?u)') _string_re = re.compile(r"[LuU8]?('([^'\\]*(?:\\.[^'\\]*)*)'" r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S) +_operator_re = re.compile(r'''(?x) + \[\s*\] + | \(\s*\) + | [!<>=/*%+-|&^]=? + | <<=? | >>=? | ~ | ^ | & | && | \| | \|\| +''') class DefinitionError(Exception): @@ -99,6 +105,15 @@ class _RefDefExpr(_DefExpr): return u'%s&' % self.typename +class _CastOpDefExpr(_DefExpr): + + def __init__(self, typename): + self.typename = typename + + def __unicode__(self): + return u'operator %s' % self.typename + + class _TemplateDefExpr(_DefExpr): def __init__(self, typename, args): @@ -153,7 +168,7 @@ class DefinitionParser(object): 'typename': None, 'unsigned': set(('char', 'int', 'long')), 'signed': set(('char', 'int', 'long')), - 'short': set(('int',)), + 'short': set(('int', 'short')), 'long': set(('int', 'long')) } @@ -206,10 +221,30 @@ class DefinitionParser(object): if self.last_match is not None: return self.last_match.group() + def _parse_operator(self): + # thank god, a regular operator definition + if self.match(_operator_re): + return _NameDefExpr('operator' + + _whitespace_re.sub('', self.matched_text)) + + # oh well, looks like a cast operator definition. + # In that case, eat another type. + type = self._parse_type() + return _CastOpDefExpr(type) + def _parse_name(self): if not self.match(_identifier_re): self.fail('expected name') - return _NameDefExpr(self.matched_text) + identifier = self.matched_text + + # strictly speaking, operators are not regular identifiers + # but because operator is a keyword, it might not be used + # for variable names anyways, so we can safely parse the + # operator here as identifier + if identifier == 'operator': + return self._parse_operator() + + return _NameDefExpr(identifier) def _parse_type_expr(self): typename = self._parse_name() @@ -387,6 +422,9 @@ class DefinitionParser(object): name = self._parse_type() return rv, _FunctionDefExpr(name, *self._parse_signature()) + def parse_typename(self): + return self._parse_type() + def assert_end(self): self.skip_ws() if not self.eof: @@ -397,11 +435,49 @@ class DefinitionParser(object): class CPPObject(ObjectDescription): """Description of a C++ language object.""" - def _attach_return_type(self, node, return_type): + def _attach_type(self, node, type): # XXX: link? how could we do that - text = unicode(return_type) + u' ' - tnode = nodes.Text(text, text) - node += tnode + text = unicode(type) + u' ' + pnode = addnodes.pending_xref( + '', refdomain='cpp', reftype='type', + reftarget=text, modname=None, classname=None) + pnode += nodes.Text(text) + node += pnode + + def handle_signature(self, sig, signode): + """Transform a C++ signature into RST nodes.""" + parser = DefinitionParser(sig) + typename = parser.parse_typename() + parser.assert_end() + + signode += addnodes.desc_type('', '') + self._attach_type(signode, typename) + return unicode(typename) + + +class CPPTypedObject(CPPObject): + + def _attach_var(self, node, var): + owner, name = var.name.split_owner() + varname = unicode(name) + if owner is not None: + owner = unicode(owner) + '::' + node += addnodes.desc_addname(owner, owner) + node += addnodes.desc_name(varname, varname) + + def handle_signature(self, sig, signode): + """Transform a C++ signature into RST nodes.""" + parser = DefinitionParser(sig) + rv, var = parser.parse_variable() + parser.assert_end() + + signode += addnodes.desc_type('', '') + self._attach_type(signode, rv) + self._attach_var(signode, var) + return str(func.name) + + +class CPPFunctionObject(CPPTypedObject): def _attach_function(self, node, func): owner, name = func.name.split_owner() @@ -409,16 +485,17 @@ class CPPObject(ObjectDescription): if owner is not None: owner = unicode(owner) + '::' node += addnodes.desc_addname(owner, owner) - node += addnodes.desc_name(funcname, funcname) - else: - node += addnodes.desc_name(funcname, funcname) + node += addnodes.desc_name(funcname, funcname) paramlist = addnodes.desc_parameterlist() for arg in func.signature: - # XXX: is that okay? maybe more semantic here - strarg = unicode(arg) param = addnodes.desc_parameter('', '', noemph=True) - param += nodes.emphasis(strarg, strarg) + if arg.type is not None: + self._attach_type(param, arg.type) + param += nodes.emphasis(unicode(arg.name), unicode(arg.name)) + if arg.default is not None: + def_ = u'=' + unicode(arg.default) + param += nodes.emphasis(def_, def_) paramlist += param node += paramlist @@ -428,13 +505,13 @@ class CPPObject(ObjectDescription): node += addnodes.desc_addname(' = 0', ' = 0') def handle_signature(self, sig, signode): - """Transform a C (or C++) signature into RST nodes.""" + """Transform a C++ signature into RST nodes.""" parser = DefinitionParser(sig) rv, func = parser.parse_function() parser.assert_end() signode += addnodes.desc_type('', '') - self._attach_return_type(signode, rv) + self._attach_type(signode, rv) self._attach_function(signode, func) return str(func.name) @@ -447,18 +524,16 @@ class CPPDomain(Domain): 'class': ObjType(l_('C++ class'), 'class'), 'function': ObjType(l_('C++ function'), 'func'), 'member': ObjType(l_('C++ member'), 'member'), - 'macro': ObjType(l_('C++ macro'), 'macro'), 'type': ObjType(l_('C++ type'), 'type'), 'var': ObjType(l_('C++ variable'), 'data'), } directives = { 'class': CPPObject, - 'function': CPPObject, - 'member': CPPObject, - 'macro': CPPObject, - 'type': CPPObject, - 'var': CPPObject, + 'function': CPPFunctionObject, + 'member': CPPTypedObject, + 'type': CPPTypedObject, + 'var': CPPTypedObject, } roles = { 'class': XRefRole(), -- cgit v1.2.1 From ec88c58cc6ed37349089fc32d03d6d9a12b5d0eb Mon Sep 17 00:00:00 2001 From: mitsuhiko Date: Mon, 1 Mar 2010 03:17:09 +0100 Subject: Proper indices for the c++ object descriptions now. Also broke up the monolithic class into multiple separate classes and refactored a bit. Still does not handle overloads. --- sphinx/domains/cpp.py | 175 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 122 insertions(+), 53 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index e2181aec..1da6783e 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -39,7 +39,7 @@ class DefinitionError(Exception): pass -class _DefExpr(object): +class DefExpr(object): def __unicode__(self): raise NotImplementedError() @@ -54,7 +54,7 @@ class _DefExpr(object): return '' % self -class _NameDefExpr(_DefExpr): +class NameDefExpr(DefExpr): def __init__(self, name): self.name = name @@ -63,21 +63,21 @@ class _NameDefExpr(_DefExpr): return unicode(self.name) -class _PathDefExpr(_DefExpr): +class PathDefExpr(DefExpr): def __init__(self, parts): self.path = parts def split_owner(self): if len(self.path) > 1: - return _PathDefExpr(self.path[:-1]), self.path[-1] - return _DefExpr.split_owner(self) + return PathDefExpr(self.path[:-1]), self.path[-1] + return DefExpr.split_owner(self) def __unicode__(self): return u'::'.join(map(unicode, self.path)) -class _ModifierDefExpr(_DefExpr): +class ModifierDefExpr(DefExpr): def __init__(self, modifiers, typename): self.modifiers = modifiers @@ -87,7 +87,7 @@ class _ModifierDefExpr(_DefExpr): return u' '.join(map(unicode, list(self.modifiers) + [self.typename])) -class _PtrDefExpr(_DefExpr): +class PtrDefExpr(DefExpr): def __init__(self, typename): self.typename = typename @@ -96,7 +96,7 @@ class _PtrDefExpr(_DefExpr): return u'%s*' % self.typename -class _RefDefExpr(_DefExpr): +class RefDefExpr(DefExpr): def __init__(self, typename): self.typename = typename @@ -105,7 +105,7 @@ class _RefDefExpr(_DefExpr): return u'%s&' % self.typename -class _CastOpDefExpr(_DefExpr): +class CastOpDefExpr(DefExpr): def __init__(self, typename): self.typename = typename @@ -114,7 +114,7 @@ class _CastOpDefExpr(_DefExpr): return u'operator %s' % self.typename -class _TemplateDefExpr(_DefExpr): +class TemplateDefExpr(DefExpr): def __init__(self, typename, args): self.typename = typename @@ -124,7 +124,7 @@ class _TemplateDefExpr(_DefExpr): return u'%s<%s>' % (self.typename, u', '.join(map(unicode, self.args))) -class _ArgumentDefExpr(_DefExpr): +class ArgumentDefExpr(DefExpr): def __init__(self, type, name, default=None): self.type = type @@ -137,7 +137,7 @@ class _ArgumentDefExpr(_DefExpr): u'=%s' % self.default or u'') -class _FunctionDefExpr(_DefExpr): +class FunctionDefExpr(DefExpr): def __init__(self, name, signature, const, pure_virtual): self.name = name @@ -224,13 +224,13 @@ class DefinitionParser(object): def _parse_operator(self): # thank god, a regular operator definition if self.match(_operator_re): - return _NameDefExpr('operator' + + return NameDefExpr('operator' + _whitespace_re.sub('', self.matched_text)) # oh well, looks like a cast operator definition. # In that case, eat another type. type = self._parse_type() - return _CastOpDefExpr(type) + return CastOpDefExpr(type) def _parse_name(self): if not self.match(_identifier_re): @@ -244,7 +244,7 @@ class DefinitionParser(object): if identifier == 'operator': return self._parse_operator() - return _NameDefExpr(identifier) + return NameDefExpr(identifier) def _parse_type_expr(self): typename = self._parse_name() @@ -262,7 +262,7 @@ class DefinitionParser(object): self.fail('"," or ">" in template expected') self.skip_ws() args.append(self._parse_type(True)) - return _TemplateDefExpr(typename, args) + return TemplateDefExpr(typename, args) def _guess_typename(self, path): if not path: @@ -280,9 +280,9 @@ class DefinitionParser(object): def _attach_refptr(self, expr): self.skip_ws() if self.skip_string('*'): - return _PtrDefExpr(expr) + return PtrDefExpr(expr) elif self.skip_string('&'): - return _RefDefExpr(expr) + return RefDefExpr(expr) return expr def _parse_builtin(self, modifier): @@ -301,7 +301,7 @@ class DefinitionParser(object): self.backout() break modifiers, typename = self._guess_typename(path) - rv = _ModifierDefExpr(modifiers, _NameDefExpr(typename)) + rv = ModifierDefExpr(modifiers, _NameDefExpr(typename)) return self._attach_refptr(rv) def _parse_type(self, in_template=False): @@ -341,9 +341,9 @@ class DefinitionParser(object): if len(result) == 1: rv = result[0] else: - rv = _PathDefExpr(result) + rv = PathDefExpr(result) if modifiers: - rv = _ModifierDefExpr(modifiers, rv) + rv = ModifierDefExpr(modifiers, rv) return self._attach_refptr(rv) def _parse_default_expr(self): @@ -394,7 +394,7 @@ class DefinitionParser(object): if self.skip_string('='): default = self._parse_default_expr() - args.append(_ArgumentDefExpr(argtype, argname, default)) + args.append(ArgumentDefExpr(argtype, argname, default)) self.skip_ws() const = self.skip_string('const') if const: @@ -419,8 +419,12 @@ class DefinitionParser(object): def parse_function(self): rv = self._parse_type() - name = self._parse_type() - return rv, _FunctionDefExpr(name, *self._parse_signature()) + if isinstance(rv, CastOpDefExpr): + name = rv + rv = None + else: + name = self._parse_type() + return rv, FunctionDefExpr(name, *self._parse_signature()) def parse_typename(self): return self._parse_type() @@ -435,24 +439,72 @@ class DefinitionParser(object): class CPPObject(ObjectDescription): """Description of a C++ language object.""" - def _attach_type(self, node, type): + def attach_type(self, node, type): # XXX: link? how could we do that - text = unicode(type) + u' ' - pnode = addnodes.pending_xref( - '', refdomain='cpp', reftype='type', - reftarget=text, modname=None, classname=None) - pnode += nodes.Text(text) - node += pnode + if type is not None: + text = unicode(type) + pnode = addnodes.pending_xref( + '', refdomain='cpp', reftype='type', + reftarget=text, modname=None, classname=None) + pnode += nodes.Text(text) + node += pnode + + def add_target_and_index(self, name, sig, signode): + if name not in self.state.document.ids: + # XXX: how to handle method overloading? + signode['names'].append(name) + signode['ids'].append(name) + signode['first'] = (not self.names) + self.state.document.note_explicit_target(signode) + self.env.domaindata['cpp']['objects'][name] = \ + (self.env.docname, self.objtype) + + indextext = self.get_index_text(name) + if indextext: + self.indexnode['entries'].append(('single', indextext, name, name)) + + def before_content(self): + lastname = self.names and self.names[-1] + if lastname and not self.env.temp_data.get('cpp:parent'): + self.env.temp_data['cpp:parent'] = lastname + self.parentname_set = True + else: + self.parentname_set = False + + def after_content(self): + if self.parentname_set: + self.env.temp_data['cpp:parent'] = None + + def parse_definition(self, parser): + raise NotImplementedError() + + def describe_signature(self, signode, arg): + raise NotImplementedError() def handle_signature(self, sig, signode): - """Transform a C++ signature into RST nodes.""" parser = DefinitionParser(sig) - typename = parser.parse_typename() + rv = self.parse_definition(parser) parser.assert_end() + name = self.describe_signature(signode, rv) + + parentname = self.env.temp_data.get('cpp:parent') + if parentname: + return u'%s::%s' % (parentname, name) + return unicode(name) + + +class CPPClassObject(CPPObject): + + def get_index_text(self, name): + return _('%s (C++ class)') % name + def parse_definition(self, parser): + return parser.parse_typename() + + def describe_signature(self, signode, typename): signode += addnodes.desc_type('', '') - self._attach_type(signode, typename) - return unicode(typename) + self.attach_type(signode, typename) + return typename class CPPTypedObject(CPPObject): @@ -465,33 +517,48 @@ class CPPTypedObject(CPPObject): node += addnodes.desc_addname(owner, owner) node += addnodes.desc_name(varname, varname) - def handle_signature(self, sig, signode): - """Transform a C++ signature into RST nodes.""" - parser = DefinitionParser(sig) - rv, var = parser.parse_variable() - parser.assert_end() + def get_index_text(self, name): + if self.objtype == 'member': + return _('%s (C++ member)') % name + elif self.objtype == 'type': + return _('%s (C++ type)') % name + elif self.objtype == 'var': + return _('%s (C++ variable)') % name + return '' + + def parse_definition(self, parser): + return parser.parse_variable() + def describe_signature(self, signode, (rv, var)): signode += addnodes.desc_type('', '') - self._attach_type(signode, rv) + self.attach_type(signode, rv) + signode += nodes.Text(' ') self._attach_var(signode, var) - return str(func.name) + return var.name class CPPFunctionObject(CPPTypedObject): def _attach_function(self, node, func): owner, name = func.name.split_owner() - funcname = unicode(name) if owner is not None: owner = unicode(owner) + '::' node += addnodes.desc_addname(owner, owner) - node += addnodes.desc_name(funcname, funcname) + + if isinstance(name, CastOpDefExpr): + node += addnodes.desc_name('operator', 'operator') + node += nodes.Text(u' ') + self.attach_type(node, name.typename) + else: + funcname = unicode(name) + node += addnodes.desc_name(funcname, funcname) paramlist = addnodes.desc_parameterlist() for arg in func.signature: param = addnodes.desc_parameter('', '', noemph=True) if arg.type is not None: - self._attach_type(param, arg.type) + self.attach_type(param, arg.type) + param += nodes.Text(u' ') param += nodes.emphasis(unicode(arg.name), unicode(arg.name)) if arg.default is not None: def_ = u'=' + unicode(arg.default) @@ -504,16 +571,18 @@ class CPPFunctionObject(CPPTypedObject): if func.pure_virtual: node += addnodes.desc_addname(' = 0', ' = 0') - def handle_signature(self, sig, signode): - """Transform a C++ signature into RST nodes.""" - parser = DefinitionParser(sig) - rv, func = parser.parse_function() - parser.assert_end() + def get_index_text(self, name): + return _('%s (C++ function)') % name + + def parse_definition(self, parser): + return parser.parse_function() + def describe_signature(self, signode, (rv, func)): signode += addnodes.desc_type('', '') - self._attach_type(signode, rv) + self.attach_type(signode, rv) + signode += nodes.Text(u' ') self._attach_function(signode, func) - return str(func.name) + return func.name class CPPDomain(Domain): @@ -529,7 +598,7 @@ class CPPDomain(Domain): } directives = { - 'class': CPPObject, + 'class': CPPClassObject, 'function': CPPFunctionObject, 'member': CPPTypedObject, 'type': CPPTypedObject, -- cgit v1.2.1 From 7d2e24b99cdae24921237144bb77b8a34915b871 Mon Sep 17 00:00:00 2001 From: mitsuhiko Date: Mon, 1 Mar 2010 03:25:28 +0100 Subject: Prefix classes with "class " and removed useless desc_type node. --- sphinx/domains/cpp.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 1da6783e..1031d61d 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -502,7 +502,7 @@ class CPPClassObject(CPPObject): return parser.parse_typename() def describe_signature(self, signode, typename): - signode += addnodes.desc_type('', '') + signode += addnodes.desc_annotation('class ', 'class ') self.attach_type(signode, typename) return typename @@ -522,15 +522,12 @@ class CPPTypedObject(CPPObject): return _('%s (C++ member)') % name elif self.objtype == 'type': return _('%s (C++ type)') % name - elif self.objtype == 'var': - return _('%s (C++ variable)') % name return '' def parse_definition(self, parser): return parser.parse_variable() def describe_signature(self, signode, (rv, var)): - signode += addnodes.desc_type('', '') self.attach_type(signode, rv) signode += nodes.Text(' ') self._attach_var(signode, var) @@ -578,7 +575,6 @@ class CPPFunctionObject(CPPTypedObject): return parser.parse_function() def describe_signature(self, signode, (rv, func)): - signode += addnodes.desc_type('', '') self.attach_type(signode, rv) signode += nodes.Text(u' ') self._attach_function(signode, func) @@ -593,24 +589,20 @@ class CPPDomain(Domain): 'class': ObjType(l_('C++ class'), 'class'), 'function': ObjType(l_('C++ function'), 'func'), 'member': ObjType(l_('C++ member'), 'member'), - 'type': ObjType(l_('C++ type'), 'type'), - 'var': ObjType(l_('C++ variable'), 'data'), + 'type': ObjType(l_('C++ type'), 'type') } directives = { 'class': CPPClassObject, 'function': CPPFunctionObject, 'member': CPPTypedObject, - 'type': CPPTypedObject, - 'var': CPPTypedObject, + 'type': CPPTypedObject } roles = { 'class': XRefRole(), 'func' : XRefRole(fix_parens=True), 'member': XRefRole(), - 'macro': XRefRole(), - 'data': XRefRole(), - 'type': XRefRole(), + 'type': XRefRole() } initial_data = { 'objects': {}, # fullname -> docname, objtype @@ -623,8 +615,8 @@ class CPPDomain(Domain): def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): - # strip pointer asterisk - target = target.rstrip(' *') + # strip pointer and reference info + target = target.rstrip(' *&') if target not in self.data['objects']: return None obj = self.data['objects'][target] -- cgit v1.2.1 From eca97348424cfaef9fd113d553eb1ea6bd1e85d0 Mon Sep 17 00:00:00 2001 From: mitsuhiko Date: Mon, 1 Mar 2010 11:05:52 +0100 Subject: Refactored description objects again. --- sphinx/domains/cpp.py | 107 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 72 insertions(+), 35 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 1031d61d..6765f231 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -137,16 +137,28 @@ class ArgumentDefExpr(DefExpr): u'=%s' % self.default or u'') +class TypedObjDefExpr(DefExpr): + + def __init__(self, typename, name): + self.typename = typename + self.name = name + + def __unicode__(self): + return u'%s %s' % (self.typename, name) + + class FunctionDefExpr(DefExpr): - def __init__(self, name, signature, const, pure_virtual): + def __init__(self, name, rv, signature, const, pure_virtual): self.name = name + self.rv = rv self.signature = signature self.const = const self.pure_virtual = pure_virtual def __unicode__(self): - return u'%s(%s)%s%s' % ( + return u'%s%s(%s)%s%s' % ( + self.rv is not None and self.rv + u' ' or u'', self.name, u', '.join(map(unicode, self.signature)), self.const and u' const' or u'', @@ -412,19 +424,23 @@ class DefinitionParser(object): pure_virtual = False return args, const, pure_virtual - def parse_variable(self): - type = self._parse_type() + def parse_typed_object(self): + typename = self._parse_type() name = self._parse_type() - return type, name + # XXX: for constants it would be useful to be able to parse + # an assigned value here as well. + return TypedObjDefExpr(typename, name) def parse_function(self): rv = self._parse_type() - if isinstance(rv, CastOpDefExpr): + self.skip_ws() + # some things just don't have return values + if self.current_char == '(': name = rv rv = None else: name = self._parse_type() - return rv, FunctionDefExpr(name, *self._parse_signature()) + return FunctionDefExpr(name, rv, *self._parse_signature()) def parse_typename(self): return self._parse_type() @@ -439,21 +455,32 @@ class DefinitionParser(object): class CPPObject(ObjectDescription): """Description of a C++ language object.""" + def attach_name(self, node, name): + owner, name = name.split_owner() + varname = unicode(name) + if owner is not None: + owner = unicode(owner) + '::' + node += addnodes.desc_addname(owner, owner) + node += addnodes.desc_name(varname, varname) + def attach_type(self, node, type): - # XXX: link? how could we do that - if type is not None: - text = unicode(type) - pnode = addnodes.pending_xref( - '', refdomain='cpp', reftype='type', - reftarget=text, modname=None, classname=None) - pnode += nodes.Text(text) - node += pnode + # XXX: link to c? + text = unicode(type) + pnode = addnodes.pending_xref( + '', refdomain='cpp', reftype='type', + reftarget=text, modname=None, classname=None) + pnode += nodes.Text(text) + node += pnode + + def make_id(self, name, sig): + return name def add_target_and_index(self, name, sig, signode): if name not in self.state.document.ids: # XXX: how to handle method overloading? - signode['names'].append(name) - signode['ids'].append(name) + theid = self.make_id(name, sig) + signode['names'].append(theid) + signode['ids'].append(theid) signode['first'] = (not self.names) self.state.document.note_explicit_target(signode) self.env.domaindata['cpp']['objects'][name] = \ @@ -503,20 +530,12 @@ class CPPClassObject(CPPObject): def describe_signature(self, signode, typename): signode += addnodes.desc_annotation('class ', 'class ') - self.attach_type(signode, typename) + self.attach_name(signode, typename) return typename class CPPTypedObject(CPPObject): - def _attach_var(self, node, var): - owner, name = var.name.split_owner() - varname = unicode(name) - if owner is not None: - owner = unicode(owner) + '::' - node += addnodes.desc_addname(owner, owner) - node += addnodes.desc_name(varname, varname) - def get_index_text(self, name): if self.objtype == 'member': return _('%s (C++ member)') % name @@ -525,23 +544,25 @@ class CPPTypedObject(CPPObject): return '' def parse_definition(self, parser): - return parser.parse_variable() + return parser.parse_typed_object() - def describe_signature(self, signode, (rv, var)): - self.attach_type(signode, rv) + def describe_signature(self, signode, obj): + self.attach_type(signode, obj.typename) signode += nodes.Text(' ') - self._attach_var(signode, var) - return var.name + self.attach_name(signode, obj.name) + return obj.name class CPPFunctionObject(CPPTypedObject): - def _attach_function(self, node, func): + def attach_function(self, node, func): owner, name = func.name.split_owner() if owner is not None: owner = unicode(owner) + '::' node += addnodes.desc_addname(owner, owner) + # cast operator is special. in this case the return value + # is reversed. if isinstance(name, CastOpDefExpr): node += addnodes.desc_name('operator', 'operator') node += nodes.Text(u' ') @@ -568,16 +589,32 @@ class CPPFunctionObject(CPPTypedObject): if func.pure_virtual: node += addnodes.desc_addname(' = 0', ' = 0') + def make_id(self, name, sig): + # XXX: can we reuse somehow the parsed definition here? ideally + # what we could do would be checking if a function is overloaded + # after we found everything and if it is, go over all parsed + # signatures and find a short code. + # + # eg: + # ns::foo(int a, int b) --> ns::foo__i.i + # ns::foo(char a, std::string b) --> ns::foo__c.std::string + # ns::foo(char a, std::string b, int c) --> ns::foo__c.std::string.i + return name + def get_index_text(self, name): return _('%s (C++ function)') % name def parse_definition(self, parser): return parser.parse_function() - def describe_signature(self, signode, (rv, func)): - self.attach_type(signode, rv) + def describe_signature(self, signode, func): + # return value is None for things with a reverse return value + # such as casting operator definitions or constructors + # and destructors. + if func.rv is not None: + self.attach_type(signode, func.rv) signode += nodes.Text(u' ') - self._attach_function(signode, func) + self.attach_function(signode, func) return func.name -- cgit v1.2.1 From a85a6cd1f5d17dfbfb493fc8c3ea9c378e012e77 Mon Sep 17 00:00:00 2001 From: mitsuhiko Date: Mon, 1 Mar 2010 12:29:05 +0100 Subject: Heavily improved const support, create proper identifiers that pass for HTML4 and support overloading. --- sphinx/domains/cpp.py | 217 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 167 insertions(+), 50 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 6765f231..dbc24efb 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -23,7 +23,7 @@ from sphinx.util.nodes import make_refnode from sphinx.util.docfields import Field, TypedField -_identifier_re = re.compile(r'(~?[a-zA-Z_][a-zA-Z0-9_]*)') +_identifier_re = re.compile(r'\b(~?[a-zA-Z_][a-zA-Z0-9_]*)\b') _whitespace_re = re.compile(r'\s+(?u)') _string_re = re.compile(r"[LuU8]?('([^'\\]*(?:\\.[^'\\]*)*)'" r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S) @@ -31,9 +31,64 @@ _operator_re = re.compile(r'''(?x) \[\s*\] | \(\s*\) | [!<>=/*%+-|&^]=? - | <<=? | >>=? | ~ | ^ | & | && | \| | \|\| + | \+\+ | -- + | <<=? | >>=? | ~ | && | \| | \|\| ''') +_id_shortwords = { + 'char': 'c', + 'signed char': 'c', + 'unsigned char': 'C', + 'int': 'i', + 'signed int': 'i', + 'unsigned int': 'U', + 'long': 'l', + 'signed long': 'l', + 'unsigned long': 'L', + 'bool': 'b', + 'size_t': 's', + 'std::string': 'ss', + 'std::ostream': 'os', + 'std::istream': 'is', + 'std::iostream': 'ios', + 'std::vector': 'v', + 'std::map': 'm', + 'operator[]': 'subscript-operator', + 'operator()': 'call-operator', + 'operator!': 'not-operator', + 'operator<': 'lt-operator', + 'operator<=': 'lte-operator', + 'operator>': 'gt-operator', + 'operator>=': 'gte-operator', + 'operator=': 'assign-operator', + 'operator/': 'div-operator', + 'operator*': 'mul-operator', + 'operator%': 'mod-operator', + 'operator+': 'add-operator', + 'operator-': 'sub-operator', + 'operator|': 'or-operator', + 'operator&': 'and-operator', + 'operator^': 'xor-operator', + 'operator&&': 'sand-operator', + 'operator||': 'sor-operator', + 'operator==': 'eq-operator', + 'operator!=': 'neq-operator', + 'operator<<': 'lshift-operator', + 'operator>>': 'rshift-operator', + 'operator-=': 'sub-assign-operator', + 'operator+=': 'add-assign-operator', + 'operator*-': 'mul-assign-operator', + 'operator/=': 'div-assign-operator', + 'operator%=': 'mod-assign-operator', + 'operator&=': 'and-assign-operator', + 'operator|=': 'or-assign-operator', + 'operator<<=': 'lshift-assign-operator', + 'operator>>=': 'rshift-assign-operator', + 'operator~': 'inv-operator', + 'operator++': 'inc-operator', + 'operator--': 'dec-operator' +} + class DefinitionError(Exception): pass @@ -44,6 +99,9 @@ class DefExpr(object): def __unicode__(self): raise NotImplementedError() + def to_id(self): + return u'' + def split_owner(self): return None, self @@ -59,6 +117,12 @@ class NameDefExpr(DefExpr): def __init__(self, name): self.name = name + def to_id(self): + name = _id_shortwords.get(self.name) + if name is not None: + return name + return self.name.replace(u' ', u'-') + def __unicode__(self): return unicode(self.name) @@ -68,6 +132,10 @@ class PathDefExpr(DefExpr): def __init__(self, parts): self.path = parts + def to_id(self): + rv = u'::'.join(x.to_id() for x in self.path) + return _id_shortwords.get(rv, rv) + def split_owner(self): if len(self.path) > 1: return PathDefExpr(self.path[:-1]), self.path[-1] @@ -83,6 +151,12 @@ class ModifierDefExpr(DefExpr): self.modifiers = modifiers self.typename = typename + def to_id(self): + pieces = [_id_shortwords.get(unicode(x), unicode(x)) + for x in self.modifiers] + pieces.append(self.typename.to_id()) + return u'-'.join(pieces) + def __unicode__(self): return u' '.join(map(unicode, list(self.modifiers) + [self.typename])) @@ -92,6 +166,9 @@ class PtrDefExpr(DefExpr): def __init__(self, typename): self.typename = typename + def to_id(self): + return self.typename.to_id() + u'P' + def __unicode__(self): return u'%s*' % self.typename @@ -101,15 +178,34 @@ class RefDefExpr(DefExpr): def __init__(self, typename): self.typename = typename + def to_id(self): + return self.typename.to_id() + u'R' + def __unicode__(self): return u'%s&' % self.typename +class ConstDefExpr(DefExpr): + + def __init__(self, typename, prefix=False): + self.typename = typename + self.prefix = prefix + + def to_id(self): + return self.typename.to_id() + u'C' + + def __unicode__(self): + return (self.prefix and u'const %s' or u'%s const') % self.typename + + class CastOpDefExpr(DefExpr): def __init__(self, typename): self.typename = typename + def to_id(self): + return u'to-%s-operator' % self.typename.to_id() + def __unicode__(self): return u'operator %s' % self.typename @@ -120,6 +216,10 @@ class TemplateDefExpr(DefExpr): self.typename = typename self.args = args + def to_id(self): + return u'%s:%s:' % (self.typename.to_id(), + u'.'.join(x.to_id() for x in self.args)) + def __unicode__(self): return u'%s<%s>' % (self.typename, u', '.join(map(unicode, self.args))) @@ -131,6 +231,9 @@ class ArgumentDefExpr(DefExpr): self.name = name self.default = default + def to_id(self): + return self.type.to_id() + def __unicode__(self): return (self.type is not None and u'%s %s' % (self.type, self.name) or unicode(self.name)) + (self.default is not None and @@ -143,11 +246,14 @@ class TypedObjDefExpr(DefExpr): self.typename = typename self.name = name + def to_id(self): + return u'%s__%s' % (self.name.to_id(), self.typename.to_id()) + def __unicode__(self): - return u'%s %s' % (self.typename, name) + return u'%s %s' % (self.typename, self.name) -class FunctionDefExpr(DefExpr): +class FuncDefExpr(DefExpr): def __init__(self, name, rv, signature, const, pure_virtual): self.name = name @@ -156,9 +262,17 @@ class FunctionDefExpr(DefExpr): self.const = const self.pure_virtual = pure_virtual + def to_id(self): + return u'%s%s%s' % ( + self.name.to_id(), + self.signature and u'__' + + u'.'.join(x.to_id() for x in self.signature) or u'', + self.const and u'C' or u'' + ) + def __unicode__(self): return u'%s%s(%s)%s%s' % ( - self.rv is not None and self.rv + u' ' or u'', + self.rv is not None and unicode(self.rv) + u' ' or u'', self.name, u', '.join(map(unicode, self.signature)), self.const and u' const' or u'', @@ -171,12 +285,12 @@ class DefinitionParser(object): # mapping of valid type modifiers. if the set is None it means # the modifier can prefix all types, otherwise only the types # (actually more keywords) in the set. Also check - # _guess_typename when changing this + # _guess_typename when changing this. _modifiers = { 'volatile': None, 'register': None, - 'const': None, 'mutable': None, + 'const': None, 'typename': None, 'unsigned': set(('char', 'int', 'long')), 'signed': set(('char', 'int', 'long')), @@ -214,8 +328,11 @@ class DefinitionParser(object): return True return False + def skip_word(self, word): + return self.match(re.compile(r'\b%s\b' % re.escape(word))) + def skip_ws(self): - return self.match(_whitespace_re) is not None + return self.match(_whitespace_re) @property def eof(self): @@ -289,13 +406,26 @@ class DefinitionParser(object): return path[:-1], path[-1] return path, 'int' - def _attach_refptr(self, expr): - self.skip_ws() - if self.skip_string('*'): - return PtrDefExpr(expr) - elif self.skip_string('&'): - return RefDefExpr(expr) - return expr + def _attach_crefptr(self, expr, is_const=False): + if is_const: + expr = ConstDefExpr(expr, prefix=True) + while 1: + self.skip_ws() + if self.skip_word('const'): + expr = ConstDefExpr(expr) + elif self.skip_string('*'): + expr = PtrDefExpr(expr) + elif self.skip_string('&'): + expr = RefDefExpr(expr) + else: + return expr + + def _peek_const(self, path): + try: + path.remove('const') + return True + except ValueError: + return False def _parse_builtin(self, modifier): path = [modifier] @@ -312,9 +442,11 @@ class DefinitionParser(object): else: self.backout() break + + is_const = self._peek_const(path) modifiers, typename = self._guess_typename(path) rv = ModifierDefExpr(modifiers, _NameDefExpr(typename)) - return self._attach_refptr(rv) + return self._attach_crefptr(rv, is_const) def _parse_type(self, in_template=False): result = [] @@ -325,7 +457,7 @@ class DefinitionParser(object): # don't have to check for type modifiers if not self.skip_string('::'): self.skip_ws() - if self.match(_identifier_re): + while self.match(_identifier_re): modifier = self.matched_text if modifier in self._modifiers: following = self._modifiers[modifier] @@ -339,6 +471,7 @@ class DefinitionParser(object): modifiers.append(modifier) else: self.backout() + break while 1: self.skip_ws() @@ -354,9 +487,10 @@ class DefinitionParser(object): rv = result[0] else: rv = PathDefExpr(result) + is_const = self._peek_const(modifiers) if modifiers: rv = ModifierDefExpr(modifiers, rv) - return self._attach_refptr(rv) + return self._attach_crefptr(rv, is_const) def _parse_default_expr(self): self.skip_ws() @@ -408,14 +542,14 @@ class DefinitionParser(object): args.append(ArgumentDefExpr(argtype, argname, default)) self.skip_ws() - const = self.skip_string('const') + const = self.skip_word('const') if const: self.skip_ws() if self.skip_string('='): self.skip_ws() if not (self.skip_string('0') or \ - self.skip_string('NULL') or \ - self.skip_string('nullptr')): + self.skip_word('NULL') or \ + self.skip_word('nullptr')): self.fail('pure virtual functions must be defined with ' 'either 0, NULL or nullptr, other macros are ' 'not allowed') @@ -440,7 +574,7 @@ class DefinitionParser(object): rv = None else: name = self._parse_type() - return FunctionDefExpr(name, rv, *self._parse_signature()) + return FuncDefExpr(name, rv, *self._parse_signature()) def parse_typename(self): return self._parse_type() @@ -472,26 +606,21 @@ class CPPObject(ObjectDescription): pnode += nodes.Text(text) node += pnode - def make_id(self, name, sig): - return name - - def add_target_and_index(self, name, sig, signode): - if name not in self.state.document.ids: - # XXX: how to handle method overloading? - theid = self.make_id(name, sig) - signode['names'].append(theid) - signode['ids'].append(theid) - signode['first'] = (not self.names) - self.state.document.note_explicit_target(signode) - self.env.domaindata['cpp']['objects'][name] = \ - (self.env.docname, self.objtype) + def add_target_and_index(self, (name, sigobj), sig, signode): + theid = sigobj.to_id() + signode['names'].append(theid) + signode['ids'].append(theid) + signode['first'] = (not self.names) + self.state.document.note_explicit_target(signode) + self.env.domaindata['cpp']['objects'][name] = \ + (self.env.docname, self.objtype) indextext = self.get_index_text(name) if indextext: self.indexnode['entries'].append(('single', indextext, name, name)) def before_content(self): - lastname = self.names and self.names[-1] + lastname = self.names and self.names[-1][0] if lastname and not self.env.temp_data.get('cpp:parent'): self.env.temp_data['cpp:parent'] = lastname self.parentname_set = True @@ -516,8 +645,8 @@ class CPPObject(ObjectDescription): parentname = self.env.temp_data.get('cpp:parent') if parentname: - return u'%s::%s' % (parentname, name) - return unicode(name) + return u'%s::%s' % (parentname, name), rv + return unicode(name), rv class CPPClassObject(CPPObject): @@ -589,18 +718,6 @@ class CPPFunctionObject(CPPTypedObject): if func.pure_virtual: node += addnodes.desc_addname(' = 0', ' = 0') - def make_id(self, name, sig): - # XXX: can we reuse somehow the parsed definition here? ideally - # what we could do would be checking if a function is overloaded - # after we found everything and if it is, go over all parsed - # signatures and find a short code. - # - # eg: - # ns::foo(int a, int b) --> ns::foo__i.i - # ns::foo(char a, std::string b) --> ns::foo__c.std::string - # ns::foo(char a, std::string b, int c) --> ns::foo__c.std::string.i - return name - def get_index_text(self, name): return _('%s (C++ function)') % name -- cgit v1.2.1 From facfe81728130a5c54c677b82683ae57a97a729d Mon Sep 17 00:00:00 2001 From: mitsuhiko Date: Mon, 1 Mar 2010 13:37:44 +0100 Subject: Added type support --- sphinx/domains/cpp.py | 82 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 70 insertions(+), 12 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index dbc24efb..e7211f6e 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -240,19 +240,40 @@ class ArgumentDefExpr(DefExpr): u'=%s' % self.default or u'') -class TypedObjDefExpr(DefExpr): +class TypeObjDefExpr(DefExpr): def __init__(self, typename, name): self.typename = typename self.name = name def to_id(self): + if self.typename is None: + return self.name.to_id() return u'%s__%s' % (self.name.to_id(), self.typename.to_id()) def __unicode__(self): + if self.typename is None: + return unicode(self.name) return u'%s %s' % (self.typename, self.name) +class MemberObjDefExpr(DefExpr): + + def __init__(self, typename, name, value): + self.typename = typename + self.name = name + self.value = value + + def to_id(self): + return u'%s__%s' % (self.name.to_id(), self.typename.to_id()) + + def __unicode__(self): + rv = u'%s %s' % (self.typename, self.name) + if value is not None: + rv = u'%s = %s' % (rv, self.value) + return rv + + class FuncDefExpr(DefExpr): def __init__(self, name, rv, signature, const, pure_virtual): @@ -558,12 +579,25 @@ class DefinitionParser(object): pure_virtual = False return args, const, pure_virtual - def parse_typed_object(self): + def parse_type_object(self): + typename = self._parse_type() + self.skip_ws() + if not self.eof: + name = self._parse_type() + else: + name = typename + typename = None + return TypeObjDefExpr(typename, name) + + def parse_member_object(self): typename = self._parse_type() name = self._parse_type() - # XXX: for constants it would be useful to be able to parse - # an assigned value here as well. - return TypedObjDefExpr(typename, name) + self.skip_ws() + if self.skip_string('='): + value = self.read_rest().strip() + else: + value = None + return MemberObjDefExpr(typename, name, value) def parse_function(self): rv = self._parse_type() @@ -579,6 +613,11 @@ class DefinitionParser(object): def parse_typename(self): return self._parse_type() + def read_rest(self): + rv = self.definition[self.pos:] + self.pos = self.end + return rv + def assert_end(self): self.skip_ws() if not self.eof: @@ -663,26 +702,45 @@ class CPPClassObject(CPPObject): return typename -class CPPTypedObject(CPPObject): +class CPPTypeObject(CPPObject): + + def get_index_text(self, name): + if self.objtype == 'type': + return _('%s (C++ type)') % name + return '' + + def parse_definition(self, parser): + return parser.parse_type_object() + + def describe_signature(self, signode, obj): + signode += addnodes.desc_annotation('type ', 'type ') + if obj.typename is not None: + self.attach_type(signode, obj.typename) + signode += nodes.Text(' ') + self.attach_name(signode, obj.name) + return obj.name + + +class CPPMemberObject(CPPObject): def get_index_text(self, name): if self.objtype == 'member': return _('%s (C++ member)') % name - elif self.objtype == 'type': - return _('%s (C++ type)') % name return '' def parse_definition(self, parser): - return parser.parse_typed_object() + return parser.parse_member_object() def describe_signature(self, signode, obj): self.attach_type(signode, obj.typename) signode += nodes.Text(' ') self.attach_name(signode, obj.name) + if obj.value is not None: + signode += nodes.Text(u' = ' + obj.value) return obj.name -class CPPFunctionObject(CPPTypedObject): +class CPPFunctionObject(CPPObject): def attach_function(self, node, func): owner, name = func.name.split_owner() @@ -749,8 +807,8 @@ class CPPDomain(Domain): directives = { 'class': CPPClassObject, 'function': CPPFunctionObject, - 'member': CPPTypedObject, - 'type': CPPTypedObject + 'member': CPPMemberObject, + 'type': CPPTypeObject } roles = { 'class': XRefRole(), -- cgit v1.2.1 From 58edbde2dc44e339a309991e7098f0051484db9f Mon Sep 17 00:00:00 2001 From: mitsuhiko Date: Mon, 1 Mar 2010 14:14:14 +0100 Subject: Refactored again. This time added a new type PrefixedNameDefExpr that is used to prefix a name. This is used to give a node a full name if it was defined in the context of a parent (eg, function in a class). --- sphinx/domains/cpp.py | 138 +++++++++++++++++++++++++++++++------------------- 1 file changed, 86 insertions(+), 52 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index e7211f6e..dfcab465 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -11,6 +11,7 @@ import re import string +from copy import deepcopy from docutils import nodes @@ -99,7 +100,10 @@ class DefExpr(object): def __unicode__(self): raise NotImplementedError() - def to_id(self): + def clone(self): + return deepcopy(self) + + def get_id(self): return u'' def split_owner(self): @@ -117,7 +121,7 @@ class NameDefExpr(DefExpr): def __init__(self, name): self.name = name - def to_id(self): + def get_id(self): name = _id_shortwords.get(self.name) if name is not None: return name @@ -132,8 +136,8 @@ class PathDefExpr(DefExpr): def __init__(self, parts): self.path = parts - def to_id(self): - rv = u'::'.join(x.to_id() for x in self.path) + def get_id(self): + rv = u'::'.join(x.get_id() for x in self.path) return _id_shortwords.get(rv, rv) def split_owner(self): @@ -151,10 +155,10 @@ class ModifierDefExpr(DefExpr): self.modifiers = modifiers self.typename = typename - def to_id(self): + def get_id(self): pieces = [_id_shortwords.get(unicode(x), unicode(x)) for x in self.modifiers] - pieces.append(self.typename.to_id()) + pieces.append(self.typename.get_id()) return u'-'.join(pieces) def __unicode__(self): @@ -166,8 +170,8 @@ class PtrDefExpr(DefExpr): def __init__(self, typename): self.typename = typename - def to_id(self): - return self.typename.to_id() + u'P' + def get_id(self): + return self.typename.get_id() + u'P' def __unicode__(self): return u'%s*' % self.typename @@ -178,8 +182,8 @@ class RefDefExpr(DefExpr): def __init__(self, typename): self.typename = typename - def to_id(self): - return self.typename.to_id() + u'R' + def get_id(self): + return self.typename.get_id() + u'R' def __unicode__(self): return u'%s&' % self.typename @@ -191,8 +195,8 @@ class ConstDefExpr(DefExpr): self.typename = typename self.prefix = prefix - def to_id(self): - return self.typename.to_id() + u'C' + def get_id(self): + return self.typename.get_id() + u'C' def __unicode__(self): return (self.prefix and u'const %s' or u'%s const') % self.typename @@ -203,8 +207,8 @@ class CastOpDefExpr(DefExpr): def __init__(self, typename): self.typename = typename - def to_id(self): - return u'to-%s-operator' % self.typename.to_id() + def get_id(self): + return u'castto-%s-operator' % self.typename.get_id() def __unicode__(self): return u'operator %s' % self.typename @@ -216,9 +220,9 @@ class TemplateDefExpr(DefExpr): self.typename = typename self.args = args - def to_id(self): - return u'%s:%s:' % (self.typename.to_id(), - u'.'.join(x.to_id() for x in self.args)) + def get_id(self): + return u'%s:%s:' % (self.typename.get_id(), + u'.'.join(x.get_id() for x in self.args)) def __unicode__(self): return u'%s<%s>' % (self.typename, u', '.join(map(unicode, self.args))) @@ -227,12 +231,12 @@ class TemplateDefExpr(DefExpr): class ArgumentDefExpr(DefExpr): def __init__(self, type, name, default=None): - self.type = type self.name = name + self.type = type self.default = default - def to_id(self): - return self.type.to_id() + def get_id(self): + return self.type.get_id() def __unicode__(self): return (self.type is not None and u'%s %s' % (self.type, self.name) @@ -240,16 +244,35 @@ class ArgumentDefExpr(DefExpr): u'=%s' % self.default or u'') -class TypeObjDefExpr(DefExpr): +class PrefixedNameDefExpr(DefExpr): + + def __init__(self, prefix, name): + self.prefix = prefix + self.name = name + + def get_id(self): + return u'%s::%s' % (self.prefix.get_id(), self.name.get_id()) + + def __unicode__(self): + return u'%s::%s' % (self.prefix, self.name) + + +class NamedDefExpr(DefExpr): + + def __init__(self, name): + self.name = name + + +class TypeObjDefExpr(NamedDefExpr): def __init__(self, typename, name): + NamedDefExpr.__init__(self, name) self.typename = typename - self.name = name - def to_id(self): + def get_id(self): if self.typename is None: - return self.name.to_id() - return u'%s__%s' % (self.name.to_id(), self.typename.to_id()) + return self.name.get_id() + return u'%s__%s' % (self.name.get_id(), self.typename.get_id()) def __unicode__(self): if self.typename is None: @@ -257,37 +280,37 @@ class TypeObjDefExpr(DefExpr): return u'%s %s' % (self.typename, self.name) -class MemberObjDefExpr(DefExpr): +class MemberObjDefExpr(NamedDefExpr): def __init__(self, typename, name, value): + NamedDefExpr.__init__(self, name) self.typename = typename - self.name = name self.value = value - def to_id(self): - return u'%s__%s' % (self.name.to_id(), self.typename.to_id()) + def get_id(self): + return u'%s__%s' % (self.name.get_id(), self.typename.get_id()) def __unicode__(self): rv = u'%s %s' % (self.typename, self.name) - if value is not None: + if self.value is not None: rv = u'%s = %s' % (rv, self.value) return rv -class FuncDefExpr(DefExpr): +class FuncDefExpr(NamedDefExpr): def __init__(self, name, rv, signature, const, pure_virtual): - self.name = name + NamedDefExpr.__init__(self, name) self.rv = rv self.signature = signature self.const = const self.pure_virtual = pure_virtual - def to_id(self): + def get_id(self): return u'%s%s%s' % ( - self.name.to_id(), + self.name.get_id(), self.signature and u'__' + - u'.'.join(x.to_id() for x in self.signature) or u'', + u'.'.join(x.get_id() for x in self.signature) or u'', self.const and u'C' or u'' ) @@ -301,6 +324,18 @@ class FuncDefExpr(DefExpr): ) +class ClassDefExpr(NamedDefExpr): + + def __init__(self, name): + NamedDefExpr.__init__(self, name) + + def get_id(self): + return self.name.get_id() + + def __unicode__(self): + return unicode(self.name) + + class DefinitionParser(object): # mapping of valid type modifiers. if the set is None it means @@ -610,8 +645,8 @@ class DefinitionParser(object): name = self._parse_type() return FuncDefExpr(name, rv, *self._parse_signature()) - def parse_typename(self): - return self._parse_type() + def parse_class(self): + return ClassDefExpr(self._parse_type()) def read_rest(self): rv = self.definition[self.pos:] @@ -645,8 +680,9 @@ class CPPObject(ObjectDescription): pnode += nodes.Text(text) node += pnode - def add_target_and_index(self, (name, sigobj), sig, signode): - theid = sigobj.to_id() + def add_target_and_index(self, sigobj, sig, signode): + theid = sigobj.get_id() + name = unicode(sigobj.name) signode['names'].append(theid) signode['ids'].append(theid) signode['first'] = (not self.names) @@ -659,9 +695,10 @@ class CPPObject(ObjectDescription): self.indexnode['entries'].append(('single', indextext, name, name)) def before_content(self): - lastname = self.names and self.names[-1][0] + lastname = self.names and self.names[-1] if lastname and not self.env.temp_data.get('cpp:parent'): - self.env.temp_data['cpp:parent'] = lastname + assert isinstance(lastname, NamedDefExpr) + self.env.temp_data['cpp:parent'] = lastname.name self.parentname_set = True else: self.parentname_set = False @@ -680,12 +717,13 @@ class CPPObject(ObjectDescription): parser = DefinitionParser(sig) rv = self.parse_definition(parser) parser.assert_end() - name = self.describe_signature(signode, rv) + self.describe_signature(signode, rv) parentname = self.env.temp_data.get('cpp:parent') - if parentname: - return u'%s::%s' % (parentname, name), rv - return unicode(name), rv + if parentname is not None: + rv = rv.clone() + rv.name = PrefixedNameDefExpr(parentname, rv.name) + return rv class CPPClassObject(CPPObject): @@ -694,12 +732,11 @@ class CPPClassObject(CPPObject): return _('%s (C++ class)') % name def parse_definition(self, parser): - return parser.parse_typename() + return parser.parse_class() - def describe_signature(self, signode, typename): + def describe_signature(self, signode, cls): signode += addnodes.desc_annotation('class ', 'class ') - self.attach_name(signode, typename) - return typename + self.attach_name(signode, cls.name) class CPPTypeObject(CPPObject): @@ -718,7 +755,6 @@ class CPPTypeObject(CPPObject): self.attach_type(signode, obj.typename) signode += nodes.Text(' ') self.attach_name(signode, obj.name) - return obj.name class CPPMemberObject(CPPObject): @@ -737,7 +773,6 @@ class CPPMemberObject(CPPObject): self.attach_name(signode, obj.name) if obj.value is not None: signode += nodes.Text(u' = ' + obj.value) - return obj.name class CPPFunctionObject(CPPObject): @@ -790,7 +825,6 @@ class CPPFunctionObject(CPPObject): self.attach_type(signode, func.rv) signode += nodes.Text(u' ') self.attach_function(signode, func) - return func.name class CPPDomain(Domain): -- cgit v1.2.1 From 21484180255bef33f7c3d15caa8592b8345087c4 Mon Sep 17 00:00:00 2001 From: mitsuhiko Date: Mon, 1 Mar 2010 14:30:24 +0100 Subject: Started working on support for c++ xrefs --- sphinx/domains/cpp.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index dfcab465..e2c652b9 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -614,6 +614,9 @@ class DefinitionParser(object): pure_virtual = False return args, const, pure_virtual + def parse_type(self): + return self._parse_type() + def parse_type_object(self): typename = self._parse_type() self.skip_ws() @@ -861,8 +864,12 @@ class CPPDomain(Domain): def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): - # strip pointer and reference info - target = target.rstrip(' *&') + parser = DefinitionParser(target) + expr = parser.parse_type() + parser.skip_ws() + if not parser.eof: + return None + target = unicode(expr) if target not in self.data['objects']: return None obj = self.data['objects'][target] -- cgit v1.2.1 From 63c040fcc26d9693a627e88d894562c26630ffa8 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 1 Mar 2010 14:36:08 +0100 Subject: Allow escaping "<" in x-ref roles by a backslash. --- sphinx/roles.py | 4 +++- sphinx/util/nodes.py | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/sphinx/roles.py b/sphinx/roles.py index d20898ff..50c76df1 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -105,9 +105,9 @@ class XRefRole(object): else: domain, role = typ.split(':', 1) classes = ['xref', domain, '%s-%s' % (domain, role)] - text = utils.unescape(text) # if the first character is a bang, don't cross-reference at all if text[0:1] == '!': + text = utils.unescape(text) if self.fix_parens: text, tgt = self._fix_parens(env, False, text[1:], "") innernode = self.innernodeclass(rawtext, text, classes=classes) @@ -115,6 +115,8 @@ class XRefRole(object): is_ref=False) # split title and target in role content has_explicit_title, title, target = split_explicit_title(text) + title = utils.unescape(title) + text = utils.unescape(text) # fix-up title and target if self.lowercase: target = target.lower() diff --git a/sphinx/util/nodes.py b/sphinx/util/nodes.py index 13fa3c10..04185436 100644 --- a/sphinx/util/nodes.py +++ b/sphinx/util/nodes.py @@ -17,7 +17,8 @@ from docutils import nodes from sphinx import addnodes -explicit_title_re = re.compile('^(.+?)\s*<(.*?)>$', re.DOTALL) +# \x00 means the "<" was backslash-escaped +explicit_title_re = re.compile(r'^(.+?)\s*(?$', re.DOTALL) caption_ref_re = explicit_title_re # b/w compat alias -- cgit v1.2.1 From b46f03df5da1b6cb6fb2e585cb5118d937bcbde2 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 1 Mar 2010 14:37:31 +0100 Subject: Review markup chapter. --- doc/markup/misc.rst | 10 ++++----- doc/markup/para.rst | 58 ++++++++++++++++++++++++++++------------------------- 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/doc/markup/misc.rst b/doc/markup/misc.rst index b0e78dc2..fb6e3e3b 100644 --- a/doc/markup/misc.rst +++ b/doc/markup/misc.rst @@ -11,12 +11,12 @@ File-wide metadata reST has the concept of "field lists"; these are a sequence of fields marked up like this:: - :Field name: Field content + :fieldname: Field content -A field list at the very top of a file is parsed as the "docinfo", which in -normal documents can be used to record the author, date of publication and -other metadata. In Sphinx, the docinfo is used as metadata, too, but not -displayed in the output. +A field list at the very top of a file is parsed by docutils as the "docinfo", +which is normally used to record the author, date of publication and other +metadata. *In Sphinx*, the docinfo is used as metadata, too, but not displayed +in the output. At the moment, these metadata fields are recognized: diff --git a/doc/markup/para.rst b/doc/markup/para.rst index 774b0f51..371c587b 100644 --- a/doc/markup/para.rst +++ b/doc/markup/para.rst @@ -9,7 +9,7 @@ Paragraph-level markup These directives create short paragraphs and can be used inside information units as well as normal text: -.. directive:: note +.. directive:: .. note:: An especially important bit of information about an API that a user should be aware of when using whatever bit of API the note pertains to. The content of @@ -22,13 +22,13 @@ units as well as normal text: This function is not suitable for sending spam e-mails. -.. directive:: warning +.. directive:: .. warning:: An important bit of information about an API that a user should be very aware of when using whatever bit of API the warning pertains to. The content of the directive should be written in complete sentences and include all - appropriate punctuation. This differs from ``note`` in that it is recommended - over ``note`` for information regarding security. + appropriate punctuation. This differs from :dir:`note` in that it is + recommended over :dir:`note` for information regarding security. .. directive:: .. versionadded:: version @@ -49,7 +49,7 @@ units as well as normal text: .. directive:: .. versionchanged:: version - Similar to ``versionadded``, but describes when and what changed in the named + Similar to :dir:`versionadded`, but describes when and what changed in the named feature in some way (new parameters, changed side effects, etc.). -------------- @@ -57,26 +57,27 @@ units as well as normal text: .. directive:: seealso Many sections include a list of references to module documentation or - external documents. These lists are created using the ``seealso`` directive. + external documents. These lists are created using the :dir:`seealso` + directive. - The ``seealso`` directive is typically placed in a section just before any + The :dir:`seealso` directive is typically placed in a section just before any sub-sections. For the HTML output, it is shown boxed off from the main flow of the text. - The content of the ``seealso`` directive should be a reST definition list. + The content of the :dir:`seealso` directive should be a reST definition list. Example:: .. seealso:: - Module :mod:`zipfile` - Documentation of the :mod:`zipfile` standard module. + Module :py:mod:`zipfile` + Documentation of the :py:mod:`zipfile` standard module. `GNU tar manual, Basic Tar Format `_ Documentation for tar archive files, including GNU tar extensions. There's also a "short form" allowed that looks like this:: - .. seealso:: modules :mod:`zipfile`, :mod:`tarfile` + .. seealso:: modules :py:mod:`zipfile`, :py:mod:`tarfile` .. versionadded:: 0.5 The short form. @@ -96,7 +97,8 @@ units as well as normal text: .. directive:: centered - This directive creates a centered boldfaced line of text. Use it as follows:: + This directive creates a centered boldfaced line of text. Use it as + follows:: .. centered:: LICENSE AGREEMENT @@ -126,16 +128,17 @@ Table-of-contents markup ------------------------ The :dir:`toctree` directive, which generates tables of contents of -subdocuments, is described in "Sphinx concepts". +subdocuments, is described in :ref:`toctree-directive`. -For local tables of contents, use the standard reST :dir:`contents` directive. +For local tables of contents, use the standard reST :rstdir:`contents directive +`. Index-generating markup ----------------------- -Sphinx automatically creates index entries from all information units (like -functions, classes or attributes) like discussed before. +Sphinx automatically creates index entries from all object descriptions (like +functions, classes or attributes) like discussed in :ref:`domains`. However, there is also an explicit directive available, to make the index more comprehensive and enable index entries in documents where information is not @@ -159,9 +162,9 @@ mainly contained in information units, such as the language reference. ... - This directive contains five entries, which will be converted to entries in the - generated index which link to the exact location of the index statement (or, in - case of offline media, the corresponding page number). + This directive contains five entries, which will be converted to entries in + the generated index which link to the exact location of the index statement + (or, in case of offline media, the corresponding page number). Since index directives generate cross-reference targets at their location in the source, it makes sense to put them *before* the thing they refer to -- @@ -171,18 +174,19 @@ mainly contained in information units, such as the language reference. single Creates a single index entry. Can be made a subentry by separating the - subentry text with a semicolon (this notation is also used below to describe - what entries are created). + subentry text with a semicolon (this notation is also used below to + describe what entries are created). pair ``pair: loop; statement`` is a shortcut that creates two index entries, namely ``loop; statement`` and ``statement; loop``. triple - Likewise, ``triple: module; search; path`` is a shortcut that creates three - index entries, which are ``module; search path``, ``search; path, module`` and - ``path; module search``. + Likewise, ``triple: module; search; path`` is a shortcut that creates + three index entries, which are ``module; search path``, ``search; path, + module`` and ``path; module search``. module, keyword, operator, object, exception, statement, builtin - These all create two index entries. For example, ``module: hashlib`` creates - the entries ``module; hashlib`` and ``hashlib; module``. + These all create two index entries. For example, ``module: hashlib`` + creates the entries ``module; hashlib`` and ``hashlib; module``. (These + are Python-specific and therefore deprecated.) For index directives containing only "single" entries, there is a shorthand notation:: @@ -195,7 +199,7 @@ mainly contained in information units, such as the language reference. Glossary -------- -.. directive:: glossary +.. directive:: .. glossary:: This directive must contain a reST definition list with terms and definitions. The definitions will then be referencable with the :role:`term` -- cgit v1.2.1 From 0e7872ad7b511739afe72a8b991739811bd728dc Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 1 Mar 2010 14:53:30 +0100 Subject: Move inline markup description to domains page. --- doc/domains.rst | 110 +++++++++++++++++++++++++++++ doc/intro.rst | 4 +- doc/markup/inline.rst | 187 +++++++++++++------------------------------------- doc/markup/misc.rst | 8 +-- doc/markup/para.rst | 10 +-- doc/rest.rst | 2 + 6 files changed, 172 insertions(+), 149 deletions(-) diff --git a/doc/domains.rst b/doc/domains.rst index 50854746..de114c35 100644 --- a/doc/domains.rst +++ b/doc/domains.rst @@ -90,6 +90,24 @@ giving the domain name, i.e. :: Reference to :func:`pyfunc`. +Cross-referencing syntax +~~~~~~~~~~~~~~~~~~~~~~~~ + +For cross-reference roles provided by domains, the same facilities exist as for +general cross-references. See :ref:`xref-syntax`. + +In short: + +* You may supply an explicit title and reference target: ``:role:`title + ``` will refer to *target*, but the link text will be *title*. + +* If you prefix the content with ``!``, no reference/hyperlink will be created. + +* If you prefix the content with ``~``, the link text will only be the last + component of the target. For example, ``:py:meth:`~Queue.Queue.get``` will + refer to ``Queue.Queue.get`` but only display ``get`` as the link text. + + The Python Domain ----------------- @@ -280,6 +298,75 @@ This will render like this: :rtype: list of strings +Cross-referencing Python objects +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following roles refer to objects in modules and are possibly hyperlinked if +a matching identifier is found: + +.. role:: py:mod + + Reference a module; a dotted name may be used. This should also be used for + package names. + +.. role:: py:func + + Reference a Python function; dotted names may be used. The role text needs + not include trailing parentheses to enhance readability; they will be added + automatically by Sphinx if the :confval:`add_function_parentheses` config + value is true (the default). + +.. role:: py:data + + Reference a module-level variable. + +.. role:: py:const + + Reference a "defined" constant. This may be a C-language ``#define`` or a + Python variable that is not intended to be changed. + +.. role:: py:class + + Reference a class; a dotted name may be used. + +.. role:: py:meth + + Reference a method of an object. The role text can include the type name and + the method name; if it occurs within the description of a type, the type name + can be omitted. A dotted name may be used. + +.. role:: py:attr + + Reference a data attribute of an object. + +.. role:: py:exc + + Reference an exception. A dotted name may be used. + +.. role:: py:obj + + Reference an object of unspecified type. Useful e.g. as the + :confval:`default_role`. + + .. versionadded:: 0.4 + +The name enclosed in this markup can include a module name and/or a class name. +For example, ``:py:func:`filter``` could refer to a function named ``filter`` in +the current module, or the built-in function of that name. In contrast, +``:py:func:`foo.filter``` clearly refers to the ``filter`` function in the +``foo`` module. + +Normally, names in these roles are searched first without any further +qualification, then with the current module name prepended, then with the +current module and class name (if any) prepended. If you prefix the name with a +dot, this order is reversed. For example, in the documentation of Python's +:mod:`codecs` module, ``:py:func:`open``` always refers to the built-in +function, while ``:py:func:`.open``` refers to :func:`codecs.open`. + +A similar heuristic is used to determine whether the name is an attribute of the +currently documented class. + + The C Domain ------------ @@ -328,6 +415,29 @@ The C domain (name **c**) is suited for documentation of C API. .. c:var:: PyObject* PyClass_Type +Cross-referencing C constructs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The following roles create cross-references to C-language constructs if they are +defined in the documentation: + +.. role:: c:data + + Reference a C-language variable. + +.. role:: c:func + + Reference a C-language function. Should include trailing parentheses. + +.. role:: c:macro + + Reference a "simple" C macro, as defined above. + +.. role:: c:type + + Reference a C-language type. + + The Standard Domain ------------------- diff --git a/doc/intro.rst b/doc/intro.rst index 585d04f0..600493c9 100644 --- a/doc/intro.rst +++ b/doc/intro.rst @@ -56,5 +56,5 @@ or some (not broken) SVN trunk snapshot. Usage ----- -See :doc:`the tutorial ` for an introduction. It also contains links -to more advanced sections in this manual for the topics it discusses. +See :doc:`tutorial` for an introduction. It also contains links to more +advanced sections in this manual for the topics it discusses. diff --git a/doc/markup/inline.rst b/doc/markup/inline.rst index 9b4b26e3..8c0aba3a 100644 --- a/doc/markup/inline.rst +++ b/doc/markup/inline.rst @@ -12,11 +12,13 @@ They are written as ``:rolename:`content```. free to use it for anything you like, e.g. variable names; use the :confval:`default_role` config value to set it to a known role. +See :ref:`domains` for roles added by domains. + .. _xref-syntax: Cross-referencing syntax ------------------------- +~~~~~~~~~~~~~~~~~~~~~~~~ Cross-references are generated by many semantic interpreted text roles. Basically, you only need to write ``:role:`target```, and a link will be created @@ -32,149 +34,14 @@ more versatile: * If you prefix the content with ``!``, no reference/hyperlink will be created. -* For the Python object roles, if you prefix the content with ``~``, the link - text will only be the last component of the target. For example, - ``:meth:`~Queue.Queue.get``` will refer to ``Queue.Queue.get`` but only - display ``get`` as the link text. +* If you prefix the content with ``~``, the link text will only be the last + component of the target. For example, ``:py:meth:`~Queue.Queue.get``` will + refer to ``Queue.Queue.get`` but only display ``get`` as the link text. In HTML output, the link's ``title`` attribute (that is e.g. shown as a tool-tip on mouse-hover) will always be the full target name. -Cross-referencing Python objects --------------------------------- - -The following roles refer to objects in modules and are possibly hyperlinked if -a matching identifier is found: - -.. role:: mod - - The name of a module; a dotted name may be used. This should also be used for - package names. - -.. role:: func - - The name of a Python function; dotted names may be used. The role text - needs not include trailing parentheses to enhance readability; they will be - added automatically by Sphinx if the :confval:`add_function_parentheses` - config value is true (the default). - -.. role:: data - - The name of a module-level variable. - -.. role:: const - - The name of a "defined" constant. This may be a C-language ``#define`` - or a Python variable that is not intended to be changed. - -.. role:: class - - A class name; a dotted name may be used. - -.. role:: meth - - The name of a method of an object. The role text should include the type - name and the method name; if it occurs within the description of a type, - the type name can be omitted. A dotted name may be used. - -.. role:: attr - - The name of a data attribute of an object. - -.. role:: exc - - The name of an exception. A dotted name may be used. - -.. role:: obj - - The name of an object of unspecified type. Useful e.g. as the - :confval:`default_role`. - - .. versionadded:: 0.4 - -The name enclosed in this markup can include a module name and/or a class name. -For example, ``:func:`filter``` could refer to a function named ``filter`` in -the current module, or the built-in function of that name. In contrast, -``:func:`foo.filter``` clearly refers to the ``filter`` function in the ``foo`` -module. - -Normally, names in these roles are searched first without any further -qualification, then with the current module name prepended, then with the -current module and class name (if any) prepended. If you prefix the name with a -dot, this order is reversed. For example, in the documentation of Python's -:mod:`codecs` module, ``:func:`open``` always refers to the built-in function, -while ``:func:`.open``` refers to :func:`codecs.open`. - -A similar heuristic is used to determine whether the name is an attribute of -the currently documented class. - - -Cross-referencing C constructs ------------------------------- - -The following roles create cross-references to C-language constructs if they -are defined in the documentation: - -.. role:: cdata - - The name of a C-language variable. - -.. role:: cfunc - - The name of a C-language function. Should include trailing parentheses. - -.. role:: cmacro - - The name of a "simple" C macro, as defined above. - -.. role:: ctype - - The name of a C-language type. - - -Cross-referencing other items of interest ------------------------------------------ - -The following roles do possibly create a cross-reference, but do not refer to -objects: - -.. role:: envvar - - An environment variable. Index entries are generated. Also generates a link - to the matching :dir:`envvar` directive, if it exists. - -.. role:: token - - The name of a grammar token (used to create links between - :dir:`productionlist` directives). - -.. role:: keyword - - The name of a keyword in Python. This creates a link to a reference label - with that name, if it exists. - -.. role:: option - - A command-line option to an executable program. The leading hyphen(s) must - be included. This generates a link to a :dir:`cmdoption` directive, if it - exists. - - -The following role creates a cross-reference to the term in the glossary: - -.. role:: term - - Reference to a term in the glossary. The glossary is created using the - ``glossary`` directive containing a definition list with terms and - definitions. It does not have to be in the same file as the ``term`` markup, - for example the Python docs have one global glossary in the ``glossary.rst`` - file. - - If you use a term that's not explained in a glossary, you'll get a warning - during build. - - .. _ref-role: Cross-referencing arbitrary locations @@ -405,6 +272,48 @@ Note that there are no special roles for including hyperlinks as you can use the standard reST markup for that purpose. +Cross-referencing other items of interest +----------------------------------------- + +The following roles do possibly create a cross-reference, but do not refer to +objects: + +.. role:: envvar + + An environment variable. Index entries are generated. Also generates a link + to the matching :dir:`envvar` directive, if it exists. + +.. role:: token + + The name of a grammar token (used to create links between + :dir:`productionlist` directives). + +.. role:: keyword + + The name of a keyword in Python. This creates a link to a reference label + with that name, if it exists. + +.. role:: option + + A command-line option to an executable program. The leading hyphen(s) must + be included. This generates a link to a :dir:`option` directive, if it + exists. + + +The following role creates a cross-reference to the term in the glossary: + +.. role:: term + + Reference to a term in the glossary. The glossary is created using the + ``glossary`` directive containing a definition list with terms and + definitions. It does not have to be in the same file as the ``term`` markup, + for example the Python docs have one global glossary in the ``glossary.rst`` + file. + + If you use a term that's not explained in a glossary, you'll get a warning + during build. + + .. _default-substitutions: Substitutions diff --git a/doc/markup/misc.rst b/doc/markup/misc.rst index fb6e3e3b..ba045165 100644 --- a/doc/markup/misc.rst +++ b/doc/markup/misc.rst @@ -87,10 +87,10 @@ Including content based on tags Tables ------ -Use standard reStructuredText tables. They work fine in HTML output, however -there are some gotchas when using tables in LaTeX: the column width is hard to -determine correctly automatically. For this reason, the following directive -exists: +Use :ref:`standard reStructuredText tables `. They work fine in +HTML output, however there are some gotchas when using tables in LaTeX: the +column width is hard to determine correctly automatically. For this reason, the +following directive exists: .. directive:: .. tabularcolumns:: column spec diff --git a/doc/markup/para.rst b/doc/markup/para.rst index 371c587b..9abae1dc 100644 --- a/doc/markup/para.rst +++ b/doc/markup/para.rst @@ -231,7 +231,7 @@ derived forms), but provides enough to allow context-free grammars to be displayed in a way that causes uses of a symbol to be rendered as hyperlinks to the definition of the symbol. There is this directive: -.. directive:: productionlist +.. directive:: .. productionlist:: [name] This directive is used to enclose a group of productions. Each production is given on a single line and consists of a name, separated by a colon from the @@ -239,17 +239,19 @@ the definition of the symbol. There is this directive: continuation line must begin with a colon placed at the same column as in the first line. + The argument to :dir:`productionlist` serves to distinguish different sets of + production lists that belong to different grammars. + Blank lines are not allowed within ``productionlist`` directive arguments. The definition can contain token names which are marked as interpreted text (e.g. ``sum ::= `integer` "+" `integer```) -- this generates cross-references - to the productions of these tokens. + to the productions of these tokens. Outside of the production list, you can + reference to token productions using :role:`token`. Note that no further reST parsing is done in the production, so that you don't have to escape ``*`` or ``|`` characters. -.. XXX describe optional first parameter - The following is an example taken from the Python Reference Manual:: .. productionlist:: diff --git a/doc/rest.rst b/doc/rest.rst index 37fc1a4e..040bfe96 100644 --- a/doc/rest.rst +++ b/doc/rest.rst @@ -140,6 +140,8 @@ That way, the second sentence in the above example's first paragraph would be rendered as "The next paragraph is a code sample:". +.. _rst-tables: + Tables ------ -- cgit v1.2.1 From 7fa8fbefd0fdd3c3ceebc5679b03aa754fe571c4 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 1 Mar 2010 14:58:36 +0100 Subject: Fix variable name. --- sphinx/roles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/roles.py b/sphinx/roles.py index 50c76df1..e93e00d6 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -116,7 +116,7 @@ class XRefRole(object): # split title and target in role content has_explicit_title, title, target = split_explicit_title(text) title = utils.unescape(title) - text = utils.unescape(text) + target = utils.unescape(target) # fix-up title and target if self.lowercase: target = target.lower() -- cgit v1.2.1 From 7ed251310101344fd181331719ffe2199dab2ce7 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 1 Mar 2010 15:01:46 +0100 Subject: Mention standard rst roles in inline markup. --- doc/conf.py | 2 ++ doc/markup/inline.rst | 2 ++ doc/rest.rst | 12 ++++++++++++ 3 files changed, 16 insertions(+) diff --git a/doc/conf.py b/doc/conf.py index 03dac676..98dc6565 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -50,6 +50,8 @@ autodoc_member_order = 'groupwise' todo_include_todos = True extlinks = {'rstref': ('http://docutils.sourceforge.net/docs/ref/rst/' 'restructuredtext.html#%s', ''), + 'rstrole': ('http://docutils.sourceforge.net/docs/ref/rst/' + 'roles.html#%s', ''), 'rstdir': ('http://docutils.sourceforge.net/docs/ref/rst/' 'directives.html#%s', '')} diff --git a/doc/markup/inline.rst b/doc/markup/inline.rst index 8c0aba3a..4f98f021 100644 --- a/doc/markup/inline.rst +++ b/doc/markup/inline.rst @@ -1,5 +1,7 @@ .. highlight:: rest +.. _inline-markup: + Inline markup ============= diff --git a/doc/rest.rst b/doc/rest.rst index 040bfe96..146b627d 100644 --- a/doc/rest.rst +++ b/doc/rest.rst @@ -53,6 +53,18 @@ enclosed text should be interpreted in a specific way. Sphinx uses this to provide semantic markup and cross-referencing of identifiers, as described in the appropriate section. The general syntax is ``:rolename:`content```. +Standard reST provides the following roles: + +* :rstrole:`emphasis` -- alternate spelling for ``*emphasis*`` +* :rstrole:`strong` -- alternate spelling for ``**strong**`` +* :rstrole:`literal` -- alternate spelling for ````literal```` +* :rstrole:`subscript` -- subscript text +* :rstrole:`superscript` -- superscript text +* :rstrole:`title-reference` -- for titles of books, periodicals, and other + materials + +See :ref:`inline-markup` for roles added by Sphinx. + Lists and Quote-like blocks --------------------------- -- cgit v1.2.1 From 9ec37efcc6f12db231c3af1b3ce039ee42ef1f75 Mon Sep 17 00:00:00 2001 From: mitsuhiko Date: Mon, 1 Mar 2010 15:00:58 +0100 Subject: Added namespace directive to CPP domain, warn instead of raise exceptions for broken signatures and fixed a name error. --- sphinx/domains/cpp.py | 105 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 76 insertions(+), 29 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index e2c652b9..0be636c3 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -21,6 +21,7 @@ from sphinx.locale import l_, _ from sphinx.domains import Domain, ObjType from sphinx.directives import ObjectDescription from sphinx.util.nodes import make_refnode +from sphinx.util.compat import Directive from sphinx.util.docfields import Field, TypedField @@ -92,7 +93,15 @@ _id_shortwords = { class DefinitionError(Exception): - pass + + def __init__(self, description): + self.description = description + + def __unicode__(self): + return self.description + + def __str__(self): + return unicode(self.encode('utf-8')) class DefExpr(object): @@ -431,24 +440,6 @@ class DefinitionParser(object): return NameDefExpr(identifier) - def _parse_type_expr(self): - typename = self._parse_name() - self.skip_ws() - if not self.skip_string('<'): - return typename - - args = [] - while 1: - self.skip_ws() - if self.skip_string('>'): - break - if args: - if not self.skip_string(','): - self.fail('"," or ">" in template expected') - self.skip_ws() - args.append(self._parse_type(True)) - return TemplateDefExpr(typename, args) - def _guess_typename(self, path): if not path: return [], 'int' @@ -501,9 +492,27 @@ class DefinitionParser(object): is_const = self._peek_const(path) modifiers, typename = self._guess_typename(path) - rv = ModifierDefExpr(modifiers, _NameDefExpr(typename)) + rv = ModifierDefExpr(modifiers, NameDefExpr(typename)) return self._attach_crefptr(rv, is_const) + def _parse_type_expr(self): + typename = self._parse_name() + self.skip_ws() + if not self.skip_string('<'): + return typename + + args = [] + while 1: + self.skip_ws() + if self.skip_string('>'): + break + if args: + if not self.skip_string(','): + self.fail('"," or ">" in template expected') + self.skip_ws() + args.append(self._parse_type(True)) + return TemplateDefExpr(typename, args) + def _parse_type(self, in_template=False): result = [] modifiers = [] @@ -718,8 +727,13 @@ class CPPObject(ObjectDescription): def handle_signature(self, sig, signode): parser = DefinitionParser(sig) - rv = self.parse_definition(parser) - parser.assert_end() + try: + rv = self.parse_definition(parser) + parser.assert_end() + except DefinitionError, e: + self.env.warn(self.env.docname, + e.description, self.lineno) + raise ValueError self.describe_signature(signode, rv) parentname = self.env.temp_data.get('cpp:parent') @@ -830,6 +844,34 @@ class CPPFunctionObject(CPPObject): self.attach_function(signode, func) +class CPPCurrentNamespace(Directive): + """This directive is just to tell Sphinx that we're documenting + stuff in namespace foo. + """ + + has_content = False + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + option_spec = {} + + def run(self): + env = self.state.document.settings.env + if self.arguments[0].strip() in ('NULL', '0', 'nullptr'): + env.temp_data['cpp:prefix'] = None + else: + parser = DefinitionParser(self.arguments[0]) + try: + prefix = parser.parse_type() + parser.assert_end() + except DefinitionError, e: + self.env.warn(self.env.docname, + e.description, self.lineno) + else: + env.temp_data['cpp:prefix'] = prefix + return [] + + class CPPDomain(Domain): """C++ language domain.""" name = 'cpp' @@ -842,10 +884,11 @@ class CPPDomain(Domain): } directives = { - 'class': CPPClassObject, - 'function': CPPFunctionObject, - 'member': CPPMemberObject, - 'type': CPPTypeObject + 'class': CPPClassObject, + 'function': CPPFunctionObject, + 'member': CPPMemberObject, + 'type': CPPTypeObject, + 'namespace': CPPCurrentNamespace } roles = { 'class': XRefRole(), @@ -865,9 +908,13 @@ class CPPDomain(Domain): def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): parser = DefinitionParser(target) - expr = parser.parse_type() - parser.skip_ws() - if not parser.eof: + # XXX: warn? + try: + expr = parser.parse_type() + parser.skip_ws() + if not parser.eof: + return None + except DefinitionError: return None target = unicode(expr) if target not in self.data['objects']: -- cgit v1.2.1 From 64d61b8eead01b14c36d42a6b7b70ebcf20ca8b0 Mon Sep 17 00:00:00 2001 From: mitsuhiko Date: Mon, 1 Mar 2010 19:54:11 +0100 Subject: Added basic modifier support --- sphinx/domains/cpp.py | 99 +++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 73 insertions(+), 26 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 0be636c3..9c1a1c24 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -29,6 +29,7 @@ _identifier_re = re.compile(r'\b(~?[a-zA-Z_][a-zA-Z0-9_]*)\b') _whitespace_re = re.compile(r'\s+(?u)') _string_re = re.compile(r"[LuU8]?('([^'\\]*(?:\\.[^'\\]*)*)'" r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S) +_visibility_re = re.compile(r'\b(public|private|protected)\b') _operator_re = re.compile(r'''(?x) \[\s*\] | \(\s*\) @@ -268,14 +269,24 @@ class PrefixedNameDefExpr(DefExpr): class NamedDefExpr(DefExpr): - def __init__(self, name): + def __init__(self, name, visibility, static): self.name = name + self.visibility = visibility + self.static = static + + def get_modifiers(self): + rv = [] + if self.visibility != 'public': + rv.append(self.visibility) + if self.static: + rv.append(u'static') + return rv class TypeObjDefExpr(NamedDefExpr): - def __init__(self, typename, name): - NamedDefExpr.__init__(self, name) + def __init__(self, name, visibility, static, typename): + NamedDefExpr.__init__(self, name, visibility, static) self.typename = typename def get_id(self): @@ -284,15 +295,18 @@ class TypeObjDefExpr(NamedDefExpr): return u'%s__%s' % (self.name.get_id(), self.typename.get_id()) def __unicode__(self): + buf = self.get_modifiers() if self.typename is None: - return unicode(self.name) - return u'%s %s' % (self.typename, self.name) + buf.append(unicode(self.name)) + else: + buf.extend(map(unicode, (self.typename, self.name))) + return u' '.join(buf) class MemberObjDefExpr(NamedDefExpr): - def __init__(self, typename, name, value): - NamedDefExpr.__init__(self, name) + def __init__(self, name, visibility, static, typename, value): + NamedDefExpr.__init__(self, name, visibility, static) self.typename = typename self.value = value @@ -300,16 +314,18 @@ class MemberObjDefExpr(NamedDefExpr): return u'%s__%s' % (self.name.get_id(), self.typename.get_id()) def __unicode__(self): - rv = u'%s %s' % (self.typename, self.name) + buf = self.get_modifiers() + buf.append(u'%s %s' % (self.typename, self.name)) if self.value is not None: - rv = u'%s = %s' % (rv, self.value) - return rv + buf.append(u'%s = %s' % (rv, self.value)) + return u' '.join(buf) class FuncDefExpr(NamedDefExpr): - def __init__(self, name, rv, signature, const, pure_virtual): - NamedDefExpr.__init__(self, name) + def __init__(self, name, visibility, static, rv, signature, + const, pure_virtual): + NamedDefExpr.__init__(self, name, visibility, static) self.rv = rv self.signature = signature self.const = const @@ -324,25 +340,30 @@ class FuncDefExpr(NamedDefExpr): ) def __unicode__(self): - return u'%s%s(%s)%s%s' % ( - self.rv is not None and unicode(self.rv) + u' ' or u'', - self.name, - u', '.join(map(unicode, self.signature)), - self.const and u' const' or u'', - self.pure_virtual and ' = 0' or '' - ) + buf = self.get_modifiers() + if self.rv is not None: + buf.append(unicode(self.rv)) + buf.append(u'%s(%s)' % (self.name, u', '.join( + map(unicode, self.signature)))) + if self.const: + buf.append(u'const') + if self.pure_virtual: + buf.append(u'= 0') + return u' '.join(buf) class ClassDefExpr(NamedDefExpr): - def __init__(self, name): - NamedDefExpr.__init__(self, name) + def __init__(self, name, visibility, static): + NamedDefExpr.__init__(self, name, visibility, static) def get_id(self): return self.name.get_id() def __unicode__(self): - return unicode(self.name) + buf = self.get_modifiers() + buf.append(unicode(self.name)) + return u' '.join(buf) class DefinitionParser(object): @@ -514,6 +535,7 @@ class DefinitionParser(object): return TemplateDefExpr(typename, args) def _parse_type(self, in_template=False): + self.skip_ws() result = [] modifiers = [] @@ -623,10 +645,18 @@ class DefinitionParser(object): pure_virtual = False return args, const, pure_virtual + def _parse_visibility_static(self): + visibility = 'public' + if self.match(_visibility_re): + visibility = self.matched_text + static = self.skip_word('static') + return visibility, static + def parse_type(self): return self._parse_type() def parse_type_object(self): + visibility, static = self._parse_visibility_static() typename = self._parse_type() self.skip_ws() if not self.eof: @@ -634,9 +664,10 @@ class DefinitionParser(object): else: name = typename typename = None - return TypeObjDefExpr(typename, name) + return TypeObjDefExpr(name, visibility, static, typename) def parse_member_object(self): + visibility, static = self._parse_visibility_static() typename = self._parse_type() name = self._parse_type() self.skip_ws() @@ -644,9 +675,10 @@ class DefinitionParser(object): value = self.read_rest().strip() else: value = None - return MemberObjDefExpr(typename, name, value) + return MemberObjDefExpr(name, visibility, static, typename, value) def parse_function(self): + visibility, static = self._parse_visibility_static() rv = self._parse_type() self.skip_ws() # some things just don't have return values @@ -655,10 +687,12 @@ class DefinitionParser(object): rv = None else: name = self._parse_type() - return FuncDefExpr(name, rv, *self._parse_signature()) + return FuncDefExpr(name, visibility, static, rv, + *self._parse_signature()) def parse_class(self): - return ClassDefExpr(self._parse_type()) + visibility, static = self._parse_visibility_static() + return ClassDefExpr(self._parse_type(), visibility, static) def read_rest(self): rv = self.definition[self.pos:] @@ -692,6 +726,15 @@ class CPPObject(ObjectDescription): pnode += nodes.Text(text) node += pnode + def attach_modifiers(self, node, obj): + if obj.visibility != 'public': + node += addnodes.desc_annotation(obj.visibility, + obj.visibility) + node += nodes.Text(' ') + if obj.static: + node += addnodes.desc_annotation('static', 'static') + node += nodes.Text(' ') + def add_target_and_index(self, sigobj, sig, signode): theid = sigobj.get_id() name = unicode(sigobj.name) @@ -752,6 +795,7 @@ class CPPClassObject(CPPObject): return parser.parse_class() def describe_signature(self, signode, cls): + self.attach_modifiers(signode, cls) signode += addnodes.desc_annotation('class ', 'class ') self.attach_name(signode, cls.name) @@ -767,6 +811,7 @@ class CPPTypeObject(CPPObject): return parser.parse_type_object() def describe_signature(self, signode, obj): + self.attach_modifiers(signode, obj) signode += addnodes.desc_annotation('type ', 'type ') if obj.typename is not None: self.attach_type(signode, obj.typename) @@ -785,6 +830,7 @@ class CPPMemberObject(CPPObject): return parser.parse_member_object() def describe_signature(self, signode, obj): + self.attach_modifiers(signode, obj) self.attach_type(signode, obj.typename) signode += nodes.Text(' ') self.attach_name(signode, obj.name) @@ -835,6 +881,7 @@ class CPPFunctionObject(CPPObject): return parser.parse_function() def describe_signature(self, signode, func): + self.attach_modifiers(signode, func) # return value is None for things with a reverse return value # such as casting operator definitions or constructors # and destructors. -- cgit v1.2.1 From 3553132a3aa409c2e82010857e4ecc2aed6da6ff Mon Sep 17 00:00:00 2001 From: mitsuhiko Date: Mon, 1 Mar 2010 21:48:40 +0100 Subject: Relative references work now for the C++ domain --- sphinx/domains/cpp.py | 198 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 136 insertions(+), 62 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 9c1a1c24..b391040e 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -111,13 +111,30 @@ class DefExpr(object): raise NotImplementedError() def clone(self): + """Close a definition expression node""" return deepcopy(self) def get_id(self): + """Returns the id for the node""" return u'' + def get_name(self): + """Returns the name. Returns either `None` or a node with + a name you might call :meth:`split_owner` on. + """ + return None + def split_owner(self): - return None, self + """Nodes returned by :meth:`get_name` can split off their + owning parent. This function returns the owner and the + name as a tuple of two items. If a node does not support + it, :exc:`NotImplementedError` is raised. + """ + raise NotImplementedError() + + def prefix(self, prefix): + """Prefixes a name node (a node returned by :meth:`get_name`).""" + raise NotImplementedError() def __str__(self): return unicode(self).encode('utf-8') @@ -126,7 +143,23 @@ class DefExpr(object): return '' % self -class NameDefExpr(DefExpr): +class PrimaryDefExpr(DefExpr): + + def get_name(self): + return self + + def split_owner(self): + return None, self + + def prefix(self, prefix): + if isinstance(prefix, PathDefExpr): + prefix = prefix.clone() + prefix.path.append(self) + return prefix + return PathDefExpr([prefix, self]) + + +class NameDefExpr(PrimaryDefExpr): def __init__(self, name): self.name = name @@ -141,7 +174,7 @@ class NameDefExpr(DefExpr): return unicode(self.name) -class PathDefExpr(DefExpr): +class PathDefExpr(PrimaryDefExpr): def __init__(self, parts): self.path = parts @@ -153,17 +186,51 @@ class PathDefExpr(DefExpr): def split_owner(self): if len(self.path) > 1: return PathDefExpr(self.path[:-1]), self.path[-1] - return DefExpr.split_owner(self) + return None, self + + def prefix(self, prefix): + if isinstance(prefix, PathDefExpr): + prefix = prefix.clone() + prefix.path.extend(self.path) + return prefix + return PathDefExpr([prefix] + self.path) def __unicode__(self): return u'::'.join(map(unicode, self.path)) -class ModifierDefExpr(DefExpr): +class TemplateDefExpr(PrimaryDefExpr): - def __init__(self, modifiers, typename): - self.modifiers = modifiers + def __init__(self, typename, args): self.typename = typename + self.args = args + + def split_owner(self): + owner, typename = self.typename.split_owner() + return owner, TemplateDefExpr(typename, self.args) + + def get_id(self): + return u'%s:%s:' % (self.typename.get_id(), + u'.'.join(x.get_id() for x in self.args)) + + def __unicode__(self): + return u'%s<%s>' % (self.typename, u', '.join(map(unicode, self.args))) + + +class WrappingDefExpr(DefExpr): + + def __init__(self, typename): + self.typename = typename + + def get_name(self): + return self.typename + + +class ModifierDefExpr(WrappingDefExpr): + + def __init__(self, typename, modifiers): + WrappingDefExpr.__init__(self, typename) + self.modifiers = modifiers def get_id(self): pieces = [_id_shortwords.get(unicode(x), unicode(x)) @@ -175,10 +242,7 @@ class ModifierDefExpr(DefExpr): return u' '.join(map(unicode, list(self.modifiers) + [self.typename])) -class PtrDefExpr(DefExpr): - - def __init__(self, typename): - self.typename = typename +class PtrDefExpr(WrappingDefExpr): def get_id(self): return self.typename.get_id() + u'P' @@ -187,10 +251,7 @@ class PtrDefExpr(DefExpr): return u'%s*' % self.typename -class RefDefExpr(DefExpr): - - def __init__(self, typename): - self.typename = typename +class RefDefExpr(WrappingDefExpr): def get_id(self): return self.typename.get_id() + u'R' @@ -199,10 +260,10 @@ class RefDefExpr(DefExpr): return u'%s&' % self.typename -class ConstDefExpr(DefExpr): +class ConstDefExpr(WrappingDefExpr): def __init__(self, typename, prefix=False): - self.typename = typename + WrappingDefExpr.__init__(self, typename) self.prefix = prefix def get_id(self): @@ -212,7 +273,7 @@ class ConstDefExpr(DefExpr): return (self.prefix and u'const %s' or u'%s const') % self.typename -class CastOpDefExpr(DefExpr): +class CastOpDefExpr(PrimaryDefExpr): def __init__(self, typename): self.typename = typename @@ -224,20 +285,6 @@ class CastOpDefExpr(DefExpr): return u'operator %s' % self.typename -class TemplateDefExpr(DefExpr): - - def __init__(self, typename, args): - self.typename = typename - self.args = args - - def get_id(self): - return u'%s:%s:' % (self.typename.get_id(), - u'.'.join(x.get_id() for x in self.args)) - - def __unicode__(self): - return u'%s<%s>' % (self.typename, u', '.join(map(unicode, self.args))) - - class ArgumentDefExpr(DefExpr): def __init__(self, type, name, default=None): @@ -245,6 +292,9 @@ class ArgumentDefExpr(DefExpr): self.type = type self.default = default + def get_name(self): + return self.name + def get_id(self): return self.type.get_id() @@ -254,19 +304,6 @@ class ArgumentDefExpr(DefExpr): u'=%s' % self.default or u'') -class PrefixedNameDefExpr(DefExpr): - - def __init__(self, prefix, name): - self.prefix = prefix - self.name = name - - def get_id(self): - return u'%s::%s' % (self.prefix.get_id(), self.name.get_id()) - - def __unicode__(self): - return u'%s::%s' % (self.prefix, self.name) - - class NamedDefExpr(DefExpr): def __init__(self, name, visibility, static): @@ -274,6 +311,9 @@ class NamedDefExpr(DefExpr): self.visibility = visibility self.static = static + def get_name(self): + return self.name + def get_modifiers(self): rv = [] if self.visibility != 'public': @@ -513,7 +553,7 @@ class DefinitionParser(object): is_const = self._peek_const(path) modifiers, typename = self._guess_typename(path) - rv = ModifierDefExpr(modifiers, NameDefExpr(typename)) + rv = ModifierDefExpr(NameDefExpr(typename), modifiers) return self._attach_crefptr(rv, is_const) def _parse_type_expr(self): @@ -576,7 +616,7 @@ class DefinitionParser(object): rv = PathDefExpr(result) is_const = self._peek_const(modifiers) if modifiers: - rv = ModifierDefExpr(modifiers, rv) + rv = ModifierDefExpr(rv, modifiers) return self._attach_crefptr(rv, is_const) def _parse_default_expr(self): @@ -779,10 +819,10 @@ class CPPObject(ObjectDescription): raise ValueError self.describe_signature(signode, rv) - parentname = self.env.temp_data.get('cpp:parent') - if parentname is not None: + parent = self.env.temp_data.get('cpp:parent') + if parent is not None: rv = rv.clone() - rv.name = PrefixedNameDefExpr(parentname, rv.name) + rv.name = rv.name.prefix(parent) return rv @@ -919,6 +959,22 @@ class CPPCurrentNamespace(Directive): return [] +class CPPXRefRole(XRefRole): + + def process_link(self, env, refnode, has_explicit_title, title, target): + refnode['cpp:parent'] = env.temp_data.get('cpp:parent') + if not has_explicit_title: + target = target.lstrip('~') # only has a meaning for the title + # if the first character is a tilde, don't display the module/class + # parts of the contents + if title[:1] == '~': + title = title[1:] + dcolon = title.rfind('::') + if dcolon != -1: + title = title[dot + 2:] + return title, target + + class CPPDomain(Domain): """C++ language domain.""" name = 'cpp' @@ -938,10 +994,10 @@ class CPPDomain(Domain): 'namespace': CPPCurrentNamespace } roles = { - 'class': XRefRole(), - 'func' : XRefRole(fix_parens=True), - 'member': XRefRole(), - 'type': XRefRole() + 'class': CPPXRefRole(), + 'func' : CPPXRefRole(fix_parens=True), + 'member': CPPXRefRole(), + 'type': CPPXRefRole() } initial_data = { 'objects': {}, # fullname -> docname, objtype @@ -954,21 +1010,39 @@ class CPPDomain(Domain): def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): + def _create_refnode(expr): + target = unicode(expr) + if target not in self.data['objects']: + return None + obj = self.data['objects'][target] + if obj[1] != typ: + return None + return make_refnode(builder, fromdocname, obj[0], target, + contnode, target) + parser = DefinitionParser(target) # XXX: warn? try: - expr = parser.parse_type() + expr = parser.parse_type().get_name() parser.skip_ws() - if not parser.eof: + if not parser.eof or expr is None: return None except DefinitionError: return None - target = unicode(expr) - if target not in self.data['objects']: - return None - obj = self.data['objects'][target] - return make_refnode(builder, fromdocname, obj[0], target, - contnode, target) + + parent = node.get('cpp:parent') + + rv = _create_refnode(expr) + if rv is not None or parent is None: + return rv + parent = parent.get_name() + + rv = _create_refnode(expr.prefix(parent)) + if rv is not None: + return rv + + parent, name = parent.split_owner() + return _create_refnode(expr.prefix(parent)) def get_objects(self): for refname, (docname, type) in self.data['objects'].iteritems(): -- cgit v1.2.1 From 147cc83c4e5a34537b19cac6c7ec0c4c1e53323d Mon Sep 17 00:00:00 2001 From: mitsuhiko Date: Mon, 1 Mar 2010 22:23:56 +0100 Subject: Fixed two undefined errors. --- sphinx/domains/cpp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index b391040e..ed12ccfd 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -357,7 +357,7 @@ class MemberObjDefExpr(NamedDefExpr): buf = self.get_modifiers() buf.append(u'%s %s' % (self.typename, self.name)) if self.value is not None: - buf.append(u'%s = %s' % (rv, self.value)) + buf.append(u'= %s' % self.value) return u' '.join(buf) @@ -971,7 +971,7 @@ class CPPXRefRole(XRefRole): title = title[1:] dcolon = title.rfind('::') if dcolon != -1: - title = title[dot + 2:] + title = title[dcolon + 2:] return title, target -- cgit v1.2.1 From cdc6b4a13a6cedd937b710e383f2696c22d8630b Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 1 Mar 2010 22:31:17 +0100 Subject: Add shortcut for SPHINXOPTS. --- doc/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/Makefile b/doc/Makefile index a8897d2b..48a66ed1 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -9,7 +9,7 @@ PAPER = PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) \ - $(SPHINXOPTS) . + $(SPHINXOPTS) $(O) . .PHONY: help clean html dirhtml pickle htmlhelp qthelp latex changes linkcheck doctest man -- cgit v1.2.1 From 7b7938b3c2cd86d6a15f64faf669971f65c78582 Mon Sep 17 00:00:00 2001 From: mitsuhiko Date: Mon, 1 Mar 2010 23:25:33 +0100 Subject: Fixed reference targets --- sphinx/domains/cpp.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index ed12ccfd..eecff1d9 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -223,7 +223,7 @@ class WrappingDefExpr(DefExpr): self.typename = typename def get_name(self): - return self.typename + return self.typename.get_name() class ModifierDefExpr(WrappingDefExpr): @@ -293,7 +293,7 @@ class ArgumentDefExpr(DefExpr): self.default = default def get_name(self): - return self.name + return self.name.get_name() def get_id(self): return self.type.get_id() @@ -312,7 +312,7 @@ class NamedDefExpr(DefExpr): self.static = static def get_name(self): - return self.name + return self.name.get_name() def get_modifiers(self): rv = [] @@ -763,6 +763,7 @@ class CPPObject(ObjectDescription): pnode = addnodes.pending_xref( '', refdomain='cpp', reftype='type', reftarget=text, modname=None, classname=None) + pnode['cpp:parent'] = self.env.temp_data.get('cpp:parent') pnode += nodes.Text(text) node += pnode @@ -1011,14 +1012,14 @@ class CPPDomain(Domain): def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): def _create_refnode(expr): - target = unicode(expr) - if target not in self.data['objects']: + name = unicode(expr) + if name not in self.data['objects']: return None - obj = self.data['objects'][target] + obj = self.data['objects'][name] if obj[1] != typ: return None - return make_refnode(builder, fromdocname, obj[0], target, - contnode, target) + return make_refnode(builder, fromdocname, obj[0], expr.get_id(), + contnode, name) parser = DefinitionParser(target) # XXX: warn? @@ -1030,7 +1031,7 @@ class CPPDomain(Domain): except DefinitionError: return None - parent = node.get('cpp:parent') + parent = node['cpp:parent'] rv = _create_refnode(expr) if rv is not None or parent is None: -- cgit v1.2.1 From b52054154c9bf9ab90f041b514f6581f34e6cb5b Mon Sep 17 00:00:00 2001 From: mitsuhiko Date: Tue, 2 Mar 2010 01:27:44 +0100 Subject: Added first CPP test --- sphinx/domains/cpp.py | 14 ++++++++++++++ tests/test_cpp_domain.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 tests/test_cpp_domain.py diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index eecff1d9..d8c65d68 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -110,6 +110,20 @@ class DefExpr(object): def __unicode__(self): raise NotImplementedError() + def __eq__(self, other): + if type(self) is not type(other): + return False + try: + for key, value in self.__dict__.iteritems(): + if value != getattr(other, value): + return False + except AttributeError: + return False + return True + + def __ne__(self, other): + return not self.__eq__(other) + def clone(self): """Close a definition expression node""" return deepcopy(self) diff --git a/tests/test_cpp_domain.py b/tests/test_cpp_domain.py new file mode 100644 index 00000000..48de51c1 --- /dev/null +++ b/tests/test_cpp_domain.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +""" + test_cpp_domain + ~~~~~~~~~~~~~~~ + + Tests the C++ Domain + + :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +from util import * + +from sphinx.domains.cpp import DefinitionParser + + +def parse(name, string): + return getattr(DefinitionParser(string), 'parse_' + name)() + + +def test_type_definitions(): + """Tests the type definition parsing""" + rv = parse('member_object', ' const std::string & name = 42') + assert unicode(rv) == 'const std::string& name = 42' + + rv = parse('member_object', ' const std::string & name leftover') + assert unicode(rv) == 'const std::string& name' + + rv = parse('member_object', 'const std::vector< unsigned int, long> &name') + assert unicode(rv) == 'const std::vector& name' + + x = 'std::vector>& module::test(register ' \ + 'foo, bar, std::string baz="foobar, blah, bleh") const = 0' + assert unicode(parse('function', x)) == x + + x = 'module::myclass::operator std::vector()' + assert unicode(parse('function', x)) == x + + x = 'std::vector> module::blah' + assert unicode(parse('type_object', x)) == x + + assert unicode(parse('type_object', 'long long int foo')) == 'long long foo' -- cgit v1.2.1 From 8e3e8cd0d1b1ff2f6efd61bb1553c284dd4639bc Mon Sep 17 00:00:00 2001 From: mitsuhiko Date: Tue, 2 Mar 2010 13:10:44 +0100 Subject: More operators for C++. Forgot about new/delete and some other less used such as ->*. --- sphinx/domains/cpp.py | 128 +++++++++++++++++++++++++++-------------------- tests/test_cpp_domain.py | 6 ++- 2 files changed, 80 insertions(+), 54 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index d8c65d68..76c7767e 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -33,63 +33,72 @@ _visibility_re = re.compile(r'\b(public|private|protected)\b') _operator_re = re.compile(r'''(?x) \[\s*\] | \(\s*\) - | [!<>=/*%+-|&^]=? + | [!<>=/*%+|&^-]=? | \+\+ | -- - | <<=? | >>=? | ~ | && | \| | \|\| + | (<<|>>)=? | ~ | && | \| | \|\| + | ->\*? | \, ''') _id_shortwords = { - 'char': 'c', - 'signed char': 'c', - 'unsigned char': 'C', - 'int': 'i', - 'signed int': 'i', - 'unsigned int': 'U', - 'long': 'l', - 'signed long': 'l', - 'unsigned long': 'L', - 'bool': 'b', - 'size_t': 's', - 'std::string': 'ss', - 'std::ostream': 'os', - 'std::istream': 'is', - 'std::iostream': 'ios', - 'std::vector': 'v', - 'std::map': 'm', - 'operator[]': 'subscript-operator', - 'operator()': 'call-operator', - 'operator!': 'not-operator', - 'operator<': 'lt-operator', - 'operator<=': 'lte-operator', - 'operator>': 'gt-operator', - 'operator>=': 'gte-operator', - 'operator=': 'assign-operator', - 'operator/': 'div-operator', - 'operator*': 'mul-operator', - 'operator%': 'mod-operator', - 'operator+': 'add-operator', - 'operator-': 'sub-operator', - 'operator|': 'or-operator', - 'operator&': 'and-operator', - 'operator^': 'xor-operator', - 'operator&&': 'sand-operator', - 'operator||': 'sor-operator', - 'operator==': 'eq-operator', - 'operator!=': 'neq-operator', - 'operator<<': 'lshift-operator', - 'operator>>': 'rshift-operator', - 'operator-=': 'sub-assign-operator', - 'operator+=': 'add-assign-operator', - 'operator*-': 'mul-assign-operator', - 'operator/=': 'div-assign-operator', - 'operator%=': 'mod-assign-operator', - 'operator&=': 'and-assign-operator', - 'operator|=': 'or-assign-operator', - 'operator<<=': 'lshift-assign-operator', - 'operator>>=': 'rshift-assign-operator', - 'operator~': 'inv-operator', - 'operator++': 'inc-operator', - 'operator--': 'dec-operator' + 'char': 'c', + 'signed char': 'c', + 'unsigned char': 'C', + 'int': 'i', + 'signed int': 'i', + 'unsigned int': 'U', + 'long': 'l', + 'signed long': 'l', + 'unsigned long': 'L', + 'bool': 'b', + 'size_t': 's', + 'std::string': 'ss', + 'std::ostream': 'os', + 'std::istream': 'is', + 'std::iostream': 'ios', + 'std::vector': 'v', + 'std::map': 'm', + 'operator[]': 'subscript-operator', + 'operator()': 'call-operator', + 'operator!': 'not-operator', + 'operator<': 'lt-operator', + 'operator<=': 'lte-operator', + 'operator>': 'gt-operator', + 'operator>=': 'gte-operator', + 'operator=': 'assign-operator', + 'operator/': 'div-operator', + 'operator*': 'mul-operator', + 'operator%': 'mod-operator', + 'operator+': 'add-operator', + 'operator-': 'sub-operator', + 'operator|': 'or-operator', + 'operator&': 'and-operator', + 'operator^': 'xor-operator', + 'operator&&': 'sand-operator', + 'operator||': 'sor-operator', + 'operator==': 'eq-operator', + 'operator!=': 'neq-operator', + 'operator<<': 'lshift-operator', + 'operator>>': 'rshift-operator', + 'operator-=': 'sub-assign-operator', + 'operator+=': 'add-assign-operator', + 'operator*-': 'mul-assign-operator', + 'operator/=': 'div-assign-operator', + 'operator%=': 'mod-assign-operator', + 'operator&=': 'and-assign-operator', + 'operator|=': 'or-assign-operator', + 'operator<<=': 'lshift-assign-operator', + 'operator>>=': 'rshift-assign-operator', + 'operator^=': 'xor-assign-operator', + 'operator,': 'comma-operator', + 'operator->': 'pointer-operator', + 'operator->*': 'pointer-by-pointer-operator', + 'operator~': 'inv-operator', + 'operator++': 'inc-operator', + 'operator--': 'dec-operator', + 'operator new': 'new-operator', + 'operator new[]': 'new-array-operator', + 'operator delete': 'delete-operator', + 'operator delete[]': 'delete-array-operator' } @@ -491,11 +500,24 @@ class DefinitionParser(object): return self.last_match.group() def _parse_operator(self): + self.skip_ws() # thank god, a regular operator definition if self.match(_operator_re): return NameDefExpr('operator' + _whitespace_re.sub('', self.matched_text)) + # new/delete operator? + for allocop in 'new', 'delete': + if not self.skip_word(allocop): + continue + self.skip_ws() + if self.skip_string('['): + self.skip_ws() + if not self.skip_string(']'): + self.fail('expected "]" for ' + allocop) + allocop += '[]' + return NameDefExpr('operator ' + allocop) + # oh well, looks like a cast operator definition. # In that case, eat another type. type = self._parse_type() diff --git a/tests/test_cpp_domain.py b/tests/test_cpp_domain.py index 48de51c1..b12e57af 100644 --- a/tests/test_cpp_domain.py +++ b/tests/test_cpp_domain.py @@ -19,7 +19,6 @@ def parse(name, string): def test_type_definitions(): - """Tests the type definition parsing""" rv = parse('member_object', ' const std::string & name = 42') assert unicode(rv) == 'const std::string& name = 42' @@ -40,3 +39,8 @@ def test_type_definitions(): assert unicode(parse('type_object', x)) == x assert unicode(parse('type_object', 'long long int foo')) == 'long long foo' + + +def test_operators(): + x = parse('function', 'void operator new [ ] ()') + assert unicode(x) == 'void operator new[]()' -- cgit v1.2.1 From ebf7735e4033fa95e67bd860dbdf02e51be7853c Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 9 Mar 2010 19:32:59 +0100 Subject: Write a bit more of the tutorial. --- doc/config.rst | 2 + doc/markup/inline.rst | 54 ++++++++++----------- doc/rest.rst | 2 + doc/tutorial.rst | 127 ++++++++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 148 insertions(+), 37 deletions(-) diff --git a/doc/config.rst b/doc/config.rst index fae85fc4..611b7476 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -1,5 +1,7 @@ .. highlightlang:: python +.. _build-config: + The build configuration file ============================ diff --git a/doc/markup/inline.rst b/doc/markup/inline.rst index 4f98f021..298c999c 100644 --- a/doc/markup/inline.rst +++ b/doc/markup/inline.rst @@ -49,47 +49,47 @@ more versatile: Cross-referencing arbitrary locations ------------------------------------- -.. index:: pair: ref; role +.. role:: ref -To support cross-referencing to arbitrary locations in any document, the -standard reST labels are used. For this to work label names must be unique -throughout the entire documentation. There are two ways in which you can refer -to labels: + To support cross-referencing to arbitrary locations in any document, the + standard reST labels are used. For this to work label names must be unique + throughout the entire documentation. There are two ways in which you can + refer to labels: -* If you place a label directly before a section title, you can reference to it - with ``:ref:`label-name```. Example:: + * If you place a label directly before a section title, you can reference to + it with ``:ref:`label-name```. Example:: - .. _my-reference-label: + .. _my-reference-label: - Section to cross-reference - -------------------------- + Section to cross-reference + -------------------------- - This is the text of the section. + This is the text of the section. - It refers to the section itself, see :ref:`my-reference-label`. + It refers to the section itself, see :ref:`my-reference-label`. - The ``:ref:`` role would then generate a link to the section, with the link - title being "Section to cross-reference". This works just as well when - section and reference are in different source files. + The ``:ref:`` role would then generate a link to the section, with the link + title being "Section to cross-reference". This works just as well when + section and reference are in different source files. - Automatic labels also work with figures: given :: + Automatic labels also work with figures: given :: - .. _my-figure: + .. _my-figure: - .. figure:: whatever + .. figure:: whatever - Figure caption + Figure caption - a reference ``:ref:`my-figure``` would insert a reference to the figure with - link text "Figure caption". + a reference ``:ref:`my-figure``` would insert a reference to the figure + with link text "Figure caption". -* Labels that aren't placed before a section title can still be referenced to, - but you must give the link an explicit title, using this syntax: ``:ref:`Link - title ```. + * Labels that aren't placed before a section title can still be referenced + to, but you must give the link an explicit title, using this syntax: + ``:ref:`Link title ```. -Using :role:`ref` is advised over standard reStructuredText links to sections -(like ```Section title`_``) because it works across files, when section headings -are changed, and for all builders that support cross-references. + Using :role:`ref` is advised over standard reStructuredText links to sections + (like ```Section title`_``) because it works across files, when section + headings are changed, and for all builders that support cross-references. Cross-referencing documents diff --git a/doc/rest.rst b/doc/rest.rst index 146b627d..1194903a 100644 --- a/doc/rest.rst +++ b/doc/rest.rst @@ -1,5 +1,7 @@ .. highlightlang:: rest +.. _rst-primer: + reStructuredText Primer ======================= diff --git a/doc/tutorial.rst b/doc/tutorial.rst index d3e40dee..4ab8c06b 100644 --- a/doc/tutorial.rst +++ b/doc/tutorial.rst @@ -27,8 +27,8 @@ configuration values from a few questions it asks you. Just run :: and answer its questions. (Be sure to say yes to the "autodoc" extension.) -Adding some content -------------------- +Defining document structure +--------------------------- Let's assume you've run :program:`sphinx-quickstart`. It created a source directory with :file:`conf.py` and a master document, :file:`index.rst` (if you @@ -77,11 +77,27 @@ leave off the file name extension and use slashes as directory separators. |more| Read more about :ref:`the toctree directive `. -You can now create the files you listed in the toctree, and their section titles -will be inserted (up to the "maxdepth" level) at the place where the toctree -directive is placed. Also, Sphinx now knows about the order and hierarchy of -your documents. (They may contain ``toctree`` directives themselves, which -means you can create deeply nested hierarchies if necessary.) +You can now create the files you listed in the toctree and add content, and +their section titles will be inserted (up to the "maxdepth" level) at the place +where the toctree directive is placed. Also, Sphinx now knows about the order +and hierarchy of your documents. (They may contain ``toctree`` directives +themselves, which means you can create deeply nested hierarchies if necessary.) + + +Adding content +-------------- + +In Sphinx source files, you can use most features of standard reStructuredText. +There are also several features added by Sphinx. For example, you can add +cross-file references in a portable way (which works for all output types) using +the :role:`ref` role. Here is a small example of how a source file could look +like:: + + XXX + +|more| See :ref:`rst-primer` for a more in-depth introduction to +reStructuredText and :ref:`sphinxmarkup` for a full list of markup added by +Sphinx. Running the build @@ -112,15 +128,106 @@ an argument to see which targets are available. Documenting objects ------------------- +One of Sphinx' main objectives is easy documentation of :dfn:`objects` (in a +very general sense) in any :dfn:`domain`. A domain is a collection of object +types that belong together, complete with markup to create and reference +descriptions of these objects. + +The most prominent domain is the Python domain. To e.g. document the Python +built-in function ``enumerate()``, you would add this to one of your source +files:: + + .. py:function:: enumerate(sequence[, start=0]) + + Return an iterator that yields tuples of an index and an item of the + *sequence*. (And so on.) + +This is rendered like this: + +.. py:function:: enumerate(sequence[, start=0]) + + Return an iterator that yields tuples of an index and an item of the + *sequence*. (And so on.) + +The argument of the directive is the :dfn:`signature` of the object you +describe, the content is the documentation for it. Multiple signatures can be +given, each in its own line. + +The Python domain also happens to be the default domain, so you don't need to +prefix the markup with the domain name:: + + .. function:: enumerate(sequence[, start=0]) + + ... + +does the same job if you keep the default setting for the default domain. + +There are several more directives for documenting other types of Python objects, +for example :dir:`py:class` or :dir:`py:method`. There is also a +cross-referencing :dfn:`role` for each of these object types. This markup will +create a link to the documentation of ``enumerate()``:: + + The :py:func:`enumerate` function can be used for ... + +And here is the proof: A link to :func:`enumerate`. + +Again, the ``py:`` can be left out if the Python domain is the default one. It +doesn't matter which file contains the actual documentation for ``enumerate()``; +Sphinx will find it and create a link to it. + +Each domain will have special rules for how the signatures can look like, and +make the formatted output look pretty, or add specific features like links to +parameter types, e.g. in the C/C++ domains. + +|more| See :ref:`domains` for all the available domains and their +directives/roles. + + +Basic configuration +------------------- + +Earlier we mentioned that the :file:`conf.py` file controls how Sphinx processes +your documents. In that file, which is executed as a Python source file, you +assign configuration values. For advanced users: since it is executed by +Sphinx, you can do non-trivial tasks in it, like extending :data:`sys.path` or +importing a module to find out the version your are documenting. + +The config values that you probably want to change are already put into the +:file:`conf.py` by :program:`sphinx-quickstart` and initially commented out +(with standard Python syntax: a ``#`` comments the rest of the line). To change +the default value, remove the hash sign and modify the value. To customize a +config value that is not automatically added by :program:`sphinx-quickstart`, +just add an additional assignment. + +Keep in mind that the file uses Python syntax for strings, numbers, lists and so +on. The file is saved in UTF-8 by default, as indicated by the encoding +declaration in the first line. If you use non-ASCII characters in any string +value, you need to use Python Unicode strings (like ``project = u'Exposé'``). + +|more| See :ref:`build-config` for documentation of all available config values. + + +Autodoc +------- + +When documenting Python code, it is common to put a lot of documentation in the +source files, in documentation strings. Sphinx supports the inclusion of +docstrings from your modules with an :dfn:`extension` (an extension is a Python +module that provides additional features for Sphinx projects) called "autodoc". + +In order to use autodoc, you need to activate it in :file:`conf.py` by putting +the string ``'sphinx.ext.autodoc'`` into the list assigned to the +:confval:`extensions` config value. Then, you have a few additional directives +at your disposal. +|more| See :mod:`sphinx.ext.autodoc` for the complete description of the +features of autodoc. Topics to be covered -------------------- -- Autodoc -- Other Domains -- Basic configuration +- Other extensions (math, intersphinx, viewcode, doctest) - Static files - Selecting a theme - Templating -- cgit v1.2.1 From cb956ff5d88c609d2921a58d98835a7b744a4054 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 9 Mar 2010 19:38:49 +0100 Subject: Add minimal documentation for the C++ domain. --- doc/domains.rst | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/doc/domains.rst b/doc/domains.rst index de114c35..b8922920 100644 --- a/doc/domains.rst +++ b/doc/domains.rst @@ -438,6 +438,40 @@ defined in the documentation: Reference a C-language type. +The C++ Domain +-------------- + +The C++ domain (name **cpp**) supports documenting C++ projects. + +The following directives are available: + +.. directive:: .. cpp:class:: signatures + .. cpp:function:: signatures + .. cpp:member:: signatures + .. cpp:type:: signatures + + Describe a C++ object. Full signature specification is supported -- give the + signature as you would in the declaration. Example:: + + .. cpp:function:: const int IntArray::operator[] + + Describes the indexing operator of IntArrays. + +.. directive:: .. cpp:namespace:: namespace + + Select the current C++ namespace for the following objects. + +These roles link to the given object types: + +.. role:: cpp:class + cpp:func + cpp:member + cpp:type + + Reference a C++ object. You can give the full signature (and need to, for + overloaded functions.) + + The Standard Domain ------------------- -- cgit v1.2.1 From a7e982eeb5b5fce695790d9840c1b3ab8fd9e0be Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 14 Mar 2010 14:31:45 +0100 Subject: Add link to inline markup spec. --- doc/rest.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/rest.rst b/doc/rest.rst index 1194903a..856775e1 100644 --- a/doc/rest.rst +++ b/doc/rest.rst @@ -468,8 +468,10 @@ Gotchas There are some problems one commonly runs into while authoring reST documents: * **Separation of inline markup:** As said above, inline markup spans must be - separated from the surrounding text by non-word characters, you have to use - a backslash-escaped space to get around that. + separated from the surrounding text by non-word characters, you have to use a + backslash-escaped space to get around that. See `the reference + `_ + for the details. * **No nested inline markup:** Something like ``*see :func:`foo`*`` is not possible. -- cgit v1.2.1 From fc031d02297ccf1a6224fca0b0322236f310147d Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 14 Mar 2010 19:46:54 +0100 Subject: A bit more tutorial. --- doc/ext/autodoc.rst | 3 +++ doc/tutorial.rst | 30 +++++++++++++++++++++++------- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/doc/ext/autodoc.rst b/doc/ext/autodoc.rst index 08a54082..7e280b34 100644 --- a/doc/ext/autodoc.rst +++ b/doc/ext/autodoc.rst @@ -89,6 +89,9 @@ directive. .. autoclass:: Noodle :members: eat, slurp + * If you want to make the ``members`` option the default, see + :confval:`autodoc_default_flags`. + * Members without docstrings will be left out, unless you give the ``undoc-members`` flag option:: diff --git a/doc/tutorial.rst b/doc/tutorial.rst index 4ab8c06b..bb4116ba 100644 --- a/doc/tutorial.rst +++ b/doc/tutorial.rst @@ -90,10 +90,10 @@ Adding content In Sphinx source files, you can use most features of standard reStructuredText. There are also several features added by Sphinx. For example, you can add cross-file references in a portable way (which works for all output types) using -the :role:`ref` role. Here is a small example of how a source file could look -like:: +the :role:`ref` role. - XXX +For an example, if you are viewing the HTML version you can look at the source +for this document -- use the "Show Source" link in the sidebar. |more| See :ref:`rst-primer` for a more in-depth introduction to reStructuredText and :ref:`sphinxmarkup` for a full list of markup added by @@ -103,8 +103,9 @@ Sphinx. Running the build ----------------- -Now that you have added some files and content, let's build the docs. A build -is started with the :program:`sphinx-build` script, called like this:: +Now that you have added some files and content, let's make a first build of the +docs. A build is started with the :program:`sphinx-build` program, called like +this:: $ sphinx-build -b html sourcedir builddir @@ -220,12 +221,27 @@ the string ``'sphinx.ext.autodoc'`` into the list assigned to the :confval:`extensions` config value. Then, you have a few additional directives at your disposal. +For example, to document the function ``io.open()``, reading its +signature and docstring from the source file, you'd write this:: + + .. autofunction:: io.open + +You can also document whole classes or even modules automatically, using member +options for the auto directives, like :: + + .. automodule:: io + :members: + +autodoc needs to import your modules in order to extract the docstrings. +Therefore, you must add the appropriate path to :py:data:`sys.path` in your +:file:`conf.py`. + |more| See :mod:`sphinx.ext.autodoc` for the complete description of the features of autodoc. -Topics to be covered --------------------- +More topics to be covered +------------------------- - Other extensions (math, intersphinx, viewcode, doctest) - Static files -- cgit v1.2.1 From a329a4a4b874de99e942f7e94006dcaa4125dd85 Mon Sep 17 00:00:00 2001 From: Andi Albrecht Date: Mon, 15 Mar 2010 08:50:56 +0100 Subject: Rename 'Contents' to 'Table Of Contents' in agogo theme to match other themes (fixes issue #352). --- sphinx/themes/agogo/layout.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/themes/agogo/layout.html b/sphinx/themes/agogo/layout.html index 73636376..f1429770 100644 --- a/sphinx/themes/agogo/layout.html +++ b/sphinx/themes/agogo/layout.html @@ -42,7 +42,7 @@