From 25429bcdf4b9b53154f62cc0257c954e703be442 Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 28 Jun 2009 16:17:28 +0300 Subject: [PATCH 01/85] devhelp: produce better index term sub-items --- sphinx/builders/devhelp.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sphinx/builders/devhelp.py b/sphinx/builders/devhelp.py index ba117e622..c648bd27c 100644 --- a/sphinx/builders/devhelp.py +++ b/sphinx/builders/devhelp.py @@ -12,6 +12,7 @@ """ import os +import re import cgi import sys from os import path @@ -114,11 +115,14 @@ class DevhelpBuilder(StandaloneHTMLBuilder): else: for i, ref in enumerate(refs): etree.SubElement(functions, 'function', - name="%s [%d]" % (title, i), link=ref) + name="[%d] %s" % (i, title), + link=ref) if subitems: + parent_title = re.sub(r'\s*\(.*\)\s*$', '', title) for subitem in subitems: - write_index(subitem[0], subitem[1], []) + write_index("%s %s" % (parent_title, subitem[0]), + subitem[1], []) for (key, group) in index: for title, (refs, subitems) in group: From 53bdb60a77b19860ba782511af1fafc754aa70cf Mon Sep 17 00:00:00 2001 From: Pauli Virtanen Date: Sun, 28 Jun 2009 17:09:58 +0300 Subject: [PATCH 02/85] Fix cElementTree import --- sphinx/builders/devhelp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/builders/devhelp.py b/sphinx/builders/devhelp.py index c648bd27c..e5a9a453c 100644 --- a/sphinx/builders/devhelp.py +++ b/sphinx/builders/devhelp.py @@ -31,7 +31,7 @@ except ImportError: try: import elementtree.ElementTree as etree except ImportError: - import cElementTree.ElemenTree as etree + import cElementTree as etree try: import gzip From 2ea1de50a9414d4c1bc3a6633af6c8d1c5a2cfd6 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 29 Jun 2009 17:04:33 +0200 Subject: [PATCH 03/85] Introduce "domains". --- sphinx/application.py | 6 ++++ sphinx/directives/desc.py | 66 +++++++++++++++++++++++++++++---------- sphinx/domains.py | 17 ++++++++++ sphinx/environment.py | 25 ++++++++++++++- 4 files changed, 97 insertions(+), 17 deletions(-) create mode 100644 sphinx/domains.py diff --git a/sphinx/application.py b/sphinx/application.py index 412a55863..cfe37745b 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -23,6 +23,7 @@ import sphinx from sphinx.roles import xfileref_role, innernodetypes from sphinx.config import Config from sphinx.errors import SphinxError, SphinxWarning, ExtensionError +from sphinx.domains import domains from sphinx.builders import BUILTIN_BUILDERS from sphinx.directives import GenericDesc, Target, additional_xref_types from sphinx.environment import SphinxStandaloneReader @@ -298,6 +299,11 @@ class Sphinx(object): role = roles.GenericRole(name, nodeclass) roles.register_local_role(name, role) + def add_domain(self, domain): + if domain.name in domains: + raise ExtensionError('domain %s already registered' % domain.name) + domains[domain.name] = domain + def add_description_unit(self, directivename, rolename, indextemplate='', parse_node=None, ref_nodeclass=None): additional_xref_types[directivename] = (rolename, indextemplate, diff --git a/sphinx/directives/desc.py b/sphinx/directives/desc.py index db261a878..47c640b5e 100644 --- a/sphinx/directives/desc.py +++ b/sphinx/directives/desc.py @@ -14,6 +14,7 @@ from docutils import nodes from docutils.parsers.rst import directives from sphinx import addnodes +from sphinx.domains import Domain, domains from sphinx.util import ws_re from sphinx.util.compat import Directive, directive_dwim @@ -735,6 +736,24 @@ class Target(Directive): env.note_reftarget(rolename, fullname, targetname) return ret + +class DefaultDomain(Directive): + """ + Directive to (re-)set the default domain for this source file. + """ + + has_content = False + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = False + option_spec = {} + + def run(self): + env = self.state.document.settings.env + domain_name = arguments[0] + env.default_domain = domains.get(domain_name) + + # Note: the target directive is not registered here, it is used by the # application when registering additional xref types. @@ -750,22 +769,37 @@ additional_xref_types = { del _ +directives.register_directive('default-domain', directive_dwim(DefaultDomain)) directives.register_directive('describe', directive_dwim(DescDirective)) - -directives.register_directive('function', directive_dwim(ModulelevelDesc)) -directives.register_directive('data', directive_dwim(ModulelevelDesc)) -directives.register_directive('class', directive_dwim(ClasslikeDesc)) -directives.register_directive('exception', directive_dwim(ClasslikeDesc)) -directives.register_directive('method', directive_dwim(ClassmemberDesc)) -directives.register_directive('classmethod', directive_dwim(ClassmemberDesc)) -directives.register_directive('staticmethod', directive_dwim(ClassmemberDesc)) -directives.register_directive('attribute', directive_dwim(ClassmemberDesc)) - -directives.register_directive('cfunction', directive_dwim(CDesc)) -directives.register_directive('cmember', directive_dwim(CDesc)) -directives.register_directive('cmacro', directive_dwim(CDesc)) -directives.register_directive('ctype', directive_dwim(CDesc)) -directives.register_directive('cvar', directive_dwim(CDesc)) - directives.register_directive('cmdoption', directive_dwim(CmdoptionDesc)) directives.register_directive('envvar', directive_dwim(GenericDesc)) + + +class PythonDomain(Domain): + name = 'py' + label = 'Python' + directives = { + 'function': ModulelevelDesc, + 'data': ModulelevelDesc, + 'class': ClasslikeDesc, + 'exception': ClasslikeDesc, + 'method': ClassmemberDesc, + 'classmethod': ClassmemberDesc, + 'staticmethod': ClassmemberDesc, + 'attribute': ClassmemberDesc, + } + +class CDomain(Domain): + name = 'c' + label = 'C' + directives = { + 'function': CDesc, + 'member': CDesc, + 'macro': CDesc, + 'type': CDesc, + 'var': CDesc, + } + + +domains['py'] = PythonDomain +domains['c'] = CDomain diff --git a/sphinx/domains.py b/sphinx/domains.py new file mode 100644 index 000000000..0da073b1f --- /dev/null +++ b/sphinx/domains.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +""" + sphinx.domains + ~~~~~~~~~~~~~~ + + Support for domains, which are groupings of description directives + describing e.g. constructs of one programming language. + + :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +class Domain(object): + name = '' + directives = {} + label = '' + diff --git a/sphinx/environment.py b/sphinx/environment.py index 1f25553ec..78f65adb1 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -34,7 +34,7 @@ from docutils.io import FileInput, NullOutput from docutils.core import Publisher from docutils.utils import Reporter, relative_path from docutils.readers import standalone -from docutils.parsers.rst import roles +from docutils.parsers.rst import roles, directives from docutils.parsers.rst.languages import en as english from docutils.parsers.rst.directives.html import MetaBody from docutils.writers import UnfilteredWriter @@ -45,8 +45,11 @@ from sphinx import addnodes from sphinx.util import movefile, get_matching_docs, SEP, ustrftime, \ docname_join, FilenameUniqDict, url_re from sphinx.errors import SphinxError +from sphinx.domains import domains from sphinx.directives import additional_xref_types +orig_directive_function = directives.directive + default_settings = { 'embed_stylesheet': False, 'cloak_email_addresses': True, @@ -597,8 +600,28 @@ class BuildEnvironment: else: return data + # defaults to the global default, but can be re-set in a document + self.default_domain = domains.get(self.config.default_domain) + + # monkey-patch, so that domain directives take precedence + def directive(directive_name, language_module, document): + if ':' in directive_name: + domain_name, directive_name = directive_name.split(':', 1) + if domain_name in domains: + domain = domains[domain_name] + if directive_name in domain.directives: + return domain.directives[directive_name], [] + elif self.default_domain is not None: + directive = self.default_domain.directives.get(directive_name) + if directive is not None: + return directive, [] + return orig_directive_function(directive_name, language_module, + document) + directives.directive = directive + # publish manually pub = Publisher(reader=SphinxStandaloneReader(), + parser=SphinxRstParser(), writer=SphinxDummyWriter(), source_class=SphinxSourceClass, destination_class=NullOutput) From 5c6697e69f579250f52acbc3e88120d6bf1557a8 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 29 Jun 2009 17:16:05 +0200 Subject: [PATCH 04/85] Fix some oversights. --- sphinx/config.py | 1 + sphinx/domains.py | 1 + sphinx/environment.py | 1 - 3 files changed, 2 insertions(+), 1 deletion(-) diff --git a/sphinx/config.py b/sphinx/config.py index b2ef29fb1..912f3eb2e 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -55,6 +55,7 @@ class Config(object): modindex_common_prefix = ([], 'html'), rst_epilog = (None, 'env'), trim_doctest_flags = (True, 'env'), + default_domain = ('py', 'env'), # HTML options html_theme = ('default', 'html'), diff --git a/sphinx/domains.py b/sphinx/domains.py index 0da073b1f..aab31e236 100644 --- a/sphinx/domains.py +++ b/sphinx/domains.py @@ -15,3 +15,4 @@ class Domain(object): directives = {} label = '' +domains = {} diff --git a/sphinx/environment.py b/sphinx/environment.py index 78f65adb1..7990389fc 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -621,7 +621,6 @@ class BuildEnvironment: # publish manually pub = Publisher(reader=SphinxStandaloneReader(), - parser=SphinxRstParser(), writer=SphinxDummyWriter(), source_class=SphinxSourceClass, destination_class=NullOutput) From 43667a2a9cb483653c30ee4ffc5d55a326175304 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 29 Jun 2009 18:43:59 +0200 Subject: [PATCH 05/85] Remove filemodules attribute which isnt used anymore. --- sphinx/environment.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/sphinx/environment.py b/sphinx/environment.py index 7990389fc..d505229b1 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -62,7 +62,7 @@ default_settings = { # This is increased every time an environment attribute is added # or changed to properly invalidate pickle files. -ENV_VERSION = 30 +ENV_VERSION = 31 default_substitutions = set([ @@ -293,7 +293,6 @@ class BuildEnvironment: # X-ref target inventory self.descrefs = {} # fullname -> docname, desctype - self.filemodules = {} # docname -> [modules] self.modules = {} # modname -> docname, synopsis, # platform, deprecated self.labels = {} # labelname -> docname, labelid, sectionname @@ -353,7 +352,6 @@ class BuildEnvironment: self.toc_secnumbers.pop(docname, None) self.toc_num_entries.pop(docname, None) self.toctree_includes.pop(docname, None) - self.filemodules.pop(docname, None) self.indexentries.pop(docname, None) self.glob_toctrees.discard(docname) self.numbered_toctrees.discard(docname) @@ -984,7 +982,6 @@ class BuildEnvironment: def note_module(self, modname, synopsis, platform, deprecated): self.modules[modname] = (self.docname, synopsis, platform, deprecated) - self.filemodules.setdefault(self.docname, []).append(modname) def note_progoption(self, optname, labelid): self.progoptions[self.currprogram, optname] = (self.docname, labelid) From f82a4a4eab1b45ccf99a0b8c928553d4a59ab80d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 30 Jun 2009 15:16:33 +0100 Subject: [PATCH 06/85] Makefile and .hgignore ignore a virtualenv called "env" in this folder by default now. --- .hgignore | 1 + Makefile | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.hgignore b/.hgignore index f617875e8..dcfd914ed 100644 --- a/.hgignore +++ b/.hgignore @@ -9,3 +9,4 @@ Sphinx.egg-info/ doc/_build/ TAGS \.ropeproject/ +^env/ diff --git a/Makefile b/Makefile index dbe7c36e2..f405fddca 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ all: clean-pyc check test check: @$(PYTHON) utils/check_sources.py -i sphinx/style/jquery.js \ -i sphinx/pycode/pgen2 -i sphinx/util/smartypants.py \ - -i doc/_build -i ez_setup.py -i tests/path.py -i tests/coverage.py . + -i doc/_build -i ez_setup.py -i tests/path.py -i tests/coverage.py -i env . clean: clean-pyc clean-patchfiles From 957be3bfa05d7d524456442f326d82826f7c0b5e Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 30 Jun 2009 15:16:46 +0100 Subject: [PATCH 07/85] Refactored role registrations. --- sphinx/application.py | 12 +- sphinx/domains.py | 1 + sphinx/roles.py | 263 +++++++++++++++++++++++------------------- 3 files changed, 150 insertions(+), 126 deletions(-) diff --git a/sphinx/application.py b/sphinx/application.py index cfe37745b..9b6cf36f7 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -20,7 +20,7 @@ from docutils import nodes from docutils.parsers.rst import directives, roles import sphinx -from sphinx.roles import xfileref_role, innernodetypes +from sphinx.roles import make_xref_role, simple_link_func from sphinx.config import Config from sphinx.errors import SphinxError, SphinxWarning, ExtensionError from sphinx.domains import domains @@ -310,17 +310,15 @@ class Sphinx(object): parse_node) directives.register_directive(directivename, directive_dwim(GenericDesc)) - roles.register_local_role(rolename, xfileref_role) - if ref_nodeclass is not None: - innernodetypes[rolename] = ref_nodeclass + role_func = make_xref_role(simple_link_func, innernodeclass=ref_nodeclass) + roles.register_local_role(rolename, role_func) def add_crossref_type(self, directivename, rolename, indextemplate='', ref_nodeclass=None): additional_xref_types[directivename] = (rolename, indextemplate, None) directives.register_directive(directivename, directive_dwim(Target)) - roles.register_local_role(rolename, xfileref_role) - if ref_nodeclass is not None: - innernodetypes[rolename] = ref_nodeclass + role_func = make_xref_role(simple_link_func, innernodeclass=ref_nodeclass) + roles.register_local_role(rolename, role_func) def add_transform(self, transform): SphinxStandaloneReader.transforms.append(transform) diff --git a/sphinx/domains.py b/sphinx/domains.py index aab31e236..6845496c5 100644 --- a/sphinx/domains.py +++ b/sphinx/domains.py @@ -13,6 +13,7 @@ class Domain(object): name = '' directives = {} + roles = {} label = '' domains = {} diff --git a/sphinx/roles.py b/sphinx/roles.py index af574b98c..1a587cf26 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -94,106 +94,40 @@ roles.register_local_role('pep', indexmarkup_role) roles.register_local_role('rfc', indexmarkup_role) -# default is `literal` -innernodetypes = { - 'ref': nodes.emphasis, - 'term': nodes.emphasis, - 'token': nodes.strong, - 'envvar': nodes.strong, - 'download': nodes.strong, - 'option': addnodes.literal_emphasis, -} +def make_xref_role(link_func, nodeclass=None, innernodeclass=None): + if nodeclass is None: + nodeclass = addnodes.pending_xref + if innernodeclass is None: + innernodeclass = nodes.literal -def _fix_parens(typ, text, env): - if typ in ('func', 'meth', 'cfunc'): - if text.endswith('()'): - # remove parentheses - text = text[:-2] - if env.config.add_function_parentheses: - # add them back to all occurrences if configured - text += '()' - return text + def func(typ, rawtext, text, lineno, inliner, options={}, content=[]): + env = inliner.document.settings.env + if not typ: + typ = env.config.default_role + else: + typ = typ.lower() + text = utils.unescape(text) + # if the first character is a bang, don't cross-reference at all + if text[0:1] == '!': + text = _fix_parens(typ, text[1:], env) + return [innernodeclass(rawtext, text, classes=['xref'])], [] + # we want a cross-reference, create the reference node + pnode = nodeclass(rawtext, reftype=typ, refcaption=False, + modname=env.currmodule, classname=env.currclass) + # we may need the line number for warnings + pnode.line = lineno + target, title = link_func(env, text, pnode) + pnode['reftarget'] = target + pnode += innernodeclass(rawtext, title, classes=['xref']) + return [pnode], [] + return func -def xfileref_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): - env = inliner.document.settings.env - if not typ: - typ = env.config.default_role - else: - typ = typ.lower() - text = utils.unescape(text) - # if the first character is a bang, don't cross-reference at all - if text[0:1] == '!': - text = _fix_parens(typ, text[1:], env) - return [innernodetypes.get(typ, nodes.literal)( - rawtext, text, classes=['xref'])], [] - # we want a cross-reference, create the reference node - nodeclass = (typ == 'download') and addnodes.download_reference or \ - addnodes.pending_xref - pnode = nodeclass(rawtext, reftype=typ, refcaption=False, - modname=env.currmodule, classname=env.currclass) - # we may need the line number for warnings - pnode.line = lineno - # look if explicit title and target are given with `foo ` syntax - has_explicit_title, title, target = split_explicit_title(text) - if has_explicit_title: - pnode['refcaption'] = True - # special target for Python object cross-references - if typ in ('data', 'exc', 'func', 'class', 'const', 'attr', - 'meth', 'mod', 'obj'): - # fix-up parentheses in link title - 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 - title = _fix_parens(typ, title, env) - # if the first character is a tilde, don't display the module/class - # parts of the contents - if title[0:1] == '~': - title = title[1:] - dot = title.rfind('.') - if dot != -1: - title = title[dot+1:] - # remove parentheses from the target too - if target.endswith('()'): - target = target[:-2] - # if the first character is a dot, search more specific namespaces first - # else search builtins first - if target[0:1] == '.': - target = target[1:] - pnode['refspecific'] = True - # some other special cases for the target - elif typ == 'option': - program = env.currprogram - if not has_explicit_title: - if ' ' in title and not (title.startswith('/') or - title.startswith('-')): - program, target = re.split(' (?=-|--|/)', title, 1) - program = ws_re.sub('-', program) - target = target.strip() - elif ' ' in target: - program, target = re.split(' (?=-|--|/)', target, 1) - program = ws_re.sub('-', program) - pnode['refprogram'] = program - elif typ == 'term': - # normalize whitespace in definition terms (if the term reference is - # broken over a line, a newline will be in target) - target = ws_re.sub(' ', target).lower() - elif typ == 'ref': - # reST label names are always lowercased - target = ws_re.sub('', target).lower() - elif typ == 'cfunc': - # fix-up parens for C functions too - if not has_explicit_title: - title = _fix_parens(typ, title, env) - # remove parentheses from the target too - if target.endswith('()'): - target = target[:-2] - else: - # remove all whitespace to avoid referencing problems - target = ws_re.sub('', target) - pnode['reftarget'] = target - pnode += innernodetypes.get(typ, nodes.literal)(rawtext, title, - classes=['xref']) - return [pnode], [] + +def menusel_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): + return [nodes.emphasis( + rawtext, + utils.unescape(text).replace('-->', u'\N{TRIANGULAR BULLET}'))], [] + return role def menusel_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): @@ -232,30 +166,121 @@ def abbr_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): return [addnodes.abbreviation(abbr, abbr, explanation=expl)], [] +def normalize_func_parens(env, target, title): + if title.endswith('()'): + # remove parentheses + title = title[:-2] + if env.config.add_function_parentheses: + # add them back to all occurrences if configured + title += '()' + # remove parentheses from the target too + if target.endswith('()'): + target = target[:-2] + return target, title + + +def generic_link_func(env, text, pnode): + has_explicit_title, title, target = split_explicit_title(text) + if has_explicit_title: + pnode['refcaption'] = True + return target, title + + +def pyref_link_func(env, text, pnode): + has_explicit_title, title, target = split_explicit_title(text) + if has_explicit_title: + pnode['refcaption'] = True + # fix-up parentheses in link title + 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 + # if the first character is a tilde, don't display the module/class + # parts of the contents + if title[0:1] == '~': + title = title[1:] + dot = title.rfind('.') + if dot != -1: + title = title[dot+1:] + # if the first character is a dot, search more specific namespaces first + # else search builtins first + if target[0:1] == '.': + target = target[1:] + pnode['refspecific'] = True + return target, title + + +def pyref_callable_link_func(env, text, pnode): + target, title = pyref_link_func(env, text, pnode) + target, title = normalize_func_parens(env, target, title) + return target, title + + +def option_link_func(env, text, pnode): + has_explicit_title, title, target = split_explicit_title(text) + program = env.currprogram + if not has_explicit_title: + if ' ' in title and not (title.startswith('/') or + title.startswith('-')): + program, target = re.split(' (?=-|--|/)', title, 1) + program = ws_re.sub('-', program) + target = target.strip() + elif ' ' in target: + program, target = re.split(' (?=-|--|/)', target, 1) + program = ws_re.sub('-', program) + pnode['refprogram'] = program + return target, title + + +def simple_link_func(env, text, pnode): + # normalize all whitespace to avoid referencing problems + has_explicit_title, title, target = split_explicit_title(text) + target = ws_re.sub(' ', target) + return target, title + + +def lowercase_link_func(env, text, pnode): + target, title = simple_link_func(env, text, pnode) + return target.lower(), title + + +def cfunc_link_func(env, text, pnode): + has_explicit_title, title, target = split_explicit_title(text) + if not has_explicit_title: + target, title = normalize_func_parens(env, target, title) + # remove parentheses from the target too + if target.endswith('()'): + target = target[:-2] + return target, title + + +generic_pyref_role = make_xref_role(pyref_link_func) +callable_pyref_role = make_xref_role(pyref_callable_link_func) +simple_xref_role = make_xref_role(simple_link_func) + specific_docroles = { - 'data': xfileref_role, - 'exc': xfileref_role, - 'func': xfileref_role, - 'class': xfileref_role, - 'const': xfileref_role, - 'attr': xfileref_role, - 'meth': xfileref_role, - 'obj': xfileref_role, - 'cfunc' : xfileref_role, - 'cmember': xfileref_role, - 'cdata': xfileref_role, - 'ctype': xfileref_role, - 'cmacro': xfileref_role, + 'data': generic_pyref_role, + 'exc': generic_pyref_role, + 'func': callable_pyref_role, + 'class': generic_pyref_role, + 'const': generic_pyref_role, + 'attr': generic_pyref_role, + 'meth': callable_pyref_role, + 'mod': generic_pyref_role, + 'obj': generic_pyref_role, - 'mod': xfileref_role, + 'keyword': simple_xref_role, + 'ref': make_xref_role(lowercase_link_func, None, nodes.emphasis), + 'token': simple_xref_role, + 'term': make_xref_role(lowercase_link_func, None, nodes.emphasis), + 'option': make_xref_role(option_link_func, None, addnodes.literal_emphasis), + 'doc': simple_xref_role, + 'download': make_xref_role(simple_link_func, addnodes.download_reference), - 'keyword': xfileref_role, - 'ref': xfileref_role, - 'token': xfileref_role, - 'term': xfileref_role, - 'option': xfileref_role, - 'doc': xfileref_role, - 'download': xfileref_role, + 'cmember': simple_xref_role, + 'cmacro': simple_xref_role, + 'cfunc' : make_xref_role(cfunc_link_func), + 'cdata': simple_xref_role, + 'ctype': simple_xref_role, 'menuselection': menusel_role, 'file': emph_literal_role, From 09d234df20d9fd93d05a703576ab95c7f6e9f49d Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 30 Jun 2009 18:11:57 +0200 Subject: [PATCH 08/85] Fix duplicate code. --- sphinx/roles.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/sphinx/roles.py b/sphinx/roles.py index 1a587cf26..fce349632 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -130,12 +130,6 @@ def menusel_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): return role -def menusel_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): - return [nodes.emphasis( - rawtext, - utils.unescape(text).replace('-->', u'\N{TRIANGULAR BULLET}'))], [] - - _litvar_re = re.compile('{([^}]+)}') def emph_literal_role(typ, rawtext, text, lineno, inliner, @@ -247,9 +241,6 @@ def cfunc_link_func(env, text, pnode): has_explicit_title, title, target = split_explicit_title(text) if not has_explicit_title: target, title = normalize_func_parens(env, target, title) - # remove parentheses from the target too - if target.endswith('()'): - target = target[:-2] return target, title From be192d9c68ac657ab39a8ead08b749ed12abbdd2 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 30 Jun 2009 17:34:02 +0100 Subject: [PATCH 09/85] Added a new theme inspired by the jinja / werkzeug theme. --- .hgignore | 2 + sphinx/themes/scrolls/artwork/logo.svg | 107 +++++ sphinx/themes/scrolls/genindex.html | 36 ++ sphinx/themes/scrolls/layout.html | 96 +++++ 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/darkmetal.png | Bin 0 -> 44361 bytes sphinx/themes/scrolls/static/headerbg.png | Bin 0 -> 298 bytes sphinx/themes/scrolls/static/logo.png | Bin 0 -> 11078 bytes sphinx/themes/scrolls/static/metal.png | Bin 0 -> 21543 bytes sphinx/themes/scrolls/static/navigation.png | Bin 0 -> 217 bytes sphinx/themes/scrolls/static/print.css | 5 + sphinx/themes/scrolls/static/style.css_t | 398 ++++++++++++++++++ sphinx/themes/scrolls/static/theme_extras.js | 26 ++ sphinx/themes/scrolls/static/watermark.png | Bin 0 -> 107625 bytes .../themes/scrolls/static/watermark_blur.png | Bin 0 -> 14470 bytes sphinx/themes/scrolls/theme.conf | 11 + 19 files changed, 772 insertions(+) create mode 100644 sphinx/themes/scrolls/artwork/logo.svg create mode 100644 sphinx/themes/scrolls/genindex.html create mode 100644 sphinx/themes/scrolls/layout.html create mode 100644 sphinx/themes/scrolls/modindex.html create mode 100644 sphinx/themes/scrolls/opensearch.xml create mode 100644 sphinx/themes/scrolls/page.html create mode 100644 sphinx/themes/scrolls/search.html create mode 100644 sphinx/themes/scrolls/static/darkmetal.png create mode 100644 sphinx/themes/scrolls/static/headerbg.png create mode 100644 sphinx/themes/scrolls/static/logo.png create mode 100644 sphinx/themes/scrolls/static/metal.png create mode 100644 sphinx/themes/scrolls/static/navigation.png create mode 100644 sphinx/themes/scrolls/static/print.css create mode 100644 sphinx/themes/scrolls/static/style.css_t create mode 100644 sphinx/themes/scrolls/static/theme_extras.js create mode 100644 sphinx/themes/scrolls/static/watermark.png create mode 100644 sphinx/themes/scrolls/static/watermark_blur.png create mode 100644 sphinx/themes/scrolls/theme.conf diff --git a/.hgignore b/.hgignore index f617875e8..7bd2ee92f 100644 --- a/.hgignore +++ b/.hgignore @@ -9,3 +9,5 @@ Sphinx.egg-info/ doc/_build/ TAGS \.ropeproject/ +^env/ +\.DS_Store$ diff --git a/sphinx/themes/scrolls/artwork/logo.svg b/sphinx/themes/scrolls/artwork/logo.svg new file mode 100644 index 000000000..0907a4ea3 --- /dev/null +++ b/sphinx/themes/scrolls/artwork/logo.svg @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + Project + + diff --git a/sphinx/themes/scrolls/genindex.html b/sphinx/themes/scrolls/genindex.html new file mode 100644 index 000000000..9add6e952 --- /dev/null +++ b/sphinx/themes/scrolls/genindex.html @@ -0,0 +1,36 @@ +{% 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 new file mode 100644 index 000000000..d1c66ae2f --- /dev/null +++ b/sphinx/themes/scrolls/layout.html @@ -0,0 +1,96 @@ + + + + {%- 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 %} + + +
+ +
+ {%- if prev %} + « {{ prev.title }} | + {%- endif %} + {{ title }} + {%- if next %} + | {{ next.title }} » + {%- endif %} +
+
+ {%- if display_toc %} +
+

Table Of Contents

+ {{ toc }} +
+ {%- endif %} + {% block body %}{% endblock %} +
+
+ + + diff --git a/sphinx/themes/scrolls/modindex.html b/sphinx/themes/scrolls/modindex.html new file mode 100644 index 000000000..314ebdd91 --- /dev/null +++ b/sphinx/themes/scrolls/modindex.html @@ -0,0 +1,43 @@ +{% 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 new file mode 100644 index 000000000..9f2fa4274 --- /dev/null +++ b/sphinx/themes/scrolls/opensearch.xml @@ -0,0 +1,9 @@ + + + {{ project }} + Search {{ docstitle }} + utf-8 + + {{ docstitle }} + diff --git a/sphinx/themes/scrolls/page.html b/sphinx/themes/scrolls/page.html new file mode 100644 index 000000000..ee6cad3dc --- /dev/null +++ b/sphinx/themes/scrolls/page.html @@ -0,0 +1,4 @@ +{% extends 'layout.html' %} +{% block body %} + {{ body }} +{% endblock %} diff --git a/sphinx/themes/scrolls/search.html b/sphinx/themes/scrolls/search.html new file mode 100644 index 000000000..0c942b70f --- /dev/null +++ b/sphinx/themes/scrolls/search.html @@ -0,0 +1,35 @@ +{% 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/darkmetal.png b/sphinx/themes/scrolls/static/darkmetal.png new file mode 100644 index 0000000000000000000000000000000000000000..e8c9ff62a10a18a4f1439af04ef8516f9457741e GIT binary patch literal 44361 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 literal 0 HcmV?d00001 diff --git a/sphinx/themes/scrolls/static/logo.png b/sphinx/themes/scrolls/static/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..d1961cf090d9f49b0c3f1495edea4b7d8cc85c87 GIT binary patch literal 11078 zcmaiabyyW|(Du^Zq0-&m4d)2b4Fb~L4bsxx-5^~`3ew#iM5S9Aq+9yi-+O(3y#Kv> zo!!08=IlH(^V~D{M60RDVxg0u0|0;}FDI=5?nl8@4HX&O8aRs7gF8fbsJs>`xcH-5 zM1lX&T;%lJ!RzP$Tj8Kgx!&NxHy$#&9-7Wp9$uzymVlR+7l*Bry}P-oizSD%n|1Dq z7zqGS0rJuiE$_VJ03UzSo$QxeA8QZ2#x2&Ad|jI0q(M07J{_FGK?8Tu&Ic+a^l$nK z-=z*#Px>>;qM3A9;SlI|%!7Dk|zn6@B|gh>xR|Aha=RDjJzSSV1?DlYM*l z#7!%k;kl@?c@^1RxKChv)qN^PJLR=@D{O3LrKJWzx}&!G5QNSml%h^8heSjO38x~` zQbQyP0ng!Sp=v;oWYqrmiN8u1E~Sv^Zw$Z)ybyM;8{h;O&v*u`$DzOPi~Hj!kE=Xw zHNXI24I-)^`#B2J1`K|@@I#|aff|-=?}yD7{Scd+_c*zTv8Jhi5Etmq5M1CY&35$w zCS$%}Tr3L~G66IwHrfi{kBv5H0ksRm?TFroH=Gxw zfNx;^N7|343ceM-UcMgpMyZ}q%AFXk6J~%RCH&ge5?h#K#`EdU7UzB6x>y40e=kQe zfpfsO0aOrwZYK32^ll|diKq3YL!-o{zrCBk-osv$XZe@gZ%P|gZfgX{4isSV0TY09 zmN3wZ(R-ZK%TItej4dotd3bM({V2# z;spqy^@>?Dob@sEu`TfSLqtzKHj=hz4Ci0?rW?ta_y5K}MD3db6`_3pF1dsTOPZ+BbS% z0~A8&e}qUFlB7Zm(AOT~272ROS$`1?QS6e$28Wl28=il_UXe)f_2c3Lg_7-h>tt7uWlner@!!K%|TBc|gsz&8* zpl}i$2G2QF>M-@D>207#Ee(weKGB|iiYz&Loh|9Y5$*G zaj#9*|J)ftqdHG7130d7&@7Tm0^T7C^@C3+RFn?X(02VKAPfY(S+-KB)o_VJRRyjR zH4oOwV>yw}5b?Un(U9I@H0R<@)*RlO$jXioX^h{NP9I>E+hSWd-NR}OXwu&14WLDm zQOJei80{W{mtLi1*I}twW!5Bn@)B+pR|TU}5!FO(P(^J3x7zAyJuESe zWd*ag?8(-SgQ?#J?PZWvf4u-{_Bck9a-m>1ME7?pjjA7 zUy*7PRp12!x~WI&bcj#yP;(H@43pJ9+q^VBQi0YPZNrXdcyR8WLnuRSQj8|}@Un6{ zlwfJ}W3qs*m>OWfqlY^}I@1BB;1&=~VrvgwWoy^#Nj4Gq+Nk!I1iz^NucGR-6(ApCf1X71p1`}xtows`2HyBWHt*Z;H30b zRP~Y~%f#lYhJq%lB|IeSeE9#16CU-fsy$SFv_s|Y!z-ub+?f+qpr+lRrEr4cZ7BT2 zaE&tW86X(s&aW&=pRM%VyWg46w-4Z@(rZ?wg)%~WI6%bJ6pNCF=Lk5D)96`q{ zJG`%}a7%E&{becrI_pG;H~d8RF#!6rtC}SN37FV4V%`x4oyBcgVa(R*d2Az-M-r5> z|D(ZgI~pco<4n4!lAn^b)ldZR+RTBz-qUg6+q8lx&_gP0m(CA7?pX%7oUVMklw*q1 zMBn$+9=@o52`;yl03drr@VF7jU5P*z!e``<60!kB+@L&P5AR>nnRV20P0@hGS<(tQ zCYMo%8N;sg962T~53Z4;1o~Qn5Q#db;x|C)c2?R2aS%z%$79F4flCyISWCs-&C)HJ zRUiSC)mXw=1VyJWZkO zgrWG-U&6Q&vuv@%b}7*%?>;XGybc(dit zC1OLs#OL$)*7LUHqaiDBe}l8ksTxmGJ{<;E3Sh$T|8+=o%KvfKZhmebT~91E?J+#o zt8VimtG@3*T}c9dKvxF+DJlxW`oUuEzpe6lugxBcWn|5&h(CNkqOnXDQ{=qp2ww}o z=Gh)C!Qaq?{rgBq$mDBoEhUXp$@0`i(3Om16}**gxmZ%oqB6CTq0vZzhBW@;GW#ay z!~C@=pkteJb8W%n(^Fj?3z|Xzbum*6KJpn=V@g&dktg%cx4fu?p~L0&+84L+P$3hI zDdi55?&Lo#(x>YVl!WY((Dy-D5R$>&O0#%6;@WOn@6lg^?=3RuH7-6)Y<7UAAP5?Ysf=reem{!l*9e;9q?)&QM!(5p3uzGp z(%)z&C-qd?UP~1Y;%KLH(9w7$TWw|NesvV=ij-%ggAce0J?-3mI@NQ7YSzC8f-J}K zVUKrXqX9qDR1D|W5nYyIh*&9H+6*!=lGj7NR9A~;;x3Nj+UFG9tQ~m#h$x!tb(tl$ z-*hvA1Ju4G*K^K3)ymX3`?*XI2>Lh7{7X=N-m7NGz=I^Gd{@=k1&!W=dcQ|Yn+-;m zn)1LF+w%td{uBUc?j0CP=}0r^v8+CswDVYiVcD6w*pEo2QxmnCAMB26i)X*zwTQV6Fk-~dlgAp< zAgiYv}1ep2&0ZsgLtq4~YuHN5h%dz9seDR>S zZ*lpz0NATcZPZ1_%WsMzk60XR`hmcYB1;o(&zzeam zOi&yP%^13k2z_qWb9`k*7RH(}Il>-{fOC)|HOo594+ZhcD^$oKp$u7}qi0G=N4ENm z=sLOLN)^);H+5w-Y&sr;K-kXdsi6)t!MPVxE}D_1sP}|tglqu9h>91FSBl%{fn~KT z#LJkyXtB%Zus({4cMN|<9Hav1!(9RUV{Z7hNG{V_J08&c(1e3_w#w=l=Pw;si*ih7 zRzGxZzqiQrY%5-`yshUT^^B|rOa@7_zkV32L-6)0FDfB@glgMb!VS6qMm+c%KlRZc zk=5O4lzL*5GA#6&)A~DO@e~>)9^hisiWR2zL+i)PD(AO6ZA+EC2#~?6K!xzPt@E18 za_{trX0&Rn@BdxNs>gp#E9b@5Q0w%5mTVh$uyLCd=Kn-DOg2!I1(D`}l%~lsq;|kv z@w$`UOgc@=u4Z4PjT0jwi(EmkNPg*P;b=QugW8f}rUy8AB8 z)<#dpWES zow~YPSy!xx_X%d9U_S$F+bfue=2TaxT5koh|f-uqorMTIr-#j}*Tmdd5y20edD zUgdw(RBpcQn(SLLvH6I#BHZ@}2JoVPNCdH0a}2a_Tz45T2@E6)ctRUYWuBS5pk2@n zWmyj{T-Ote6ME{YWV(7I6DNqo`WAyHp6i@^}fX_|hcI*;c8Ijtoc>G#^>E>vK`VA=?C%T&i(5S?xGwZ2{ zf`bKQnh?tmtv3S|8(PP&#hNy-#LnbUKAtES2>f;O*FaB{l?yerdU*$I_1l7)GGaEE z*FmYSC93}3M@TW0gsw%s@s31dHsx}6?$q08y|cDSTtsEKcI}`1;e_Uq&OHfrIH^+!PS-=kS$aoc zIF^0{UK2?l;YB}6op=V4j=xJDk+tixj!xs*^Gv0k?LpB8Bw}(d536TEs#$u~1Mp;%gUid-ns|hXIl9k56}MVf!E?&vhF)OrY=n%;b@8tLJ%ma*t)?Q$yJi zx0uvo6bR@IuLv4b%8ZasZ5?TT{L`eJl0}wDxI#iIk%Hhp92xE{PZ^hMXa<{c0rAK?gIt}P=RvAQK?Lne98|+X%`L^ zDKUsYjlVpd$L?}W3Tt^FyQ)o=#x+;fYFP8n-02YW=sfT5O>K@TBl}tsWDzpWp{~56 z?0`W~g^kGd{D&t4N9<wiloQXP?-<*te7x;!j(S>6+jRAav_4mNXngMjN0B;QA|7`OMm z=HF3!VL#ta*h&iMBB?P@K|0yr*E#`rrjMUur4xxF2#o+tnesmZ4?pF)P)7q?vftwJ zU@?omPHW<_)5V2;(IT`H_-)$?gMhzYZeDbsKe{K1_1fe9n4bqRq0%!83$5NJCMHkg z>8yd~dUMufBPQH&vFT$p<9!Tr=mL=O4-9rWH&2!|Cm{uhN-kbemAKa9Q4u zme}L47*yyLkEh-p;bmwTi&7K2V7gYzq}ctUx+!X7i5|ZH%g))=e*94iW~K21ZbAaE zag3d-p?C>>5RVN}x^Z`GS%~$yW?1WTgCnLCU-o`3Bj=~0dv9h>de=EG+YQAKz5Ev&QDQwv zb0KaXN%D*>X;SDdW8b$^rKZ0Hn(r!R3okdCrTyKzuG})~8nyE6dsEuX{sCjitEh^C zDonNcr&{JZlR2}*)woF5tkW00X55$hT`k>=M<1d6sV}pqc8;;=fROL=%6fOF^Lxz* zGWRq?p%-!4jp_*%6yC%f21&QgloF1v9SrO9Oh=&?K7NKyz z>F4@e?=!MX0Q!6p7`L3g*v?I<_uo@9{adau9;gu)dzYZc7c?|k(t2FT33+P>lrwvw zL$0Aor}-1F{>0$GhYryG+c`Co?%f-EG(0Q1bm$0uw!;zmF51tPrTgq`{jg-Lbd$r& z)GRs51fWv?xqX<;02e7Lm#Hzv!pF{X+Y!QjUV%^x{YJ9GSwx$h7P;wfmyFDUCIqDx zbQR^c-?=Zr+*?oBn25jP+N#s?H zHks7h8cT-5bGhEBFo(!V$t|OcZaNjPhd1Ele-qs^v+x z-f^wLH-p3z9PY)NI4@|e{czS0yA~b$BYS2?39T%H@xUO}F(BaSixgG8+j@6Epi!IK zPH2YZ>gPk>mZo=OPNy(B1~PX8eWx>^O8pwqI3eFmZCx9;{b zlqXwCorcwlAu1=M)_lsO$Y+E>l<*6H zA2M@m#0=lDh}CmmT4SwLDn4S-w0q+5c1gW`lxle3O;xseih&79VX>5hY3=XOslcOE zn=g_{9JKVeq!BwP1}91#yNk1xRV}nEWX63)z<$EeIyIvB=j!6?Db6FQm1=mxj;TCk z%D?sq8Mfo=+?P52$qo6(zmmZmDo~-}U2Q!fr4oHm=Jz9xs=8S%KYQOBhUZGAQp?W8 zKp$7p`*#6Bc9J`}qzGzhzjIS3aRmDnZPBCv#KaL^ohSs!xl7LD-Zi|_1#-<(W%HfJ zJCogSmgiGEuIA!?1eT(z%8DdRMT{!QGJ#xH0*#uM?i;rct44KR&%M392&_7_{(X&K z5?Ss7g$aTh&4yw`31!+~J8qMBP}CO6;I{xeQW6#~FSk3YR8$l(?k^P65x-+V&F20e zDlccU1~KdvoZHh^6fl=#q-IL-d+a?*q71>B`3*py>6}_`F!SnLEssPeHVrk#orh$9 z7b11K1Iy#TfAcxHDI&ZERrR8d`k{u21`}{TUcg|sTHZ@4K^2^Dv>m^O`9m7=?96K3 zj{ah08$w|S-taznaL}`#V9_Sp%1918UO`iA2vnGjE3uxJFu2}{>@yL1aoDaZ&Q==4 zv2l!1l&6!$J6rDAQp^`MMK7eSi8e9k&+025Q{}y6Y?NkNmf=d_i2$}EPLH z4xQcNk6YVMaLH3*5H{}{?_6>EzP@$-?=t*5uRC=98md@!4C^e8^7l#Hnc#!9R0|Kr z?6i#8`Mvoys9Wq?7v16X;gT|@c;>;smcN=t>$_t3M0zfgFF21Fr>95~g2_7q0O9*M zfJS3iDg$XTle|5 zD7mPN0{}R=<0|G(zcV!SJlz%a6J*o>{d@Pa{X*}7p|`B%GXtyJ!k%ZaZiu`T`YVcn zi)QQRo$8*6+dnMwxDzR&c`i2(;nnIg@G zJvIJLYJ~x`q{ZkB-N7T2UAF{#@KRH2gA-tZENi^gv+40O1%eWV+{V!Jv$PQbskb+V4&vD3n2{~oa$#5IyaV$bhRyST z1zIf4k`LZ<4`z3L%XuVv*%E;a#YE7kIxrm=s!G#!@ue;f!`++*x^QNU`tdw^Ig^tImAC0R5BA9YnU0(&TR;eIFVZP4G`!$OhmWqVM%~wkeed zi|_hM%@!dj8JllkOn4sPH0m9!nZFp2L`0@0d_f1CY#n2Fb30SE z(?=QmEfXOkBJ!!#bRa?#Ye1JzOzbf+ARu7sXuT&;hbCDrt6cb9hy~ALrW{>CBbz-E z-jKUBG4l>pkXhUEGWRo%OI+=tczf-7>EXQ@jfgU;Yeg}Wvq(6}FbTf80_wRekU3Br zk|M63@L7tC7|)PFS})syz(mgtWOfew`+7aYmn;scc6zzC-8p+&j_qd28Yu}s&TZz+ zwe0tsJ%KQ;iF!t}%sk0W-xd6r0$7!)Ugs3oc4|pgvT8@Xgmt!DJ)yCt{j%~zzLh4! zsQgHHJPEg$fM{GhDQVUW&Yta(rcJtIWYgAsvKrNv$tvx_Tryo7Vk*y9_}68qDL)uw zPq1naeLG7^wu-Pw1v;1cCXu40H)Hzx`WR7=kw0Q#V>>BUOlLZ;w7L>G#3twPJ4JY) z5el4x!9v2~jy0=oN#0}J|0SdLaQ?9U6C2)FeCSFE)3`u|bQQKAsdYqGo9f3jR}v(& zkj4zoHZJ8h8HH{(GRzg7%6QGfs-ri9T-)!gJ_Vkx^{ux}Ka5`!wwHTxUJo2`}c|H_I{ien{Z5bAj)jM5ge9tixfk{CekS!V}8o z78#;6`J`kml81Y2X;SXcT&sDB^qAWoh(n#!yH({`rN8ZSEj9`C@g3-TSL}*Trn_|( z5(E>I*+I(0ELmyAI4K%T*L;e+O>DauV^Z_ic!u+V-C(jHFwvNhp}}#ozduU2hmM~- zN*QsB5&}{#;#qSUBhTWJBXC8N};hIF(crk=;aOg3%kMb_-U0iSQV% zvn-KGBWB9BTfnKoA$s>(YZue(wTAUBFKtn;iM8Jhm9C^aX+Q~J!mKUV@prx1=GqZQ zSnxoI?L)vadHDx-*AMyhfK<9XTO5q#H9k6wC)TyZwoJ_$>>~dm+q}|;>H@viw*bXTd>5$wx71JK(}292b6E3 zNbP|NXB70bDk5Xe7=qChMz`R)K~;g}vAwa9(ag9rPq$xi0L(!3#!1Jgn0F-KcuH6+ z^qgDE=k02AeN3hQQ|K{OpCs31_+nrwO(j7wA4EyBdq2rs=^b@EX+TwhyK)YDD;|kfmnVPKi-$C>kPyo*PWFfI&^=Voc;b3HC_*wCHEplSj`wp`%-*=)A>Yq+IA!fU=(;%cvzdKCcBzyH?@5YH4vzan%gjmfi>K&BpeDnDXY za`MMOG{>h;%(&Wmpr7|tE_gABmk3QDd%=L`YZxjq=-G@s- z%1FohTUitbpLG=Z8ATBnb^l!xDk__ z3rprZ*cX2O|0;etc8(nfk5XorTdptt^@J3qtMO~!I=G8RlcUVy^Q_T0^~Qq>vlj-{ zYbjP-97+FLDPy)}rbHq~YCZ&Az=omaoVR7|lmtA@JUiK%>|^2LQQkLK+|TWt;l^F{ zSTICpv7}-OC6#<OUbo z^t(x+2k$}xAnGX#M5O-W_~>!&;~p)t53jyJBA<^*FmbDn^*yinG=oM zUpGF#5TsI}ME!&!Ch;0s9p?wzj_Z)QZ8z6@w$vSJS^UkPjh^JZ(^y65cU{oykNj5i zDUNKA!OpvGmSiDZ^kv7PbL-#vdwXV|1+MMO40_3{b;&19bwjJiyOI+9g4UwR)HOfZ z$+H@9C-cCZ1$e0zOgnN+9d@1~_Cb6jd*E`VFc-7IEL1xx`cd5(wc+)1AbEq_88v`sh6%u=J258hYZPE!g8^!Zrl39*W951mgr z!2jC?gB;(4Ai*?wZd0`$;ghJxE>;^Cy`F15!&$mQz2W1G%)7 zN1`n*UEV?AHAzs{x-7E;ZE+DBXRGY=H{teL)pBX;fR-a^zCPEtVNJ)%+Sq?UqEzUh z6+zW_4=qrhj&MJ3u&wEr$4xbX%T4w9C;sWQ+|unP5Wz3bfAe`-l7$mP2f(5j4jUiv z8YBl8I?SV&wY6>Zu5D5?hVp@R3T)(IdK~U5<;4vOpeLN?=ORTsLTiU8v1(QIzXVHp zOzgzZ<9iWPq7qa=$9d_6AU`5H@mdi1kMCYMICWtGFRIT8-it?_NibHJNh_RK?AC$y0l)l44MWr} zIjJNgX*gpnFZYcfAf=t&`JRVS;l{BAIvUQYG$p9`D z&5! zIn1`v6E|XcpAsJZl^1IeWI^yWEIv(1)5&Vr9pu)+?%Pt4c;B(b-f~@rLA@jKcbEleoycPsTK}F(>(UPX<7QVpNlKYl@x(cSKZ6G|6v47> z4A8h5q$ZiE$uK*kXof1vTnw{5@~@dyHj&w<24J*KKWLg9Qpon@V@6~pL$Sdr)G0HC zb6j9gg+QM9u5F;p*-xGvu~&$kE|)H+oFDI3Vl>krY#9(6f=vP5!kX{2rukU@&^;c~ zZLh>2bmMZlrn_w< z>Gs9smF2}-EBNvcSelb7Q*qT=UOf~0>r@N>b_pX$5ry)}G5(4jNWKCo_chuwzJJtK z+af_Y!sFQxp>=%wyNewi{9lt!-#fCSqOxBpGD}jG9c5}TWN;c61Q~kW`UvIjSac~V zIk5z?Gjh@ae~_$h;Lmv6-$o!xd%)v;o#i`50*t3UpQeQ8$4049@$xPnO{^#VA#{Wb z!f1En@XH;R0Ze3=N~3*}22@rPxl47)pm|^uDE!FGF1f78Q~FPd1f8+ z;$lg_gl8^A$K-)y1^AP8Hm2Rl%j28vgB>IcjhY5aQbDQedsN|UO9OvA@bjBa;+4@rw`} z%{?xVX?qa>dmMY~gD}M>{6A1&u&*vvEXVQ(d#mZsb(-<>&^QbYkG7+$af5);*XnNT z1ix&m8ViLN5^T50w_m?DmVkD)^z_-_5Du=W4Ryauw^_N1widm zTLTsn_7wpRU?6bNg?tJN4Nm;=4KosbhpmB0wTdD&y93T&cm7%_MJ@93{!~d+d0Gre zpfBEv-q!*c`>;is({># z4A59+&^o(8N_Ts<&6!KhmmY)#00qcrzhIw&BM}nTY4v_sNnmrIn(fAxrWGBXhT%GQ zo%LgAF2R2e`A#`bEhiYHgf&S482|Qss)+nh9}1^DodJ03DhLV*Z$_^!LB*&)07` zCaHs(=pF=>r#~r4OG1BO>w^8Am^-)@U;`h)P3D7ssHS+C^7J=moz}`f$B`)_U>9g- nK6kU(S0b%5f&Z%=^aZCJ*Vc`dMC&!!^9snzs7TjBO+x-3vCc5x literal 0 HcmV?d00001 diff --git a/sphinx/themes/scrolls/static/metal.png b/sphinx/themes/scrolls/static/metal.png new file mode 100644 index 0000000000000000000000000000000000000000..97166f131933d6808f8098ba09075c4f973dbe6c GIT binary patch literal 21543 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 NPFq9&ezh7R;y>T5HH!cM literal 0 HcmV?d00001 diff --git a/sphinx/themes/scrolls/static/navigation.png b/sphinx/themes/scrolls/static/navigation.png new file mode 100644 index 0000000000000000000000000000000000000000..1e248d4d755d58f2853b3d2b9ffab262ba2580c9 GIT binary patch literal 217 zcmeAS@N?(olHy`uVBq!ia0vp^j6kfx!2~2XTwzxL2^0spJ29*~C-V}>;VkfoEM{Qf z76xHPhFNnYfP(BLp1!W^H(1#?)wo;Ux8?$cBuiW)N}Tg^b5rw57@Uhz6H8K46v{J8 zG8EiBeFMT9`NV;W+&oSK!JzF5va%UOMF_{=LUyQn{?j#u3pt+cxkl_0X34dAH}yUGwerZ-)GxITKIalimh2k-^i|&t;uc GLK6TV%tagk literal 0 HcmV?d00001 diff --git a/sphinx/themes/scrolls/static/print.css b/sphinx/themes/scrolls/static/print.css new file mode 100644 index 000000000..fb633d879 --- /dev/null +++ b/sphinx/themes/scrolls/static/print.css @@ -0,0 +1,5 @@ +div.header, div.relnav, #toc { display: none; } +#contentwrapper { padding: 0; margin: 0; border: none; } +body { color: black; background-color: white; } +div.footer { border-top: 1px solid #888; color: #888; margin-top: 1cm; } +div.footer a { text-decoration: none; } diff --git a/sphinx/themes/scrolls/static/style.css_t b/sphinx/themes/scrolls/static/style.css_t new file mode 100644 index 000000000..0c346d04a --- /dev/null +++ b/sphinx/themes/scrolls/static/style.css_t @@ -0,0 +1,398 @@ +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; +} diff --git a/sphinx/themes/scrolls/static/theme_extras.js b/sphinx/themes/scrolls/static/theme_extras.js new file mode 100644 index 000000000..1c0421878 --- /dev/null +++ b/sphinx/themes/scrolls/static/theme_extras.js @@ -0,0 +1,26 @@ +$(function() { + + var + toc = $('#toc').show(), + items = $('#toc > ul').hide(); + + $('#toc h3') + .click(function() { + if (items.is(':visible')) { + items.animate({ + height: 'hide', + opacity: 'hide' + }, 300, function() { + toc.removeClass('expandedtoc'); + }); + } + else { + items.animate({ + height: 'show', + opacity: 'show' + }, 400); + toc.addClass('expandedtoc'); + } + }); + +}); diff --git a/sphinx/themes/scrolls/static/watermark.png b/sphinx/themes/scrolls/static/watermark.png new file mode 100644 index 0000000000000000000000000000000000000000..eb1b6be957b2f137a0667b35f43c0d4cb6291cf6 GIT binary patch literal 107625 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 Ii5my~Kde%1k^lez literal 0 HcmV?d00001 diff --git a/sphinx/themes/scrolls/static/watermark_blur.png b/sphinx/themes/scrolls/static/watermark_blur.png new file mode 100644 index 0000000000000000000000000000000000000000..563f6cde06f058a9e1dcec895265c0445822a5df GIT binary patch literal 14470 zcma*uRZJvYupnUEokkiOcc*c8cXxMpml<3ecXx-u9cFNMcXx+D21eLFH}_?eeb|SS zO6s{%seDx@R!v0~9fb%53JMBcUQS8_3JN;;zsHOO^Iyhd)g$A-0Q8d9^U`#+@$xnI zu!a)1a<#A~mv=U|wbrmUxAJ$Nv=)Mb;-i9WMJh{Ppwgetuv(>&O1c#jYskkED4~OQG3> zX;{^-pMT9bV69qx@fUL73iJg7vsquS@i5lrI!n$KKVAr;J&{Gb>LE{^dt_&D!r{6y zjb9s3KmVoOlLH3cFMpJTA;2fO!{Z7`6_+47E;D>2LA%0XK4S1^qPos|(m+oT@W@7E zKI)M-uvBC?iCf*fzkV-)G9|SEHG~E{g}^Znh0?9a++O=d1V6VV4Q5MVuH>V%akos1 zaLvYFX)lEsZ2!d4+#+64}$GQbMUm(l|xk$)^n@ zESuOAWZrg^tV6x=VuUSaAVR{AS24;%Zd7RET(*!j?C0}uiCvF;;(C^zpz#i0^l6k6 zc}s1E#pk#Lq2uDSY6rt!5o_u6@nPG`5t%d08->o}V?Vo#vjh?EXSE0l;XLs&G9~@M zEbr5tnyT4hn?Rdcvw{v0T!(m%VMrpS0nG-y6|Io)^EYfOb&ri?LMDTI;v`RY&Xn=p z`;+VkTt>(cSJgh)`o!LI7WG6HZRE2-LmB`?lp0ai;i_*X59E>w;MgwRbrKw3OSn=oj4$?lU^WrI{HT{6icAK_S>mPN8gyRDP+QjY_ z&Co=nV-LDt&^VDCMu_*4Swil)NQ3pGhprTp!%ujKQi48Sj$aa3Mz=nnO=@ zqU?S@gk~6O(ku(OX@RQ}zwpthjGJwO?i(WBx_Sth6c+9~A+q}>@fc;b)uFtfge{)D zm5C;d;B`oHQ)Nd&nHu+4tBYRFuhCSxgdaFeiKDxKOeusiqo0TKt9AwSnwG?eVl(q# zD0Z^RcOl7!lIEi9E(Q`+gv2;Lf(EWP|rfAX&dcodD^o**Wh#n?uAGNJDPDv znbHhTiI`AAkg!Xyymz8(BkQ{BYOi?L2pyEwfTJCH?Akt)6ChXG4Yg2M%_hj&a)c)z zYNV?_f&arxJ9b7rEgZl_Md)Q!W^sj_ey2Cp6^!Y4p!1A zN7M zn86Acirc6icV^6dRsvrlrxzanf){;cM@=^=Fh1EbfH2IFRV0D5@k3dR!cb@ba$--K z2xg8GlLzon#+!iFDIrm^VRdyZ%YZJH6XFM%=6>XPf(|82hQpD3py8gR%tozW9PI*k z8aWs^Gb+(p&p2Hw$cXBb|A1lAdJr;6vOvJo&M`*@XMcE=Vbf^+H+B&t~u3Hy*nMewW#DMl>{Qh^RMuN=F$vKekzL|5= z5K|a4RpR9W(b3p@GqlHY}ksS*0r`1)r=L(26sZx83>(|RlyQRblg^=)l2`j z#3mr$`h5b^jmFp3vjC!#_$!8VEWwzfMY+r#+R|cHx6o^ceB2=#chu@(fNxF1NEr*4 z43haS_ZV|Is(VlRo>%;yXV;=f?7@#2Jrgdk*jR2{wKm0LE*SW;DedjDpc;e?S=PvJdRg9m8`jfWRgmp zRaE>pcH~A)?=Lo9o1O%0OJI>s>=nZs`1#ToF`kn75VZ+6apgN)C^=>4K}^=YqNEg_ zHvpBFH$l3#PxA-M7LeP73?;h;6_G28SCT|9QheuCbaskDX*(qZL4B&XMYz_{3IUAts{~2#^H|-o$?bXI8d;1mGu2kD1Jse+22VFL6ZwQsp5DJQ6nn#< zUmW_5Jv~sYhV&yt&aFUfWNo0wv>|5Z_P4le)=7pL9$7qKs2M2HMs`WVx2MMcAYeUQk;UM3xg_fk{ zQ=&vzVyb;Y^96hEbKckrAc3=YaR-#^-~5rQPPf)#7S1=Mlv93 zZ9CWoTF_?AvWYr%F2UzR0=mT--?X0)CshR({_BX?xey+846*0k!29&4@sNv~D_i{#sK1gowPtr61I4tX*PbF<5Y z#Dg?xSkn*zY?*vMOZ}8OQb_bPRYrKidl;BetY0)xeh_efsYk=|P8qf~BR&?&u2JGF1K+anF{rx{12gi@ z9_O$*hU)9?P>9&TPj~)zsXMF^k0UpMuyloulHC9%cGl%3gUDLc8Uhv;!(5f1rqj_g zuh4wOAf$&Nb4@E59F~MKXx^bbhrm1aBt+rXihQTMn%dsoOc=l)^ymXlN~PK?6|$QO zwYp8|A$w$d2GL7&W0R&IYIr^9_Y{#R^Q0UnFyg=oHk^!_mGxT2&p& zU~DVbxZOIt*K$UU)rL|^KlN_7#{By5^R6b{xc)`j1*t$Y3@te0Il~x^jV0S_$wN-6cD8-6iiX^vGxEDtn=S@{;WvfEvc_o@jxLST1X#dN}{a9 zTQy1&UT6Z~>Ub5KF91nAD2CZTQWL||BzW{i${ts$Z^ctJ2*>*zVlJX|3*ue;GVeRh zo!XW6i?W%qY-p7Yo%CE})fpO&Fw!({xnx!YjE!kL@dpx}+I;UsE&`dEJ2YR8t z{fH1aWR0u%MnoJhfce18OO{OXE`V3 zD|$!lYQRb=7}zw_=RcvisRvS-qcXN(yV#QA5OZKIF}~5d|fwt z=vNLuOlUvHIdDp^wNu4fZ9UN;OTDr3gf(o$jF3ke~% z*HG;A;Q>&`(h*~6CMl*hk11Cdeq}8&kxD`F206jb%$MrE?QcJfxR~b-{{@^bP8Wxx z;|INp)3;WID?+G`3WHN3ERK$OmI_U5w-s_8=vf)bi}>J#-w}^ouzzv|LX;)&F5Som zgM0$JL3|upq38nevrYG7sRL>__(>wEL?FSgQtJSb zuGYOA6SESB6Bf?n%B18R=T2mN=G))K&KtBZ%JK!oHN?If zGIiM*(vm`Ns$XTO3xQ&QKQdNizh2vzV4h1A?b`Q&h7%&#H6ShR9V#f;dpx~7b=m_D zwb${^Z3OIzNXz{FC*$?(gp71U#+B4~!-Fz0pB4WRE_x4GS_7g;S3fcp+@JUW6X(8{ z@ry#XI2_%A+*Q@g%M$~`O)mM!&ooyBHU1qT1$xf3{JQ&pDaf|eR)c`Hg5T-$rXnKv zLRvoox){;k-Rc!0sZIT2@ibc@~cy2N+__2Ro=7+zbc z3NKv+St+Ga$SS4HD7{@0gr4Xb%tRJdKv4ar_|W!3#J}P1L;&j@4yF&LFFjOeLnepX z0mkl0sAgu4Y;Ibpe1$%#<>w^WuniPI$=J!HQs*bXU+1&6#N<&I%Nh)2OzO5FLMK(( zpw&IA|IW|LLFnLKUL|Kk4k3<7a(C?*XO0j(h7(v=>t2oSEKIYEr477HLaD5F9A>{N zxrw`Z-Z%`))O=t|_C!b37PEk+){FKdWgxJ~UbZY$jVft1i|B@ek?gvlG3ot&e1mY= z;dDO7K4ducp(FnL9=nlC+00Ay&n%QU+K+#r-{(xgV};TQU#u)O9zafAMVsn1$kWuN?kn%be7LyxFvm!j0a*+(iW zS}Dxhk@$N*a?=~ipNbzzW8v}1DG}k;QAsDDhL6iYCb;MZvN9DalIsAqc>S-*`yadakW8DNiQDgxG0>RdB_L=+MY zbW+74x(l?K#I9nHqo9BAYr(@P0;!neJkEwfe*SdwN?e1E9b-NjLtd&n6QZOokQqU~ z|1zUrs{@NF9`XC{GqbMQ9wWD`0Xkf+<;tDil<@V5>(GJq$C^3Wuj`n&4C0WX~og5h$ zV#qtWW8VlE(432FLgT?Y|5w;io){gKu$8qA?s$`uWpmuVe>mr9+1jn$NWV7I(J|m_ zgZSL(1_V0eIAC&SUjB)iUUv)dz9+v559o@96ym)F42QY1_%hr0)v$bP@D5dreQk($ zx8K5YSGZv|e;$s~yqRmn82q5aUtMyl^6!N;#m*+1oa4JXU zKk+8HT|2OP0G6oV>PA#2zgn?om<&!?h1qi!I27+8oriQ~`5la@X`EkeGy3hiR5Oj; zP?naeb8%8YT~G00nUKjl9;y?ugi2zg+Kj-VEj$oTse6m~=T7a>Isw315q?I+#5q)j z*7%WCcM|0#8iD?qF@Dq6D{(6z#I+C!&)Nz4tZ$@O{OHX@6$8W1hB5lOp3eh`+>&1< zwAC4vI;(^nJvozn3W5l8e>hs(9Nm*@JGz0n6GO|MT@Vc2e|Nvy=xxQ7(cy*Tw|Ye_ zIOF?WWsOlXEqW5E3nqNf0%SS2hl&_EkiXoK^$ScBPnW#iA8;pV8#0(dV9%f7pF5|eYH{fm*7S*NDv#rE_A zBKBp0L&^I`u!fq$g%eZsxeUa*Xt1nCeM^M4Wh8+&cAm^BT-0)&Ut{g?I#>>k%CYFu z$UcJov0Aav#NDIE7I^=Z1Ij$Cxg|W~J#l3jj@|G&EY0;`BZ(zwBM6nwuROe%#qNVe zns?y?kNft>fy~nzPoP&6N5TBdQ{5{*Xa)(#SMSb{PE|{pNMR!(;^oWq)o5`KdNC8$E z3NeO+6OVHEs+QQ(?1ZqqJuXx^hU1F-8ZzwO0g#=ZdI$XEUTy*?#e>6n9inBGsg|rs z92`AM2}8^tjEXpFDq+Fy6uRYnfuy}MfIv!Vw-m<_sxX2D;B8$1cR_=x`&_afn@Z8K ziAPv#p8O^~Bq^qeo!2B155fDqw`6nj#>Jav@bz^{&V4!d-M#(K7bN#&;mEFK`pxC9<7O}B;tbzF^wl!^VB(~1z(&S*!hlO)Q2hb(nsVg7=1xteXqRmypZVsjQgg}E z{Qt(G;+)tT;_WF*Ak(bN#aysHtRw2@N0Mtx_mZ6djR(GyOOIZ@l^?tr*FpQflbmzmT5tBCd{p zd(^%BBV0_Mpo$&)PT2MFNi8R*E`?nrg=fN5pK&5J-y^Yc)k#9KoJHR^eGsH0pW<&< zj#u<4uxA0j(pe=LxU+?!Jjw_p>YHuaBLANhpAsSA0Zt=^j0h$!OPH9L5cIW+rrD7&1SbOL* zjDehGHQvNQGdToPUU9@?v^|Jh#P0F?3e{4Ea(`1@@CIbdIa5~*X;CL>KfzX|xLS?- zEKfYa;$$dglS`jB^^uJ7j|Sd=ywFp_gkXlRdsN4k#jEp6^}}qE5LqlM9Bl_em8<`b z>_W_*@4$g(4r9me_14!WIV-}@PK$Y*9HsXsFo{)_{Dj3B#6UACcrPNO=dZ6>=CS@i zEms0q2;2!{gP7N&9It~OR6!xnM3ZVQ|hv|H=2Oe5lg^zL`a)p6dRHQTW)uS zbXdmI%MLnb=J6}JDvELTwPI7+nFin?+kYVct!MMV}D{mY%IKZ)X9dP1%*E_VyC zu93CwAT-PYT)@MYRVdOwtklCivX-GNnn6&j#cl9|f80#-fPys9N#yl?5SFc_RQ1f4 zvlV`#MvCdpC;8cV1?zF$j)Mf$Tvauh9SGx03cP_=@Ie7TZc9%8S%efKlSxy{U=|lY zviinFX4njp1RL_Uuq*LczC60|;Phq9O{C#DSJG2A;+{5v4AScb$G|R05%0}w zOY(9ktu*IM$L%<_UA;riFcT^&y>Xt@D^Sy6R*9A1MMW<4%j1MX>bXk$aWO8XhQXj9 z$w{GigDs(o9@pb`bILVxLk*0kbNHG#&Qolrf7un;%)?Ay1F$A8rQA6Vjg2w} zy1VV}ere9y%I9%5bInOqWGPS>r{$Q)pj34zXYn+n1dw;1sw;n}` z-0W_n-6o&PmEb>C|A=X@1GS-2P^l@rzeV#Nu&9b>-I3Pq`B?S%Xs|L8e65ymEiSI=-@pWpqV zxSwfDxZJgg+ZT>;a>9uo%y&jf2HpG6fTxj8SPIGQD3=Gm4s$E8JL-j!MaK=%!`Zc^ zdB^BuZ~CB*m~Du+&?Xh#R`w1bK0Fb=g}6Tr)gDC)07q_FQCB{drH>B|2Z~sOx)2ry z)vP?Ha^EAyNiZXt-9~FVsZOBnxqD`n&AMCs6520>T@qr~rKjHHd1JC=^X?@igJD)~ z9(y)&SiWb7(3y-A&)?_xc3-VoD7mIaDvNx|aA#v3_@Z!Uy}hFILX-X{Xr0!DaD-XI ziv02)OZ!ZKxm&Kk-32_iP$?X%$*MDygC4gE;QK`7c15-Vd}eQ~yi0mK;C-x)B^}Jw zTiy|0?}7PcBl)nwL8wmKCyLG0ctM`9&Y#Ks#OQ|a9f-GFWF6N8@?Eh*+#HIoT68k& zIE1EDAk{=EN^0A(A-nx~tHU58ZjXTaX3_$Dci5-DPiEZny2~^yuG8@T;6;8mpZTr` zm$J^|F@MQGO4sS+ju0%V6R6nrVRK zH#4&}s$Wh#0ApR1DRHU&;uzF!3*@>w&O39aNJgVlh9b)lv-_>p#uYx#Im8vUFyb*X zY?p(3X0PYU#AZ21+@B<&L*CuNr=EqYmq%0K3z50oQ3_%4Ne&zb(_zeulPul($~C5T z?;4|lI$=`0hrIu?*;OMJMKGd;gzhv*SiV#bUGgT^Jau4FsZ%J{$j--p*U~d9CUH zZK{^Ml#FzU1DVNOnB111RYO)gZeqvtVge0wfTR;O?e}!iN>Z`75x&g*AJ*>l-@i80 z**E;sRN*L30F7|z`90_^#r0+j%N9w1fx%{GPmUT`5m8~V#q@i+(Kcwto^#S)(S%yV z`gA8N_nB8j2`MfNQ;JY@*U$_XMUDZ(%s-k*cp?54}rNhzg|o$&@UF8rXE3#g407g z+yvlMs8FM~hPJ9rCMm5+2%VgP0DhJ=AZjcrBVAEfc|N4W%PHo__(C0=da42Na>!j$tq+8w|bU|HspQwl~1 zb{924$3=pfeuU?k&Q@3yJR+37n5p=V^xb(o$9xi=(5mMoj)e$}=gBR9Z|>~^OP!;U zgJ*O880o0^%IQWr3H9^+5Lwe)(u96Js*{3(v)(t)@*NJ+bFv zI!1DGy#xd5kBHgzcu5mCU^#}^U$}5kI0O`@xcigi>Z!Guw`eWtj9FI-X%Y?BciZ10IT?hH~T>pz12wNkezjBDVoY9!cyH>UZ*5XW6 z0C8pQSNI%B*HWubSQR>{nYsKXlyW(fH&U}NBUm^*@P)kgCRbQN{XxT!Z>Bp6c{ZbR z)!wby17+_=A!B(xU(V4sBm~iM6k5d349w&DinaR>1)vwj)-S-qJh7>gb}_w1O>IVu zq>qCkdO%()Owue28mh|7NZW%Q@wAZ3k%jdmIl=Xx z!#LC+w~@G4#oLYDxjDDYeCK*!6t%rGrXtOj<$;T@z|_YqmQ*uiYRZ^@n+zf0eukIi zR*`!C>FW_fx=v|7%-s{F{O=In5!!$KZR4u{hY_`HI0rIBC_tp$&33?CMpfsgfqlB- z8A1h@kEYkzp($5Thpx*1m4Fv-2oxmOq8(7W5G{&kV*a4~?2f#3W!e>;3g6JU#GUji zk-rP46Gtz!mK)9F*cPalc$NpnR!BT}!t6-saab-S4xnfWw+Q_0(w&HtS825r)j}OU z>i9d%`z2Q=n`~#{#Bl)8V}!;VJusX+cWUBW_HER*vu-@;5dq zD9Y7(SCtKS_D&9k%VjN5C5?k-o-vNmX4^fYwE)Q+dY0Tm>=!?al|C~*4Lnt`gENw~ z6RRAYwqpu&TCc7}76}(Q5EeZ?>D~)B{I+_J{x?_%OAp5Jo$8HT?yk`Bj=MDQw0`$j z3-YKj@+c}wk#IDU9~fqD=A`##nqaCZ5cSB58A%hk`S&v+(L(EpMrMz-5onk*vH(EBds<04bP+rC4@%fvZ4;AqGIGD zJSO?pnw1U>4tciZ3fhrwMypDeQ)r`@Z!4)1Y(>;S@pCYRCm~Ty2!v9qr;bEPv)n&N@_N7Zg%{C zLP9JcM`rQVgH1Hur76a{-G)ZA6?W^eqSVJe(CbuR_$VgNeSzo5sj_6O% zytUZw=NmLN4x=Jx?87cYFxRbV?WMTbFwS`1Y*Y82i+=#{9M;-%M#{_s9>XFFko<~TTcict9cYNPL z)j$T@-eGe)!)&GHP^YKxemLw_x(L4ZI%PUykQ?p{Q-CdD`2~Q-6w`Zur2RYRvY*k9 z=o=@;jqcs^e#1ADk^$6|ESayFngea(5NlgAS8AppdK|rRdfdTtaC*Ts;M=c^%zBQ$ zuYzVk;iZD zT0#eB3Zj*{!{GjTg(GCAt-dp)ITEQ>nC~YeF&R0XNy8Y~xkC+-AEoV^9zPJBr<5|# zOG9zM z3>qwOiX#i?=Z77%$02nSQnwXls!xS{A=D=EBV<3!E3^ew7BBKVkN)XoUz08Tju@2a z(c?*U@DIO2m4Q=plRJBffxO`{!34_Rh6nW(+a-i&t|4DDJ(TRAo3+=_tsbv(Gljls zuKD*UD|zNjRF2xUf?6mqwoAEI02zg0M>&^FE#>gK=p6O^?^x^hOvXXC_|3l6vZ3i! z?g}*-Kq6M;m!D4#)OMeEeUxDa=Hta>dOf&zX7V<(Rw&k%?H|a!?vF8va(nhTEvfvl z>noB7Y$%~Fd7<4YHPLhe+nCjD%xy6ITB_i$i^~bou`%J%V)}BQWjO!AlvEGwqM5Xt z+CU@1>(z>{)ZCu`=fj^R&Dk`k1f*p|iQ#9p=XG3U%u zB|Gx0i8FiF-GF3}j^u5>BKvjuOpD#4*Jm6{F|ytZ;4chg$nXM)92U`;_ll9)1UT6kYLl z5%Pwl?UPXc(F!vNc)t6M)TX_tIhJdFq_`Ba!yik{*|KMEjM?AF-}^m4ym|Td6U<;# z0_4#zDK^zQOAS`@;W0J0wrOJQ6F)!qOiOY0v?y@l?M_1$L^&{sr_17PRO*Bd_*6ec znFdxGDpZsnqw4`A^+cJp#s=n>nM~ku1K65WDrIVHY#v|ewq1KH?lkKb}K0=eIUbd_JqHP zdH;VE!ZLL#SH+Ye_Sv+}KOHoX2j_oFf%UEd(zRD>Ry9fZG;;$BMz)^b z!lkgaEFWA_Z#Kde4hYKBZPvM+7gh&||J!kV@?jr(>a{$=DsCCK{SI;QnSP`U@io9g zMA!~TELr}mQ=`>Y@De?M&YAtC+H8T!ExJMO1O$rPhF4z)1*0&rixK~KW|cv5t3DQ) z=H|J@Hfk^bt;%I|nl#5SpTwi`h(f6IcSKCOPO{8*kB6VsjZv$B@Wg(+lo4V|UFl zc*8Qn8X?hdzkSGT+L`?W`liH>uKp;^Psd@a$@c$WTJXg2KUz>*J>=m*g=}f;q;Ioi z6#J!D&E}K=45_SZ;8U1gq9n&QEi=UZj1jSCZtXnW^{nqz`o(S}s!HdTSIORwRu?3X zQTQc?UP*@>g0rM7L2bc~lCLZBpbFyAycr*CMOZ2JUueM&V)}342nI>6~B!0QsJm zv{S86*)T%k2BI!3Kv;TERI^-vGbOFIYlI@e1))l~-gcLsId!H}9^p%{4^ZY&go|#? zrDo?F{cW>Pmwph?fZQ<7Z0xXneFYdX-RgeTh&ap%L393(f&dbmN*H5euZHy%P(0E;M;-<2OTt$GzX4`XS5q_}= z@G(y4gt)OoZEWEtb&IuSO8A$#WK?JEW4+EM@Oz)PIGuU@S;I(*XY=uit(MZ)Ookzk z9rZF2D^#*Vnby$FD8v|g|9rY2ktK(`;I{cr!wN$93Lx`N;H1q-U2s{SWUb$Fx1meu zH-**9ed>j1@NZUZDUNe2iPQ>))Az4FKqSAG!xZHU!;0FCcgzHNENq{iidTR9!Use* zf(vz-$Su5O>%olmDx+Ov@+pCKp0cd|1r5LFY)}1yEi&Af&UD>}MKH7Cs5=KOM&p#r zLCl6A*KK;)e_Buq{{PW}Vl&|ff0^tttX#cBKSCDIp#ySNLw}&>-bFB=1!l5+G`1&y zV2c^KrYQhUdxNoDl-f$hj+ett6pWHw9nOpR|JH((of0&nI$1oGAE8Paoxy~$DEbP) zojM~L=FMI~q1pfo(Ztp%0h#yp?xfrDXs91f^3yfY+?c`XhXWUj zzg(J^RAxQ{U&!8D{VryNKr(;798hHrmzs=XP@cNjR8v<}6gGop2jxkcmIQX7=W-3) zOm&l-Q4847edSru_&VyUh9{GB@^k-crDPWI`_N!nj2%F0;OLf9i;w(nT4oGB%;u2;w z#M;@iPX6G^e(wCf2#^%Jz&`+4=--l`RR`8NZ{jBLV!w58a0(@BI_|4tHc*yi6ad1$`RmE*m^n*0;6L#pqWLVT49$IIC_xN&nO*Z&2 zg&I0QD>P49k>kDYg}os~sJ#$%upiJV%_3YsLBJVG)32r~2S|wtDS2YMvB2BWv&FOP z5pjozjdHZcX4G3O`$FCijHa&5j4{Qb9xqKHAe|4GGX3EeLn7{a;Vk;Ou7(tgv_j)9 znn0Q|Byj+>mvWRjgH5_ZiP{v*gcgOMlqNB3S`LumOx6&yygUzUMP7v$UDlvZm4F+S zNYSd7&l)T2f-dE}lhEdjFEXoVs9BbVcHg6~T_VyT|Giu%T^3UY-La1I7-nJ@G}{}> z;$={2pFI2_AAE8i7vuH-H$o+?nD*`s#C!Fx^5#+c9jC9@jn=) zQM7>1KF30gvZApScxncg0qlvCZgYVi1Lk809c}JuPritVlxl*Qk7HV;J58k`m*lDw z{u6M`6*+)fIM5txkRK2-twWQALXUu;LB7q9+5m)AH?JSg@urR7Dmlox#F`;a zMoowY8k%|77Yed}vARia65-%Yqt>CcM%0C{I_kFFS7z|gn;&ldxEx#UAh41qKNjdZ zG>OhU*Yva#xJ%8BbEH4B9M%JH6)R^~*-b_t|L4DNZr~+D>aF#nb zCTeR5#Za(d*IGQG`~3@k3?qM{grgskKMzd_c8YgL{*H zHSRcyTDkK05|Ms8HQ8r}rB%`d7ht=mNBVv~5; z4=hUqWk)BT*y(@E2KhoP4ojL@o4hf9KWiwo|4Z}rJru^A6b#lAQSvzvNXpV&o;F1) zz>n8q#VK-h(XwH}r<7pqmGS_C&h1}k7cC-;`A}ic-HL)8z{WaQ`7*d;$k7IiWU$IB zNX%!NC1CQ+NMteI{r8uLH~B02jRPvK9eFjvN<$e8pP^Ta<Q^*qhL@s5mFCp^&ov z;Yk>vwygeFhiGULXw{VmOe0ScrUSbzB%j7;s@u53CRdzI+6LhR!({e0++%N!RGljL zLzQKH2;w?z3XvbCS>o2XnS7__TGJnfO48Out)+?D*tdw`CUas+OM?2XsmG?mVZ`zJ z0)ob$xAoGaj%ew|+oHEA?pw3Pa@-XSA5$A14OIRi!n>i2qmS5xE?ISa32gO1INji) zumun{9Q1fvKA{0KedkZ~;P_CqLaMZ;n~4y2#S;}vn_So9Th>824W@Of)g_cO?><^z zcR%vGXwnuMg$(yOO`W}Dxv2%~@je+hFO(SLT6}!3%T%v?lhX*4-&7Oa^~V-n-M-d` zuOvFPxJNSIQ_=s{$qEg!xBiToqHe1rxO!HMC=SuFBPW`<7cmv^C(5V~&a#){F7N1p zqijCU)S3ESeX>Q^vH;EhYJl*C!Y|n}1ra?4!86Y21 zvrAN$q8yzjG5o2u(zetDYcZW`&*px%UkqAQ-{kl~iDlOzu5J8#!UBOZvD`$7Vu$}@ z6>8#@3LK|-?y-%F#SNgX>XG*WoNFj|%SG8RrYLQV5`|S;)*CLnp5qI{y-v_46{;BC z7AI}10sE^;OXT%NsskV=B=YJmyq2#4iq`pa*h5aAL C8W-6B literal 0 HcmV?d00001 diff --git a/sphinx/themes/scrolls/theme.conf b/sphinx/themes/scrolls/theme.conf new file mode 100644 index 000000000..b42050460 --- /dev/null +++ b/sphinx/themes/scrolls/theme.conf @@ -0,0 +1,11 @@ +[theme] +inherit = default +stylesheet = scrolls.css +pygments_style = tango + +[options] +headerbordercolor = #1752b4 +subheadlinecolor = #0d306b +linkcolor = #1752b4 +visitedlinkcolor = #444 +admonitioncolor = #28437f From 5c678f02cdc74d440e8fb875be5bfd6d3722e138 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 30 Jun 2009 17:34:18 +0100 Subject: [PATCH 10/85] Added new minimal theme called scrolls. --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index b83565eb4..15946e1e7 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,8 @@ Release 1.0 (in development) ============================ +* Added new minimal theme called scrolls. + * Added ``html_output_encoding`` config value. * Added ``latexpdf`` target in quickstart Makefile. From 22f3fbb388c549652238f9662f9b6871e20d627f Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 30 Jun 2009 18:59:36 +0200 Subject: [PATCH 11/85] Refactor a bit to fix b/w compat of cfunc role. --- sphinx/roles.py | 49 ++++++++++++++++++++++--------------------------- 1 file changed, 22 insertions(+), 27 deletions(-) diff --git a/sphinx/roles.py b/sphinx/roles.py index fce349632..7b96c73ab 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -116,7 +116,8 @@ def make_xref_role(link_func, nodeclass=None, innernodeclass=None): modname=env.currmodule, classname=env.currclass) # we may need the line number for warnings pnode.line = lineno - target, title = link_func(env, text, pnode) + has_explicit_title, title, target = split_explicit_title(text) + target, title = link_func(env, pnode, has_explicit_title, title, target) pnode['reftarget'] = target pnode += innernodeclass(rawtext, title, classes=['xref']) return [pnode], [] @@ -160,32 +161,31 @@ def abbr_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): return [addnodes.abbreviation(abbr, abbr, explanation=expl)], [] -def normalize_func_parens(env, target, title): - if title.endswith('()'): - # remove parentheses - title = title[:-2] - if env.config.add_function_parentheses: - # add them back to all occurrences if configured - title += '()' +def normalize_func_parens(env, has_explicit_title, target, title): + if has_explicit_title: + if title.endswith('()'): + # remove parentheses + title = title[:-2] + if env.config.add_function_parentheses: + # add them back to all occurrences if configured + title += '()' # remove parentheses from the target too if target.endswith('()'): target = target[:-2] return target, title -def generic_link_func(env, text, pnode): - has_explicit_title, title, target = split_explicit_title(text) +def generic_link_func(env, pnode, has_explicit_title, title, target): if has_explicit_title: pnode['refcaption'] = True return target, title -def pyref_link_func(env, text, pnode): - has_explicit_title, title, target = split_explicit_title(text) +def pyref_link_func(env, pnode, has_explicit_title, title, target): if has_explicit_title: pnode['refcaption'] = True # fix-up parentheses in link title - if not has_explicit_title: + else: title = title.lstrip('.') # only has a meaning for the target target = target.lstrip('~') # only has a meaning for the title # if the first character is a tilde, don't display the module/class @@ -203,14 +203,13 @@ def pyref_link_func(env, text, pnode): return target, title -def pyref_callable_link_func(env, text, pnode): - target, title = pyref_link_func(env, text, pnode) - target, title = normalize_func_parens(env, target, title) +def pyref_callable_link_func(env, pnode, has_explicit_title, title, target): + target, title = pyref_link_func(env, pnode, has_explicit_title, title, target) + target, title = normalize_func_parens(env, has_explicit_title, target, title) return target, title -def option_link_func(env, text, pnode): - has_explicit_title, title, target = split_explicit_title(text) +def option_link_func(env, pnode, has_explicit_title, title, target): program = env.currprogram if not has_explicit_title: if ' ' in title and not (title.startswith('/') or @@ -225,23 +224,19 @@ def option_link_func(env, text, pnode): return target, title -def simple_link_func(env, text, pnode): +def simple_link_func(env, pnode, has_explicit_title, title, target): # normalize all whitespace to avoid referencing problems - has_explicit_title, title, target = split_explicit_title(text) target = ws_re.sub(' ', target) return target, title -def lowercase_link_func(env, text, pnode): - target, title = simple_link_func(env, text, pnode) +def lowercase_link_func(env, pnode, has_explicit_title, title, target): + target, title = simple_link_func(env, pnode, has_explicit_title, title, target) return target.lower(), title -def cfunc_link_func(env, text, pnode): - has_explicit_title, title, target = split_explicit_title(text) - if not has_explicit_title: - target, title = normalize_func_parens(env, target, title) - return target, title +def cfunc_link_func(env, pnode, has_explicit_title, title, target): + return normalize_func_parens(env, has_explicit_title, target, title) generic_pyref_role = make_xref_role(pyref_link_func) From e3c59615fc79337b19996100224fb6781c2db2d3 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 1 Jul 2009 09:28:24 +0200 Subject: [PATCH 12/85] Make the parameter order consistent. --- sphinx/roles.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sphinx/roles.py b/sphinx/roles.py index 7b96c73ab..e59cf0703 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -161,7 +161,7 @@ def abbr_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): return [addnodes.abbreviation(abbr, abbr, explanation=expl)], [] -def normalize_func_parens(env, has_explicit_title, target, title): +def normalize_func_parens(env, has_explicit_title, title, target): if has_explicit_title: if title.endswith('()'): # remove parentheses @@ -205,7 +205,7 @@ def pyref_link_func(env, pnode, has_explicit_title, title, target): def pyref_callable_link_func(env, pnode, has_explicit_title, title, target): target, title = pyref_link_func(env, pnode, has_explicit_title, title, target) - target, title = normalize_func_parens(env, has_explicit_title, target, title) + target, title = normalize_func_parens(env, has_explicit_title, title, target) return target, title @@ -236,7 +236,7 @@ def lowercase_link_func(env, pnode, has_explicit_title, title, target): def cfunc_link_func(env, pnode, has_explicit_title, title, target): - return normalize_func_parens(env, has_explicit_title, target, title) + return normalize_func_parens(env, has_explicit_title, title, target) generic_pyref_role = make_xref_role(pyref_link_func) From 68a15b5b7409c5cd9b083c5af0e33f35996076fa Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 2 Jul 2009 10:58:29 +0200 Subject: [PATCH 13/85] Add comment. --- sphinx/domains.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sphinx/domains.py b/sphinx/domains.py index 6845496c5..3b31bd5a1 100644 --- a/sphinx/domains.py +++ b/sphinx/domains.py @@ -16,4 +16,5 @@ class Domain(object): roles = {} label = '' +# this contains all registered domains domains = {} From 1f18db60a49e0fc6552bcfd11fe4b2be5153c68a Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Fri, 3 Jul 2009 12:11:42 +0200 Subject: [PATCH 14/85] Add another sectionchar. --- sphinx/writers/text.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/writers/text.py b/sphinx/writers/text.py index ceca5c8a0..9e7bb63d2 100644 --- a/sphinx/writers/text.py +++ b/sphinx/writers/text.py @@ -47,7 +47,7 @@ STDINDENT = 3 class TextTranslator(nodes.NodeVisitor): - sectionchars = '*=-~"+' + sectionchars = '*=-~"+`' def __init__(self, document, builder): nodes.NodeVisitor.__init__(self, document) From c8e8b1bfd4d76f13b51cd86b48cb32ccbe7559e0 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Fri, 3 Jul 2009 13:34:08 +0200 Subject: [PATCH 15/85] Use a new "lazy" gettext function instead of using dummy _() to help text extraction. --- sphinx/builders/__init__.py | 22 +---- sphinx/config.py | 4 + sphinx/directives/desc.py | 47 ++++----- sphinx/locale/__init__.py | 190 +++++++++++++++++++++++++++++++----- 4 files changed, 193 insertions(+), 70 deletions(-) diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py index a8fc8871c..348e2e8fd 100644 --- a/sphinx/builders/__init__.py +++ b/sphinx/builders/__init__.py @@ -172,32 +172,18 @@ class Builder(object): Load translated strings from the configured localedirs if enabled in the configuration. """ - self.translator = None if self.config.language is not None: self.info(bold('loading translations [%s]... ' % self.config.language), nonl=True) - # the None entry is the system's default locale path locale_dirs = [None, path.join(package_dir, 'locale')] + \ [path.join(self.srcdir, x) for x in self.config.locale_dirs] - for dir_ in locale_dirs: - try: - trans = gettext.translation('sphinx', localedir=dir_, - languages=[self.config.language]) - if self.translator is None: - self.translator = trans - else: - self.translator._catalog.update(trans.catalog) - except Exception: - # Language couldn't be found in the specified path - pass - if self.translator is not None: + self.translator, has_translation = locale.init(locale_dirs, + self.config.language) + if self.config.language is not None: + if has_translation: self.info('done') else: self.info('locale not available') - if self.translator is None: - self.translator = gettext.NullTranslations() - self.translator.install(unicode=True) - locale.init() # translate common labels def load_env(self): """Set up the build environment.""" diff --git a/sphinx/config.py b/sphinx/config.py index 912f3eb2e..dbe569fb7 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -113,6 +113,10 @@ class Config(object): latex_docclass = ({}, None), # now deprecated - use latex_elements latex_preamble = ('', None), + + # text options + text_sectionchars = ('*=-~"+`', 'text'), + text_windows_newlines = (False, 'text'), ) def __init__(self, dirname, filename, overrides, tags): diff --git a/sphinx/directives/desc.py b/sphinx/directives/desc.py index 47c640b5e..666397c39 100644 --- a/sphinx/directives/desc.py +++ b/sphinx/directives/desc.py @@ -15,6 +15,7 @@ from docutils.parsers.rst import directives from sphinx import addnodes from sphinx.domains import Domain, domains +from sphinx.locale import l_ from sphinx.util import ws_re from sphinx.util.compat import Directive, directive_dwim @@ -84,8 +85,6 @@ class DescDirective(Directive): 'module': directives.unchanged, } - _ = lambda x: x # make gettext extraction in constants possible - doc_fields_with_arg = { 'param': '%param', 'parameter': '%param', @@ -95,23 +94,23 @@ class DescDirective(Directive): 'kwarg': '%param', 'kwparam': '%param', 'type': '%type', - 'raises': _('Raises'), - 'raise': 'Raises', - 'exception': 'Raises', - 'except': 'Raises', - 'var': _('Variable'), - 'ivar': 'Variable', - 'cvar': 'Variable', - 'returns': _('Returns'), - 'return': 'Returns', + 'raises': l_('Raises'), + 'raise': l_('Raises'), + 'exception': l_('Raises'), + 'except': l_('Raises'), + 'var': l_('Variable'), + 'ivar': l_('Variable'), + 'cvar': l_('Variable'), + 'returns': l_('Returns'), + 'return': l_('Returns'), } doc_fields_with_linked_arg = ('raises', 'raise', 'exception', 'except') doc_fields_without_arg = { - 'returns': 'Returns', - 'return': 'Returns', - 'rtype': _('Return type'), + 'returns': l_('Returns'), + 'return': l_('Returns'), + 'rtype': l_('Return type'), } def handle_doc_fields(self, node): @@ -132,7 +131,7 @@ class DescDirective(Directive): fname, fbody = field try: typ, obj = fname.astext().split(None, 1) - typdesc = _(self.doc_fields_with_arg[typ]) + typdesc = self.doc_fields_with_arg[typ] if _is_only_paragraph(fbody): children = fbody.children[0].children else: @@ -176,7 +175,7 @@ class DescDirective(Directive): except (KeyError, ValueError): fnametext = fname.astext() try: - typ = _(self.doc_fields_without_arg[fnametext]) + typ = self.doc_fields_without_arg[fnametext] except KeyError: # at least capitalize the field name typ = fnametext.capitalize() @@ -499,14 +498,14 @@ class ClassmemberDesc(PythonDesc): clsname, methname = name.rsplit('.', 1) except ValueError: if modname: - return '%s() (in module %s)' % (name, modname) + return _('%s() (in module %s)') % (name, modname) else: return '%s()' % name if modname: - return '%s() (%s.%s class method)' % (methname, modname, - clsname) + return _('%s() (%s.%s class method)') % (methname, modname, + clsname) else: - return '%s() (%s class method)' % (methname, clsname) + return _('%s() (%s class method)') % (methname, clsname) elif self.desctype == 'attribute': try: clsname, attrname = name.rsplit('.', 1) @@ -692,7 +691,7 @@ class GenericDesc(DescDirective): signode['ids'].append(targetname) self.state.document.note_explicit_target(signode) if indextemplate: - indexentry = _(indextemplate) % (name,) + indexentry = indextemplate % (name,) indextype = 'single' colon = indexentry.find(':') if colon != -1: @@ -757,17 +756,13 @@ class DefaultDomain(Directive): # Note: the target directive is not registered here, it is used by the # application when registering additional xref types. -_ = lambda x: x - # Generic cross-reference types; they can be registered in the application; # the directives are either desc_directive or target_directive. additional_xref_types = { # directive name: (role name, index text, function to parse the desc node) - 'envvar': ('envvar', _('environment variable; %s'), None), + 'envvar': ('envvar', l_('environment variable; %s'), None), } -del _ - directives.register_directive('default-domain', directive_dwim(DefaultDomain)) directives.register_directive('describe', directive_dwim(DescDirective)) diff --git a/sphinx/locale/__init__.py b/sphinx/locale/__init__.py index 36fabc615..a9abc9cb9 100644 --- a/sphinx/locale/__init__.py +++ b/sphinx/locale/__init__.py @@ -8,41 +8,179 @@ :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ +import gettext +import UserString + +from sphinx import package_dir + + +class _TranslationProxy(UserString.UserString, object): + """Class for proxy strings from gettext translations. This is a helper + for the lazy_* functions from this module. + + The proxy implementation attempts to be as complete as possible, so that + the lazy objects should mostly work as expected, for example for sorting. + + This inherits from UserString because some docutils versions use UserString + for their Text nodes, which then checks its argument for being either a + basestring or UserString, otherwise calls str() -- not unicode() -- on it. + This also inherits from object to make the __new__ method work. + """ + __slots__ = ('_func', '_args') + + def __new__(cls, func, *args): + if not args: + # not called with "function" and "arguments", but a plain string + return unicode(func) + return object.__new__(cls) + + def __init__(self, func, *args): + self._func = func + self._args = args + + data = property(lambda x: x._func(*x._args)) + + def __contains__(self, key): + return key in self.data + + def __nonzero__(self): + return bool(self.data) + + def __dir__(self): + return dir(unicode) + + def __iter__(self): + return iter(self.data) + + def __len__(self): + return len(self.data) + + def __str__(self): + return str(self.data) + + def __unicode__(self): + return unicode(self.data) + + def __add__(self, other): + return self.data + other + + def __radd__(self, other): + return other + self.data + + def __mod__(self, other): + return self.data % other + + def __rmod__(self, other): + return other % self.data + + def __mul__(self, other): + return self.data * other + + def __rmul__(self, other): + return other * self.data + + def __lt__(self, other): + return self.data < other + + def __le__(self, other): + return self.data <= other + + def __eq__(self, other): + return self.data == other + + def __ne__(self, other): + return self.data != other + + def __gt__(self, other): + return self.data > other + + def __ge__(self, other): + return self.data >= other + + def __getattr__(self, name): + if name == '__members__': + return self.__dir__() + return getattr(self.data, name) + + def __getstate__(self): + return self._func, self._args + + def __setstate__(self, tup): + self._func, self._args = tup + + def __getitem__(self, key): + return self.data[key] + + def __copy__(self): + return self + + def __repr__(self): + try: + return 'i' + repr(unicode(self.data)) + except: + return '<%s broken>' % self.__class__.__name__ + +def mygettext(string): + """Used instead of _ when creating TranslationProxies, because _ is not bound + yet at that time.""" + return _(string) + +def lazy_gettext(string): + """A lazy version of `gettext`.""" + #if isinstance(string, _TranslationProxy): + # return string + return _TranslationProxy(mygettext, string) + +l_ = lazy_gettext -_ = lambda x: x admonitionlabels = { - 'attention': _('Attention'), - 'caution': _('Caution'), - 'danger': _('Danger'), - 'error': _('Error'), - 'hint': _('Hint'), - 'important': _('Important'), - 'note': _('Note'), - 'seealso': _('See Also'), - 'tip': _('Tip'), - 'warning': _('Warning'), + 'attention': l_('Attention'), + 'caution': l_('Caution'), + 'danger': l_('Danger'), + 'error': l_('Error'), + 'hint': l_('Hint'), + 'important': l_('Important'), + 'note': l_('Note'), + 'seealso': l_('See Also'), + 'tip': l_('Tip'), + 'warning': l_('Warning'), } versionlabels = { - 'versionadded': _('New in version %s'), - 'versionchanged': _('Changed in version %s'), - 'deprecated': _('Deprecated since version %s'), + 'versionadded': l_('New in version %s'), + 'versionchanged': l_('Changed in version %s'), + 'deprecated': l_('Deprecated since version %s'), } pairindextypes = { - 'module': _('module'), - 'keyword': _('keyword'), - 'operator': _('operator'), - 'object': _('object'), - 'exception': _('exception'), - 'statement': _('statement'), - 'builtin': _('built-in function'), + 'module': l_('module'), + 'keyword': l_('keyword'), + 'operator': l_('operator'), + 'object': l_('object'), + 'exception': l_('exception'), + 'statement': l_('statement'), + 'builtin': l_('built-in function'), } -del _ -def init(): - for dct in (admonitionlabels, versionlabels, pairindextypes): - for key in dct: - dct[key] = _(dct[key]) +def init(locale_dirs, language): + # the None entry is the system's default locale path + translator = None + has_translation = True + for dir_ in locale_dirs: + try: + trans = gettext.translation('sphinx', localedir=dir_, + languages=[language]) + if translator is None: + translator = trans + else: + translator._catalog.update(trans.catalog) + except Exception: + # Language couldn't be found in the specified path + pass + if translator is None: + translator = gettext.NullTranslations() + has_translation = False + translator.install(unicode=True) + return translator, has_translation From 2c2ea9ae5f0922023ddabddeefc0b265b5b212a4 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Fri, 3 Jul 2009 13:37:38 +0200 Subject: [PATCH 16/85] Teach babel to extract l_() messages. --- setup.cfg | 1 + sphinx/locale/sphinx.pot | 128 ++++++++++++++++++++++----------------- 2 files changed, 72 insertions(+), 57 deletions(-) diff --git a/setup.cfg b/setup.cfg index 23986bcff..525e24ea3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -8,6 +8,7 @@ release = egg_info -RDb '' [extract_messages] mapping_file = babel.cfg output_file = sphinx/locale/sphinx.pot +keywords = _ l_ lazy_gettext [update_catalog] input_file = sphinx/locale/sphinx.pot diff --git a/sphinx/locale/sphinx.pot b/sphinx/locale/sphinx.pot index 9ac89224d..e12c1a4ee 100644 --- a/sphinx/locale/sphinx.pot +++ b/sphinx/locale/sphinx.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Sphinx 1.0\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2009-05-22 18:51+0200\n" +"POT-Creation-Date: 2009-07-03 13:36+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,29 +17,29 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.4\n" -#: sphinx/environment.py:104 sphinx/writers/latex.py:184 +#: sphinx/environment.py:107 sphinx/writers/latex.py:184 #, python-format msgid "%B %d, %Y" msgstr "" -#: sphinx/environment.py:323 sphinx/themes/basic/genindex-single.html:2 +#: sphinx/environment.py:327 sphinx/themes/basic/genindex-single.html:2 #: sphinx/themes/basic/genindex-split.html:2 #: sphinx/themes/basic/genindex-split.html:5 #: sphinx/themes/basic/genindex.html:2 sphinx/themes/basic/genindex.html:5 -#: sphinx/themes/basic/genindex.html:48 sphinx/themes/basic/layout.html:131 +#: sphinx/themes/basic/genindex.html:48 sphinx/themes/basic/layout.html:134 #: sphinx/writers/latex.py:190 msgid "Index" msgstr "" -#: sphinx/environment.py:324 sphinx/writers/latex.py:189 +#: sphinx/environment.py:328 sphinx/writers/latex.py:189 msgid "Module Index" msgstr "" -#: sphinx/environment.py:325 sphinx/themes/basic/defindex.html:16 +#: sphinx/environment.py:329 sphinx/themes/basic/defindex.html:16 msgid "Search Page" msgstr "" -#: sphinx/roles.py:55 sphinx/directives/desc.py:747 +#: sphinx/roles.py:55 sphinx/directives/desc.py:763 #, python-format msgid "environment variable; %s" msgstr "" @@ -57,34 +57,35 @@ msgstr "" msgid "Module level" msgstr "" -#: sphinx/builders/html.py:219 +#: sphinx/builders/html.py:222 #, python-format msgid "%b %d, %Y" msgstr "" -#: sphinx/builders/html.py:238 sphinx/themes/basic/defindex.html:21 +#: sphinx/builders/html.py:241 sphinx/themes/basic/defindex.html:21 msgid "General Index" msgstr "" -#: sphinx/builders/html.py:238 +#: sphinx/builders/html.py:241 msgid "index" msgstr "" -#: sphinx/builders/html.py:240 sphinx/builders/htmlhelp.py:184 +#: sphinx/builders/html.py:243 sphinx/builders/htmlhelp.py:184 #: sphinx/builders/qthelp.py:132 sphinx/themes/basic/defindex.html:19 #: sphinx/themes/basic/modindex.html:2 sphinx/themes/basic/modindex.html:13 +#: sphinx/themes/scrolls/modindex.html:2 sphinx/themes/scrolls/modindex.html:13 msgid "Global Module Index" msgstr "" -#: sphinx/builders/html.py:241 +#: sphinx/builders/html.py:244 msgid "modules" msgstr "" -#: sphinx/builders/html.py:296 +#: sphinx/builders/html.py:300 msgid "next" msgstr "" -#: sphinx/builders/html.py:305 +#: sphinx/builders/html.py:309 msgid "previous" msgstr "" @@ -92,15 +93,18 @@ msgstr "" msgid " (in " msgstr "" -#: sphinx/directives/desc.py:97 +#: sphinx/directives/desc.py:97 sphinx/directives/desc.py:98 +#: sphinx/directives/desc.py:99 sphinx/directives/desc.py:100 msgid "Raises" msgstr "" -#: sphinx/directives/desc.py:101 +#: sphinx/directives/desc.py:101 sphinx/directives/desc.py:102 +#: sphinx/directives/desc.py:103 msgid "Variable" msgstr "" -#: sphinx/directives/desc.py:104 +#: sphinx/directives/desc.py:104 sphinx/directives/desc.py:105 +#: sphinx/directives/desc.py:111 sphinx/directives/desc.py:112 msgid "Returns" msgstr "" @@ -122,7 +126,7 @@ msgid "%s() (built-in function)" msgstr "" #: sphinx/directives/desc.py:419 sphinx/directives/desc.py:476 -#: sphinx/directives/desc.py:488 +#: sphinx/directives/desc.py:488 sphinx/directives/desc.py:501 #, python-format msgid "%s() (in module %s)" msgstr "" @@ -167,6 +171,16 @@ msgstr "" msgid "%s() (%s static method)" msgstr "" +#: sphinx/directives/desc.py:505 +#, python-format +msgid "%s() (%s.%s class method)" +msgstr "" + +#: sphinx/directives/desc.py:508 +#, python-format +msgid "%s() (%s class method)" +msgstr "" + #: sphinx/directives/desc.py:518 #, python-format msgid "%s (%s.%s attribute)" @@ -207,37 +221,37 @@ msgstr "" msgid "%scommand line option; %s" msgstr "" -#: sphinx/directives/other.py:138 +#: sphinx/directives/other.py:140 msgid "Platforms: " msgstr "" -#: sphinx/directives/other.py:144 +#: sphinx/directives/other.py:146 #, python-format msgid "%s (module)" msgstr "" -#: sphinx/directives/other.py:193 +#: sphinx/directives/other.py:195 msgid "Section author: " msgstr "" -#: sphinx/directives/other.py:195 +#: sphinx/directives/other.py:197 msgid "Module author: " msgstr "" -#: sphinx/directives/other.py:197 +#: sphinx/directives/other.py:199 msgid "Author: " msgstr "" -#: sphinx/directives/other.py:317 +#: sphinx/directives/other.py:319 msgid "See also" msgstr "" -#: sphinx/ext/autodoc.py:888 +#: sphinx/ext/autodoc.py:895 #, python-format msgid " Bases: %s" msgstr "" -#: sphinx/ext/autodoc.py:919 +#: sphinx/ext/autodoc.py:926 #, python-format msgid "alias of :class:`%s`" msgstr "" @@ -255,86 +269,86 @@ msgstr "" msgid "here" msgstr "" -#: sphinx/locale/__init__.py:15 +#: sphinx/locale/__init__.py:138 msgid "Attention" msgstr "" -#: sphinx/locale/__init__.py:16 +#: sphinx/locale/__init__.py:139 msgid "Caution" msgstr "" -#: sphinx/locale/__init__.py:17 +#: sphinx/locale/__init__.py:140 msgid "Danger" msgstr "" -#: sphinx/locale/__init__.py:18 +#: sphinx/locale/__init__.py:141 msgid "Error" msgstr "" -#: sphinx/locale/__init__.py:19 +#: sphinx/locale/__init__.py:142 msgid "Hint" msgstr "" -#: sphinx/locale/__init__.py:20 +#: sphinx/locale/__init__.py:143 msgid "Important" msgstr "" -#: sphinx/locale/__init__.py:21 +#: sphinx/locale/__init__.py:144 msgid "Note" msgstr "" -#: sphinx/locale/__init__.py:22 +#: sphinx/locale/__init__.py:145 msgid "See Also" msgstr "" -#: sphinx/locale/__init__.py:23 +#: sphinx/locale/__init__.py:146 msgid "Tip" msgstr "" -#: sphinx/locale/__init__.py:24 +#: sphinx/locale/__init__.py:147 msgid "Warning" msgstr "" -#: sphinx/locale/__init__.py:28 +#: sphinx/locale/__init__.py:151 #, python-format msgid "New in version %s" msgstr "" -#: sphinx/locale/__init__.py:29 +#: sphinx/locale/__init__.py:152 #, python-format msgid "Changed in version %s" msgstr "" -#: sphinx/locale/__init__.py:30 +#: sphinx/locale/__init__.py:153 #, python-format msgid "Deprecated since version %s" msgstr "" -#: sphinx/locale/__init__.py:34 +#: sphinx/locale/__init__.py:157 msgid "module" msgstr "" -#: sphinx/locale/__init__.py:35 +#: sphinx/locale/__init__.py:158 msgid "keyword" msgstr "" -#: sphinx/locale/__init__.py:36 +#: sphinx/locale/__init__.py:159 msgid "operator" msgstr "" -#: sphinx/locale/__init__.py:37 +#: sphinx/locale/__init__.py:160 msgid "object" msgstr "" -#: sphinx/locale/__init__.py:38 +#: sphinx/locale/__init__.py:161 msgid "exception" msgstr "" -#: sphinx/locale/__init__.py:39 +#: sphinx/locale/__init__.py:162 msgid "statement" msgstr "" -#: sphinx/locale/__init__.py:40 +#: sphinx/locale/__init__.py:163 msgid "built-in function" msgstr "" @@ -430,47 +444,47 @@ msgstr "" msgid "Enter search terms or a module, class or function name." msgstr "" -#: sphinx/themes/basic/layout.html:119 +#: sphinx/themes/basic/layout.html:122 #, python-format msgid "Search within %(docstitle)s" msgstr "" -#: sphinx/themes/basic/layout.html:128 +#: sphinx/themes/basic/layout.html:131 msgid "About these documents" msgstr "" -#: sphinx/themes/basic/layout.html:134 sphinx/themes/basic/search.html:2 +#: sphinx/themes/basic/layout.html:137 sphinx/themes/basic/search.html:2 #: sphinx/themes/basic/search.html:5 msgid "Search" msgstr "" -#: sphinx/themes/basic/layout.html:137 +#: sphinx/themes/basic/layout.html:140 msgid "Copyright" msgstr "" -#: sphinx/themes/basic/layout.html:184 +#: sphinx/themes/basic/layout.html:187 sphinx/themes/scrolls/layout.html:83 #, python-format msgid "© Copyright %(copyright)s." msgstr "" -#: sphinx/themes/basic/layout.html:186 +#: sphinx/themes/basic/layout.html:189 sphinx/themes/scrolls/layout.html:85 #, python-format msgid "© Copyright %(copyright)s." msgstr "" -#: sphinx/themes/basic/layout.html:190 +#: sphinx/themes/basic/layout.html:193 sphinx/themes/scrolls/layout.html:89 #, python-format msgid "Last updated on %(last_updated)s." msgstr "" -#: sphinx/themes/basic/layout.html:193 +#: sphinx/themes/basic/layout.html:196 sphinx/themes/scrolls/layout.html:92 #, python-format msgid "" "Created using Sphinx " "%(sphinx_version)s." msgstr "" -#: sphinx/themes/basic/modindex.html:36 +#: sphinx/themes/basic/modindex.html:36 sphinx/themes/scrolls/modindex.html:37 msgid "Deprecated" msgstr "" @@ -578,11 +592,11 @@ msgstr "" msgid "Release" msgstr "" -#: sphinx/writers/latex.py:639 +#: sphinx/writers/latex.py:644 msgid "continued from previous page" msgstr "" -#: sphinx/writers/latex.py:643 +#: sphinx/writers/latex.py:648 msgid "Continued on next page" msgstr "" From 5e335724884d7f5d1945fc2ba0a5e44cd72bae0b Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Fri, 3 Jul 2009 20:06:33 +0200 Subject: [PATCH 17/85] Fix unbound local. --- sphinx/builders/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py index 348e2e8fd..94b16b0a9 100644 --- a/sphinx/builders/__init__.py +++ b/sphinx/builders/__init__.py @@ -177,6 +177,8 @@ class Builder(object): self.config.language), nonl=True) locale_dirs = [None, path.join(package_dir, 'locale')] + \ [path.join(self.srcdir, x) for x in self.config.locale_dirs] + else: + locale_dirs = [] self.translator, has_translation = locale.init(locale_dirs, self.config.language) if self.config.language is not None: From 9199b21f85aed69a6a28ed0a622c0d145a397cab Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 5 Jul 2009 12:22:01 +0200 Subject: [PATCH 18/85] Add new builtin theme to test suite. --- 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 349a9ce4a..1c894c2b6 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', 'sphinxdoc', 'traditional', + set(['basic', 'default', 'scrolls', 'sphinxdoc', 'traditional', 'testtheme', 'ziptheme']) assert Theme.themes['testtheme'][1] is None assert isinstance(Theme.themes['ziptheme'][1], zipfile.ZipFile) From c02b7149aa3eb30184f3523d714128926bef75b8 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 5 Jul 2009 12:24:27 +0200 Subject: [PATCH 19/85] Move domain-specific code around a bit; builtin domains are now completely in sphinx.domains. Test suite does not pass yet. --- doc/markup/desc.rst | 4 +- sphinx/application.py | 8 +- sphinx/directives/desc.py | 414 --------------------------------- sphinx/domains.py | 467 +++++++++++++++++++++++++++++++++++++- sphinx/environment.py | 80 ++----- sphinx/roles.py | 210 +++++++---------- tests/root/markup.txt | 4 +- 7 files changed, 573 insertions(+), 614 deletions(-) diff --git a/doc/markup/desc.rst b/doc/markup/desc.rst index ec8ede37d..4cfba05fd 100644 --- a/doc/markup/desc.rst +++ b/doc/markup/desc.rst @@ -119,8 +119,8 @@ The directives are: 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 :cmacro:`PyObject_HEAD` and - :cmacro:`Py_BEGIN_ALLOW_THREADS`. + of its use in the Python documentation include :c:macro:`PyObject_HEAD` and + :c:macro:`Py_BEGIN_ALLOW_THREADS`. .. directive:: .. ctype:: name diff --git a/sphinx/application.py b/sphinx/application.py index 9b6cf36f7..5e34232c0 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -20,7 +20,7 @@ from docutils import nodes from docutils.parsers.rst import directives, roles import sphinx -from sphinx.roles import make_xref_role, simple_link_func +from sphinx.roles import XRefRole from sphinx.config import Config from sphinx.errors import SphinxError, SphinxWarning, ExtensionError from sphinx.domains import domains @@ -310,14 +310,16 @@ class Sphinx(object): parse_node) directives.register_directive(directivename, directive_dwim(GenericDesc)) - role_func = make_xref_role(simple_link_func, innernodeclass=ref_nodeclass) + # XXX support more options? + role_func = XRefRole('', innernodeclass=ref_nodeclass) roles.register_local_role(rolename, role_func) def add_crossref_type(self, directivename, rolename, indextemplate='', ref_nodeclass=None): additional_xref_types[directivename] = (rolename, indextemplate, None) directives.register_directive(directivename, directive_dwim(Target)) - role_func = make_xref_role(simple_link_func, innernodeclass=ref_nodeclass) + # XXX support more options + role_func = XRefRole('', innernodeclass=ref_nodeclass) roles.register_local_role(rolename, role_func) def add_transform(self, transform): diff --git a/sphinx/directives/desc.py b/sphinx/directives/desc.py index 666397c39..dfd713821 100644 --- a/sphinx/directives/desc.py +++ b/sphinx/directives/desc.py @@ -14,7 +14,6 @@ from docutils import nodes from docutils.parsers.rst import directives from sphinx import addnodes -from sphinx.domains import Domain, domains from sphinx.locale import l_ from sphinx.util import ws_re from sphinx.util.compat import Directive, directive_dwim @@ -33,39 +32,10 @@ def _is_only_paragraph(node): return False -# REs for Python signatures -py_sig_re = re.compile( - r'''^ ([\w.]*\.)? # class name(s) - (\w+) \s* # thing name - (?: \((.*)\) # optional: arguments - (?:\s* -> \s* (.*))? # return annotation - )? $ # and nothing more - ''', re.VERBOSE) - -py_paramlist_re = re.compile(r'([\[\],])') # split at '[', ']' and ',' - -# REs for C signatures -c_sig_re = re.compile( - r'''^([^(]*?) # return type - ([\w:]+) \s* # thing name (colon allowed for C++ class names) - (?: \((.*)\) )? # optionally arguments - (\s+const)? $ # const specifier - ''', re.VERBOSE) -c_funcptr_sig_re = re.compile( - r'''^([^(]+?) # return type - (\( [^()]+ \)) \s* # name in parentheses - \( (.*) \) # arguments - (\s+const)? $ # const specifier - ''', re.VERBOSE) -c_funcptr_name_re = re.compile(r'^\(\s*\*\s*(.*?)\s*\)$') - # RE for option descriptions option_desc_re = re.compile( r'((?:/|-|--)[-_a-zA-Z0-9]+)(\s*.*?)(?=,\s+(?:/|-|--)|$)') -# RE to split at word boundaries -wsplit_re = re.compile(r'(\W+)') - # RE to strip backslash escapes strip_backslash_re = re.compile(r'\\(?=[^\\])') @@ -278,360 +248,6 @@ class DescDirective(Directive): return [self.indexnode, node] -class PythonDesc(DescDirective): - """ - Description of a general Python object. - """ - - def get_signature_prefix(self, sig): - """ - May return a prefix to put before the object name in the signature. - """ - return '' - - def needs_arglist(self): - """ - May return true if an empty argument list is to be generated even if - the document contains none. - """ - return False - - def parse_signature(self, sig, signode): - """ - Transform a Python signature into RST nodes. - Returns (fully qualified name of the thing, classname if any). - - If inside a class, the current class name is handled intelligently: - * it is stripped from the displayed name if present - * it is added to the full name (return value) if not present - """ - m = py_sig_re.match(sig) - if m is None: - raise ValueError - classname, name, arglist, retann = m.groups() - - if self.env.currclass: - add_module = False - if classname and classname.startswith(self.env.currclass): - fullname = classname + name - # class name is given again in the signature - classname = classname[len(self.env.currclass):].lstrip('.') - elif classname: - # class name is given in the signature, but different - # (shouldn't happen) - fullname = self.env.currclass + '.' + classname + name - else: - # class name is not given in the signature - fullname = self.env.currclass + '.' + name - else: - add_module = True - fullname = classname and classname + name or name - - prefix = self.get_signature_prefix(sig) - if prefix: - signode += addnodes.desc_annotation(prefix, prefix) - - if classname: - signode += addnodes.desc_addname(classname, classname) - # exceptions are a special case, since they are documented in the - # 'exceptions' module. - elif add_module and self.env.config.add_module_names: - modname = self.options.get('module', self.env.currmodule) - if modname and modname != 'exceptions': - nodetext = modname + '.' - signode += addnodes.desc_addname(nodetext, nodetext) - - signode += addnodes.desc_name(name, name) - if not arglist: - if self.needs_arglist(): - # for callables, add an empty parameter list - signode += addnodes.desc_parameterlist() - if retann: - signode += addnodes.desc_returns(retann, retann) - return fullname, classname - signode += addnodes.desc_parameterlist() - - stack = [signode[-1]] - for token in py_paramlist_re.split(arglist): - if token == '[': - opt = addnodes.desc_optional() - stack[-1] += opt - stack.append(opt) - elif token == ']': - try: - stack.pop() - except IndexError: - raise ValueError - elif not token or token == ',' or token.isspace(): - pass - else: - token = token.strip() - stack[-1] += addnodes.desc_parameter(token, token) - if len(stack) != 1: - raise ValueError - if retann: - signode += addnodes.desc_returns(retann, retann) - return fullname, classname - - def get_index_text(self, modname, name): - """ - Return the text for the index entry of the object. - """ - raise NotImplementedError('must be implemented in subclasses') - - def add_target_and_index(self, name_cls, sig, signode): - modname = self.options.get('module', self.env.currmodule) - fullname = (modname and modname + '.' or '') + name_cls[0] - # note target - if fullname not in self.state.document.ids: - signode['names'].append(fullname) - signode['ids'].append(fullname) - signode['first'] = (not self.names) - self.state.document.note_explicit_target(signode) - self.env.note_descref(fullname, self.desctype, self.lineno) - - indextext = self.get_index_text(modname, name_cls) - if indextext: - self.indexnode['entries'].append(('single', indextext, - fullname, fullname)) - - def before_content(self): - # needed for automatic qualification of members (reset in subclasses) - self.clsname_set = False - - def after_content(self): - if self.clsname_set: - self.env.currclass = None - - -class ModulelevelDesc(PythonDesc): - """ - Description of an object on module level (functions, data). - """ - - def needs_arglist(self): - return self.desctype == 'function' - - def get_index_text(self, modname, name_cls): - if self.desctype == 'function': - if not modname: - return _('%s() (built-in function)') % name_cls[0] - return _('%s() (in module %s)') % (name_cls[0], modname) - elif self.desctype == 'data': - if not modname: - return _('%s (built-in variable)') % name_cls[0] - return _('%s (in module %s)') % (name_cls[0], modname) - else: - return '' - - -class ClasslikeDesc(PythonDesc): - """ - Description of a class-like object (classes, interfaces, exceptions). - """ - - def get_signature_prefix(self, sig): - return self.desctype + ' ' - - def get_index_text(self, modname, name_cls): - if self.desctype == 'class': - if not modname: - return _('%s (built-in class)') % name_cls[0] - return _('%s (class in %s)') % (name_cls[0], modname) - elif self.desctype == 'exception': - return name_cls[0] - else: - return '' - - def before_content(self): - PythonDesc.before_content(self) - if self.names: - self.env.currclass = self.names[0][0] - self.clsname_set = True - - -class ClassmemberDesc(PythonDesc): - """ - Description of a class member (methods, attributes). - """ - - def needs_arglist(self): - return self.desctype.endswith('method') - - def get_signature_prefix(self, sig): - if self.desctype == 'staticmethod': - return 'static ' - elif self.desctype == 'classmethod': - return 'classmethod ' - return '' - - def get_index_text(self, modname, name_cls): - name, cls = name_cls - add_modules = self.env.config.add_module_names - if self.desctype == 'method': - try: - clsname, methname = name.rsplit('.', 1) - except ValueError: - if modname: - return _('%s() (in module %s)') % (name, modname) - else: - return '%s()' % name - if modname and add_modules: - return _('%s() (%s.%s method)') % (methname, modname, clsname) - else: - return _('%s() (%s method)') % (methname, clsname) - elif self.desctype == 'staticmethod': - try: - clsname, methname = name.rsplit('.', 1) - except ValueError: - if modname: - return _('%s() (in module %s)') % (name, modname) - else: - return '%s()' % name - if modname and add_modules: - return _('%s() (%s.%s static method)') % (methname, modname, - clsname) - else: - return _('%s() (%s static method)') % (methname, clsname) - elif self.desctype == 'classmethod': - try: - clsname, methname = name.rsplit('.', 1) - except ValueError: - if modname: - return _('%s() (in module %s)') % (name, modname) - else: - return '%s()' % name - if modname: - return _('%s() (%s.%s class method)') % (methname, modname, - clsname) - else: - return _('%s() (%s class method)') % (methname, clsname) - elif self.desctype == 'attribute': - try: - clsname, attrname = name.rsplit('.', 1) - except ValueError: - if modname: - return _('%s (in module %s)') % (name, modname) - else: - return name - if modname and add_modules: - return _('%s (%s.%s attribute)') % (attrname, modname, clsname) - else: - return _('%s (%s attribute)') % (attrname, clsname) - else: - return '' - - def before_content(self): - PythonDesc.before_content(self) - if self.names and self.names[-1][1] and not self.env.currclass: - self.env.currclass = self.names[-1][1].strip('.') - self.clsname_set = True - - -class CDesc(DescDirective): - """ - Description of a C language object. - """ - - # These C types aren't described anywhere, so don't try to create - # a cross-reference to them - stopwords = set(('const', 'void', 'char', 'int', 'long', 'FILE', 'struct')) - - def _parse_type(self, node, ctype): - # add cross-ref nodes for all words - for part in filter(None, wsplit_re.split(ctype)): - tnode = nodes.Text(part, part) - if part[0] in string.ascii_letters+'_' and \ - part not in self.stopwords: - pnode = addnodes.pending_xref( - '', reftype='ctype', reftarget=part, - modname=None, classname=None) - pnode += tnode - node += pnode - else: - node += tnode - - def parse_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) - if m is None: - m = c_sig_re.match(sig) - if m is None: - raise ValueError('no match') - rettype, name, arglist, const = m.groups() - - signode += addnodes.desc_type('', '') - self._parse_type(signode[-1], rettype) - try: - classname, funcname = name.split('::', 1) - classname += '::' - signode += addnodes.desc_addname(classname, classname) - signode += addnodes.desc_name(funcname, funcname) - # name (the full name) is still both parts - except ValueError: - signode += addnodes.desc_name(name, name) - # clean up parentheses from canonical name - m = c_funcptr_name_re.match(name) - if m: - name = m.group(1) - if not arglist: - if self.desctype == 'cfunction': - # for functions, add an empty parameter list - signode += addnodes.desc_parameterlist() - if const: - signode += addnodes.desc_addname(const, const) - return name - - paramlist = addnodes.desc_parameterlist() - arglist = arglist.replace('`', '').replace('\\ ', '') # remove markup - # this messes up function pointer types, but not too badly ;) - args = arglist.split(',') - for arg in args: - arg = arg.strip() - param = addnodes.desc_parameter('', '', noemph=True) - try: - ctype, argname = arg.rsplit(' ', 1) - except ValueError: - # no argument name given, only the type - self._parse_type(param, arg) - else: - self._parse_type(param, ctype) - param += nodes.emphasis(' '+argname, ' '+argname) - paramlist += param - signode += paramlist - if const: - signode += addnodes.desc_addname(const, const) - return name - - def get_index_text(self, name): - if self.desctype == 'cfunction': - return _('%s (C function)') % name - elif self.desctype == 'cmember': - return _('%s (C member)') % name - elif self.desctype == 'cmacro': - return _('%s (C macro)') % name - elif self.desctype == 'ctype': - return _('%s (C type)') % name - elif self.desctype == 'cvar': - return _('%s (C variable)') % name - else: - return '' - - def add_target_and_index(self, name, sig, signode): - # note target - if name not in self.state.document.ids: - signode['names'].append(name) - signode['ids'].append(name) - signode['first'] = (not self.names) - self.state.document.note_explicit_target(signode) - self.env.note_descref(name, self.desctype, self.lineno) - - indextext = self.get_index_text(name) - if indextext: - self.indexnode['entries'].append(('single', indextext, name, name)) - - class CmdoptionDesc(DescDirective): """ Description of a command-line option (.. cmdoption). @@ -768,33 +384,3 @@ directives.register_directive('default-domain', directive_dwim(DefaultDomain)) directives.register_directive('describe', directive_dwim(DescDirective)) directives.register_directive('cmdoption', directive_dwim(CmdoptionDesc)) directives.register_directive('envvar', directive_dwim(GenericDesc)) - - -class PythonDomain(Domain): - name = 'py' - label = 'Python' - directives = { - 'function': ModulelevelDesc, - 'data': ModulelevelDesc, - 'class': ClasslikeDesc, - 'exception': ClasslikeDesc, - 'method': ClassmemberDesc, - 'classmethod': ClassmemberDesc, - 'staticmethod': ClassmemberDesc, - 'attribute': ClassmemberDesc, - } - -class CDomain(Domain): - name = 'c' - label = 'C' - directives = { - 'function': CDesc, - 'member': CDesc, - 'macro': CDesc, - 'type': CDesc, - 'var': CDesc, - } - - -domains['py'] = PythonDomain -domains['c'] = CDomain diff --git a/sphinx/domains.py b/sphinx/domains.py index 3b31bd5a1..9fa41090f 100644 --- a/sphinx/domains.py +++ b/sphinx/domains.py @@ -10,11 +10,476 @@ :license: BSD, see LICENSE for details. """ +import re + +from sphinx import addnodes +from sphinx.roles import XRefRole +from sphinx.directives import DescDirective + + class Domain(object): name = '' directives = {} roles = {} label = '' + +# REs for Python signatures +py_sig_re = re.compile( + r'''^ ([\w.]*\.)? # class name(s) + (\w+) \s* # thing name + (?: \((.*)\) # optional: arguments + (?:\s* -> \s* (.*))? # return annotation + )? $ # and nothing more + ''', re.VERBOSE) + +py_paramlist_re = re.compile(r'([\[\],])') # split at '[', ']' and ',' + + +class PythonDesc(DescDirective): + """ + Description of a general Python object. + """ + + def get_signature_prefix(self, sig): + """ + May return a prefix to put before the object name in the signature. + """ + return '' + + def needs_arglist(self): + """ + May return true if an empty argument list is to be generated even if + the document contains none. + """ + return False + + def parse_signature(self, sig, signode): + """ + Transform a Python signature into RST nodes. + Returns (fully qualified name of the thing, classname if any). + + If inside a class, the current class name is handled intelligently: + * it is stripped from the displayed name if present + * it is added to the full name (return value) if not present + """ + m = py_sig_re.match(sig) + if m is None: + raise ValueError + classname, name, arglist, retann = m.groups() + + if self.env.currclass: + add_module = False + if classname and classname.startswith(self.env.currclass): + fullname = classname + name + # class name is given again in the signature + classname = classname[len(self.env.currclass):].lstrip('.') + elif classname: + # class name is given in the signature, but different + # (shouldn't happen) + fullname = self.env.currclass + '.' + classname + name + else: + # class name is not given in the signature + fullname = self.env.currclass + '.' + name + else: + add_module = True + fullname = classname and classname + name or name + + prefix = self.get_signature_prefix(sig) + if prefix: + signode += addnodes.desc_annotation(prefix, prefix) + + if classname: + signode += addnodes.desc_addname(classname, classname) + # exceptions are a special case, since they are documented in the + # 'exceptions' module. + elif add_module and self.env.config.add_module_names: + modname = self.options.get('module', self.env.currmodule) + if modname and modname != 'exceptions': + nodetext = modname + '.' + signode += addnodes.desc_addname(nodetext, nodetext) + + signode += addnodes.desc_name(name, name) + if not arglist: + if self.needs_arglist(): + # for callables, add an empty parameter list + signode += addnodes.desc_parameterlist() + if retann: + signode += addnodes.desc_returns(retann, retann) + return fullname, classname + signode += addnodes.desc_parameterlist() + + stack = [signode[-1]] + for token in py_paramlist_re.split(arglist): + if token == '[': + opt = addnodes.desc_optional() + stack[-1] += opt + stack.append(opt) + elif token == ']': + try: + stack.pop() + except IndexError: + raise ValueError + elif not token or token == ',' or token.isspace(): + pass + else: + token = token.strip() + stack[-1] += addnodes.desc_parameter(token, token) + if len(stack) != 1: + raise ValueError + if retann: + signode += addnodes.desc_returns(retann, retann) + return fullname, classname + + def get_index_text(self, modname, name): + """ + Return the text for the index entry of the object. + """ + raise NotImplementedError('must be implemented in subclasses') + + def add_target_and_index(self, name_cls, sig, signode): + modname = self.options.get('module', self.env.currmodule) + fullname = (modname and modname + '.' or '') + name_cls[0] + # note target + if fullname not in self.state.document.ids: + signode['names'].append(fullname) + signode['ids'].append(fullname) + signode['first'] = (not self.names) + self.state.document.note_explicit_target(signode) + self.env.note_descref(fullname, self.desctype, self.lineno) + + indextext = self.get_index_text(modname, name_cls) + if indextext: + self.indexnode['entries'].append(('single', indextext, + fullname, fullname)) + + def before_content(self): + # needed for automatic qualification of members (reset in subclasses) + self.clsname_set = False + + def after_content(self): + if self.clsname_set: + self.env.currclass = None + + +class ModulelevelDesc(PythonDesc): + """ + Description of an object on module level (functions, data). + """ + + def needs_arglist(self): + return self.desctype == 'function' + + def get_index_text(self, modname, name_cls): + if self.desctype == 'function': + if not modname: + return _('%s() (built-in function)') % name_cls[0] + return _('%s() (in module %s)') % (name_cls[0], modname) + elif self.desctype == 'data': + if not modname: + return _('%s (built-in variable)') % name_cls[0] + return _('%s (in module %s)') % (name_cls[0], modname) + else: + return '' + + +class ClasslikeDesc(PythonDesc): + """ + Description of a class-like object (classes, interfaces, exceptions). + """ + + def get_signature_prefix(self, sig): + return self.desctype + ' ' + + def get_index_text(self, modname, name_cls): + if self.desctype == 'class': + if not modname: + return _('%s (built-in class)') % name_cls[0] + return _('%s (class in %s)') % (name_cls[0], modname) + elif self.desctype == 'exception': + return name_cls[0] + else: + return '' + + def before_content(self): + PythonDesc.before_content(self) + if self.names: + self.env.currclass = self.names[0][0] + self.clsname_set = True + + +class ClassmemberDesc(PythonDesc): + """ + Description of a class member (methods, attributes). + """ + + def needs_arglist(self): + return self.desctype.endswith('method') + + def get_signature_prefix(self, sig): + if self.desctype == 'staticmethod': + return 'static ' + elif self.desctype == 'classmethod': + return 'classmethod ' + return '' + + def get_index_text(self, modname, name_cls): + name, cls = name_cls + add_modules = self.env.config.add_module_names + if self.desctype == 'method': + try: + clsname, methname = name.rsplit('.', 1) + except ValueError: + if modname: + return _('%s() (in module %s)') % (name, modname) + else: + return '%s()' % name + if modname and add_modules: + return _('%s() (%s.%s method)') % (methname, modname, clsname) + else: + return _('%s() (%s method)') % (methname, clsname) + elif self.desctype == 'staticmethod': + try: + clsname, methname = name.rsplit('.', 1) + except ValueError: + if modname: + return _('%s() (in module %s)') % (name, modname) + else: + return '%s()' % name + if modname and add_modules: + return _('%s() (%s.%s static method)') % (methname, modname, + clsname) + else: + return _('%s() (%s static method)') % (methname, clsname) + elif self.desctype == 'classmethod': + try: + clsname, methname = name.rsplit('.', 1) + except ValueError: + if modname: + return _('%s() (in module %s)') % (name, modname) + else: + return '%s()' % name + if modname: + return _('%s() (%s.%s class method)') % (methname, modname, + clsname) + else: + return _('%s() (%s class method)') % (methname, clsname) + elif self.desctype == 'attribute': + try: + clsname, attrname = name.rsplit('.', 1) + except ValueError: + if modname: + return _('%s (in module %s)') % (name, modname) + else: + return name + if modname and add_modules: + return _('%s (%s.%s attribute)') % (attrname, modname, clsname) + else: + return _('%s (%s attribute)') % (attrname, clsname) + else: + return '' + + def before_content(self): + PythonDesc.before_content(self) + if self.names and self.names[-1][1] and not self.env.currclass: + self.env.currclass = self.names[-1][1].strip('.') + self.clsname_set = True + + +class PyXRefRole(XRefRole): + def process_link(self, env, pnode, has_explicit_title, title, target): + pnode['modname'] = env.currmodule + pnode['classname'] = env.currclass + 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 + # if the first character is a tilde, don't display the module/class + # parts of the contents + if title[0:1] == '~': + title = title[1:] + dot = title.rfind('.') + if dot != -1: + title = title[dot+1:] + # if the first character is a dot, search more specific namespaces first + # else search builtins first + if target[0:1] == '.': + target = target[1:] + pnode['refspecific'] = True + return title, target + + +class PythonDomain(Domain): + """Python language domain.""" + name = 'py' + label = 'Python' + directives = { + 'function': ModulelevelDesc, + 'data': ModulelevelDesc, + 'class': ClasslikeDesc, + 'exception': ClasslikeDesc, + 'method': ClassmemberDesc, + 'classmethod': ClassmemberDesc, + 'staticmethod': ClassmemberDesc, + 'attribute': ClassmemberDesc, + } + roles = { + 'data': PyXRefRole('py'), + 'exc': PyXRefRole('py'), + 'func': PyXRefRole('py', True), + 'class': PyXRefRole('py'), + 'const': PyXRefRole('py'), + 'attr': PyXRefRole('py'), + 'meth': PyXRefRole('py', True), + 'mod': PyXRefRole('py'), + 'obj': PyXRefRole('py'), + } + + +# RE to split at word boundaries +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) + (?: \((.*)\) )? # optionally arguments + (\s+const)? $ # const specifier + ''', re.VERBOSE) +c_funcptr_sig_re = re.compile( + r'''^([^(]+?) # return type + (\( [^()]+ \)) \s* # name in parentheses + \( (.*) \) # arguments + (\s+const)? $ # const specifier + ''', re.VERBOSE) +c_funcptr_name_re = re.compile(r'^\(\s*\*\s*(.*?)\s*\)$') + + +class CDesc(DescDirective): + """ + Description of a C language object. + """ + + # These C types aren't described anywhere, so don't try to create + # a cross-reference to them + stopwords = set(('const', 'void', 'char', 'int', 'long', 'FILE', 'struct')) + + def _parse_type(self, node, ctype): + # add cross-ref nodes for all words + for part in filter(None, wsplit_re.split(ctype)): + tnode = nodes.Text(part, part) + if part[0] in string.ascii_letters+'_' and \ + part not in self.stopwords: + pnode = addnodes.pending_xref( + '', reftype='ctype', reftarget=part, + modname=None, classname=None) + pnode += tnode + node += pnode + else: + node += tnode + + def parse_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) + if m is None: + m = c_sig_re.match(sig) + if m is None: + raise ValueError('no match') + rettype, name, arglist, const = m.groups() + + signode += addnodes.desc_type('', '') + self._parse_type(signode[-1], rettype) + try: + classname, funcname = name.split('::', 1) + classname += '::' + signode += addnodes.desc_addname(classname, classname) + signode += addnodes.desc_name(funcname, funcname) + # name (the full name) is still both parts + except ValueError: + signode += addnodes.desc_name(name, name) + # clean up parentheses from canonical name + m = c_funcptr_name_re.match(name) + if m: + name = m.group(1) + if not arglist: + if self.desctype == 'cfunction': + # for functions, add an empty parameter list + signode += addnodes.desc_parameterlist() + if const: + signode += addnodes.desc_addname(const, const) + return name + + paramlist = addnodes.desc_parameterlist() + arglist = arglist.replace('`', '').replace('\\ ', '') # remove markup + # this messes up function pointer types, but not too badly ;) + args = arglist.split(',') + for arg in args: + arg = arg.strip() + param = addnodes.desc_parameter('', '', noemph=True) + try: + ctype, argname = arg.rsplit(' ', 1) + except ValueError: + # no argument name given, only the type + self._parse_type(param, arg) + else: + self._parse_type(param, ctype) + param += nodes.emphasis(' '+argname, ' '+argname) + paramlist += param + signode += paramlist + if const: + signode += addnodes.desc_addname(const, const) + return name + + def get_index_text(self, name): + if self.desctype == 'cfunction': + return _('%s (C function)') % name + elif self.desctype == 'cmember': + return _('%s (C member)') % name + elif self.desctype == 'cmacro': + return _('%s (C macro)') % name + elif self.desctype == 'ctype': + return _('%s (C type)') % name + elif self.desctype == 'cvar': + return _('%s (C variable)') % name + else: + return '' + + def add_target_and_index(self, name, sig, signode): + # note target + if name not in self.state.document.ids: + signode['names'].append(name) + signode['ids'].append(name) + signode['first'] = (not self.names) + self.state.document.note_explicit_target(signode) + self.env.note_descref(name, self.desctype, self.lineno) + + indextext = self.get_index_text(name) + if indextext: + self.indexnode['entries'].append(('single', indextext, name, name)) + + +class CDomain(Domain): + """C language domain.""" + name = 'c' + label = 'C' + directives = { + 'function': CDesc, + 'member': CDesc, + 'macro': CDesc, + 'type': CDesc, + 'var': CDesc, + } + roles = { + 'member': XRefRole('c'), + 'macro': XRefRole('c'), + 'func' : XRefRole('c', True), + 'data': XRefRole('c'), + 'type': XRefRole('c'), + } + + # this contains all registered domains -domains = {} +domains = { + 'py': PythonDomain, + 'c': CDomain, +} diff --git a/sphinx/environment.py b/sphinx/environment.py index d505229b1..9393b6ff9 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -48,6 +48,7 @@ from sphinx.errors import SphinxError from sphinx.domains import domains from sphinx.directives import additional_xref_types +orig_role_function = roles.role orig_directive_function = directives.directive default_settings = { @@ -617,6 +618,21 @@ class BuildEnvironment: document) directives.directive = directive + def role(role_name, language_module, lineno, reporter): + if ':' in role_name: + domain_name, role_name = role_name.split(':', 1) + if domain_name in domains: + domain = domains[domain_name] + if role_name in domain.roles: + return domain.roles[role_name], [] + elif self.default_domain is not None: + role = self.default_domain.roles.get(role_name) + if role is not None: + return role, [] + return orig_role_function(role_name, language_module, + lineno, reporter) + roles.role = role + # publish manually pub = Publisher(reader=SphinxStandaloneReader(), writer=SphinxDummyWriter(), @@ -1625,67 +1641,3 @@ class BuildEnvironment: if newname is None: return None, None return newname, self.descrefs[newname] - - def find_keyword(self, keyword, avoid_fuzzy=False, cutoff=0.6, n=20): - """ - Find keyword matches for a keyword. If there's an exact match, - just return it, else return a list of fuzzy matches if avoid_fuzzy - isn't True. - - Keywords searched are: first modules, then descrefs. - - Returns: None if nothing found - (type, docname, anchorname) if exact match found - list of (quality, type, docname, anchorname, description) - if fuzzy - """ - - if keyword in self.modules: - docname, title, system, deprecated = self.modules[keyword] - return 'module', docname, 'module-' + keyword - if keyword in self.descrefs: - docname, ref_type = self.descrefs[keyword] - return ref_type, docname, keyword - # special cases - if '.' not in keyword: - # exceptions are documented in the exceptions module - if 'exceptions.'+keyword in self.descrefs: - docname, ref_type = self.descrefs['exceptions.'+keyword] - return ref_type, docname, 'exceptions.'+keyword - # special methods are documented as object methods - if 'object.'+keyword in self.descrefs: - docname, ref_type = self.descrefs['object.'+keyword] - return ref_type, docname, 'object.'+keyword - - if avoid_fuzzy: - return - - # find fuzzy matches - s = difflib.SequenceMatcher() - s.set_seq2(keyword.lower()) - - def possibilities(): - for title, (fn, desc, _, _) in self.modules.iteritems(): - yield ('module', fn, 'module-'+title, desc) - for title, (fn, desctype) in self.descrefs.iteritems(): - yield (desctype, fn, title, '') - - def dotsearch(string): - parts = string.lower().split('.') - for idx in xrange(0, len(parts)): - yield '.'.join(parts[idx:]) - - result = [] - for type, docname, title, desc in possibilities(): - best_res = 0 - for part in dotsearch(title): - s.set_seq1(part) - if s.real_quick_ratio() >= cutoff and \ - s.quick_ratio() >= cutoff and \ - s.ratio() >= cutoff and \ - s.ratio() > best_res: - best_res = s.ratio() - if best_res: - result.append((best_res, type, docname, title, desc)) - - return heapq.nlargest(n, result) diff --git a/sphinx/roles.py b/sphinx/roles.py index e59cf0703..74f933b21 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -94,36 +94,6 @@ roles.register_local_role('pep', indexmarkup_role) roles.register_local_role('rfc', indexmarkup_role) -def make_xref_role(link_func, nodeclass=None, innernodeclass=None): - if nodeclass is None: - nodeclass = addnodes.pending_xref - if innernodeclass is None: - innernodeclass = nodes.literal - - def func(typ, rawtext, text, lineno, inliner, options={}, content=[]): - env = inliner.document.settings.env - if not typ: - typ = env.config.default_role - else: - typ = typ.lower() - text = utils.unescape(text) - # if the first character is a bang, don't cross-reference at all - if text[0:1] == '!': - text = _fix_parens(typ, text[1:], env) - return [innernodeclass(rawtext, text, classes=['xref'])], [] - # we want a cross-reference, create the reference node - pnode = nodeclass(rawtext, reftype=typ, refcaption=False, - modname=env.currmodule, classname=env.currclass) - # we may need the line number for warnings - pnode.line = lineno - has_explicit_title, title, target = split_explicit_title(text) - target, title = link_func(env, pnode, has_explicit_title, title, target) - pnode['reftarget'] = target - pnode += innernodeclass(rawtext, title, classes=['xref']) - return [pnode], [] - return func - - def menusel_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): return [nodes.emphasis( rawtext, @@ -161,112 +131,92 @@ def abbr_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): return [addnodes.abbreviation(abbr, abbr, explanation=expl)], [] -def normalize_func_parens(env, has_explicit_title, title, target): - if has_explicit_title: - if title.endswith('()'): - # remove parentheses - title = title[:-2] - if env.config.add_function_parentheses: - # add them back to all occurrences if configured - title += '()' - # remove parentheses from the target too - if target.endswith('()'): - target = target[:-2] - return target, title +# -- generic cross-reference roles --------------------------------------------- + +class XRefRole(object): + nodeclass = addnodes.pending_xref + innernodeclass = nodes.literal + + def __init__(self, domain_name, fix_parens=False, lowercase=False, + nodeclass=None, innernodeclass=None): + self.domain_name = domain_name + self.fix_parens = fix_parens + if nodeclass is not None: + self.nodeclass = nodeclass + if innernodeclass is not None: + self.innernodeclass = innernodeclass + + def process_link(self, env, pnode, has_explicit_title, title, target): + return title, ws_re.sub(' ', target) + + def normalize_func_parens(self, env, has_explicit_title, title, target): + if not has_explicit_title: + if title.endswith('()'): + # remove parentheses + title = title[:-2] + if env.config.add_function_parentheses: + # add them back to all occurrences if configured + title += '()' + # remove parentheses from the target too + if target.endswith('()'): + target = target[:-2] + return title, target + + def __call__(self, typ, rawtext, text, lineno, inliner, options={}, content=[]): + env = inliner.document.settings.env + if not typ: + typ = env.config.default_role + else: + typ = typ.lower() + if ":" in typ: + domain, role = typ.split(":", 1) + else: + domain, role = self.domain_name, typ + 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, _ = self.normalize_func_parens(env, False, text[1:], "") + return [self.innernodeclass(rawtext, text, classes=['xref'])], [] + # split title and target in role content + has_explicit_title, title, target = split_explicit_title(text) + # we want a cross-reference, create the reference node + pnode = self.nodeclass(rawtext, reftype=role, refdomain=domain, + refcaption=has_explicit_title) + # we may need the line number for warnings + pnode.line = lineno + title, target = self.process_link(env, pnode, has_explicit_title, title, target) + pnode['reftarget'] = target + pnode += self.innernodeclass(rawtext, title, classes=['xref']) + return [pnode], [] -def generic_link_func(env, pnode, has_explicit_title, title, target): - if has_explicit_title: - pnode['refcaption'] = True - return target, title +class OptionXRefRole(XRefRole): + innernodeclass = addnodes.literal_emphasis - -def pyref_link_func(env, pnode, has_explicit_title, title, target): - if has_explicit_title: - pnode['refcaption'] = True - # fix-up parentheses in link title - else: - title = title.lstrip('.') # only has a meaning for the target - 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[0:1] == '~': - title = title[1:] - dot = title.rfind('.') - if dot != -1: - title = title[dot+1:] - # if the first character is a dot, search more specific namespaces first - # else search builtins first - if target[0:1] == '.': - target = target[1:] - pnode['refspecific'] = True - return target, title - - -def pyref_callable_link_func(env, pnode, has_explicit_title, title, target): - target, title = pyref_link_func(env, pnode, has_explicit_title, title, target) - target, title = normalize_func_parens(env, has_explicit_title, title, target) - return target, title - - -def option_link_func(env, pnode, has_explicit_title, title, target): - program = env.currprogram - if not has_explicit_title: - if ' ' in title and not (title.startswith('/') or - title.startswith('-')): - program, target = re.split(' (?=-|--|/)', title, 1) + def process_link(self, env, pnode, has_explicit_title, title, target): + program = env.currprogram + if not has_explicit_title: + if ' ' in title and not (title.startswith('/') or + title.startswith('-')): + program, target = re.split(' (?=-|--|/)', title, 1) + program = ws_re.sub('-', program) + target = target.strip() + elif ' ' in target: + program, target = re.split(' (?=-|--|/)', target, 1) program = ws_re.sub('-', program) - target = target.strip() - elif ' ' in target: - program, target = re.split(' (?=-|--|/)', target, 1) - program = ws_re.sub('-', program) - pnode['refprogram'] = program - return target, title + pnode['refprogram'] = program + return title, target -def simple_link_func(env, pnode, has_explicit_title, title, target): - # normalize all whitespace to avoid referencing problems - target = ws_re.sub(' ', target) - return target, title - - -def lowercase_link_func(env, pnode, has_explicit_title, title, target): - target, title = simple_link_func(env, pnode, has_explicit_title, title, target) - return target.lower(), title - - -def cfunc_link_func(env, pnode, has_explicit_title, title, target): - return normalize_func_parens(env, has_explicit_title, title, target) - - -generic_pyref_role = make_xref_role(pyref_link_func) -callable_pyref_role = make_xref_role(pyref_callable_link_func) -simple_xref_role = make_xref_role(simple_link_func) - specific_docroles = { - 'data': generic_pyref_role, - 'exc': generic_pyref_role, - 'func': callable_pyref_role, - 'class': generic_pyref_role, - 'const': generic_pyref_role, - 'attr': generic_pyref_role, - 'meth': callable_pyref_role, - 'mod': generic_pyref_role, - 'obj': generic_pyref_role, - - 'keyword': simple_xref_role, - 'ref': make_xref_role(lowercase_link_func, None, nodes.emphasis), - 'token': simple_xref_role, - 'term': make_xref_role(lowercase_link_func, None, nodes.emphasis), - 'option': make_xref_role(option_link_func, None, addnodes.literal_emphasis), - 'doc': simple_xref_role, - 'download': make_xref_role(simple_link_func, addnodes.download_reference), - - 'cmember': simple_xref_role, - 'cmacro': simple_xref_role, - 'cfunc' : make_xref_role(cfunc_link_func), - 'cdata': simple_xref_role, - 'ctype': simple_xref_role, + 'keyword': XRefRole(''), + 'ref': XRefRole('', lowercase=True, innernodeclass=nodes.emphasis), + 'token': XRefRole(''), + 'term': XRefRole('', lowercase=True, innernodeclass=nodes.emphasis), + 'option': OptionXRefRole('', innernodeclass=addnodes.literal_emphasis), + 'doc': XRefRole(''), + 'download': XRefRole('', nodeclass=addnodes.download_reference), 'menuselection': menusel_role, 'file': emph_literal_role, @@ -276,3 +226,5 @@ specific_docroles = { for rolename, func in specific_docroles.iteritems(): roles.register_local_role(rolename, func) + + diff --git a/tests/root/markup.txt b/tests/root/markup.txt index 32b037ee1..1e5b7ebad 100644 --- a/tests/root/markup.txt +++ b/tests/root/markup.txt @@ -182,7 +182,8 @@ Testing öäü... Object markup ------------- -:cfunc:`CFunction`. +:c:func:`CFunction`. :c:func:`!malloc`. + Only directive -------------- @@ -207,3 +208,4 @@ Only directive .. rubric:: Footnotes .. [#] Like footnotes. + From 43f475b74e0ff622085d257b8f09eade5c4bb3ee Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 5 Jul 2009 12:28:45 +0200 Subject: [PATCH 20/85] Fix long lines. --- doc/builders.rst | 2 +- doc/ext/autosummary.rst | 2 +- sphinx/builders/qthelp.py | 3 ++- sphinx/ext/autosummary/__init__.py | 2 +- sphinx/ext/autosummary/generate.py | 9 ++++++--- sphinx/ext/pngmath.py | 3 ++- sphinx/locale/__init__.py | 4 ++-- sphinx/quickstart.py | 3 ++- sphinx/roles.py | 6 ++++-- sphinx/util/pycompat.py | 3 ++- sphinx/writers/latex.py | 8 +++++--- 11 files changed, 28 insertions(+), 17 deletions(-) diff --git a/doc/builders.rst b/doc/builders.rst index 9b001105b..7891ce747 100644 --- a/doc/builders.rst +++ b/doc/builders.rst @@ -58,7 +58,7 @@ The builder's "name" must be given to the **-b** command-line option of .. class:: DevhelpBuilder This builder produces the same output as the standalone HTML builder, but - also generates `GNOME Devhelp `__ + also generates `GNOME Devhelp `__ support file that allows the GNOME Devhelp reader to view them. Its name is ``devhelp``. diff --git a/doc/ext/autosummary.rst b/doc/ext/autosummary.rst index 57abd6165..f270a67b6 100644 --- a/doc/ext/autosummary.rst +++ b/doc/ext/autosummary.rst @@ -220,6 +220,6 @@ The following variables available in the templates: for classes. .. note:: - + You can use the :dir:`autosummary` directive in the stub pages. Stub pages are generated also based on these directives. diff --git a/sphinx/builders/qthelp.py b/sphinx/builders/qthelp.py index 1020bc186..62a55ee89 100644 --- a/sphinx/builders/qthelp.py +++ b/sphinx/builders/qthelp.py @@ -150,7 +150,8 @@ class QtHelpBuilder(StandaloneHTMLBuilder): staticdir = path.join(outdir, '_static') imagesdir = path.join(outdir, '_images') for root, dirs, files in os.walk(outdir): - resourcedir = root.startswith(staticdir) or root.startswith(imagesdir) + resourcedir = root.startswith(staticdir) or \ + root.startswith(imagesdir) for fn in files: if (resourcedir and not fn.endswith('.js')) or \ fn.endswith('.html'): diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index dc641674f..3c9ff466b 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -226,7 +226,7 @@ class Autosummary(Directive): ``[(name, signature, summary_string, real_name), ...]``. """ env = self.state.document.settings.env - + prefixes = [''] if env.currmodule: prefixes.insert(0, env.currmodule) diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index f8447e949..568ee99f6 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -233,9 +233,12 @@ def find_autosummary_in_lines(lines, module=None, filename=None): corresponding options set. """ autosummary_re = re.compile(r'^\s*\.\.\s+autosummary::\s*') - automodule_re = re.compile(r'^\s*\.\.\s+automodule::\s*([A-Za-z0-9_.]+)\s*$') - module_re = re.compile(r'^\s*\.\.\s+(current)?module::\s*([a-zA-Z0-9_.]+)\s*$') - autosummary_item_re = re.compile(r'^\s+(~?[_a-zA-Z][a-zA-Z0-9_.]*)\s*.*?') + automodule_re = re.compile( + r'^\s*\.\.\s+automodule::\s*([A-Za-z0-9_.]+)\s*$') + module_re = re.compile( + r'^\s*\.\.\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*$') template_arg_re = re.compile(r'^\s+:template:\s*(.*?)\s*$') diff --git a/sphinx/ext/pngmath.py b/sphinx/ext/pngmath.py index cc6d9a9b3..8816e5bb9 100644 --- a/sphinx/ext/pngmath.py +++ b/sphinx/ext/pngmath.py @@ -237,7 +237,8 @@ def setup(app): app.add_config_value('pngmath_dvipng', 'dvipng', 'html') app.add_config_value('pngmath_latex', 'latex', 'html') app.add_config_value('pngmath_use_preview', False, 'html') - app.add_config_value('pngmath_dvipng_args', ['-gamma 1.5', '-D 110'], 'html') + app.add_config_value('pngmath_dvipng_args', + ['-gamma 1.5', '-D 110'], 'html') app.add_config_value('pngmath_latex_args', [], 'html') app.add_config_value('pngmath_latex_preamble', '', 'html') app.connect('build-finished', cleanup_tempdir) diff --git a/sphinx/locale/__init__.py b/sphinx/locale/__init__.py index a9abc9cb9..63b5caf0d 100644 --- a/sphinx/locale/__init__.py +++ b/sphinx/locale/__init__.py @@ -121,8 +121,8 @@ class _TranslationProxy(UserString.UserString, object): return '<%s broken>' % self.__class__.__name__ def mygettext(string): - """Used instead of _ when creating TranslationProxies, because _ is not bound - yet at that time.""" + """Used instead of _ when creating TranslationProxies, because _ is + not bound yet at that time.""" return _(string) def lazy_gettext(string): diff --git a/sphinx/quickstart.py b/sphinx/quickstart.py index c052dd609..97aae5ff6 100644 --- a/sphinx/quickstart.py +++ b/sphinx/quickstart.py @@ -332,7 +332,8 @@ devhelp: \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 $$HOME/.local/share/devhelp/%(project_fn)s" +\t@echo "# ln -s %(rbuilddir)s/devhelp \ +$$HOME/.local/share/devhelp/%(project_fn)s" \t@echo "# devhelp" latex: diff --git a/sphinx/roles.py b/sphinx/roles.py index 74f933b21..66729ca92 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -162,7 +162,8 @@ class XRefRole(object): target = target[:-2] return title, target - def __call__(self, typ, rawtext, text, lineno, inliner, options={}, content=[]): + def __call__(self, typ, rawtext, text, lineno, inliner, + options={}, content=[]): env = inliner.document.settings.env if not typ: typ = env.config.default_role @@ -185,7 +186,8 @@ class XRefRole(object): refcaption=has_explicit_title) # we may need the line number for warnings pnode.line = lineno - title, target = self.process_link(env, pnode, has_explicit_title, title, target) + title, target = self.process_link(env, pnode, + has_explicit_title, title, target) pnode['reftarget'] = target pnode += self.innernodeclass(rawtext, title, classes=['xref']) return [pnode], [] diff --git a/sphinx/util/pycompat.py b/sphinx/util/pycompat.py index 137c35c87..74fb89644 100644 --- a/sphinx/util/pycompat.py +++ b/sphinx/util/pycompat.py @@ -27,7 +27,8 @@ if sys.version_info < (2, 5): # begin code copied from utf_8_sig.py in Python 2.6 def encode(input, errors='strict'): - return (codecs.BOM_UTF8 + codecs.utf_8_encode(input, errors)[0], len(input)) + return (codecs.BOM_UTF8 + codecs.utf_8_encode(input, errors)[0], + len(input)) def decode(input, errors='strict'): prefix = 0 diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 4d2e6169c..ff280805d 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -641,11 +641,13 @@ class LaTeXTranslator(nodes.NodeVisitor): self.body.append('\\hline\n') self.body.append('\\endfirsthead\n\n') self.body.append('\multicolumn{%s}{c}%%\n' % self.table.colcount) - self.body.append('{{\\bfseries \\tablename\\ \\thetable{} -- %s}} \\\\\n' % _('continued from previous page')) + self.body.append('{{\\bfseries \\tablename\\ \\thetable{} -- %s}} ' + '\\\\\n' % _('continued from previous page')) self.body.append('\\hline\n') self.body.append('\\endhead\n\n') - self.body.append('\\hline \multicolumn{%s}{|r|}{{%s}} \\\\ \\hline\n' % ( - self.table.colcount, _('Continued on next page'))) + self.body.append('\\hline \multicolumn{%s}{|r|}{{%s}} \\\\ ' + '\\hline\n' % (self.table.colcount, + _('Continued on next page'))) self.body.append('\\endfoot\n\n') self.body.append('\\hline\n') self.body.append('\\endlastfoot\n\n') From da12e2f36d8d0e6537628075ff6fffaee9273aa3 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 9 Jul 2009 11:54:32 +0200 Subject: [PATCH 21/85] ifconfig now allows titles as content. --- sphinx/ext/ifconfig.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sphinx/ext/ifconfig.py b/sphinx/ext/ifconfig.py index 90cd2b2c4..1bc66af82 100644 --- a/sphinx/ext/ifconfig.py +++ b/sphinx/ext/ifconfig.py @@ -41,7 +41,8 @@ class IfConfig(Directive): node.document = self.state.document node.line = self.lineno node['expr'] = self.arguments[0] - self.state.nested_parse(self.content, self.content_offset, node) + self.state.nested_parse(self.content, self.content_offset, + node, match_titles=1) return [node] From ab7cfb4a99e1496443fba87a37723347513169f5 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 13 Jul 2009 16:59:45 +0200 Subject: [PATCH 22/85] Make domains instances, which have factory methods for their roles and directives. --- sphinx/application.py | 11 ++++++++--- sphinx/directives/desc.py | 3 ++- sphinx/domains.py | 32 +++++++++++++++++++++++++++++++- sphinx/environment.py | 27 +++++++++++++++------------ 4 files changed, 56 insertions(+), 17 deletions(-) diff --git a/sphinx/application.py b/sphinx/application.py index 5e34232c0..d4d8b1bf4 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -23,7 +23,7 @@ import sphinx from sphinx.roles import XRefRole from sphinx.config import Config from sphinx.errors import SphinxError, SphinxWarning, ExtensionError -from sphinx.domains import domains +from sphinx.domains import all_domains from sphinx.builders import BUILTIN_BUILDERS from sphinx.directives import GenericDesc, Target, additional_xref_types from sphinx.environment import SphinxStandaloneReader @@ -104,6 +104,11 @@ class Sphinx(object): # now that we know all config values, collect them from conf.py self.config.init_values() + # intialize domains + self.domains = {} + for domain in all_domains.keys(): + self.domains[domain] = all_domains[domain](self) + if buildername is None: print >>status, 'No builder selected, using default: html' buildername = 'html' @@ -300,9 +305,9 @@ class Sphinx(object): roles.register_local_role(name, role) def add_domain(self, domain): - if domain.name in domains: + if domain.name in all_domains: raise ExtensionError('domain %s already registered' % domain.name) - domains[domain.name] = domain + all_domains[domain.name] = domain def add_description_unit(self, directivename, rolename, indextemplate='', parse_node=None, ref_nodeclass=None): diff --git a/sphinx/directives/desc.py b/sphinx/directives/desc.py index dfd713821..e30181003 100644 --- a/sphinx/directives/desc.py +++ b/sphinx/directives/desc.py @@ -366,7 +366,8 @@ class DefaultDomain(Directive): def run(self): env = self.state.document.settings.env domain_name = arguments[0] - env.default_domain = domains.get(domain_name) + # XXX won't work + env.default_domain = env.domains.get(domain_name) # Note: the target directive is not registered here, it is used by the diff --git a/sphinx/domains.py b/sphinx/domains.py index 9fa41090f..82f1a5669 100644 --- a/sphinx/domains.py +++ b/sphinx/domains.py @@ -23,6 +23,36 @@ class Domain(object): roles = {} label = '' + def __init__(self, app): + self.app = app + self._role_cache = {} + self._directive_cache = {} + + def role(self, name): + if name in self._role_cache: + return self._role_cache[name] + if name not in self.roles: + return None + def role_adapter(typ, rawtext, text, lineno, inliner, + options={}, content=[]): + return self.roles[name](name, rawtext, text, lineno, + inliner, options, content) + self._role_cache[name] = role_adapter + return role_adapter + + def directive(self, name): + if name in self._directive_cache: + return self._directive_cache[name] + if name not in self.directives: + return None + BaseDirective = self.directives[name] + class DirectiveAdapter(BaseDirective): + def run(self): + self.name = name + return BaseDirective.run(self) + self._directive_cache[name] = DirectiveAdapter + return DirectiveAdapter + # REs for Python signatures py_sig_re = re.compile( @@ -479,7 +509,7 @@ class CDomain(Domain): # this contains all registered domains -domains = { +all_domains = { 'py': PythonDomain, 'c': CDomain, } diff --git a/sphinx/environment.py b/sphinx/environment.py index 9393b6ff9..d12d51138 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -45,7 +45,6 @@ from sphinx import addnodes from sphinx.util import movefile, get_matching_docs, SEP, ustrftime, \ docname_join, FilenameUniqDict, url_re from sphinx.errors import SphinxError -from sphinx.domains import domains from sphinx.directives import additional_xref_types orig_role_function = roles.role @@ -561,6 +560,7 @@ class BuildEnvironment: # remove all inventory entries for that file if app: app.emit('env-purge-doc', self, docname) + self.clear_doc(docname) if src_path is None: @@ -600,18 +600,19 @@ class BuildEnvironment: return data # defaults to the global default, but can be re-set in a document - self.default_domain = domains.get(self.config.default_domain) + self.default_domain = app.domains.get(self.config.default_domain) # monkey-patch, so that domain directives take precedence def directive(directive_name, language_module, document): if ':' in directive_name: domain_name, directive_name = directive_name.split(':', 1) - if domain_name in domains: - domain = domains[domain_name] - if directive_name in domain.directives: - return domain.directives[directive_name], [] + if domain_name in app.domains: + domain = app.domains[domain_name] + directive = domain.directive(directive_name) + if directive is not None: + return directive, [] elif self.default_domain is not None: - directive = self.default_domain.directives.get(directive_name) + directive = self.default_domain.directive(directive_name) if directive is not None: return directive, [] return orig_directive_function(directive_name, language_module, @@ -621,12 +622,13 @@ class BuildEnvironment: def role(role_name, language_module, lineno, reporter): if ':' in role_name: domain_name, role_name = role_name.split(':', 1) - if domain_name in domains: - domain = domains[domain_name] - if role_name in domain.roles: - return domain.roles[role_name], [] + if domain_name in app.domains: + domain = app.domains[domain_name] + role = domain.role(role_name) + if role is not None: + return role, [] elif self.default_domain is not None: - role = self.default_domain.roles.get(role_name) + role = self.default_domain.role(role_name) if role is not None: return role, [] return orig_role_function(role_name, language_module, @@ -678,6 +680,7 @@ class BuildEnvironment: self.docname = None self.currmodule = None self.currclass = None + self.default_domain = None self.gloss_entries = set() if save_parsed: From dd20b14b197774c12fbc7abe71e617da528c0d24 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 13 Jul 2009 17:08:05 +0200 Subject: [PATCH 23/85] Fix role class to not take a domain name anymore. --- sphinx/domains.py | 34 ++++++++++++++++++---------------- sphinx/roles.py | 23 +++++++++++------------ 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/sphinx/domains.py b/sphinx/domains.py index 82f1a5669..125496a77 100644 --- a/sphinx/domains.py +++ b/sphinx/domains.py @@ -33,9 +33,10 @@ class Domain(object): return self._role_cache[name] if name not in self.roles: return None + fullname = '%s:%s' % (self.name, name) def role_adapter(typ, rawtext, text, lineno, inliner, options={}, content=[]): - return self.roles[name](name, rawtext, text, lineno, + return self.roles[name](fullname, rawtext, text, lineno, inliner, options, content) self._role_cache[name] = role_adapter return role_adapter @@ -45,10 +46,11 @@ class Domain(object): return self._directive_cache[name] if name not in self.directives: return None + fullname = '%s:%s' % (self.name, name) BaseDirective = self.directives[name] class DirectiveAdapter(BaseDirective): def run(self): - self.name = name + self.name = fullname return BaseDirective.run(self) self._directive_cache[name] = DirectiveAdapter return DirectiveAdapter @@ -353,15 +355,15 @@ class PythonDomain(Domain): 'attribute': ClassmemberDesc, } roles = { - 'data': PyXRefRole('py'), - 'exc': PyXRefRole('py'), - 'func': PyXRefRole('py', True), - 'class': PyXRefRole('py'), - 'const': PyXRefRole('py'), - 'attr': PyXRefRole('py'), - 'meth': PyXRefRole('py', True), - 'mod': PyXRefRole('py'), - 'obj': PyXRefRole('py'), + 'data': PyXRefRole(), + 'exc': PyXRefRole(), + 'func': PyXRefRole(fix_parens=True), + 'class': PyXRefRole(), + 'const': PyXRefRole(), + 'attr': PyXRefRole(), + 'meth': PyXRefRole(fix_parens=True), + 'mod': PyXRefRole(), + 'obj': PyXRefRole(), } @@ -500,11 +502,11 @@ class CDomain(Domain): 'var': CDesc, } roles = { - 'member': XRefRole('c'), - 'macro': XRefRole('c'), - 'func' : XRefRole('c', True), - 'data': XRefRole('c'), - 'type': XRefRole('c'), + 'member': XRefRole(), + 'macro': XRefRole(), + 'func' : XRefRole(fix_parens=True), + 'data': XRefRole(), + 'type': XRefRole(), } diff --git a/sphinx/roles.py b/sphinx/roles.py index 66729ca92..8833c275d 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -137,9 +137,8 @@ class XRefRole(object): nodeclass = addnodes.pending_xref innernodeclass = nodes.literal - def __init__(self, domain_name, fix_parens=False, lowercase=False, + def __init__(self, fix_parens=False, lowercase=False, nodeclass=None, innernodeclass=None): - self.domain_name = domain_name self.fix_parens = fix_parens if nodeclass is not None: self.nodeclass = nodeclass @@ -169,10 +168,10 @@ class XRefRole(object): typ = env.config.default_role else: typ = typ.lower() - if ":" in typ: - domain, role = typ.split(":", 1) + if ':' not in typ: + domain, role = '', typ else: - domain, role = self.domain_name, typ + domain, role = typ.split(':', 1) text = utils.unescape(text) # if the first character is a bang, don't cross-reference at all if text[0:1] == '!': @@ -212,13 +211,13 @@ class OptionXRefRole(XRefRole): specific_docroles = { - 'keyword': XRefRole(''), - 'ref': XRefRole('', lowercase=True, innernodeclass=nodes.emphasis), - 'token': XRefRole(''), - 'term': XRefRole('', lowercase=True, innernodeclass=nodes.emphasis), - 'option': OptionXRefRole('', innernodeclass=addnodes.literal_emphasis), - 'doc': XRefRole(''), - 'download': XRefRole('', nodeclass=addnodes.download_reference), + 'keyword': XRefRole(), + 'ref': XRefRole(lowercase=True, innernodeclass=nodes.emphasis), + 'token': XRefRole(), + 'term': XRefRole(lowercase=True, innernodeclass=nodes.emphasis), + 'option': OptionXRefRole(innernodeclass=addnodes.literal_emphasis), + 'doc': XRefRole(), + 'download': XRefRole(nodeclass=addnodes.download_reference), 'menuselection': menusel_role, 'file': emph_literal_role, From 9941b0942839c00a2f39e5bd4ef081024d092698 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 13 Jul 2009 17:27:43 +0200 Subject: [PATCH 24/85] Fix usage of XRefRole with domain parameter. --- sphinx/application.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sphinx/application.py b/sphinx/application.py index d4d8b1bf4..4bd0063da 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -316,7 +316,7 @@ class Sphinx(object): directives.register_directive(directivename, directive_dwim(GenericDesc)) # XXX support more options? - role_func = XRefRole('', innernodeclass=ref_nodeclass) + role_func = XRefRole(innernodeclass=ref_nodeclass) roles.register_local_role(rolename, role_func) def add_crossref_type(self, directivename, rolename, indextemplate='', @@ -324,7 +324,7 @@ class Sphinx(object): additional_xref_types[directivename] = (rolename, indextemplate, None) directives.register_directive(directivename, directive_dwim(Target)) # XXX support more options - role_func = XRefRole('', innernodeclass=ref_nodeclass) + role_func = XRefRole(innernodeclass=ref_nodeclass) roles.register_local_role(rolename, role_func) def add_transform(self, transform): From c92f97d848d459a2a969ff7f7b05730b97e28c3b Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 13 Jul 2009 17:28:07 +0200 Subject: [PATCH 25/85] Move xref resolution to domain class. --- sphinx/domains.py | 117 ++++++++++++++++++++++++++++++++++++++++-- sphinx/environment.py | 102 ++++-------------------------------- 2 files changed, 124 insertions(+), 95 deletions(-) diff --git a/sphinx/domains.py b/sphinx/domains.py index 125496a77..fdf5a9d7c 100644 --- a/sphinx/domains.py +++ b/sphinx/domains.py @@ -4,7 +4,7 @@ ~~~~~~~~~~~~~~ Support for domains, which are groupings of description directives - describing e.g. constructs of one programming language. + and roles describing e.g. constructs of one programming language. :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. @@ -23,12 +23,22 @@ class Domain(object): roles = {} label = '' - def __init__(self, app): - self.app = app + def __init__(self): self._role_cache = {} self._directive_cache = {} + def __getstate__(self): + # can't pickle the adapter caches + state = self.__dict__.copy() + state['_role_cache'] = {} + state['_directive_cache'] = {} + return state + def role(self, name): + """ + Return a role adapter function that always gives the registered + role its full name ('domain:name') as the first argument. + """ if name in self._role_cache: return self._role_cache[name] if name not in self.roles: @@ -42,6 +52,10 @@ class Domain(object): return role_adapter def directive(self, name): + """ + Return a directive adapter class that always gives the registered + directive its full name ('domain:name') as ``self.name``. + """ if name in self._directive_cache: return self._directive_cache[name] if name not in self.directives: @@ -55,6 +69,9 @@ class Domain(object): self._directive_cache[name] = DirectiveAdapter return DirectiveAdapter + def resolve_xref(self, typ, target, node, contnode): + pass + # REs for Python signatures py_sig_re = re.compile( @@ -366,6 +383,100 @@ class PythonDomain(Domain): 'obj': PyXRefRole(), } + def find_desc(self, env, modname, classname, name, type, searchorder=0): + """Find a description node matching "name", perhaps using + the given module and/or classname.""" + # skip parens + if name[-2:] == '()': + name = name[:-2] + + if not name: + return None, None + + # don't add module and class names for C things + if type[0] == 'c' and type not in ('class', 'const'): + # skip trailing star and whitespace + name = name.rstrip(' *') + if name in env.descrefs and env.descrefs[name][1][0] == 'c': + return name, env.descrefs[name] + return None, None + + newname = None + if searchorder == 1: + if modname and classname and \ + modname + '.' + classname + '.' + name in env.descrefs: + newname = modname + '.' + classname + '.' + name + elif modname and modname + '.' + name in env.descrefs: + newname = modname + '.' + name + elif name in env.descrefs: + newname = name + else: + if name in env.descrefs: + newname = name + elif modname and modname + '.' + name in env.descrefs: + newname = modname + '.' + name + elif modname and classname and \ + modname + '.' + classname + '.' + name in env.descrefs: + newname = modname + '.' + classname + '.' + name + # special case: builtin exceptions have module "exceptions" set + elif type == 'exc' and '.' not in name and \ + 'exceptions.' + name in env.descrefs: + newname = 'exceptions.' + name + # special case: object methods + elif type in ('func', 'meth') and '.' not in name and \ + 'object.' + name in env.descrefs: + newname = 'object.' + name + if newname is None: + return None, None + return newname, env.descrefs[newname] + + def resolve_xref(self, env, fromdocname, builder, + typ, target, node, contnode): + + if typ == 'mod' or \ + typ == 'obj' and target in env.modules: + docname, synopsis, platform, deprecated = \ + env.modules.get(target, ('','','', '')) + if not docname: + newnode = builder.app.emit_firstresult( + 'missing-reference', env, node, contnode) + if not newnode: + newnode = contnode + elif docname == fromdocname: + # don't link to self + newnode = contnode + else: + newnode = nodes.reference('', '') + newnode['refuri'] = builder.get_relative_uri( + fromdocname, docname) + '#module-' + target + newnode['reftitle'] = '%s%s%s' % ( + (platform and '(%s) ' % platform), + synopsis, (deprecated and ' (deprecated)' or '')) + newnode.append(contnode) + elif typ in env.descroles: + # "descrefs" + modname = node['modname'] + clsname = node['classname'] + searchorder = node.hasattr('refspecific') and 1 or 0 + name, desc = self.find_desc(env, modname, clsname, + target, typ, searchorder) + if not desc: + newnode = builder.app.emit_firstresult( + 'missing-reference', env, node, contnode) + if not newnode: + newnode = contnode + else: + newnode = nodes.reference('', '') + if desc[0] == fromdocname: + newnode['refid'] = name + else: + newnode['refuri'] = ( + builder.get_relative_uri(fromdocname, desc[0]) + + '#' + name) + newnode['reftitle'] = name + newnode.append(contnode) + + # RE to split at word boundaries wsplit_re = re.compile(r'(\W+)') diff --git a/sphinx/environment.py b/sphinx/environment.py index d12d51138..80bc77535 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -1204,6 +1204,7 @@ class BuildEnvironment: 'cdata', 'ctype', 'cmacro')) def resolve_references(self, doctree, fromdocname, builder): + # XXX remove this reftarget_roles = set(('token', 'term', 'citation')) # add all custom xref types too reftarget_roles.update(i[0] for i in additional_xref_types.values()) @@ -1216,7 +1217,15 @@ class BuildEnvironment: target = node['reftarget'] try: - if typ == 'ref': + if node.has_key('refdomain'): + # let the domain resolve the reference + try: + domain = builder.app.domains[node['refdomain']] + except KeyError: + raise NoUri + newnode = domain.resolve_xref(self, fromdocname, builder, + typ, target, node, contnode) + elif typ == 'ref': if node['refcaption']: # reference to anonymous label; the reference uses # the supplied link caption @@ -1323,48 +1332,6 @@ class BuildEnvironment: newnode['refuri'] = builder.get_relative_uri( fromdocname, docname, typ) + '#' + labelid newnode.append(contnode) - elif typ == 'mod' or \ - typ == 'obj' and target in self.modules: - docname, synopsis, platform, deprecated = \ - self.modules.get(target, ('','','', '')) - if not docname: - newnode = builder.app.emit_firstresult( - 'missing-reference', self, node, contnode) - if not newnode: - newnode = contnode - elif docname == fromdocname: - # don't link to self - newnode = contnode - else: - newnode = nodes.reference('', '') - newnode['refuri'] = builder.get_relative_uri( - fromdocname, docname) + '#module-' + target - newnode['reftitle'] = '%s%s%s' % ( - (platform and '(%s) ' % platform), - synopsis, (deprecated and ' (deprecated)' or '')) - newnode.append(contnode) - elif typ in self.descroles: - # "descrefs" - modname = node['modname'] - clsname = node['classname'] - searchorder = node.hasattr('refspecific') and 1 or 0 - name, desc = self.find_desc(modname, clsname, - target, typ, searchorder) - if not desc: - newnode = builder.app.emit_firstresult( - 'missing-reference', self, node, contnode) - if not newnode: - newnode = contnode - else: - newnode = nodes.reference('', '') - if desc[0] == fromdocname: - newnode['refid'] = name - else: - newnode['refuri'] = ( - builder.get_relative_uri(fromdocname, desc[0]) - + '#' + name) - newnode['reftitle'] = name - newnode.append(contnode) else: raise RuntimeError('unknown xfileref node encountered: %s' % node) @@ -1595,52 +1562,3 @@ class BuildEnvironment: # the master file is not included anywhere ;) continue self.warn(docname, 'document isn\'t included in any toctree') - - # --------- QUERYING ------------------------------------------------------- - - def find_desc(self, modname, classname, name, type, searchorder=0): - """Find a description node matching "name", perhaps using - the given module and/or classname.""" - # skip parens - if name[-2:] == '()': - name = name[:-2] - - if not name: - return None, None - - # don't add module and class names for C things - if type[0] == 'c' and type not in ('class', 'const'): - # skip trailing star and whitespace - name = name.rstrip(' *') - if name in self.descrefs and self.descrefs[name][1][0] == 'c': - return name, self.descrefs[name] - return None, None - - newname = None - if searchorder == 1: - if modname and classname and \ - modname + '.' + classname + '.' + name in self.descrefs: - newname = modname + '.' + classname + '.' + name - elif modname and modname + '.' + name in self.descrefs: - newname = modname + '.' + name - elif name in self.descrefs: - newname = name - else: - if name in self.descrefs: - newname = name - elif modname and modname + '.' + name in self.descrefs: - newname = modname + '.' + name - elif modname and classname and \ - modname + '.' + classname + '.' + name in self.descrefs: - newname = modname + '.' + classname + '.' + name - # special case: builtin exceptions have module "exceptions" set - elif type == 'exc' and '.' not in name and \ - 'exceptions.' + name in self.descrefs: - newname = 'exceptions.' + name - # special case: object methods - elif type in ('func', 'meth') and '.' not in name and \ - 'object.' + name in self.descrefs: - newname = 'object.' + name - if newname is None: - return None, None - return newname, self.descrefs[newname] From d4f957da8c436347c4fbb740db73a73dbddf2324 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 13 Jul 2009 18:02:42 +0200 Subject: [PATCH 26/85] Move the initialization of env and i18n into the app. --- sphinx/application.py | 62 +++++++++++++++++++++++++++++++++---- sphinx/builders/__init__.py | 62 +++---------------------------------- sphinx/builders/html.py | 3 +- sphinx/jinja2glue.py | 4 +-- 4 files changed, 65 insertions(+), 66 deletions(-) diff --git a/sphinx/application.py b/sphinx/application.py index 4bd0063da..e26d619e8 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -20,13 +20,14 @@ from docutils import nodes from docutils.parsers.rst import directives, roles import sphinx +from sphinx import package_dir, locale from sphinx.roles import XRefRole from sphinx.config import Config from sphinx.errors import SphinxError, SphinxWarning, ExtensionError from sphinx.domains import all_domains from sphinx.builders import BUILTIN_BUILDERS from sphinx.directives import GenericDesc, Target, additional_xref_types -from sphinx.environment import SphinxStandaloneReader +from sphinx.environment import BuildEnvironment, SphinxStandaloneReader from sphinx.util import pycompat from sphinx.util.tags import Tags from sphinx.util.compat import Directive, directive_dwim @@ -50,6 +51,7 @@ events = { } CONFIG_FILENAME = 'conf.py' +ENV_PICKLE_FILENAME = 'environment.pickle' class Sphinx(object): @@ -62,6 +64,7 @@ class Sphinx(object): self._listeners = {} self.builderclasses = BUILTIN_BUILDERS.copy() self.builder = None + self.env = None self.srcdir = srcdir self.confdir = confdir @@ -107,10 +110,59 @@ class Sphinx(object): # intialize domains self.domains = {} for domain in all_domains.keys(): - self.domains[domain] = all_domains[domain](self) + self.domains[domain] = all_domains[domain]() + # set up translation infrastructure + self._init_i18n() + # set up the build environment + self._init_env(freshenv) + # set up the builder + self._init_builder(buildername) + + def _init_i18n(self): + """ + Load translated strings from the configured localedirs if + enabled in the configuration. + """ + if self.config.language is not None: + self.info(bold('loading translations [%s]... ' % + self.config.language), nonl=True) + locale_dirs = [None, path.join(package_dir, 'locale')] + \ + [path.join(self.srcdir, x) for x in self.config.locale_dirs] + else: + locale_dirs = [] + self.translator, has_translation = locale.init(locale_dirs, + self.config.language) + if self.config.language is not None: + if has_translation: + self.info('done') + else: + self.info('locale not available') + + def _init_env(self, freshenv): + if freshenv: + self.env = BuildEnvironment(self.srcdir, self.doctreedir, + self.config) + self.env.find_files(self.config) + else: + try: + self.info(bold('loading pickled environment... '), nonl=True) + self.env = BuildEnvironment.frompickle(self.config, + path.join(self.doctreedir, ENV_PICKLE_FILENAME)) + self.info('done') + except Exception, err: + if type(err) is IOError and err.errno == 2: + self.info('not yet created') + else: + self.info('failed: %s' % err) + self.env = BuildEnvironment(self.srcdir, self.doctreedir, + self.config) + self.env.find_files(self.config) + self.env.set_warnfunc(self.warn) + + def _init_builder(self, buildername): if buildername is None: - print >>status, 'No builder selected, using default: html' + print >>self._status, 'No builder selected, using default: html' buildername = 'html' if buildername not in self.builderclasses: raise SphinxError('Builder name %s not registered' % buildername) @@ -121,9 +173,7 @@ class Sphinx(object): mod, cls = builderclass builderclass = getattr( __import__('sphinx.builders.' + mod, None, None, [cls]), cls) - self.builder = builderclass(self, freshenv=freshenv) - self.builder.tags = self.tags - self.builder.tags.add(self.builder.format) + self.builder = builderclass(self) self.emit('builder-inited') def build(self, all_files, filenames): diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py index 94b16b0a9..ca3c11473 100644 --- a/sphinx/builders/__init__.py +++ b/sphinx/builders/__init__.py @@ -15,9 +15,7 @@ from os import path from docutils import nodes -from sphinx import package_dir, locale from sphinx.util import SEP, relative_uri -from sphinx.environment import BuildEnvironment from sphinx.util.console import bold, purple, darkgreen, term_width_line # side effect: registers roles and directives @@ -25,9 +23,6 @@ from sphinx import roles from sphinx import directives -ENV_PICKLE_FILENAME = 'environment.pickle' - - class Builder(object): """ Builds target formats from the reST sources. @@ -38,7 +33,8 @@ class Builder(object): # builder's output format, or '' if no document output is produced format = '' - def __init__(self, app, env=None, freshenv=False): + def __init__(self, app): + self.env = app.env self.srcdir = app.srcdir self.confdir = app.confdir self.outdir = app.outdir @@ -50,18 +46,13 @@ class Builder(object): self.warn = app.warn self.info = app.info self.config = app.config - - self.load_i18n() + self.tags = app.tags + self.tags.add(self.format) # images that need to be copied over (source -> dest) self.images = {} - # if None, this is set in load_env() - self.env = env - self.freshenv = freshenv - self.init() - self.load_env() # helper methods @@ -167,50 +158,6 @@ class Builder(object): # build methods - def load_i18n(self): - """ - Load translated strings from the configured localedirs if - enabled in the configuration. - """ - if self.config.language is not None: - self.info(bold('loading translations [%s]... ' % - self.config.language), nonl=True) - locale_dirs = [None, path.join(package_dir, 'locale')] + \ - [path.join(self.srcdir, x) for x in self.config.locale_dirs] - else: - locale_dirs = [] - self.translator, has_translation = locale.init(locale_dirs, - self.config.language) - if self.config.language is not None: - if has_translation: - self.info('done') - else: - self.info('locale not available') - - def load_env(self): - """Set up the build environment.""" - if self.env: - return - if not self.freshenv: - try: - self.info(bold('loading pickled environment... '), nonl=True) - self.env = BuildEnvironment.frompickle(self.config, - path.join(self.doctreedir, ENV_PICKLE_FILENAME)) - self.info('done') - except Exception, err: - if type(err) is IOError and err.errno == 2: - self.info('not found') - else: - self.info('failed: %s' % err) - self.env = BuildEnvironment(self.srcdir, self.doctreedir, - self.config) - self.env.find_files(self.config) - else: - self.env = BuildEnvironment(self.srcdir, self.doctreedir, - self.config) - self.env.find_files(self.config) - self.env.set_warnfunc(self.warn) - def build_all(self): """Build all source files.""" self.build(None, summary='all source files', method='all') @@ -290,6 +237,7 @@ class Builder(object): if updated_docnames: # save the environment + from sphinx.application import ENV_PICKLE_FILENAME self.info(bold('pickling environment... '), nonl=True) self.env.topickle(path.join(self.doctreedir, ENV_PICKLE_FILENAME)) self.info('done') diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index 54327fa1e..20e84b41d 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -33,7 +33,8 @@ from sphinx.util import SEP, os_path, relative_uri, ensuredir, \ from sphinx.errors import SphinxError from sphinx.search import js_index from sphinx.theming import Theme -from sphinx.builders import Builder, ENV_PICKLE_FILENAME +from sphinx.builders import Builder +from sphinx.application import ENV_PICKLE_FILENAME from sphinx.highlighting import PygmentsBridge from sphinx.util.console import bold from sphinx.writers.html import HTMLWriter, HTMLTranslator, \ diff --git a/sphinx/jinja2glue.py b/sphinx/jinja2glue.py index c584a0cde..626e7f8a8 100644 --- a/sphinx/jinja2glue.py +++ b/sphinx/jinja2glue.py @@ -94,7 +94,7 @@ class BuiltinTemplateLoader(TemplateBridge, BaseLoader): # make the paths into loaders self.loaders = map(SphinxFileSystemLoader, chain) - use_i18n = builder.translator is not None + use_i18n = builder.app.translator is not None extensions = use_i18n and ['jinja2.ext.i18n'] or [] self.environment = SandboxedEnvironment(loader=self, extensions=extensions) @@ -102,7 +102,7 @@ class BuiltinTemplateLoader(TemplateBridge, BaseLoader): self.environment.globals['debug'] = contextfunction(pformat) self.environment.globals['accesskey'] = contextfunction(accesskey) if use_i18n: - self.environment.install_gettext_translations(builder.translator) + self.environment.install_gettext_translations(builder.app.translator) def render(self, template, context): return self.environment.get_template(template).render(context) From db8395f9e18cfc1f620d7c86befa98cae91079ba Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 13 Jul 2009 18:27:34 +0200 Subject: [PATCH 27/85] Add make_refnode() utility function. --- sphinx/domains.py | 69 +++++++++++++++++------------------------ sphinx/environment.py | 43 +++++++++---------------- sphinx/util/__init__.py | 15 +++++++++ 3 files changed, 58 insertions(+), 69 deletions(-) diff --git a/sphinx/domains.py b/sphinx/domains.py index fdf5a9d7c..f3389da1a 100644 --- a/sphinx/domains.py +++ b/sphinx/domains.py @@ -15,6 +15,7 @@ import re from sphinx import addnodes from sphinx.roles import XRefRole from sphinx.directives import DescDirective +from sphinx.util import make_refnode class Domain(object): @@ -50,7 +51,7 @@ class Domain(object): inliner, options, content) self._role_cache[name] = role_adapter return role_adapter - + def directive(self, name): """ Return a directive adapter class that always gives the registered @@ -384,8 +385,10 @@ class PythonDomain(Domain): } def find_desc(self, env, modname, classname, name, type, searchorder=0): - """Find a description node matching "name", perhaps using - the given module and/or classname.""" + """ + Find a Python object description for "name", perhaps using the given + module and/or classname. + """ # skip parens if name[-2:] == '()': name = name[:-2] @@ -393,14 +396,6 @@ class PythonDomain(Domain): if not name: return None, None - # don't add module and class names for C things - if type[0] == 'c' and type not in ('class', 'const'): - # skip trailing star and whitespace - name = name.rstrip(' *') - if name in env.descrefs and env.descrefs[name][1][0] == 'c': - return name, env.descrefs[name] - return None, None - newname = None if searchorder == 1: if modname and classname and \ @@ -432,28 +427,22 @@ class PythonDomain(Domain): def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): - - if typ == 'mod' or \ - typ == 'obj' and target in env.modules: + if (typ == 'mod' or + typ == 'obj' and target in env.modules): docname, synopsis, platform, deprecated = \ env.modules.get(target, ('','','', '')) if not docname: - newnode = builder.app.emit_firstresult( - 'missing-reference', env, node, contnode) - if not newnode: - newnode = contnode + return None elif docname == fromdocname: # don't link to self - newnode = contnode + return contnode else: - newnode = nodes.reference('', '') - newnode['refuri'] = builder.get_relative_uri( - fromdocname, docname) + '#module-' + target - newnode['reftitle'] = '%s%s%s' % ( - (platform and '(%s) ' % platform), - synopsis, (deprecated and ' (deprecated)' or '')) - newnode.append(contnode) - elif typ in env.descroles: + title = '%s%s%s' % ((platform and '(%s) ' % platform), + synopsis, + (deprecated and ' (deprecated)' or '')) + return make_refnode(builder, fromdocname, docname, + 'module-' + target, contnode, title) + else: # "descrefs" modname = node['modname'] clsname = node['classname'] @@ -461,20 +450,10 @@ class PythonDomain(Domain): name, desc = self.find_desc(env, modname, clsname, target, typ, searchorder) if not desc: - newnode = builder.app.emit_firstresult( - 'missing-reference', env, node, contnode) - if not newnode: - newnode = contnode + return None else: - newnode = nodes.reference('', '') - if desc[0] == fromdocname: - newnode['refid'] = name - else: - newnode['refuri'] = ( - builder.get_relative_uri(fromdocname, desc[0]) - + '#' + name) - newnode['reftitle'] = name - newnode.append(contnode) + return make_refnode(builder, fromdocname, desc[0], name, + contnode, name) @@ -620,6 +599,16 @@ class CDomain(Domain): 'type': XRefRole(), } + def resolve_xref(self, env, fromdocname, builder, + typ, target, node, contnode): + # strip pointer asterisk + target = target.rstrip(' *') + # XXX descrefs + if target not in env.descrefs: + return None + desc = env.descrefs[target] + return make_refnode(builder, fromdocname, desc[0], contnode, target) + # this contains all registered domains all_domains = { diff --git a/sphinx/environment.py b/sphinx/environment.py index 80bc77535..a5bf49ebb 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -43,7 +43,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 + docname_join, FilenameUniqDict, url_re, make_refnode from sphinx.errors import SphinxError from sphinx.directives import additional_xref_types @@ -1199,10 +1199,6 @@ class BuildEnvironment: docname, refnode['refuri']) + refnode['anchorname'] return newnode - descroles = frozenset(('data', 'exc', 'func', 'class', 'const', - 'attr', 'obj', 'meth', 'cfunc', 'cmember', - 'cdata', 'ctype', 'cmacro')) - def resolve_references(self, doctree, fromdocname, builder): # XXX remove this reftarget_roles = set(('token', 'term', 'citation')) @@ -1218,7 +1214,7 @@ class BuildEnvironment: try: if node.has_key('refdomain'): - # let the domain resolve the reference + # let the domain try to resolve the reference try: domain = builder.app.domains[node['refdomain']] except KeyError: @@ -1290,13 +1286,8 @@ class BuildEnvironment: #self.warn(fromdocname, 'unknown keyword: %s' % target) newnode = contnode else: - newnode = nodes.reference('', '') - if docname == fromdocname: - newnode['refid'] = labelid - else: - newnode['refuri'] = builder.get_relative_uri( - fromdocname, docname) + '#' + labelid - newnode.append(contnode) + newnode = make_refnode(builder, fromdocname, docname, + labelid, contnode) elif typ == 'option': progname = node['refprogram'] docname, labelid = self.progoptions.get((progname, target), @@ -1304,13 +1295,8 @@ class BuildEnvironment: if not docname: newnode = contnode else: - newnode = nodes.reference('', '') - if docname == fromdocname: - newnode['refid'] = labelid - else: - newnode['refuri'] = builder.get_relative_uri( - fromdocname, docname) + '#' + labelid - newnode.append(contnode) + newnode = make_refnode(builder, fromdocname, docname, + labelid, contnode) elif typ in reftarget_roles: docname, labelid = self.reftargets.get((typ, target), ('', '')) @@ -1325,20 +1311,19 @@ class BuildEnvironment: node.line) newnode = contnode else: - newnode = nodes.reference('', '') - if docname == fromdocname: - newnode['refid'] = labelid - else: - newnode['refuri'] = builder.get_relative_uri( - fromdocname, docname, typ) + '#' + labelid - newnode.append(contnode) + newnode = make_refnode(builder, fromdocname, docname, + labelid, contnode) else: raise RuntimeError('unknown xfileref node encountered: %s' % node) + + # no new node found? try the missing-reference event + if newnode is None: + newnode = builder.app.emit_firstresult( + 'missing-reference', env, node, contnode) except NoUri: newnode = contnode - if newnode: - node.replace_self(newnode) + node.replace_self(newnode or contnode) for node in doctree.traverse(addnodes.only): try: diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index 3465403df..2d59edfcd 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -435,6 +435,21 @@ def split_explicit_title(text): else: return False, text, text + +def make_refnode(builder, fromdocname, todocname, targetid, child, title=None): + """Shortcut to create a reference node.""" + node = nodes.reference('', '') + if fromdocname == todocname: + node['refid'] = targetid + else: + node['refuri'] = (builder.get_relative_uri(fromdocname, todocname) + + '#' + targetid) + if title: + node['reftitle'] = title + node.append(child) + return node + + # 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! From 51a78a9b880993bb0075ee0fdee55cc45ee1c338 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 13 Jul 2009 18:41:20 +0200 Subject: [PATCH 28/85] PEP 8 --- sphinx/util/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index 2d59edfcd..f8a14cace 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -396,8 +396,10 @@ def movefile(source, dest): def copyfile(source, dest): """Copy a file and its modification times, if possible.""" shutil.copyfile(source, dest) - try: shutil.copystat(source, dest) - except shutil.Error: pass + try: + shutil.copystat(source, dest) + except shutil.Error: + pass def copy_static_entry(source, target, builder, context={}): From a1dd4695f3358f3dd57fed05d9910324e80a95c4 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 13 Jul 2009 20:53:11 +0200 Subject: [PATCH 29/85] First step of domain handling in environment. --- sphinx/application.py | 16 ++++++++-------- sphinx/domains.py | 20 ++++++++++++++++++-- sphinx/environment.py | 27 +++++++++++++++++++-------- sphinx/util/__init__.py | 11 ++++++----- tests/root/desc.txt | 10 +++++----- tests/test_env.py | 7 ++++--- 6 files changed, 60 insertions(+), 31 deletions(-) diff --git a/sphinx/application.py b/sphinx/application.py index e26d619e8..3566bc50c 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -107,11 +107,6 @@ class Sphinx(object): # now that we know all config values, collect them from conf.py self.config.init_values() - # intialize domains - self.domains = {} - for domain in all_domains.keys(): - self.domains[domain] = all_domains[domain]() - # set up translation infrastructure self._init_i18n() # set up the build environment @@ -144,20 +139,25 @@ class Sphinx(object): self.env = BuildEnvironment(self.srcdir, self.doctreedir, self.config) self.env.find_files(self.config) + for domain in all_domains.keys(): + self.env.domains[domain] = all_domains[domain](self.env) else: try: self.info(bold('loading pickled environment... '), nonl=True) self.env = BuildEnvironment.frompickle(self.config, path.join(self.doctreedir, ENV_PICKLE_FILENAME)) + self.env.domains = {} + for domain in all_domains.keys(): + # this can raise if the data version doesn't fit + self.env.domains[domain] = all_domains[domain](self.env) self.info('done') except Exception, err: if type(err) is IOError and err.errno == 2: self.info('not yet created') else: self.info('failed: %s' % err) - self.env = BuildEnvironment(self.srcdir, self.doctreedir, - self.config) - self.env.find_files(self.config) + return self._init_env(freshenv=True) + self.env.set_warnfunc(self.warn) def _init_builder(self, buildername): diff --git a/sphinx/domains.py b/sphinx/domains.py index f3389da1a..664481116 100644 --- a/sphinx/domains.py +++ b/sphinx/domains.py @@ -11,6 +11,9 @@ """ import re +import string + +from docutils import nodes from sphinx import addnodes from sphinx.roles import XRefRole @@ -24,7 +27,18 @@ class Domain(object): roles = {} label = '' - def __init__(self): + # data value for a fresh environment + initial_data = { + + } + # data version + data_version = 0 + + def __init__(self, env): + self.env = env + self.data = env.domaindata.get(self.name, self.initial_data) + if 'version' in self.data and self.data['version'] < self.data_version: + raise IOError('data of %r domain out of date' % self.label) self._role_cache = {} self._directive_cache = {} @@ -35,6 +49,8 @@ class Domain(object): state['_directive_cache'] = {} return state + #def clear_doc + def role(self, name): """ Return a role adapter function that always gives the registered @@ -492,7 +508,7 @@ class CDesc(DescDirective): if part[0] in string.ascii_letters+'_' and \ part not in self.stopwords: pnode = addnodes.pending_xref( - '', reftype='ctype', reftarget=part, + '', refdomain='c', reftype='type', reftarget=part, modname=None, classname=None) pnode += tnode node += pnode diff --git a/sphinx/environment.py b/sphinx/environment.py index a5bf49ebb..62d3875b7 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -213,9 +213,9 @@ class BuildEnvironment: env = pickle.load(picklefile) finally: picklefile.close() - env.config.values = config.values if env.version != ENV_VERSION: raise IOError('env version not current') + env.config.values = config.values return env def topickle(self, filename): @@ -224,6 +224,8 @@ class BuildEnvironment: self.set_warnfunc(None) values = self.config.values del self.config.values + domains = self.domains + del self.domains # first write to a temporary file, so that if dumping fails, # the existing environment won't be overwritten picklefile = open(filename + '.tmp', 'wb') @@ -240,6 +242,7 @@ class BuildEnvironment: picklefile.close() movefile(filename + '.tmp', filename) # reset attributes + self.domains = domains self.config.values = values self.set_warnfunc(warnfunc) @@ -253,6 +256,9 @@ class BuildEnvironment: # the application object; only set while update() runs self.app = None + # all the registered domains, set by the application + self.domains = {} + # the docutils settings for building self.settings = default_settings.copy() self.settings['env'] = self @@ -291,6 +297,9 @@ class BuildEnvironment: self.glob_toctrees = set() # docnames that have :glob: toctrees self.numbered_toctrees = set() # docnames that have :numbered: toctrees + # domain-specific inventories + self.domaindata = {} # domainname -> object + # X-ref target inventory self.descrefs = {} # fullname -> docname, desctype self.modules = {} # modname -> docname, synopsis, @@ -600,14 +609,15 @@ class BuildEnvironment: return data # defaults to the global default, but can be re-set in a document - self.default_domain = app.domains.get(self.config.default_domain) + self.default_domain = self.domains.get(self.config.default_domain) # monkey-patch, so that domain directives take precedence def directive(directive_name, language_module, document): + directive_name = directive_name.lower() if ':' in directive_name: domain_name, directive_name = directive_name.split(':', 1) - if domain_name in app.domains: - domain = app.domains[domain_name] + if domain_name in self.domains: + domain = self.domains[domain_name] directive = domain.directive(directive_name) if directive is not None: return directive, [] @@ -620,10 +630,11 @@ class BuildEnvironment: directives.directive = directive def role(role_name, language_module, lineno, reporter): + role_name = role_name.lower() if ':' in role_name: domain_name, role_name = role_name.split(':', 1) - if domain_name in app.domains: - domain = app.domains[domain_name] + if domain_name in self.domains: + domain = self.domains[domain_name] role = domain.role(role_name) if role is not None: return role, [] @@ -1216,7 +1227,7 @@ class BuildEnvironment: if node.has_key('refdomain'): # let the domain try to resolve the reference try: - domain = builder.app.domains[node['refdomain']] + domain = self.domains[node['refdomain']] except KeyError: raise NoUri newnode = domain.resolve_xref(self, fromdocname, builder, @@ -1320,7 +1331,7 @@ class BuildEnvironment: # no new node found? try the missing-reference event if newnode is None: newnode = builder.app.emit_firstresult( - 'missing-reference', env, node, contnode) + 'missing-reference', self, node, contnode) except NoUri: newnode = contnode node.replace_self(newnode or contnode) diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index f8a14cace..c238fd47e 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -438,6 +438,8 @@ def split_explicit_title(text): return False, text, text +from docutils import nodes + def make_refnode(builder, fromdocname, todocname, targetid, child, title=None): """Shortcut to create a reference node.""" node = nodes.reference('', '') @@ -481,8 +483,7 @@ def _new_traverse(self, condition=None, return self._old_traverse(condition, include_self, descend, siblings, ascend) -import docutils.nodes -docutils.nodes.Node._old_traverse = docutils.nodes.Node.traverse -docutils.nodes.Node._all_traverse = _all_traverse -docutils.nodes.Node._fast_traverse = _fast_traverse -docutils.nodes.Node.traverse = _new_traverse +nodes.Node._old_traverse = nodes.Node.traverse +nodes.Node._all_traverse = _all_traverse +nodes.Node._fast_traverse = _fast_traverse +nodes.Node.traverse = _new_traverse diff --git a/tests/root/desc.txt b/tests/root/desc.txt index d6915dc27..bdceafc0a 100644 --- a/tests/root/desc.txt +++ b/tests/root/desc.txt @@ -43,15 +43,15 @@ Testing description units C items ======= -.. cfunction:: Sphinx_DoSomething() +.. c:function:: Sphinx_DoSomething() -.. cmember:: SphinxStruct.member +.. c:member:: SphinxStruct.member -.. cmacro:: SPHINX_USE_PYTHON +.. c:macro:: SPHINX_USE_PYTHON -.. ctype:: SphinxType +.. c:type:: SphinxType -.. cvar:: sphinx_global +.. c:var:: sphinx_global Testing references diff --git a/tests/test_env.py b/tests/test_env.py index a06656d69..d5f40f8c2 100644 --- a/tests/test_env.py +++ b/tests/test_env.py @@ -21,7 +21,8 @@ warnings = [] def setup_module(): global app, env app = TestApp(srcdir='(temp)') - env = BuildEnvironment(app.srcdir, app.doctreedir, app.config) + env = app.env + #env = BuildEnvironment(app.srcdir, app.doctreedir, app.config) env.set_warnfunc(lambda *args: warnings.append(args)) def teardown_module(): @@ -51,7 +52,7 @@ def test_images(): tree = env.get_doctree('images') app._warning.reset() - htmlbuilder = StandaloneHTMLBuilder(app, env) + htmlbuilder = StandaloneHTMLBuilder(app) htmlbuilder.post_process_images(tree) assert "no matching candidate for image URI u'foo.*'" in \ app._warning.content[-1] @@ -61,7 +62,7 @@ def test_images(): set(['img.png', 'img1.png', 'simg.png', 'svgimg.svg']) app._warning.reset() - latexbuilder = LaTeXBuilder(app, env) + latexbuilder = LaTeXBuilder(app) latexbuilder.post_process_images(tree) assert "no matching candidate for image URI u'foo.*'" in \ app._warning.content[-1] From 1c030b415e2d82c71de8da57c3ad89921f94ef9d Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 13 Jul 2009 21:28:01 +0200 Subject: [PATCH 30/85] With a few disabled features (see XXX), the test suite runs again. --- sphinx/application.py | 1 + sphinx/builders/html.py | 20 +++-- sphinx/directives/desc.py | 6 +- sphinx/directives/other.py | 71 ---------------- sphinx/domains.py | 170 ++++++++++++++++++++++++++++++------- sphinx/environment.py | 29 ++----- sphinx/ext/coverage.py | 14 +-- sphinx/search.py | 4 + tests/test_env.py | 9 +- 9 files changed, 179 insertions(+), 145 deletions(-) diff --git a/sphinx/application.py b/sphinx/application.py index 3566bc50c..7d9ee49db 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -14,6 +14,7 @@ import sys import types import posixpath +from os import path from cStringIO import StringIO from docutils import nodes diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index 20e84b41d..de4c9a393 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -240,7 +240,9 @@ class StandaloneHTMLBuilder(Builder): rellinks = [] if self.config.html_use_index: rellinks.append(('genindex', _('General Index'), 'I', _('index'))) - if self.config.html_use_modindex and self.env.modules: + # XXX generalization of modindex? + if self.config.html_use_modindex and \ + self.env.domains['py'].data['modules']: rellinks.append(('modindex', _('Global Module Index'), 'M', _('modules'))) @@ -405,12 +407,13 @@ class StandaloneHTMLBuilder(Builder): # the global module index - if self.config.html_use_modindex and self.env.modules: + moduleindex = self.env.domains['py'].data['modules'] + if self.config.html_use_modindex and moduleindex: # the sorted list of all modules, for the global module index modules = sorted(((mn, (self.get_relative_uri('modindex', fn) + '#module-' + mn, sy, pl, dep)) for (mn, (fn, sy, pl, dep)) in - self.env.modules.iteritems()), + moduleindex.iteritems()), key=lambda x: x[0].lower()) # collect all platforms platforms = set() @@ -710,14 +713,15 @@ class StandaloneHTMLBuilder(Builder): self.info(bold('dumping object inventory... '), nonl=True) f = open(path.join(self.outdir, INVENTORY_FILENAME), 'w') try: + # XXX inventory version 2 f.write('# Sphinx inventory version 1\n') f.write('# Project: %s\n' % self.config.project.encode('utf-8')) f.write('# Version: %s\n' % self.config.version) - for modname, info in self.env.modules.iteritems(): - f.write('%s mod %s\n' % (modname, self.get_target_uri(info[0]))) - for refname, (docname, desctype) in self.env.descrefs.iteritems(): - f.write('%s %s %s\n' % (refname, desctype, - self.get_target_uri(docname))) + #for modname, info in self.env.modules.iteritems(): + # f.write('%s mod %s\n' % (modname, self.get_target_uri(info[0]))) + #for refname, (docname, desctype) in self.env.descrefs.iteritems(): + # f.write('%s %s %s\n' % (refname, desctype, + # self.get_target_uri(docname))) finally: f.close() self.info('done') diff --git a/sphinx/directives/desc.py b/sphinx/directives/desc.py index e30181003..06fc4c983 100644 --- a/sphinx/directives/desc.py +++ b/sphinx/directives/desc.py @@ -204,7 +204,10 @@ class DescDirective(Directive): pass def run(self): - self.desctype = self.name + if ':' in self.name: + self.domain, self.desctype = self.name.split(':', 1) + else: + self.domain, self.desctype = '', self.name self.env = self.state.document.settings.env self.indexnode = addnodes.index(entries=[]) @@ -366,7 +369,6 @@ class DefaultDomain(Directive): def run(self): env = self.state.document.settings.env domain_name = arguments[0] - # XXX won't work env.default_domain = env.domains.get(domain_name) diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py index d056f3dce..ee938cfd9 100644 --- a/sphinx/directives/other.py +++ b/sphinx/directives/other.py @@ -103,75 +103,6 @@ class TocTree(Directive): return ret -class Module(Directive): - """ - Directive to mark description of a new module. - """ - - has_content = False - required_arguments = 1 - optional_arguments = 0 - final_argument_whitespace = False - option_spec = { - 'platform': lambda x: x, - 'synopsis': lambda x: x, - 'noindex': directives.flag, - 'deprecated': directives.flag, - } - - def run(self): - env = self.state.document.settings.env - modname = self.arguments[0].strip() - noindex = 'noindex' in self.options - env.currmodule = modname - env.note_module(modname, 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] - if 'platform' in self.options: - platform = self.options['platform'] - modulenode['platform'] = platform - node = nodes.paragraph() - node += nodes.emphasis('', _('Platforms: ')) - node += nodes.Text(platform, platform) - ret.append(node) - # the synopsis isn't printed; in fact, it is only used in the - # modindex currently - if not noindex: - indextext = _('%s (module)') % modname - inode = addnodes.index(entries=[('single', indextext, - 'module-' + modname, modname)]) - ret.insert(0, inode) - return ret - - -class CurrentModule(Directive): - """ - This directive is just to tell Sphinx that we're documenting - stuff in module foo, but links to module foo won't lead here. - """ - - has_content = False - required_arguments = 1 - optional_arguments = 0 - final_argument_whitespace = False - option_spec = {} - - def run(self): - env = self.state.document.settings.env - modname = self.arguments[0].strip() - if modname == 'None': - env.currmodule = None - else: - env.currmodule = modname - return [] - - class Author(Directive): """ Directive to give the name of the author of the current document @@ -559,8 +490,6 @@ class Only(Directive): directives.register_directive('toctree', directive_dwim(TocTree)) -directives.register_directive('module', directive_dwim(Module)) -directives.register_directive('currentmodule', directive_dwim(CurrentModule)) directives.register_directive('sectionauthor', directive_dwim(Author)) directives.register_directive('moduleauthor', directive_dwim(Author)) directives.register_directive('program', directive_dwim(Program)) diff --git a/sphinx/domains.py b/sphinx/domains.py index 664481116..3e5b22e81 100644 --- a/sphinx/domains.py +++ b/sphinx/domains.py @@ -14,11 +14,13 @@ import re import string from docutils import nodes +from docutils.parsers.rst import directives from sphinx import addnodes from sphinx.roles import XRefRole from sphinx.directives import DescDirective from sphinx.util import make_refnode +from sphinx.util.compat import Directive class Domain(object): @@ -28,28 +30,28 @@ class Domain(object): label = '' # data value for a fresh environment - initial_data = { - - } + initial_data = {} # data version data_version = 0 def __init__(self, env): self.env = env - self.data = env.domaindata.get(self.name, self.initial_data) - if 'version' in self.data and self.data['version'] < self.data_version: - raise IOError('data of %r domain out of date' % self.label) + if self.name not in env.domaindata: + new_data = self.initial_data.copy() + new_data['version'] = self.data_version + self.data = env.domaindata[self.name] = new_data + else: + self.data = env.domaindata[self.name] + if self.data['version'] < self.data_version: + raise IOError('data of %r domain out of date' % self.label) self._role_cache = {} self._directive_cache = {} - def __getstate__(self): - # can't pickle the adapter caches - state = self.__dict__.copy() - state['_role_cache'] = {} - state['_directive_cache'] = {} - return state - - #def clear_doc + def clear_doc(self, docname): + """ + Remove traces of a document in the domain-specific inventories. + """ + pass def role(self, name): """ @@ -212,7 +214,15 @@ class PythonDesc(DescDirective): signode['ids'].append(fullname) signode['first'] = (not self.names) self.state.document.note_explicit_target(signode) - self.env.note_descref(fullname, self.desctype, self.lineno) + objects = self.env.domains['py'].data['objects'] + if fullname in objects: + self.env.warn( + self.env.docname, + 'duplicate object description of %s, ' % fullname + + 'other instance in ' + + self.env.doc2path(objects[fullname][0]), + self.lineno) + objects[fullname] = (self.env.docname, self.desctype) indextext = self.get_index_text(modname, name_cls) if indextext: @@ -352,6 +362,75 @@ class ClassmemberDesc(PythonDesc): self.clsname_set = True +class PyModule(Directive): + """ + Directive to mark description of a new module. + """ + + has_content = False + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = False + option_spec = { + 'platform': lambda x: x, + 'synopsis': lambda x: x, + 'noindex': directives.flag, + 'deprecated': directives.flag, + } + + def run(self): + env = self.state.document.settings.env + modname = self.arguments[0].strip() + noindex = 'noindex' in self.options + env.currmodule = modname + env.domains['py'].data['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] + if 'platform' in self.options: + platform = self.options['platform'] + modulenode['platform'] = platform + node = nodes.paragraph() + node += nodes.emphasis('', _('Platforms: ')) + node += nodes.Text(platform, platform) + ret.append(node) + # the synopsis isn't printed; in fact, it is only used in the + # modindex currently + if not noindex: + indextext = _('%s (module)') % modname + inode = addnodes.index(entries=[('single', indextext, + 'module-' + modname, modname)]) + ret.insert(0, inode) + return ret + + +class PyCurrentModule(Directive): + """ + This directive is just to tell Sphinx that we're documenting + stuff in module foo, but links to module foo won't lead here. + """ + + has_content = False + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = False + option_spec = {} + + def run(self): + env = self.state.document.settings.env + modname = self.arguments[0].strip() + if modname == 'None': + env.currmodule = None + else: + env.currmodule = modname + return [] + + class PyXRefRole(XRefRole): def process_link(self, env, pnode, has_explicit_title, title, target): pnode['modname'] = env.currmodule @@ -387,6 +466,8 @@ class PythonDomain(Domain): 'classmethod': ClassmemberDesc, 'staticmethod': ClassmemberDesc, 'attribute': ClassmemberDesc, + 'module': PyModule, + 'currentmodule': PyCurrentModule, } roles = { 'data': PyXRefRole(), @@ -399,6 +480,18 @@ class PythonDomain(Domain): 'mod': PyXRefRole(), 'obj': PyXRefRole(), } + initial_data = { + 'objects': {}, # fullname -> docname, desctype + 'modules': {}, # modname -> docname, synopsis, platform, deprecated + } + + def clear_doc(self, docname): + for fullname, (fn, _) in self.data['objects'].items(): + if fn == docname: + del self.data['objects'][fullname] + for modname, (fn, _, _, _) in self.data['modules'].items(): + if fn == docname: + del self.data['modules'][modname] def find_desc(self, env, modname, classname, name, type, searchorder=0): """ @@ -412,41 +505,43 @@ class PythonDomain(Domain): if not name: return None, None + objects = self.data['objects'] + newname = None if searchorder == 1: if modname and classname and \ - modname + '.' + classname + '.' + name in env.descrefs: + modname + '.' + classname + '.' + name in objects: newname = modname + '.' + classname + '.' + name - elif modname and modname + '.' + name in env.descrefs: + elif modname and modname + '.' + name in objects: newname = modname + '.' + name - elif name in env.descrefs: + elif name in objects: newname = name else: - if name in env.descrefs: + if name in objects: newname = name - elif modname and modname + '.' + name in env.descrefs: + elif modname and modname + '.' + name in objects: newname = modname + '.' + name elif modname and classname and \ - modname + '.' + classname + '.' + name in env.descrefs: + modname + '.' + classname + '.' + name in objects: newname = modname + '.' + classname + '.' + name # special case: builtin exceptions have module "exceptions" set elif type == 'exc' and '.' not in name and \ - 'exceptions.' + name in env.descrefs: + 'exceptions.' + name in objects: newname = 'exceptions.' + name # special case: object methods elif type in ('func', 'meth') and '.' not in name and \ - 'object.' + name in env.descrefs: + 'object.' + name in objects: newname = 'object.' + name if newname is None: return None, None - return newname, env.descrefs[newname] + return newname, objects[newname] def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode): if (typ == 'mod' or - typ == 'obj' and target in env.modules): + typ == 'obj' and target in self.data['modules']): docname, synopsis, platform, deprecated = \ - env.modules.get(target, ('','','', '')) + self.data['modules'].get(target, ('','','', '')) if not docname: return None elif docname == fromdocname: @@ -459,7 +554,6 @@ class PythonDomain(Domain): return make_refnode(builder, fromdocname, docname, 'module-' + target, contnode, title) else: - # "descrefs" modname = node['modname'] clsname = node['classname'] searchorder = node.hasattr('refspecific') and 1 or 0 @@ -589,7 +683,14 @@ class CDesc(DescDirective): signode['ids'].append(name) signode['first'] = (not self.names) self.state.document.note_explicit_target(signode) - self.env.note_descref(name, self.desctype, self.lineno) + inv = self.env.domains['c'].data['objects'] + if name in inv: + self.env.warn( + self.env.docname, + 'duplicate C object description of %s, ' % name + + 'other instance in ' + self.env.doc2path(inv[name][0]), + self.lineno) + inv[name] = (self.env.docname, self.desctype) indextext = self.get_index_text(name) if indextext: @@ -614,15 +715,22 @@ class CDomain(Domain): 'data': XRefRole(), 'type': XRefRole(), } + initial_data = { + 'objects': {}, # fullname -> docname, desctype + } + + 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(' *') - # XXX descrefs - if target not in env.descrefs: + if target not in self.data: return None - desc = env.descrefs[target] + desc = self.data[target] return make_refnode(builder, fromdocname, desc[0], contnode, target) diff --git a/sphinx/environment.py b/sphinx/environment.py index 62d3875b7..2f64b147b 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -297,13 +297,10 @@ class BuildEnvironment: self.glob_toctrees = set() # docnames that have :glob: toctrees self.numbered_toctrees = set() # docnames that have :numbered: toctrees - # domain-specific inventories - self.domaindata = {} # domainname -> object + # domain-specific inventories, here to be pickled + self.domaindata = {} # domainname -> domain-specific object # X-ref target inventory - self.descrefs = {} # fullname -> docname, desctype - self.modules = {} # modname -> docname, synopsis, - # platform, deprecated self.labels = {} # labelname -> docname, labelid, sectionname self.anonlabels = {} # labelname -> docname, labelid self.progoptions = {} # (program, name) -> docname, labelid @@ -371,12 +368,6 @@ class BuildEnvironment: fnset.discard(docname) if not fnset: del self.files_to_rebuild[subfn] - for fullname, (fn, _) in self.descrefs.items(): - if fn == docname: - del self.descrefs[fullname] - for modname, (fn, _, _, _) in self.modules.items(): - if fn == docname: - del self.modules[modname] for labelname, (fn, _, _) in self.labels.items(): if fn == docname: del self.labels[labelname] @@ -390,6 +381,10 @@ class BuildEnvironment: new = [change for change in changes if change[1] != docname] changes[:] = new + # XXX why does this not work inside the if? + for domain in self.domains.values(): + domain.clear_doc(docname) + def doc2path(self, docname, base=True, suffix=None): """ Return the filename for the document name. @@ -1001,18 +996,6 @@ class BuildEnvironment: # ------- # these are called from docutils directives and therefore use self.docname # - def note_descref(self, fullname, desctype, line): - if fullname in self.descrefs: - self.warn(self.docname, - 'duplicate canonical description name %s, ' % fullname + - 'other instance in ' + - self.doc2path(self.descrefs[fullname][0]), - line) - self.descrefs[fullname] = (self.docname, desctype) - - def note_module(self, modname, synopsis, platform, deprecated): - self.modules[modname] = (self.docname, synopsis, platform, deprecated) - def note_progoption(self, optname, labelid): self.progoptions[self.currprogram, optname] = (self.docname, labelid) diff --git a/sphinx/ext/coverage.py b/sphinx/ext/coverage.py index 964e58eec..6eb9bc8b3 100644 --- a/sphinx/ext/coverage.py +++ b/sphinx/ext/coverage.py @@ -79,6 +79,7 @@ class CoverageBuilder(Builder): def build_c_coverage(self): # Fetch all the info from the header files + c_objects = self.env.domains['c'].data['objects'] for filename in self.c_sourcefiles: undoc = [] f = open(filename, 'r') @@ -88,7 +89,7 @@ class CoverageBuilder(Builder): match = regex.match(line) if match: name = match.groups()[0] - if name not in self.env.descrefs: + if name not in c_objects: for exp in self.c_ignorexps.get(key, ()): if exp.match(name): break @@ -116,7 +117,10 @@ class CoverageBuilder(Builder): op.close() def build_py_coverage(self): - for mod_name in self.env.modules: + objects = self.env.domains['py'].data['objects'] + modules = self.env.domains['py'].data['modules'] + + for mod_name in modules: ignore = False for exp in self.mod_ignorexps: if exp.match(mod_name): @@ -151,7 +155,7 @@ class CoverageBuilder(Builder): full_name = '%s.%s' % (mod_name, name) if inspect.isfunction(obj): - if full_name not in self.env.descrefs: + if full_name not in objects: for exp in self.fun_ignorexps: if exp.match(name): break @@ -162,7 +166,7 @@ class CoverageBuilder(Builder): if exp.match(name): break else: - if full_name not in self.env.descrefs: + if full_name not in objects: # not documented at all classes[name] = [] continue @@ -176,7 +180,7 @@ class CoverageBuilder(Builder): continue full_attr_name = '%s.%s' % (full_name, attr_name) - if full_attr_name not in self.env.descrefs: + if full_attr_name not in objects: attrs.append(attr_name) if attrs: diff --git a/sphinx/search.py b/sphinx/search.py index 499c4aa9e..f283881fa 100644 --- a/sphinx/search.py +++ b/sphinx/search.py @@ -149,6 +149,8 @@ class IndexBuilder(object): def get_modules(self, fn2index): rv = {} + # XXX implement search capability + return rv for name, (doc, _, _, _) in self.env.modules.iteritems(): if doc in fn2index: rv[name] = fn2index[doc] @@ -157,6 +159,8 @@ class IndexBuilder(object): def get_descrefs(self, fn2index): rv = {} dt = self._desctypes + # XXX implement search capability + return rv for fullname, (doc, desctype) in self.env.descrefs.iteritems(): if doc not in fn2index: continue diff --git a/tests/test_env.py b/tests/test_env.py index d5f40f8c2..921d893c0 100644 --- a/tests/test_env.py +++ b/tests/test_env.py @@ -20,9 +20,8 @@ warnings = [] def setup_module(): global app, env - app = TestApp(srcdir='(temp)') + app = TestApp(srcdir='(temp)', freshenv=True) env = app.env - #env = BuildEnvironment(app.srcdir, app.doctreedir, app.config) env.set_warnfunc(lambda *args: warnings.append(args)) def teardown_module(): @@ -93,7 +92,7 @@ def test_second_update(): assert 'autodoc' not in env.found_docs def test_object_inventory(): - refs = env.descrefs + refs = env.domains['py'].data['objects'] assert 'func_without_module' in refs assert refs['func_without_module'] == ('desc', 'function') @@ -110,5 +109,5 @@ def test_object_inventory(): assert 'func_in_module' not in refs assert 'func_noindex' not in refs - assert 'mod' in env.modules - assert env.modules['mod'] == ('desc', 'Module synopsis.', 'UNIX', False) + assert env.domains['py'].data['modules']['mod'] == \ + ('desc', 'Module synopsis.', 'UNIX', False) From c5c62b252c5522d4f5053e750432e6206f6d5766 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 13 Jul 2009 21:31:05 +0200 Subject: [PATCH 31/85] Access domain data via env.domaindata. --- sphinx/builders/html.py | 4 ++-- sphinx/domains.py | 6 +++--- sphinx/ext/coverage.py | 6 +++--- tests/test_env.py | 7 +++++-- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index de4c9a393..02f621640 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -242,7 +242,7 @@ class StandaloneHTMLBuilder(Builder): rellinks.append(('genindex', _('General Index'), 'I', _('index'))) # XXX generalization of modindex? if self.config.html_use_modindex and \ - self.env.domains['py'].data['modules']: + self.env.domaindata['py']['modules']: rellinks.append(('modindex', _('Global Module Index'), 'M', _('modules'))) @@ -407,7 +407,7 @@ class StandaloneHTMLBuilder(Builder): # the global module index - moduleindex = self.env.domains['py'].data['modules'] + moduleindex = self.env.domaindata['py']['modules'] if self.config.html_use_modindex and moduleindex: # the sorted list of all modules, for the global module index modules = sorted(((mn, (self.get_relative_uri('modindex', fn) + diff --git a/sphinx/domains.py b/sphinx/domains.py index 3e5b22e81..d40915472 100644 --- a/sphinx/domains.py +++ b/sphinx/domains.py @@ -214,7 +214,7 @@ class PythonDesc(DescDirective): signode['ids'].append(fullname) signode['first'] = (not self.names) self.state.document.note_explicit_target(signode) - objects = self.env.domains['py'].data['objects'] + objects = self.env.domaindata['py']['objects'] if fullname in objects: self.env.warn( self.env.docname, @@ -383,7 +383,7 @@ class PyModule(Directive): modname = self.arguments[0].strip() noindex = 'noindex' in self.options env.currmodule = modname - env.domains['py'].data['modules'][modname] = \ + env.domaindata['py']['modules'][modname] = \ (env.docname, self.options.get('synopsis', ''), self.options.get('platform', ''), 'deprecated' in self.options) modulenode = addnodes.module() @@ -683,7 +683,7 @@ class CDesc(DescDirective): signode['ids'].append(name) signode['first'] = (not self.names) self.state.document.note_explicit_target(signode) - inv = self.env.domains['c'].data['objects'] + inv = self.env.domaindata['c']['objects'] if name in inv: self.env.warn( self.env.docname, diff --git a/sphinx/ext/coverage.py b/sphinx/ext/coverage.py index 6eb9bc8b3..ed5d92a4f 100644 --- a/sphinx/ext/coverage.py +++ b/sphinx/ext/coverage.py @@ -79,7 +79,7 @@ class CoverageBuilder(Builder): def build_c_coverage(self): # Fetch all the info from the header files - c_objects = self.env.domains['c'].data['objects'] + c_objects = self.env.domaindata['c']['objects'] for filename in self.c_sourcefiles: undoc = [] f = open(filename, 'r') @@ -117,8 +117,8 @@ class CoverageBuilder(Builder): op.close() def build_py_coverage(self): - objects = self.env.domains['py'].data['objects'] - modules = self.env.domains['py'].data['modules'] + objects = self.env.domaindata['py']['objects'] + modules = self.env.domaindata['py']['modules'] for mod_name in modules: ignore = False diff --git a/tests/test_env.py b/tests/test_env.py index 921d893c0..e9118a633 100644 --- a/tests/test_env.py +++ b/tests/test_env.py @@ -92,7 +92,7 @@ def test_second_update(): assert 'autodoc' not in env.found_docs def test_object_inventory(): - refs = env.domains['py'].data['objects'] + refs = env.domaindata['py']['objects'] assert 'func_without_module' in refs assert refs['func_without_module'] == ('desc', 'function') @@ -109,5 +109,8 @@ def test_object_inventory(): assert 'func_in_module' not in refs assert 'func_noindex' not in refs - assert env.domains['py'].data['modules']['mod'] == \ + assert env.domaindata['py']['modules']['mod'] == \ ('desc', 'Module synopsis.', 'UNIX', False) + + assert env.domains['py'].data is env.domaindata['py'] + assert env.domains['c'].data is env.domaindata['c'] From ae34f70782b895ab0225b00bbb411576a48cc8b5 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 14 Jul 2009 11:43:39 +0200 Subject: [PATCH 32/85] Write theme information payload binary, not as text. Otherwise windows users will be unhappy. --- sphinx/theming.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/theming.py b/sphinx/theming.py index e58070293..217dd81c9 100644 --- a/sphinx/theming.py +++ b/sphinx/theming.py @@ -78,7 +78,7 @@ class Theme(object): dirname = path.dirname(name) if not path.isdir(path.join(self.themedir, dirname)): os.makedirs(path.join(self.themedir, dirname)) - fp = open(path.join(self.themedir, name), 'w') + fp = open(path.join(self.themedir, name), 'wb') fp.write(tinfo.read(name)) fp.close() From 279dd4fa41c5b799883755e92b477b5f24ab845b Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 26 Jul 2009 15:00:18 +0200 Subject: [PATCH 33/85] Builder.load_env is no more. --- doc/ext/builderapi.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/ext/builderapi.rst b/doc/ext/builderapi.rst index bb11bfe2f..3ace26871 100644 --- a/doc/ext/builderapi.rst +++ b/doc/ext/builderapi.rst @@ -13,7 +13,6 @@ Writing new builders These methods are predefined and will be called from the application: - .. automethod:: load_env .. automethod:: get_relative_uri .. automethod:: build_all .. automethod:: build_specific From 72516863b672bd34f88501f630c671bbdf495169 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 28 Jul 2009 17:45:02 +0000 Subject: [PATCH 34/85] Add API to add components to a domain. --- sphinx/application.py | 23 ++++++++++++++++++++--- sphinx/environment.py | 1 + 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/sphinx/application.py b/sphinx/application.py index 7d9ee49db..c9dac9817 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -334,17 +334,21 @@ class Sphinx(object): if depart: setattr(translator, 'depart_'+node.__name__, depart) - def add_directive(self, name, obj, content=None, arguments=None, **options): + def _directive_helper(self, obj, content=None, arguments=None, **options): if isinstance(obj, clstypes) and issubclass(obj, Directive): if content or arguments or options: raise ExtensionError('when adding directive classes, no ' 'additional arguments may be given') - directives.register_directive(name, directive_dwim(obj)) + return directive_dwim(obj) else: obj.content = content obj.arguments = arguments obj.options = options - directives.register_directive(name, obj) + return obj + + def add_directive(self, name, obj, content=None, arguments=None, **options): + directives.register_directive( + name, self._directive_helper(obj, content, arguments, **options)) def add_role(self, name, role): roles.register_local_role(name, role) @@ -356,10 +360,23 @@ class Sphinx(object): roles.register_local_role(name, role) def add_domain(self, domain): + # XXX needs to be documented if domain.name in all_domains: raise ExtensionError('domain %s already registered' % domain.name) all_domains[domain.name] = domain + def add_directive_to_domain(self, domain, name, obj): + # XXX needs to be documented + if domain not in all_domains: + raise ExtensionError('domain %s not yet registered' % domain) + all_domains[domain].directives[name] = self._directive_helper(obj) + + def add_role_to_domain(self, domain, name, role): + # XXX needs to be documented + if domain not in all_domains: + raise ExtensionError('domain %s not yet registered' % domain) + all_domains[domain].roles[name] = role + def add_description_unit(self, directivename, rolename, indextemplate='', parse_node=None, ref_nodeclass=None): additional_xref_types[directivename] = (rolename, indextemplate, diff --git a/sphinx/environment.py b/sphinx/environment.py index 2f64b147b..9cd7fc2a2 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -319,6 +319,7 @@ class BuildEnvironment: # These are set while parsing a file self.docname = None # current document name + # XXX remove currmodule and currclass from here self.currmodule = None # current module name self.currclass = None # current class name self.currdesc = None # current descref name From dcf6a1c386c31ea9de9e01a242060066cc180021 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 29 Jul 2009 16:34:15 +0000 Subject: [PATCH 35/85] Add some docstrings. --- sphinx/domains.py | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/sphinx/domains.py b/sphinx/domains.py index d40915472..6e00d5402 100644 --- a/sphinx/domains.py +++ b/sphinx/domains.py @@ -24,6 +24,28 @@ from sphinx.util.compat import Directive class Domain(object): + """ + A Domain is meant to be a group of "object" description directives for + objects of a similar nature, and corresponding roles to create references to + them. Examples would be Python modules, classes, functions etc., elements + 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 + for objects in a domain-agnostic way. + + About `self.data`: since all object and cross-referencing information is + stored on a BuildEnvironment instance, the `domain.data` object is also + stored in the `env.domaindata` dict under the key `domain.name`. Before the + 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. + """ + name = '' directives = {} roles = {} @@ -37,12 +59,13 @@ class Domain(object): def __init__(self, env): self.env = env if self.name not in env.domaindata: + assert isinstance(self.initial_data, dict) new_data = self.initial_data.copy() new_data['version'] = self.data_version self.data = env.domaindata[self.name] = new_data else: self.data = env.domaindata[self.name] - if self.data['version'] < self.data_version: + if self.data['version'] != self.data_version: raise IOError('data of %r domain out of date' % self.label) self._role_cache = {} self._directive_cache = {} @@ -80,6 +103,7 @@ class Domain(object): if name not in self.directives: return None fullname = '%s:%s' % (self.name, name) + # XXX what about function-style directives? BaseDirective = self.directives[name] class DirectiveAdapter(BaseDirective): def run(self): @@ -89,6 +113,20 @@ class Domain(object): return DirectiveAdapter def resolve_xref(self, typ, target, node, contnode): + """ + Resolve the ``pending_xref`` *node* with the given *typ* and *target*. + + This method should return a new node, to replace the xref node, + containing the *contnode* which is the markup content of the + cross-reference. + + If no resolution can be found, None can be returned; the xref node will + 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. + """ pass From 59c3696a7daf18c61fa362e87ef7873ea480a4cc Mon Sep 17 00:00:00 2001 From: Pau Fern?ndez Date: Sun, 7 Jun 2009 14:30:57 +0200 Subject: [PATCH 36/85] Added the Catalan translation --- sphinx/locale/ca/LC_MESSAGES/sphinx.js | 1 + sphinx/locale/ca/LC_MESSAGES/sphinx.mo | Bin 0 -> 8764 bytes sphinx/locale/ca/LC_MESSAGES/sphinx.po | 607 +++++++++++++++++++++++++ 3 files changed, 608 insertions(+) create mode 100644 sphinx/locale/ca/LC_MESSAGES/sphinx.js create mode 100644 sphinx/locale/ca/LC_MESSAGES/sphinx.mo create mode 100644 sphinx/locale/ca/LC_MESSAGES/sphinx.po diff --git a/sphinx/locale/ca/LC_MESSAGES/sphinx.js b/sphinx/locale/ca/LC_MESSAGES/sphinx.js new file mode 100644 index 000000000..2511f53a1 --- /dev/null +++ b/sphinx/locale/ca/LC_MESSAGES/sphinx.js @@ -0,0 +1 @@ +Documentation.addTranslations({"locale": "es", "plural_expr": "(n != 1)", "messages": {"module, in ": "m\u00f2dul, a ", "Preparing search...": "Preparant la cerca", "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories.": "La cerca no ha retornat resultats. Assegura't que les paraules estan ben escrites i que has seleccionat suficients categories", "Search finished, found %s page(s) matching the search query.": "Cerca finalitzada, trobades %s pàgin(a/es) amb resultats.", ", in ": ", en ", "Permalink to this headline": "Link permanent a aquest títol", "Searching": "Cercant", "Permalink to this definition": "Link permanent a aquesta definició", "Hide Search Matches": "Oculta Resultats", "Search Results": "Resultats de la Cerca"}}); \ No newline at end of file diff --git a/sphinx/locale/ca/LC_MESSAGES/sphinx.mo b/sphinx/locale/ca/LC_MESSAGES/sphinx.mo new file mode 100644 index 0000000000000000000000000000000000000000..6e44415fba8f1e96ea3524ad61fe3c2ef17f83ac GIT binary patch literal 8764 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 literal 0 HcmV?d00001 diff --git a/sphinx/locale/ca/LC_MESSAGES/sphinx.po b/sphinx/locale/ca/LC_MESSAGES/sphinx.po new file mode 100644 index 000000000..475582ac0 --- /dev/null +++ b/sphinx/locale/ca/LC_MESSAGES/sphinx.po @@ -0,0 +1,607 @@ +# Catalan translations for Sphinx. +# Copyright (C) 2009 ORGANIZATION +# This file is distributed under the same license as the Sphinx project. +# FIRST AUTHOR , 2009. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: Sphinx 1.0\n" +"Report-Msgid-Bugs-To: pau.fernandez@upc.edu\n" +"POT-Creation-Date: 2009-05-22 18:51+0200\n" +"PO-Revision-Date: 2009-06-07 14:20+0200\n" +"Last-Translator: Pau Fernández \n" +"Language-Team: ca \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Generated-By: Babel 0.9.4\n" + +#: sphinx/environment.py:104 sphinx/writers/latex.py:184 +#, python-format +msgid "%B %d, %Y" +msgstr "%d de %B de %Y" + +#: sphinx/environment.py:323 sphinx/themes/basic/genindex-single.html:2 +#: sphinx/themes/basic/genindex-split.html:2 +#: sphinx/themes/basic/genindex-split.html:5 +#: sphinx/themes/basic/genindex.html:2 sphinx/themes/basic/genindex.html:5 +#: sphinx/themes/basic/genindex.html:48 sphinx/themes/basic/layout.html:131 +#: sphinx/writers/latex.py:190 +msgid "Index" +msgstr "Índex" + +#: sphinx/environment.py:324 sphinx/writers/latex.py:189 +msgid "Module Index" +msgstr "Índex de Mòduls" + +#: sphinx/environment.py:325 sphinx/themes/basic/defindex.html:16 +msgid "Search Page" +msgstr "Pàgina de Cerca" + +#: sphinx/roles.py:55 sphinx/directives/desc.py:747 +#, python-format +msgid "environment variable; %s" +msgstr "variable d'entorn; %s" + +#: sphinx/roles.py:62 +#, python-format +msgid "Python Enhancement Proposals!PEP %s" +msgstr "Python Enhancement Proposals!PEP %s" + +#: sphinx/builders/changes.py:71 +msgid "Builtins" +msgstr "Mòduls Interns" + +#: sphinx/builders/changes.py:73 +msgid "Module level" +msgstr "Nivell de mòdul" + +#: sphinx/builders/html.py:219 +#, python-format +msgid "%b %d, %Y" +msgstr "%d %b, %Y" + +#: sphinx/builders/html.py:238 sphinx/themes/basic/defindex.html:21 +msgid "General Index" +msgstr "Índex General" + +#: sphinx/builders/html.py:238 +msgid "index" +msgstr "índex" + +#: sphinx/builders/html.py:240 sphinx/builders/htmlhelp.py:184 +#: sphinx/builders/qthelp.py:132 sphinx/themes/basic/defindex.html:19 +#: sphinx/themes/basic/modindex.html:2 sphinx/themes/basic/modindex.html:13 +msgid "Global Module Index" +msgstr "Índex Global de Mòduls" + +#: sphinx/builders/html.py:241 +msgid "modules" +msgstr "mòduls" + +#: sphinx/builders/html.py:296 +msgid "next" +msgstr "següent" + +#: sphinx/builders/html.py:305 +msgid "previous" +msgstr "anterior" + +#: sphinx/builders/latex.py:162 +msgid " (in " +msgstr " (a " + +#: sphinx/directives/desc.py:97 +msgid "Raises" +msgstr "Llença" + +#: sphinx/directives/desc.py:101 +msgid "Variable" +msgstr "Variable" + +#: sphinx/directives/desc.py:104 +msgid "Returns" +msgstr "Retorna" + +#: sphinx/directives/desc.py:113 +msgid "Return type" +msgstr "Tipus de retorn" + +#: sphinx/directives/desc.py:186 +msgid "Parameter" +msgstr "Paràmetre" + +#: sphinx/directives/desc.py:190 +msgid "Parameters" +msgstr "Paràmetres" + +#: sphinx/directives/desc.py:418 +#, python-format +msgid "%s() (built-in function)" +msgstr "%s() (funció interna)" + +#: sphinx/directives/desc.py:419 sphinx/directives/desc.py:476 +#: sphinx/directives/desc.py:488 +#, python-format +msgid "%s() (in module %s)" +msgstr "%s() (al mòdul %s)" + +#: sphinx/directives/desc.py:422 +#, python-format +msgid "%s (built-in variable)" +msgstr "%s (variable interna)" + +#: sphinx/directives/desc.py:423 sphinx/directives/desc.py:514 +#, python-format +msgid "%s (in module %s)" +msgstr "%s (al mòdul %s)" + +#: sphinx/directives/desc.py:439 +#, python-format +msgid "%s (built-in class)" +msgstr "%s (classe interna)" + +#: sphinx/directives/desc.py:440 +#, python-format +msgid "%s (class in %s)" +msgstr "%s (class a %s)" + +#: sphinx/directives/desc.py:480 +#, python-format +msgid "%s() (%s.%s method)" +msgstr "%s() (mètode %s.%s)" + +#: sphinx/directives/desc.py:482 +#, python-format +msgid "%s() (%s method)" +msgstr "%s() (mètode %s)" + +#: sphinx/directives/desc.py:492 +#, python-format +msgid "%s() (%s.%s static method)" +msgstr "%s() (mètode estàtic %s.%s)" + +#: sphinx/directives/desc.py:495 +#, python-format +msgid "%s() (%s static method)" +msgstr "%s() (mètode estàtic %s)" + +#: sphinx/directives/desc.py:518 +#, python-format +msgid "%s (%s.%s attribute)" +msgstr "%s (atribut %s.%s)" + +#: sphinx/directives/desc.py:520 +#, python-format +msgid "%s (%s attribute)" +msgstr "%s (atribut %s)" + +#: sphinx/directives/desc.py:609 +#, python-format +msgid "%s (C function)" +msgstr "%s (funció de C)" + +#: sphinx/directives/desc.py:611 +#, python-format +msgid "%s (C member)" +msgstr "%s (membre de C)" + +#: sphinx/directives/desc.py:613 +#, python-format +msgid "%s (C macro)" +msgstr "%s (macro de C)" + +#: sphinx/directives/desc.py:615 +#, python-format +msgid "%s (C type)" +msgstr "%s (tipus de C)" + +#: sphinx/directives/desc.py:617 +#, python-format +msgid "%s (C variable)" +msgstr "%s (variable de C)" + +#: sphinx/directives/desc.py:665 +#, python-format +msgid "%scommand line option; %s" +msgstr "opció de línia de comandes %s; %s" + +#: sphinx/directives/other.py:138 +msgid "Platforms: " +msgstr "Plataformes: " + +#: sphinx/directives/other.py:144 +#, python-format +msgid "%s (module)" +msgstr "%s (mòdul)" + +#: sphinx/directives/other.py:193 +msgid "Section author: " +msgstr "Autor de la secció:" + +#: sphinx/directives/other.py:195 +msgid "Module author: " +msgstr "Autor del mòdul: " + +#: sphinx/directives/other.py:197 +msgid "Author: " +msgstr "Autor: " + +#: sphinx/directives/other.py:317 +msgid "See also" +msgstr "Vegeu també" + +#: sphinx/ext/autodoc.py:888 +#, python-format +msgid " Bases: %s" +msgstr " Bases: %s" + +#: sphinx/ext/autodoc.py:919 +#, python-format +msgid "alias of :class:`%s`" +msgstr "àlies de :class:`%s`" + +#: sphinx/ext/todo.py:41 +msgid "Todo" +msgstr "Pendent" + +#: sphinx/ext/todo.py:99 +#, python-format +msgid "(The original entry is located in %s, line %d and can be found " +msgstr "(La entrada original està a %s, línia %d i es pot trobar " + +#: sphinx/ext/todo.py:105 +msgid "here" +msgstr "aquí" + +#: sphinx/locale/__init__.py:15 +msgid "Attention" +msgstr "Atenció" + +#: sphinx/locale/__init__.py:16 +msgid "Caution" +msgstr "Compte" + +#: sphinx/locale/__init__.py:17 +msgid "Danger" +msgstr "Perill" + +#: sphinx/locale/__init__.py:18 +msgid "Error" +msgstr "Error" + +#: sphinx/locale/__init__.py:19 +msgid "Hint" +msgstr "Suggerència" + +#: sphinx/locale/__init__.py:20 +msgid "Important" +msgstr "Important" + +#: sphinx/locale/__init__.py:21 +msgid "Note" +msgstr "Nota" + +#: sphinx/locale/__init__.py:22 +msgid "See Also" +msgstr "Vegeu També" + +#: sphinx/locale/__init__.py:23 +msgid "Tip" +msgstr "Truc" + +#: sphinx/locale/__init__.py:24 +msgid "Warning" +msgstr "Avís" + +#: sphinx/locale/__init__.py:28 +#, python-format +msgid "New in version %s" +msgstr "Novetat de la versió %s" + +#: sphinx/locale/__init__.py:29 +#, python-format +msgid "Changed in version %s" +msgstr "Canviat a la versió %s" + +#: sphinx/locale/__init__.py:30 +#, python-format +msgid "Deprecated since version %s" +msgstr "Obsolet desde la versió %s" + +#: sphinx/locale/__init__.py:34 +msgid "module" +msgstr "mòdul" + +#: sphinx/locale/__init__.py:35 +msgid "keyword" +msgstr "paraula clau" + +#: sphinx/locale/__init__.py:36 +msgid "operator" +msgstr "operador" + +#: sphinx/locale/__init__.py:37 +msgid "object" +msgstr "objecte" + +#: sphinx/locale/__init__.py:38 +msgid "exception" +msgstr "excepció" + +#: sphinx/locale/__init__.py:39 +msgid "statement" +msgstr "sentència" + +#: sphinx/locale/__init__.py:40 +msgid "built-in function" +msgstr "funció interna" + +#: sphinx/themes/basic/defindex.html:2 +msgid "Overview" +msgstr "Resum" + +#: sphinx/themes/basic/defindex.html:11 +msgid "Indices and tables:" +msgstr "Índexs i taules:" + +#: sphinx/themes/basic/defindex.html:14 +msgid "Complete Table of Contents" +msgstr "Taula de Contingut Completa" + +#: sphinx/themes/basic/defindex.html:15 +msgid "lists all sections and subsections" +msgstr "llista totes les seccions i subseccions" + +#: sphinx/themes/basic/defindex.html:17 +msgid "search this documentation" +msgstr "cerca aquesta documentació" + +#: sphinx/themes/basic/defindex.html:20 +msgid "quick access to all modules" +msgstr "accés ràpid a tots els mòduls" + +#: sphinx/themes/basic/defindex.html:22 +msgid "all functions, classes, terms" +msgstr "totes les funcions, classes, termes" + +#: sphinx/themes/basic/genindex-single.html:5 +#, python-format +msgid "Index – %(key)s" +msgstr "Índes – %(key)s" + +#: sphinx/themes/basic/genindex-single.html:44 +#: sphinx/themes/basic/genindex-split.html:14 +#: sphinx/themes/basic/genindex-split.html:27 +#: sphinx/themes/basic/genindex.html:54 +msgid "Full index on one page" +msgstr "Índex complet en una pàgina" + +#: sphinx/themes/basic/genindex-split.html:7 +msgid "Index pages by letter" +msgstr "Pàgines d'índex per lletra" + +#: sphinx/themes/basic/genindex-split.html:15 +msgid "can be huge" +msgstr "pot ser gegant" + +#: sphinx/themes/basic/layout.html:10 +msgid "Navigation" +msgstr "Navegació" + +#: sphinx/themes/basic/layout.html:42 +msgid "Table Of Contents" +msgstr "Taula de Contingut" + +#: sphinx/themes/basic/layout.html:48 +msgid "Previous topic" +msgstr "Tema anterior" + +#: sphinx/themes/basic/layout.html:50 +msgid "previous chapter" +msgstr "capítol anterior" + +#: sphinx/themes/basic/layout.html:53 +msgid "Next topic" +msgstr "Tema següent" + +#: sphinx/themes/basic/layout.html:55 +msgid "next chapter" +msgstr "capítol següent" + +#: sphinx/themes/basic/layout.html:60 +msgid "This Page" +msgstr "Aquesta Pàgina" + +#: sphinx/themes/basic/layout.html:63 +msgid "Show Source" +msgstr "Mostra Codi Font" + +#: sphinx/themes/basic/layout.html:73 +msgid "Quick search" +msgstr "Cerca ràpida" + +#: sphinx/themes/basic/layout.html:76 +msgid "Go" +msgstr "Ves a" + +#: sphinx/themes/basic/layout.html:81 +msgid "Enter search terms or a module, class or function name." +msgstr "Entra paraules de cerca o el nom d'un mòdul, classe o funció." + +#: sphinx/themes/basic/layout.html:119 +#, python-format +msgid "Search within %(docstitle)s" +msgstr "Cerca dins de %(docstitle)s" + +#: sphinx/themes/basic/layout.html:128 +msgid "About these documents" +msgstr "Quant a aquests documents" + +#: sphinx/themes/basic/layout.html:134 sphinx/themes/basic/search.html:2 +#: sphinx/themes/basic/search.html:5 +msgid "Search" +msgstr "Cerca" + +#: sphinx/themes/basic/layout.html:137 +msgid "Copyright" +msgstr "Copyright" + +#: sphinx/themes/basic/layout.html:184 +#, python-format +msgid "© Copyright %(copyright)s." +msgstr "© Copyright %(copyright)s." + +#: sphinx/themes/basic/layout.html:186 +#, python-format +msgid "© Copyright %(copyright)s." +msgstr "© Copyright %(copyright)s." + +#: sphinx/themes/basic/layout.html:190 +#, python-format +msgid "Last updated on %(last_updated)s." +msgstr "Última actualització el %(last_updated)s." + +#: sphinx/themes/basic/layout.html:193 +#, python-format +msgid "" +"Created using Sphinx " +"%(sphinx_version)s." +msgstr "" +"Creat amb Sphinx " +"%(sphinx_version)s." + +#: sphinx/themes/basic/modindex.html:36 +msgid "Deprecated" +msgstr "Obsolet" + +#: sphinx/themes/basic/opensearch.xml:4 +#, python-format +msgid "Search %(docstitle)s" +msgstr "Cercar a %(docstitle)s" + +#: sphinx/themes/basic/search.html:9 +msgid "" +"Please activate JavaScript to enable the search\n" +" functionality." +msgstr "" +"Activa JavaScript per utilitzar la funcionalitat\n" +"de cerca." + +#: sphinx/themes/basic/search.html:14 +msgid "" +"From here you can search these documents. Enter your search\n" +" words into the box below and click \"search\". Note that the search\n" +" function will automatically search for all of the words. Pages\n" +" containing fewer words won't appear in the result list." +msgstr "" +"Des d'aquí pots fer cerques en aquests documents. Entra les \n" +"paraules de la teva cerca i clica el botó \"cerca\". Tingues en compte\n" +"que la cerca inclourà totes les paraules que posis. Les pàgines que no\n" +"tenen totes les paraules no sortiràn." + +#: sphinx/themes/basic/search.html:21 +msgid "search" +msgstr "cerca" + +#: sphinx/themes/basic/search.html:25 +#: sphinx/themes/basic/static/searchtools.js:462 +msgid "Search Results" +msgstr "Resultats de la Cerca" + +#: sphinx/themes/basic/search.html:27 +msgid "Your search did not match any results." +msgstr "La teva cerca no té resultats." + +#: sphinx/themes/basic/changes/frameset.html:5 +#: sphinx/themes/basic/changes/versionchanges.html:12 +#, python-format +msgid "Changes in Version %(version)s — %(docstitle)s" +msgstr "Canvis a la Versió %(version)s — %(docstitle)s" + +#: sphinx/themes/basic/changes/rstsource.html:5 +#, python-format +msgid "%(filename)s — %(docstitle)s" +msgstr "%(filename)s — %(docstitle)s" + +#: sphinx/themes/basic/changes/versionchanges.html:17 +#, python-format +msgid "Automatically generated list of changes in version %(version)s" +msgstr "Llista de canvis de la versió %(version)s generada automàticament" + +#: sphinx/themes/basic/changes/versionchanges.html:18 +msgid "Library changes" +msgstr "Canvis a la llibreria" + +#: sphinx/themes/basic/changes/versionchanges.html:23 +msgid "C API changes" +msgstr "Canvis a la API de C" + +#: sphinx/themes/basic/changes/versionchanges.html:25 +msgid "Other changes" +msgstr "Altres canvis" + +#: sphinx/themes/basic/static/doctools.js:139 sphinx/writers/html.py:473 +#: sphinx/writers/html.py:478 +msgid "Permalink to this headline" +msgstr "Link permanent a aquest títol" + +#: sphinx/themes/basic/static/doctools.js:145 sphinx/writers/html.py:80 +msgid "Permalink to this definition" +msgstr "Link permanent a aquesta definició" + +#: sphinx/themes/basic/static/doctools.js:174 +msgid "Hide Search Matches" +msgstr "Oculta Resultats de Cerca" + +#: sphinx/themes/basic/static/searchtools.js:274 +msgid "Searching" +msgstr "Cercant" + +#: sphinx/themes/basic/static/searchtools.js:279 +msgid "Preparing search..." +msgstr "Preparant la cerca..." + +#: sphinx/themes/basic/static/searchtools.js:347 +msgid "module, in " +msgstr "mòdule, a " + +#: sphinx/themes/basic/static/searchtools.js:356 +msgid ", in " +msgstr ", a " + +#: sphinx/themes/basic/static/searchtools.js:464 +msgid "" +"Your search did not match any documents. Please make sure that all words " +"are spelled correctly and that you've selected enough categories." +msgstr "" +"La teva cerca no ha donat resultats. Assegura't que totes les paraules " +"estan ben escrites i que has seleccionat prou categories." + +#: sphinx/themes/basic/static/searchtools.js:466 +#, python-format +msgid "Search finished, found %s page(s) matching the search query." +msgstr "Cerca finalitzada, s'han trobat %s pàgin(a/es) de resultats." + +#: sphinx/writers/latex.py:187 +msgid "Release" +msgstr "Versió" + +#: sphinx/writers/latex.py:639 +msgid "continued from previous page" +msgstr "ve de la pàgina anterior" + +#: sphinx/writers/latex.py:643 +msgid "Continued on next page" +msgstr "Continua a la pàgina següent" + +#: sphinx/writers/text.py:166 +#, python-format +msgid "Platform: %s" +msgstr "Plataforma: %s" + +#: sphinx/writers/text.py:428 +msgid "[image]" +msgstr "[imatge]" + From af8fe705a22dc81083617366ecdb4a977568f475 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 29 Jul 2009 16:39:06 +0000 Subject: [PATCH 37/85] =?UTF-8?q?Added=20Catalan=20translation,=20thanks?= =?UTF-8?q?=20to=20Pau=20Fern=C3=A1ndez.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGES | 2 ++ doc/config.rst | 1 + sphinx/locale/ca/LC_MESSAGES/sphinx.js | 2 +- sphinx/locale/ca/LC_MESSAGES/sphinx.mo | Bin 8764 -> 8114 bytes sphinx/locale/ca/LC_MESSAGES/sphinx.po | 1 - 5 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 387dd6577..e91940ac2 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,8 @@ Release 1.0 (in development) ============================ +* Added Catalan translation, thanks to Pau Fernández. + * Added new minimal theme called scrolls. * Added ``html_output_encoding`` config value. diff --git a/doc/config.rst b/doc/config.rst index 3ce8d53bb..47fc2bdd0 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -240,6 +240,7 @@ Project information Currently supported languages are: + * ``ca`` -- Catalan * ``cs`` -- Czech * ``de`` -- German * ``en`` -- English diff --git a/sphinx/locale/ca/LC_MESSAGES/sphinx.js b/sphinx/locale/ca/LC_MESSAGES/sphinx.js index 2511f53a1..8ad043b0a 100644 --- a/sphinx/locale/ca/LC_MESSAGES/sphinx.js +++ b/sphinx/locale/ca/LC_MESSAGES/sphinx.js @@ -1 +1 @@ -Documentation.addTranslations({"locale": "es", "plural_expr": "(n != 1)", "messages": {"module, in ": "m\u00f2dul, a ", "Preparing search...": "Preparant la cerca", "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories.": "La cerca no ha retornat resultats. Assegura't que les paraules estan ben escrites i que has seleccionat suficients categories", "Search finished, found %s page(s) matching the search query.": "Cerca finalitzada, trobades %s pàgin(a/es) amb resultats.", ", in ": ", en ", "Permalink to this headline": "Link permanent a aquest títol", "Searching": "Cercant", "Permalink to this definition": "Link permanent a aquesta definició", "Hide Search Matches": "Oculta Resultats", "Search Results": "Resultats de la Cerca"}}); \ No newline at end of file +Documentation.addTranslations({"locale": "ca", "plural_expr": "(n != 1)", "messages": {"Search Results": "Resultats de la Cerca", "Preparing search...": "Preparant la cerca...", "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories.": "La teva cerca no ha donat resultats. Assegura't que totes les paraules estan ben escrites i que has seleccionat prou categories.", "Search finished, found %s page(s) matching the search query.": "Cerca finalitzada, s'han trobat %s p\u00e0gin(a/es) de resultats.", ", in ": ", a ", "Permalink to this headline": "Link permanent a aquest t\u00edtol", "Searching": "Cercant", "Permalink to this definition": "Link permanent a aquesta definici\u00f3", "module, in ": "m\u00f2dule, a ", "Hide Search Matches": "Oculta Resultats de Cerca"}}); \ No newline at end of file diff --git a/sphinx/locale/ca/LC_MESSAGES/sphinx.mo b/sphinx/locale/ca/LC_MESSAGES/sphinx.mo index 6e44415fba8f1e96ea3524ad61fe3c2ef17f83ac..d9cc44ce1acf8723ea39958ea88b8ca337a8fdf3 100644 GIT binary patch delta 2163 zcmYk+ZAhL~7{Kw9SvpNyrB80)9}>BL_c`}{u5+F1ocsA? z?Xi~8f^_)b-(A@uTtmAOufi1Gf@^RQJ|6AE>uB%70^En!;y60~eayoXn2V?5^KbA5 z+B2BIzq3O+Eaql0(G6IN4yZx$A#5b{cRIfwO=OgKnKGronkFQO~>6|?X%IxvImR$>vF$b;xgdeDGrdl=0gqChETKfHHfU($qGd`b058tu){1g%}e2tCx z6I${T4w8vBpl7O$iwo$Sm;K+ujT7{sD<41y?7=H>6s^Fk=)l*}#17+&_#T#FEracU z6iuuPi*YydAI7*i&%0=)KExvC59hdX!dY~pKhZtCj0OzXFMNjCXk{wUK-K8;W^`OT zTEPzVy@%0+w?uo8T*FgnWri{Bi{sq*`AndROyMYp@-!N76U%XeZuIw{7cKRE^!;(9 z+Tk5!Ncb4ZJ$x1We?%vqi|v2W_wozbe-A?`N7vI^%f&z)Xn;r1mG;Hqe$7j!Z~{ayLzbXck%(2~wzE6$+_Rq-q2g!iBo z>qP#;W-hfjh?aB`m*FY2GCyMnUP8w;@~dV1R`mUjG&e2WbfbHI1pmOJ=*r$8J4-!< zbcM6%d%qz6VUCMilto#bumGKC8IpUr9X)J~Xhk<-1#Ux%6w+hy!BKQirqI1Qhkn-= z(TRUUzyB=KwseJPdnuYwIr{$H=$5TUx1bHJ$P?&7_M+p)Fy;3@!HtZJFcLaUqw)U10_G2y)Y%d)iI$-OYS4V^IriPpcg$e184%z;IsHVde|~qw$E=v6RAWO){4gK#(d@vySQ;L_niYdIs;n?_+-kvsv;*Xkvxv#Kq{Im&N}2*uN(F0G9c}dTx@~g--YaTB;Fr zPmh!wzmQXro42a6dUdj@E>%-sv0}w5iNwa`>v{)wZ0)UV-@c=7XDZqM;m{r~^~ delta 2818 zcmYk+X>3(R7=Ym^yOgpOXwhO@5iFG21==E8kws_=7Ajj6s6s7*A|PTB#0!6LLn2~S z{#X+=Y79t6;z*XTgf`le=*gElx8>u@sm zz^9Sqh3D`ZY{g1Ej1H(XK_#SxZ~;xpMe;=3VF1&2#<|!9$Hw~#?8bc!+F>2qPy;%^ z#^@&W`=>A;ThS%>7)eI>KHevOj1M}{k^X}&MGr3CXQLesM+a7bW~c%kKo$C4HRj*~ zG&38}j&`9-x(}z}Yv{nvAahQHf2j=TLHfYd2gPXWCZVaHjy6ym?-$448_~_z9Dm=6 zCi5|zgwLZXKZXwUcVuqi0y=@-jG`C)hfFGVkb{gR6rdkWz^*t0&A@E*!@20d7U7e) z97p2W_`E-(a$wn9hG7xfZY|o+GBi`GF`NFwPAYcTigxrSx~2!vj^06!;Sn@5KcNl% z8h`%-{q7=~@=NG@eb^8VI5V1qW?%%GnF>t!Vm%d)PXjuTjo8el+=e#z3p>*e&Y=T2 zkEVJkXVdo!(9KnbWH(GhXS^^zUxjwu67L^J-+M8O{FAlt77yqvd>CIijy7-#o$254 zzAJ~`%{3UAWGF#XJQF=VE7A70pnGaJj>k64#oururZZb7G;1jNcjj}rn5t$p9Q8|aMc_~jj zu@aqGAv?uXS0jTDOVIZ=Abo}wbcyz$9lnHi^ah%dw~@bal#3bt84K|*G~$Pado6+;X2TfgTynhuPP#gOG`{*HMF-Z3cKkZJvAiLQj@HewXvAS`zi3yCsf65)Po zB*;P{JW9ng+l9EYutAf9;$S3aQ67JM0Jjq3;{C1IK+Gk~&Yi>xqK;TaSauL*?8@R& z+J9()8N_&EQR;Ps1{T)k|*VDVR&F zBrMkx_Ma^7OxN79fv6`ur}^l)f&ufp_< bilUOrywY(K%1R1L%J#0vY&di(vpxNPxe4NJ diff --git a/sphinx/locale/ca/LC_MESSAGES/sphinx.po b/sphinx/locale/ca/LC_MESSAGES/sphinx.po index 475582ac0..440b0594e 100644 --- a/sphinx/locale/ca/LC_MESSAGES/sphinx.po +++ b/sphinx/locale/ca/LC_MESSAGES/sphinx.po @@ -3,7 +3,6 @@ # This file is distributed under the same license as the Sphinx project. # FIRST AUTHOR , 2009. # -#, fuzzy msgid "" msgstr "" "Project-Id-Version: Sphinx 1.0\n" From b8c7b107e390234e281f12548144d148c155a1c2 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 29 Jul 2009 16:39:21 +0000 Subject: [PATCH 38/85] Fix uncompilable locales. --- sphinx/locale/de/LC_MESSAGES/sphinx.mo | Bin 8240 -> 8240 bytes sphinx/locale/de/LC_MESSAGES/sphinx.po | 2 +- sphinx/locale/fi/LC_MESSAGES/sphinx.js | 2 +- sphinx/locale/fi/LC_MESSAGES/sphinx.mo | Bin 7537 -> 7563 bytes sphinx/locale/fi/LC_MESSAGES/sphinx.po | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sphinx/locale/de/LC_MESSAGES/sphinx.mo b/sphinx/locale/de/LC_MESSAGES/sphinx.mo index a89052590354a1a99ccf029eb0a2dffa784e0fa8..681091ce897291de8b324189deeaa334eeab32aa 100644 GIT binary patch delta 33 ocmdnsu)$$NkT9>gu92mJp_!Gjg|>mg=6K-{X2z7wTf}NO0j3QJYybcN delta 33 ocmdnsu)$$NkT9>AuA!NNp{13PnYMw^=6K-{X2#;pTf}NO0j3uTcmMzZ diff --git a/sphinx/locale/de/LC_MESSAGES/sphinx.po b/sphinx/locale/de/LC_MESSAGES/sphinx.po index 692419d72..0c1b61d83 100644 --- a/sphinx/locale/de/LC_MESSAGES/sphinx.po +++ b/sphinx/locale/de/LC_MESSAGES/sphinx.po @@ -248,7 +248,7 @@ msgstr "Zu tun" #: sphinx/ext/todo.py:99 #, python-format msgid "(The original entry is located in %s, line %d and can be found " -msgstr "(Der ursprüngliche Eintrag steht in %s, Zeile %s, siehe " +msgstr "(Der ursprüngliche Eintrag steht in %s, Zeile %d, siehe " #: sphinx/ext/todo.py:105 msgid "here" diff --git a/sphinx/locale/fi/LC_MESSAGES/sphinx.js b/sphinx/locale/fi/LC_MESSAGES/sphinx.js index f654e7e11..82f02c292 100644 --- a/sphinx/locale/fi/LC_MESSAGES/sphinx.js +++ b/sphinx/locale/fi/LC_MESSAGES/sphinx.js @@ -1 +1 @@ -Documentation.addTranslations({"locale": "fi", "plural_expr": "(n != 1)", "messages": {"module, in ": "", "Preparing search...": "Valmistellaan etsint\u00e4\u00e4...", "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories.": "Ei l\u00f6ytynyt yht\u00e4\u00e4n. Tarkista hakuehdot, sanahaku, ei sen osia", "Search finished, found %s page(s) matching the search query.": "Etsint\u00e4 tehty, l\u00f6ydetty %s sivu(a).", ", in ": "", "Permalink to this headline": "", "Searching": "Etsit\u00e4\u00e4n", "Permalink to this definition": "", "Hide Search Matches": "Piilota l\u00f6ydetyt", "Search Results": "Etsinn\u00e4n tulos"}}); \ No newline at end of file +Documentation.addTranslations({"locale": "fi", "plural_expr": "(n != 1)", "messages": {"Search Results": "Etsinn\u00e4n tulos", "Preparing search...": "Valmistellaan etsint\u00e4\u00e4...", "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories.": "Ei l\u00f6ytynyt yht\u00e4\u00e4n. Tarkista hakuehdot, sanahaku, ei sen osia", "Search finished, found %s page(s) matching the search query.": "Etsint\u00e4 tehty, l\u00f6ydetty %s sivu(a).", ", in ": "", "Permalink to this headline": "", "Searching": "Etsit\u00e4\u00e4n", "Permalink to this definition": "", "module, in ": "", "Hide Search Matches": "Piilota l\u00f6ydetyt"}}); \ No newline at end of file diff --git a/sphinx/locale/fi/LC_MESSAGES/sphinx.mo b/sphinx/locale/fi/LC_MESSAGES/sphinx.mo index 44d19f8c30b6bc71ddf64217c1c1386dbe8fabd5..54b25dc8496e42c48c06f5b8aec5a2e163d7b662 100644 GIT binary patch delta 1358 zcmaLWOGs2v9LMparQ;(R#~JfoV^gjg$sRVTq|$@1MW6)fBCs06AT=KYJ@C>(2zxBL zfvbc9OM*}v5~D(dh;q@xs72ta$c+XDZA78(Z#?VZ&i$No?!Eu>|DSVax$0*1c+Cc9 z&=}K}ZA=+1qX+YS#sn~kCAbf{OcS?EjAAif!hF1g>G&8o;smDPBr4u3+=g#36&Fx( ze*27*cwvPBeVCkM%qH}pUJPIw7Fo9=IhrsQ;X%}UEm(l9s6?*fKoUPxyiBT5Liwmf zwxHtH2b}dr0|Vq>nvlyxxq0ylD!?FS;SehG5&L`s{q$#WGk!%SxQL3khWg%{yFOlw zdGy1m?~XV$3TZT>ma^AAxQ(jl0aoHm+=_p&7ITop~ zOvVvZ0;8yv7{d(aH?L`^;&+JwW9Cp5%%fIh1r<1#P6SI)hpz<{IEJe5GOCdKsEHn- zPXANX_s?yA2DMf1QRmNmprHhQpekF!9k_;?D8x+_)uL9S0TuAH^#W?eVyKF5+3{i2 z);&QT%5hYJGuByj>KOP!Llp!lTNy`?gKO$gTW}H;xCeEb2awC$<;L!qF-)u=>iap{ z|8Dy~QGdHd+xHSw1>}{O=0rvz2Kc+0ov0-(M=fCmD&vTKUT>ctLUJ}oQA>FS*(Gxx zmB@8;*Zh(A;j~bio9rub?`1C)p6%@DZSUxdo(r~hcU}y3bw^|EoqavQuGVZ zuq22y$cpWq6dNA3R} zFfa9k1Pj`j#2j37Kg=vJ^D)nLhL9XBjK#PE6|fc8;9*oEC-F*}SuZNy-@>JYvZ+Ri z1W<8ngWl4hkp<4QX5?dSe5o^Cr~nsmCHA2*A9T-0kk~ebMfe_-;8#?<1=RlZRZHu6 z7-Akq?TdH})-q^8wX)MaxQMFgCf4B?mf{cGgBhHr7DrK6a{$%B7^XUaP0V{yl|Dli z@Y>C1kx=G+W>Cf8Cn}LJ=_x=19>gf>paHi&ggWp(*5QnMzKE5~RYeJGM-|Y5<=BC` zsy|+waAnN67MFozbDm;NIVO{9TYU|=6&pYmFoxRq)XiVH z`3Edx{gay~Q3d=du_LLB{6y#PY6Ym422m{xp)y|Yo^Nu`w;;LNR#YqZBe!I&s6;v^ z{k~(q+^*9zBWGgWXJ;PQhby|{3;Ct-%lV1;cuD!>!$5nQzh+x?ZMZ5@zcXG~m`tz9 Yis$$ulY4{xnYr!%ZH<2{o@UpVe-tZ=yZ`_I diff --git a/sphinx/locale/fi/LC_MESSAGES/sphinx.po b/sphinx/locale/fi/LC_MESSAGES/sphinx.po index 48691deb5..7be2e618b 100644 --- a/sphinx/locale/fi/LC_MESSAGES/sphinx.po +++ b/sphinx/locale/fi/LC_MESSAGES/sphinx.po @@ -590,7 +590,7 @@ msgstr "Hakemisto yhtenä luettelona" #: sphinx/writers/text.py:166 #, fuzzy, python-format msgid "Platform: %s" -msgstr "Ympäristö" +msgstr "Ympäristö: %s" #: sphinx/writers/text.py:428 msgid "[image]" From 678faca6cec7f8db3d2ffddb12d20d5426d719d8 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 4 Aug 2009 23:37:48 +0200 Subject: [PATCH 39/85] Add The Wine Cellar Book. --- sphinx/__init__.py | 3 +-- sphinx/builders/__init__.py | 1 - sphinx/ext/inheritance_diagram.py | 1 + sphinx/roles.py | 1 + 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sphinx/__init__.py b/sphinx/__init__.py index 41017014a..9ba8282aa 100644 --- a/sphinx/__init__.py +++ b/sphinx/__init__.py @@ -12,8 +12,7 @@ import sys from os import path -__revision__ = '$Revision$' -__version__ = '1.0' +__version__ = '1.0pre' __released__ = '1.0 (hg)' package_dir = path.abspath(path.dirname(__file__)) diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py index ca3c11473..08628c7b6 100644 --- a/sphinx/builders/__init__.py +++ b/sphinx/builders/__init__.py @@ -55,7 +55,6 @@ class Builder(object): self.init() # helper methods - def init(self): """ Load necessary templates and perform initialization. The default diff --git a/sphinx/ext/inheritance_diagram.py b/sphinx/ext/inheritance_diagram.py index 9c037c998..483c774a4 100644 --- a/sphinx/ext/inheritance_diagram.py +++ b/sphinx/ext/inheritance_diagram.py @@ -49,6 +49,7 @@ except ImportError: from docutils import nodes from docutils.parsers.rst import directives +# XXX needs to be adapted from sphinx.roles import xfileref_role from sphinx.ext.graphviz import render_dot_html, render_dot_latex from sphinx.util.compat import Directive diff --git a/sphinx/roles.py b/sphinx/roles.py index 8833c275d..61d809229 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -54,6 +54,7 @@ def indexmarkup_role(typ, rawtext, etext, lineno, inliner, indexnode['entries'] = [('single', text, targetid, text), ('single', _('environment variable; %s') % text, targetid, text)] + # XXX needs to be adapted xref_nodes = xfileref_role(typ, rawtext, etext, lineno, inliner, options, content)[0] return [indexnode, targetnode] + xref_nodes, [] From 94f0c051c567d7d258344e05adaef1969c2409fb Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 9 Aug 2009 20:27:36 +0200 Subject: [PATCH 40/85] Fix remaining uses of xfileref_role. --- sphinx/directives/desc.py | 4 +- sphinx/environment.py | 8 ++ sphinx/ext/autosummary/__init__.py | 5 +- sphinx/ext/inheritance_diagram.py | 7 +- sphinx/roles.py | 190 ++++++++++++++--------------- tests/root/markup.txt | 9 ++ 6 files changed, 120 insertions(+), 103 deletions(-) diff --git a/sphinx/directives/desc.py b/sphinx/directives/desc.py index 06fc4c983..6deb1f03f 100644 --- a/sphinx/directives/desc.py +++ b/sphinx/directives/desc.py @@ -300,7 +300,7 @@ class GenericDesc(DescDirective): else: signode.clear() signode += addnodes.desc_name(sig, sig) - # normalize whitespace like xfileref_role does + # normalize whitespace like XRefRole does name = ws_re.sub('', sig) return name @@ -335,7 +335,7 @@ class Target(Directive): def run(self): env = self.state.document.settings.env rolename, indextemplate, foo = additional_xref_types[self.name] - # normalize whitespace in fullname like xfileref_role does + # normalize whitespace in fullname like XRefRole does fullname = ws_re.sub('', self.arguments[0].strip()) targetname = '%s-%s' % (rolename, fullname) node = nodes.target('', '', ids=[targetname]) diff --git a/sphinx/environment.py b/sphinx/environment.py index 068aa2a9f..5814af1bc 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -1011,6 +1011,14 @@ class BuildEnvironment: self.dependencies.setdefault(self.docname, set()).add(filename) # ------- + def get_domain(self, domainname): + """Return the domain instance with the specified name. + Raises a nicer KeyError if the domain is not registered.""" + try: + return self.domains[domainname] + except KeyError: + raise KeyError('Domain %r is not registered' % domainname) + # --------- RESOLVING REFERENCES AND TOCTREES ------------------------------ def get_doctree(self, docname): diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index 3c9ff466b..f8d8920ae 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -443,8 +443,9 @@ def autolink_role(typ, rawtext, etext, lineno, inliner, Expands to ':obj:`text`' if `text` is an object that can be imported; otherwise expands to '*text*'. """ - r = roles.xfileref_role('obj', rawtext, etext, lineno, inliner, - options, content) + env = inliner.document.settings.env + r = env.get_domain('py').roles['obj']( + 'py:obj', rawtext, etext, lineno, inliner, options, content) pnode = r[0][0] prefixes = [None] diff --git a/sphinx/ext/inheritance_diagram.py b/sphinx/ext/inheritance_diagram.py index 483c774a4..18cd025b5 100644 --- a/sphinx/ext/inheritance_diagram.py +++ b/sphinx/ext/inheritance_diagram.py @@ -49,8 +49,6 @@ except ImportError: from docutils import nodes from docutils.parsers.rst import directives -# XXX needs to be adapted -from sphinx.roles import xfileref_role from sphinx.ext.graphviz import render_dot_html, render_dot_latex from sphinx.util.compat import Directive @@ -283,6 +281,7 @@ class InheritanceDiagram(Directive): node.document = self.state.document env = self.state.document.settings.env class_names = self.arguments[0].split() + class_role = env.get_domain('py').roles['class'] # Create a graph starting with the list of classes try: @@ -296,8 +295,8 @@ class InheritanceDiagram(Directive): # references to real URLs later. These nodes will eventually be # removed from the doctree after we're done with them. for name in graph.get_all_class_names(): - refnodes, x = xfileref_role( - 'class', ':class:`%s`' % name, name, 0, self.state) + refnodes, x = class_role( + 'py:class', ':class:`%s`' % name, name, 0, self.state) node.extend(refnodes) # Store the graph object so we can use it to generate the # dot file later diff --git a/sphinx/roles.py b/sphinx/roles.py index 61d809229..d0e54c9e2 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -37,101 +37,6 @@ for rolename, nodeclass in generic_docroles.iteritems(): roles.register_local_role(rolename, role) -def indexmarkup_role(typ, rawtext, etext, lineno, inliner, - options={}, content=[]): - env = inliner.document.settings.env - if not typ: - typ = env.config.default_role - else: - typ = typ.lower() - text = utils.unescape(etext) - targetid = 'index-%s' % env.index_num - env.index_num += 1 - indexnode = addnodes.index() - targetnode = nodes.target('', '', ids=[targetid]) - inliner.document.note_explicit_target(targetnode) - if typ == 'envvar': - indexnode['entries'] = [('single', text, targetid, text), - ('single', _('environment variable; %s') % text, - targetid, text)] - # XXX needs to be adapted - xref_nodes = xfileref_role(typ, rawtext, etext, lineno, inliner, - options, content)[0] - return [indexnode, targetnode] + xref_nodes, [] - elif typ == 'pep': - indexnode['entries'] = [ - ('single', _('Python Enhancement Proposals!PEP %s') % text, - targetid, 'PEP %s' % text)] - try: - pepnum = int(text) - except ValueError: - msg = inliner.reporter.error('invalid PEP number %s' % text, - line=lineno) - prb = inliner.problematic(rawtext, rawtext, msg) - 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 += sn - return [indexnode, targetnode, rn], [] - elif typ == 'rfc': - indexnode['entries'] = [('single', 'RFC; RFC %s' % text, - targetid, 'RFC %s' % text)] - try: - rfcnum = int(text) - except ValueError: - msg = inliner.reporter.error('invalid RFC number %s' % text, - line=lineno) - prb = inliner.problematic(rawtext, rawtext, msg) - 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 += sn - return [indexnode, targetnode, rn], [] - -roles.register_local_role('envvar', indexmarkup_role) -roles.register_local_role('pep', indexmarkup_role) -roles.register_local_role('rfc', indexmarkup_role) - - -def menusel_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): - return [nodes.emphasis( - rawtext, - utils.unescape(text).replace('-->', u'\N{TRIANGULAR BULLET}'))], [] - return role - - -_litvar_re = re.compile('{([^}]+)}') - -def emph_literal_role(typ, rawtext, text, lineno, inliner, - options={}, content=[]): - text = utils.unescape(text) - pos = 0 - retnode = nodes.literal(role=typ.lower()) - for m in _litvar_re.finditer(text): - if m.start() > pos: - txt = text[pos:m.start()] - retnode += nodes.Text(txt, txt) - retnode += nodes.emphasis(m.group(1), m.group(1)) - pos = m.end() - if pos < len(text): - retnode += nodes.Text(text[pos:], text[pos:]) - return [retnode], [] - - -_abbr_re = re.compile('\((.*)\)$') - -def abbr_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): - text = utils.unescape(text) - m = _abbr_re.search(text) - if m is None: - return [addnodes.abbreviation(text, text)], [] - abbr = text[:m.start()].strip() - expl = m.group(1) - return [addnodes.abbreviation(abbr, abbr, explanation=expl)], [] - - # -- generic cross-reference roles --------------------------------------------- class XRefRole(object): @@ -211,6 +116,98 @@ class OptionXRefRole(XRefRole): return title, target +_EnvvarXrefRole = XRefRole() + +def indexmarkup_role(typ, rawtext, etext, lineno, inliner, + options={}, content=[]): + env = inliner.document.settings.env + if not typ: + typ = env.config.default_role + else: + typ = typ.lower() + text = utils.unescape(etext) + targetid = 'index-%s' % env.index_num + env.index_num += 1 + indexnode = addnodes.index() + targetnode = nodes.target('', '', ids=[targetid]) + inliner.document.note_explicit_target(targetnode) + if typ == 'envvar': + indexnode['entries'] = [('single', text, targetid, text), + ('single', _('environment variable; %s') % text, + targetid, text)] + xref_nodes = _EnvvarXrefRole(typ, rawtext, etext, lineno, inliner, + options, content)[0] + return [indexnode, targetnode] + xref_nodes, [] + elif typ == 'pep': + indexnode['entries'] = [ + ('single', _('Python Enhancement Proposals!PEP %s') % text, + targetid, 'PEP %s' % text)] + try: + pepnum = int(text) + except ValueError: + msg = inliner.reporter.error('invalid PEP number %s' % text, + line=lineno) + prb = inliner.problematic(rawtext, rawtext, msg) + 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 += sn + return [indexnode, targetnode, rn], [] + elif typ == 'rfc': + indexnode['entries'] = [('single', 'RFC; RFC %s' % text, + targetid, 'RFC %s' % text)] + try: + rfcnum = int(text) + except ValueError: + msg = inliner.reporter.error('invalid RFC number %s' % text, + line=lineno) + prb = inliner.problematic(rawtext, rawtext, msg) + 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 += 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}'))], [] + return role + + +_litvar_re = re.compile('{([^}]+)}') + +def emph_literal_role(typ, rawtext, text, lineno, inliner, + options={}, content=[]): + text = utils.unescape(text) + pos = 0 + retnode = nodes.literal(role=typ.lower()) + for m in _litvar_re.finditer(text): + if m.start() > pos: + txt = text[pos:m.start()] + retnode += nodes.Text(txt, txt) + retnode += nodes.emphasis(m.group(1), m.group(1)) + pos = m.end() + if pos < len(text): + retnode += nodes.Text(text[pos:], text[pos:]) + return [retnode], [] + + +_abbr_re = re.compile('\((.*)\)$') + +def abbr_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): + text = utils.unescape(text) + m = _abbr_re.search(text) + if m is None: + return [addnodes.abbreviation(text, text)], [] + abbr = text[:m.start()].strip() + expl = m.group(1) + return [addnodes.abbreviation(abbr, abbr, explanation=expl)], [] + + specific_docroles = { 'keyword': XRefRole(), 'ref': XRefRole(lowercase=True, innernodeclass=nodes.emphasis), @@ -220,6 +217,9 @@ specific_docroles = { 'doc': XRefRole(), 'download': XRefRole(nodeclass=addnodes.download_reference), + 'envvar': indexmarkup_role, + 'pep': indexmarkup_role, + 'rfc': indexmarkup_role, 'menuselection': menusel_role, 'file': emph_literal_role, 'samp': emph_literal_role, diff --git a/tests/root/markup.txt b/tests/root/markup.txt index 1e5b7ebad..409112633 100644 --- a/tests/root/markup.txt +++ b/tests/root/markup.txt @@ -21,6 +21,15 @@ A |subst|. some code +Inline +------ + +Inline markup: + +* :pep:`8` +* :envvar:`HOME` +* :rfc:`2014` + Admonitions ----------- From 4cb2faeb9ccfcaf2860e3c75fcb2e7a144b77325 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 9 Aug 2009 20:29:02 +0200 Subject: [PATCH 41/85] Use Domain.role(). --- sphinx/ext/autosummary/__init__.py | 4 ++-- sphinx/ext/inheritance_diagram.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index f8d8920ae..09212eed0 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -444,8 +444,8 @@ def autolink_role(typ, rawtext, etext, lineno, inliner, otherwise expands to '*text*'. """ env = inliner.document.settings.env - r = env.get_domain('py').roles['obj']( - 'py:obj', rawtext, etext, lineno, inliner, options, content) + r = env.get_domain('py').role('obj')( + 'obj', rawtext, etext, lineno, inliner, options, content) pnode = r[0][0] prefixes = [None] diff --git a/sphinx/ext/inheritance_diagram.py b/sphinx/ext/inheritance_diagram.py index 18cd025b5..6536f94c0 100644 --- a/sphinx/ext/inheritance_diagram.py +++ b/sphinx/ext/inheritance_diagram.py @@ -281,7 +281,7 @@ class InheritanceDiagram(Directive): node.document = self.state.document env = self.state.document.settings.env class_names = self.arguments[0].split() - class_role = env.get_domain('py').roles['class'] + class_role = env.get_domain('py').role('class') # Create a graph starting with the list of classes try: @@ -296,7 +296,7 @@ class InheritanceDiagram(Directive): # removed from the doctree after we're done with them. for name in graph.get_all_class_names(): refnodes, x = class_role( - 'py:class', ':class:`%s`' % name, name, 0, self.state) + 'class', ':class:`%s`' % name, name, 0, self.state) node.extend(refnodes) # Store the graph object so we can use it to generate the # dot file later From af87d555ee48fd4f9f9082628065dfaac11300d2 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 9 Aug 2009 20:32:18 +0200 Subject: [PATCH 42/85] Add b/w compatibility xfileref_role, with a DeprecationWarning. --- sphinx/roles.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sphinx/roles.py b/sphinx/roles.py index d0e54c9e2..d896641ad 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -10,6 +10,7 @@ """ import re +import warnings from docutils import nodes, utils from docutils.parsers.rst import roles @@ -40,6 +41,8 @@ for rolename, nodeclass in generic_docroles.iteritems(): # -- generic cross-reference roles --------------------------------------------- class XRefRole(object): + """XXX add docstring""" + nodeclass = addnodes.pending_xref innernodeclass = nodes.literal @@ -230,3 +233,8 @@ for rolename, func in specific_docroles.iteritems(): roles.register_local_role(rolename, func) +# compatibility alias +def xfileref_role(*args, **kwds): + warnings.warn('xfileref_role is deprecated, use XRefRole', + DeprecationWarning, stacklevel=2) + return XRefRole()(*args, **kwds) From f64d01bea2ab885a734e94db7b8b67eae87edd19 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 9 Aug 2009 22:05:03 +0200 Subject: [PATCH 43/85] Fix resolution of references without domain. --- sphinx/environment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/environment.py b/sphinx/environment.py index 5814af1bc..f52c18b5f 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -1215,7 +1215,7 @@ class BuildEnvironment: target = node['reftarget'] try: - if node.has_key('refdomain'): + if node.has_key('refdomain') and node['refdomain']: # let the domain try to resolve the reference try: domain = self.domains[node['refdomain']] From 28263b96deadd0240d90f6c8f80042921995ec38 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 9 Aug 2009 22:19:37 +0200 Subject: [PATCH 44/85] More tests for inline markup. --- tests/root/desc.txt | 6 ++++++ tests/root/markup.txt | 2 +- tests/test_build.py | 6 ++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/root/desc.txt b/tests/root/desc.txt index bdceafc0a..3b225c78a 100644 --- a/tests/root/desc.txt +++ b/tests/root/desc.txt @@ -54,6 +54,12 @@ C items .. c:var:: sphinx_global +Others +====== + +.. envvar:: HOME + + Testing references ================== diff --git a/tests/root/markup.txt b/tests/root/markup.txt index 409112633..86e039acd 100644 --- a/tests/root/markup.txt +++ b/tests/root/markup.txt @@ -27,8 +27,8 @@ Inline Inline markup: * :pep:`8` +* :rfc:`1` * :envvar:`HOME` -* :rfc:`2014` Admonitions ----------- diff --git a/tests/test_build.py b/tests/test_build.py index 6668e2453..754805e98 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -95,6 +95,9 @@ HTML_XPATH = { ".//p": 'Always present', ".//title": 'set by title directive', ".//span[@class='pre']": 'CFunction()', + ".//a[@href='desc.html#envvar-HOME']/tt/span[@class='pre']": 'HOME', + ".//a[@href='http://www.python.org/dev/peps/pep-0008']/strong": 'PEP 8', + ".//a[@href='http://tools.ietf.org/html/rfc1.html']/strong": 'RFC 1', }, 'desc.html': { ".//dt[@id='mod.Cls.meth1']": '', @@ -114,6 +117,9 @@ HTML_XPATH = { ".//title": 'Sphinx ', ".//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='search.html']/em": 'Search Page', }, 'bom.html': { ".//title": " File with UTF-8 BOM", From 2ac807a0668bb90aa653da86bd51f86e9824b8a3 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 9 Aug 2009 22:44:41 +0200 Subject: [PATCH 45/85] Add more tests for markup. --- sphinx/directives/other.py | 2 +- sphinx/roles.py | 2 +- tests/root/desc.txt | 6 +++ tests/root/markup.txt | 94 +++++++++++++++++++++++--------------- tests/test_build.py | 45 +++++++++++++++--- 5 files changed, 104 insertions(+), 45 deletions(-) diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py index 977b16919..ed42e3d23 100644 --- a/sphinx/directives/other.py +++ b/sphinx/directives/other.py @@ -260,7 +260,7 @@ class SeeAlso(Directive): return ret -token_re = re.compile('`([a-z_]+)`') +token_re = re.compile('`([a-z_][a-z0-9_]*)`') def token_xrefs(text, env): retnodes = [] diff --git a/sphinx/roles.py b/sphinx/roles.py index d896641ad..db231463f 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -29,7 +29,7 @@ generic_docroles = { 'manpage' : addnodes.literal_emphasis, 'mimetype' : addnodes.literal_emphasis, 'newsgroup' : addnodes.literal_emphasis, - 'program' : nodes.strong, + 'program' : nodes.strong, # XXX should be an x-ref 'regexp' : nodes.literal, } diff --git a/tests/root/desc.txt b/tests/root/desc.txt index 3b225c78a..43519773e 100644 --- a/tests/root/desc.txt +++ b/tests/root/desc.txt @@ -54,6 +54,12 @@ C items .. c:var:: sphinx_global +References +========== + +:c:func:`CFunction`. :c:func:`!malloc`. + + Others ====== diff --git a/tests/root/markup.txt b/tests/root/markup.txt index 86e039acd..3d328e8d3 100644 --- a/tests/root/markup.txt +++ b/tests/root/markup.txt @@ -5,6 +5,9 @@ Testing various markup ====================== +Meta markup +----------- + .. sectionauthor:: Georg Brandl .. contents:: TOC @@ -13,7 +16,11 @@ Testing various markup :author: Me :keywords: docs, sphinx -A |subst|. + +Generic reST +------------ + +A |subst| (the definition is in rst_epilog). .. _label: @@ -21,31 +28,14 @@ A |subst|. some code -Inline ------- +Option list: -Inline markup: - -* :pep:`8` -* :rfc:`1` -* :envvar:`HOME` - -Admonitions ------------ - -.. note:: Note - Note text. - -.. warning:: Warning - - Warning text. - -.. tip:: - Tip text. +-h help +--help also help Body directives ---------------- +^^^^^^^^^^^^^^^ .. topic:: Title @@ -78,7 +68,51 @@ Body directives b - + +Admonitions +^^^^^^^^^^^ + +.. admonition:: My Admonition + + Admonition text. + +.. note:: + Note text. + +.. warning:: + + Warning text. + +.. tip:: + Tip text. + + +Inline markup +------------- + +*Generic inline markup* + +* :command:`command` +* :dfn:`dfn` +* :guilabel:`guilabel` +* :kbd:`kbd` +* :mailheader:`mailheader` +* :makevar:`makevar` +* :manpage:`manpage` +* :mimetype:`mimetype` +* :newsgroup:`newsgroup` +* :program:`program` +* :regexp:`regexp` + +*Linking inline markup* + +* :pep:`8` +* :rfc:`1` +* :envvar:`HOME` + +Test :abbr:`abbr (abbreviation)` and another :abbr:`abbr (abbreviation)`. + + Tables ------ @@ -133,11 +167,6 @@ This is a side note. This tests :CLASS:`role names in uppercase`. -Option list: - --h help ---help also help - .. centered:: LICENSE AGREEMENT .. acks:: @@ -155,7 +184,7 @@ Option list: Particle with half-integer spin. .. productionlist:: - try_stmt: try1_stmt | try2_stmt + try_stmt: `try1_stmt` | `try2_stmt` try1_stmt: "try" ":" `suite` : ("except" [`expression` ["," `target`]] ":" `suite`)+ : ["else" ":" `suite`] @@ -163,7 +192,6 @@ Option list: try2_stmt: "try" ":" `suite` : "finally" ":" `suite` -Test :abbr:`abbr (abbreviation)` and another :abbr:`abbr (abbreviation)`. Index markup ------------ @@ -188,12 +216,6 @@ Invalid index markup... Testing öäü... -Object markup -------------- - -:c:func:`CFunction`. :c:func:`!malloc`. - - Only directive -------------- diff --git a/tests/test_build.py b/tests/test_build.py index 754805e98..1e36b12d3 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -84,20 +84,50 @@ HTML_XPATH = { ".//dd": r'Return spam\.', }, 'markup.html': { + ".//title": 'set by title directive', + # created by the meta directive ".//meta[@name='author'][@content='Me']": '', ".//meta[@name='keywords'][@content='docs, sphinx']": '', - ".//a[@href='contents.html#ref1']": '', + # a label created by ``.. _label:`` ".//div[@id='label']": '', + # code with standard code blocks + ".//pre": '^some code$', + # an option list ".//span[@class='option']": '--help', + # admonitions + ".//p[@class='first admonition-title']": 'My Admonition', + ".//p[@class='last']": 'Note text.', + ".//p[@class='last']": 'Warning text.', + # inline markup + ".//li/strong": '^command$', + ".//li/strong": '^program$', + ".//li/em": '^dfn$', + ".//li/tt/span[@class='pre']": '^kbd$', + ".//a[@href='desc.html#envvar-HOME']/tt/span[@class='pre']": 'HOME', + ".//a[@href='http://www.python.org/dev/peps/pep-0008']/strong": 'PEP 8', + ".//a[@href='http://tools.ietf.org/html/rfc1.html']/strong": 'RFC 1', + # abbreviations + ".//abbr[@title='abbreviation']": '^abbr$', + # version stuff + ".//span[@class='versionmodified']": 'New in version 0.6', + # footnote reference + ".//a[@class='footnote-reference']": r'\[1\]', + # created by reference lookup + ".//a[@href='contents.html#ref1']": '', + # a ``hlist`` directive + ".//table[@class='hlist']/tr/td/ul/li": '^This$', + # a ``centered`` directive + ".//p[@class='centered']/strong": 'LICENSE', + # a glossary + ".//dl/dt[@id='term-boson']": 'boson', + # a production list + ".//pre/strong": 'try_stmt', + ".//pre/a[@href='grammar-token-try1_stmt']/tt/span": 'try1_stmt', + # tests for ``only`` directive ".//p": 'A global substitution.', ".//p": 'In HTML.', ".//p": 'In both.', ".//p": 'Always present', - ".//title": 'set by title directive', - ".//span[@class='pre']": 'CFunction()', - ".//a[@href='desc.html#envvar-HOME']/tt/span[@class='pre']": 'HOME', - ".//a[@href='http://www.python.org/dev/peps/pep-0008']/strong": 'PEP 8', - ".//a[@href='http://tools.ietf.org/html/rfc1.html']/strong": 'RFC 1', }, 'desc.html': { ".//dt[@id='mod.Cls.meth1']": '', @@ -106,6 +136,7 @@ HTML_XPATH = { ".//dl[@class='userdesc']": '', ".//dt[@id='userdescrole-myobj']": '', ".//a[@href='#userdescrole-myobj']": '', + ".//span[@class='pre']": 'CFunction()', }, 'contents.html': { ".//meta[@name='hc'][@content='hcval']": '', @@ -113,7 +144,7 @@ HTML_XPATH = { #".//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', + ".//li[@class='toctree-l2']/a": 'Inline markup', ".//title": 'Sphinx ', ".//div[@class='footer']": 'Georg Brandl & Team', ".//a[@href='http://python.org/']": '', From 631ccfa3b574374445d4404fd13de5c9ac3c7edc Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 9 Aug 2009 22:47:56 +0200 Subject: [PATCH 46/85] Fix test. --- tests/test_build.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_build.py b/tests/test_build.py index 1e36b12d3..e4d29319f 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -122,7 +122,7 @@ HTML_XPATH = { ".//dl/dt[@id='term-boson']": 'boson', # a production list ".//pre/strong": 'try_stmt', - ".//pre/a[@href='grammar-token-try1_stmt']/tt/span": 'try1_stmt', + ".//pre/a[@href='#grammar-token-try1_stmt']/tt/span": 'try1_stmt', # tests for ``only`` directive ".//p": 'A global substitution.', ".//p": 'In HTML.', From 8ba78e88e73750c604821e3b780db6b4ca983fdc Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 9 Aug 2009 23:01:48 +0200 Subject: [PATCH 47/85] More tests for inline markup. --- tests/root/desc.txt | 8 ++++++++ tests/root/markup.txt | 16 ++++++++++++++++ tests/test_build.py | 10 +++++++++- 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/tests/root/desc.txt b/tests/root/desc.txt index 43519773e..ee70b0507 100644 --- a/tests/root/desc.txt +++ b/tests/root/desc.txt @@ -65,6 +65,14 @@ Others .. envvar:: HOME +.. program:: python + +.. cmdoption:: -c command + +.. program:: perl + +.. cmdoption:: -c + Testing references ================== diff --git a/tests/root/markup.txt b/tests/root/markup.txt index 3d328e8d3..07f8deb6d 100644 --- a/tests/root/markup.txt +++ b/tests/root/markup.txt @@ -103,16 +103,32 @@ Inline markup * :newsgroup:`newsgroup` * :program:`program` * :regexp:`regexp` +* :menuselection:`File --> Close` +* :file:`a/{varpart}/b` +* :samp:`print {i}` *Linking inline markup* * :pep:`8` * :rfc:`1` * :envvar:`HOME` +* :keyword:`with` +* :token:`try statement ` +* :doc:`subdir/includes` +* ``:download:`` is tested in includes.txt +* :option:`Python -c option ` Test :abbr:`abbr (abbreviation)` and another :abbr:`abbr (abbreviation)`. +.. _with: + +With +---- + +(Empty section.) + + Tables ------ diff --git a/tests/test_build.py b/tests/test_build.py index e4d29319f..f9704fdf8 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -103,9 +103,17 @@ HTML_XPATH = { ".//li/strong": '^program$', ".//li/em": '^dfn$', ".//li/tt/span[@class='pre']": '^kbd$', - ".//a[@href='desc.html#envvar-HOME']/tt/span[@class='pre']": 'HOME', + ".//li/em": u'File \N{TRIANGULAR BULLET} Close', + ".//li/tt/span[@class='pre']": '^a/$', + ".//li/tt/em/span[@class='pre']": '^varpart$', + ".//li/tt/em/span[@class='pre']": '^i$', ".//a[@href='http://www.python.org/dev/peps/pep-0008']/strong": 'PEP 8', ".//a[@href='http://tools.ietf.org/html/rfc1.html']/strong": 'RFC 1', + ".//a[@href='desc.html#envvar-HOME']/tt/span[@class='pre']": 'HOME', + ".//a[@href='#with']/tt/span[@class='pre']": '^with$', + ".//a[@href='#grammar-token-try_stmt']/tt/span": '^statement$', + ".//a[@href='subdir/includes.html']/em": 'Including in subdir', + ".//a[@href='desc.html#cmdoption-python-c']/em": 'Python -c option', # abbreviations ".//abbr[@title='abbreviation']": '^abbr$', # version stuff From 8d0c50c5b4cee02a9f382c9cb5e6cf26b055a010 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 9 Aug 2009 23:12:01 +0200 Subject: [PATCH 48/85] More block markup tests. --- tests/root/conf.py | 1 + tests/root/markup.txt | 12 ++++++++++++ tests/test_build.py | 4 ++++ 3 files changed, 17 insertions(+) diff --git a/tests/root/conf.py b/tests/root/conf.py index fd82be7d7..a82602fff 100644 --- a/tests/root/conf.py +++ b/tests/root/conf.py @@ -25,6 +25,7 @@ today_fmt = '%B %d, %Y' exclude_trees = ['_build'] keep_warnings = True pygments_style = 'sphinx' +show_authors = True rst_epilog = '.. |subst| replace:: global substitution' diff --git a/tests/root/markup.txt b/tests/root/markup.txt index 07f8deb6d..c5022b32f 100644 --- a/tests/root/markup.txt +++ b/tests/root/markup.txt @@ -9,6 +9,7 @@ Meta markup ----------- .. sectionauthor:: Georg Brandl +.. moduleauthor:: Georg Brandl .. contents:: TOC @@ -155,6 +156,17 @@ Version markup Boring stuff. +Code blocks +----------- + +.. code-block:: ruby + :linenos: + + def ruby? + false + end + + Misc stuff ---------- diff --git a/tests/test_build.py b/tests/test_build.py index f9704fdf8..e0a59e192 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -85,6 +85,8 @@ HTML_XPATH = { }, 'markup.html': { ".//title": 'set by title directive', + ".//p/em": 'Section author: Georg Brandl', + ".//p/em": 'Module author: Georg Brandl', # created by the meta directive ".//meta[@name='author'][@content='Me']": '', ".//meta[@name='keywords'][@content='docs, sphinx']": '', @@ -122,6 +124,8 @@ HTML_XPATH = { ".//a[@class='footnote-reference']": r'\[1\]', # created by reference lookup ".//a[@href='contents.html#ref1']": '', + # ``seealso`` directive + ".//div/p[@class='first admonition-title']": 'See also', # a ``hlist`` directive ".//table[@class='hlist']/tr/td/ul/li": '^This$', # a ``centered`` directive From 2d2a58efb436a80b40150b20fa0a333c3c2a56ac Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 9 Aug 2009 23:13:35 +0200 Subject: [PATCH 49/85] Fix test now that show_authors is true. --- tests/test_config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_config.py b/tests/test_config.py index b3aa4eeaa..1560c2571 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -32,7 +32,7 @@ def test_core_config(app): # simple default values assert 'exclude_dirs' not in cfg.__dict__ assert cfg.exclude_dirs == [] - assert cfg.show_authors == False + assert cfg.trim_footnote_reference_space == False # complex default values assert 'html_title' not in cfg.__dict__ From 0e37970007122544d376ac9261cb31d7566d28f3 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 2 Sep 2009 17:31:49 +0000 Subject: [PATCH 50/85] raise ExtensionError for unregistered domains --- sphinx/environment.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sphinx/environment.py b/sphinx/environment.py index f52c18b5f..dc78951af 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -43,7 +43,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, make_refnode -from sphinx.errors import SphinxError +from sphinx.errors import SphinxError, ExtensionError from sphinx.directives import additional_xref_types orig_role_function = roles.role @@ -1013,11 +1013,11 @@ class BuildEnvironment: def get_domain(self, domainname): """Return the domain instance with the specified name. - Raises a nicer KeyError if the domain is not registered.""" + Raises an ExtensionError if the domain is not registered.""" try: return self.domains[domainname] except KeyError: - raise KeyError('Domain %r is not registered' % domainname) + raise ExtensionError('Domain %r is not registered' % domainname) # --------- RESOLVING REFERENCES AND TOCTREES ------------------------------ From fa7f8812cd4e82e2393e8a8023312f71b5199d34 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 7 Sep 2009 22:52:26 +0200 Subject: [PATCH 51/85] More refactoring for language-independent domain support. * Renamed "desc"ription unit to "object" wherever possible. * Added standard x-ref types to a StandardDomain which is always consulted. * Split domains module into a subpackage. * Removed additional_xref_types in favor of new directive classes in StandardDomain. * Implemented x-ref inventory version 2, for all object types. * Added env.doc_read_data which is for temporary data stored while reading. * Minimally updated extension tutorial. * Started to implement changes to interactive search. * Test suite passes again. --- doc/ext/tutorial.rst | 10 +- sphinx/addnodes.py | 2 +- sphinx/application.py | 39 +- sphinx/builders/html.py | 12 +- sphinx/directives/desc.py | 147 +------- sphinx/directives/other.py | 141 +------ sphinx/domains/__init__.py | 176 +++++++++ sphinx/domains/c.py | 203 ++++++++++ sphinx/{domains.py => domains/python.py} | 436 +++++----------------- sphinx/domains/std.py | 361 ++++++++++++++++++ sphinx/environment.py | 146 ++++---- sphinx/ext/autodoc.py | 26 +- sphinx/ext/autosummary/__init__.py | 5 +- sphinx/ext/inheritance_diagram.py | 3 +- sphinx/ext/intersphinx.py | 108 ++++-- sphinx/ext/refcounting.py | 2 +- sphinx/ext/todo.py | 4 +- sphinx/roles.py | 29 +- sphinx/search.py | 15 +- sphinx/themes/basic/static/searchtools.js | 14 +- sphinx/writers/html.py | 4 +- sphinx/writers/latex.py | 4 +- sphinx/writers/text.py | 4 +- tests/root/contents.txt | 2 +- tests/root/{desc.txt => objects.txt} | 0 tests/test_autodoc.py | 24 +- tests/test_build.py | 10 +- tests/test_env.py | 4 +- 28 files changed, 1086 insertions(+), 845 deletions(-) create mode 100644 sphinx/domains/__init__.py create mode 100644 sphinx/domains/c.py rename sphinx/{domains.py => domains/python.py} (53%) create mode 100644 sphinx/domains/std.py rename tests/root/{desc.txt => objects.txt} (100%) diff --git a/doc/ext/tutorial.rst b/doc/ext/tutorial.rst index c44748d23..43d12ccb6 100644 --- a/doc/ext/tutorial.rst +++ b/doc/ext/tutorial.rst @@ -201,8 +201,7 @@ The ``todo`` directive function looks like this:: def run(self): env = self.state.document.settings.env - targetid = "todo-%s" % env.index_num - env.index_num += 1 + targetid = "todo-%s" % env.new_serialno('todo') targetnode = nodes.target('', '', ids=[targetid]) ad = make_admonition(todo, self.name, [_('Todo')], self.options, @@ -225,9 +224,10 @@ to the build environment instance using ``self.state.document.settings.env``. Then, to act as a link target (from the todolist), the todo directive needs to return a target node in addition to the todo node. The target ID (in HTML, this -will be the anchor name) is generated by using ``env.index_num`` which is -persistent between directive calls and therefore leads to unique target names. -The target node is instantiated without any text (the first two arguments). +will be the anchor name) is generated by using ``env.new_serialno`` which is +returns a new integer directive on each call and therefore leads to unique +target names. The target node is instantiated without any text (the first two +arguments). An admonition is created using a standard docutils function (wrapped in Sphinx for docutils cross-version compatibility). The first argument gives the node diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py index b8c9a3544..71a26febc 100644 --- a/sphinx/addnodes.py +++ b/sphinx/addnodes.py @@ -14,7 +14,7 @@ from docutils import nodes # index markup class index(nodes.Invisible, nodes.Inline, nodes.TextElement): pass -# description units (classdesc, funcdesc etc.) +# domain-specific object description units (class, function etc.) # parent node for signature and content class desc(nodes.Admonition, nodes.Element): pass diff --git a/sphinx/application.py b/sphinx/application.py index 1296e5752..e50248729 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -25,9 +25,9 @@ from sphinx import package_dir, locale from sphinx.roles import XRefRole from sphinx.config import Config from sphinx.errors import SphinxError, SphinxWarning, ExtensionError -from sphinx.domains import all_domains +from sphinx.domains import ObjType, all_domains +from sphinx.domains.std import GenericObject, Target, StandardDomain from sphinx.builders import BUILTIN_BUILDERS -from sphinx.directives import GenericDesc, Target, additional_xref_types from sphinx.environment import BuildEnvironment, SphinxStandaloneReader from sphinx.util import pycompat # imported for side-effects from sphinx.util.tags import Tags @@ -361,6 +361,7 @@ class Sphinx(object): def add_domain(self, domain): # XXX needs to be documented + # XXX what about subclassing and overriding? if domain.name in all_domains: raise ExtensionError('domain %s already registered' % domain.name) all_domains[domain.name] = domain @@ -377,23 +378,31 @@ class Sphinx(object): raise ExtensionError('domain %s not yet registered' % domain) all_domains[domain].roles[name] = role - def add_description_unit(self, directivename, rolename, indextemplate='', - parse_node=None, ref_nodeclass=None): - additional_xref_types[directivename] = (rolename, indextemplate, - parse_node) - directives.register_directive(directivename, - directive_dwim(GenericDesc)) + def add_object_type(self, directivename, rolename, indextemplate='', + parse_node=None, ref_nodeclass=None): + StandardDomain.object_types[directivename] = \ + ObjType(directivename, rolename) + # create a subclass of GenericObject as the new directive + new_directive = type(directivename, (GenericObject, object), + {'indextemplate': indextemplate, + 'parse_node': staticmethod(parse_node)}) + StandardDomain.directives[directivename] = new_directive # XXX support more options? - role_func = XRefRole(innernodeclass=ref_nodeclass) - roles.register_local_role(rolename, role_func) + StandardDomain.roles[rolename] = XRefRole(innernodeclass=ref_nodeclass) + + # backwards compatible alias + add_description_unit = add_object_type def add_crossref_type(self, directivename, rolename, indextemplate='', ref_nodeclass=None): - additional_xref_types[directivename] = (rolename, indextemplate, None) - directives.register_directive(directivename, directive_dwim(Target)) - # XXX support more options - role_func = XRefRole(innernodeclass=ref_nodeclass) - roles.register_local_role(rolename, role_func) + StandardDomain.object_types[directivename] = \ + ObjType(directivename, rolename) + # create a subclass of Target as the new directive + new_directive = type(directivename, (Target, object), + {'indextemplate': indextemplate}) + StandardDomain.directives[directivename] = new_directive + # XXX support more options? + StandardDomain.roles[rolename] = XRefRole(innernodeclass=ref_nodeclass) def add_transform(self, transform): SphinxStandaloneReader.transforms.append(transform) diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index 02f621640..26f351157 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -713,15 +713,13 @@ class StandaloneHTMLBuilder(Builder): self.info(bold('dumping object inventory... '), nonl=True) f = open(path.join(self.outdir, INVENTORY_FILENAME), 'w') try: - # XXX inventory version 2 - f.write('# Sphinx inventory version 1\n') + f.write('# Sphinx inventory version 2\n') f.write('# Project: %s\n' % self.config.project.encode('utf-8')) f.write('# Version: %s\n' % self.config.version) - #for modname, info in self.env.modules.iteritems(): - # f.write('%s mod %s\n' % (modname, self.get_target_uri(info[0]))) - #for refname, (docname, desctype) in self.env.descrefs.iteritems(): - # f.write('%s %s %s\n' % (refname, desctype, - # self.get_target_uri(docname))) + for domain in self.env.domains.itervalues(): + for name, type, docname, anchor, prio in domain.get_objects(): + f.write('%s %s:%s %s %s\n' % (name, domain, type, prio, + self.get_target_uri(docname) + '#' + anchor)) finally: f.close() self.info('done') diff --git a/sphinx/directives/desc.py b/sphinx/directives/desc.py index 6deb1f03f..2d888930c 100644 --- a/sphinx/directives/desc.py +++ b/sphinx/directives/desc.py @@ -15,7 +15,6 @@ from docutils.parsers.rst import directives from sphinx import addnodes from sphinx.locale import l_ -from sphinx.util import ws_re from sphinx.util.compat import Directive, directive_dwim @@ -32,15 +31,11 @@ def _is_only_paragraph(node): return False -# RE for option descriptions -option_desc_re = re.compile( - r'((?:/|-|--)[-_a-zA-Z0-9]+)(\s*.*?)(?=,\s+(?:/|-|--)|$)') - # RE to strip backslash escapes strip_backslash_re = re.compile(r'\\(?=[^\\])') -class DescDirective(Directive): +class ObjectDescription(Directive): """ Directive to describe a class, function or similar object. Not used directly, but subclassed to add custom behavior. @@ -133,10 +128,12 @@ class DescDirective(Directive): nfield += nfieldname node = nfieldname if typ in self.doc_fields_with_linked_arg: + # XXX currmodule/currclass node = addnodes.pending_xref( obj, reftype='obj', refcaption=False, - reftarget=obj, modname=self.env.currmodule, - classname=self.env.currclass) + reftarget=obj) + #, modname=self.env.currmodule + #, classname=self.env.currclass nfieldname += node node += nodes.Text(obj, obj) nfield += nodes.field_body() @@ -205,15 +202,17 @@ class DescDirective(Directive): def run(self): if ':' in self.name: - self.domain, self.desctype = self.name.split(':', 1) + self.domain, self.objtype = self.name.split(':', 1) else: - self.domain, self.desctype = '', self.name + self.domain, self.objtype = '', self.name self.env = self.state.document.settings.env self.indexnode = addnodes.index(entries=[]) node = addnodes.desc() node.document = self.state.document - node['desctype'] = self.desctype + node['domain'] = self.domain + # 'desctype' is a backwards compatible attribute + node['objtype'] = node['desctype'] = self.objtype node['noindex'] = noindex = ('noindex' in self.options) self.names = [] @@ -242,117 +241,16 @@ class DescDirective(Directive): node.append(contentnode) if self.names: # needed for association of version{added,changed} directives - self.env.currdesc = self.names[0] + self.env.doc_read_data['object'] = self.names[0] self.before_content() self.state.nested_parse(self.content, self.content_offset, contentnode) self.handle_doc_fields(contentnode) - self.env.currdesc = None + self.env.doc_read_data['object'] = None self.after_content() return [self.indexnode, node] - -class CmdoptionDesc(DescDirective): - """ - Description of a command-line option (.. cmdoption). - """ - - def parse_signature(self, sig, signode): - """Transform an option description into RST nodes.""" - count = 0 - firstname = '' - for m in option_desc_re.finditer(sig): - optname, args = m.groups() - if count: - signode += addnodes.desc_addname(', ', ', ') - signode += addnodes.desc_name(optname, optname) - signode += addnodes.desc_addname(args, args) - if not count: - firstname = optname - count += 1 - if not firstname: - raise ValueError - return firstname - - def add_target_and_index(self, name, sig, signode): - targetname = name.replace('/', '-') - if self.env.currprogram: - targetname = '-' + self.env.currprogram + targetname - targetname = 'cmdoption' + targetname - signode['ids'].append(targetname) - self.state.document.note_explicit_target(signode) - self.indexnode['entries'].append( - ('pair', _('%scommand line option; %s') % - ((self.env.currprogram and - self.env.currprogram + ' ' or ''), sig), - targetname, targetname)) - self.env.note_progoption(name, targetname) - - -class GenericDesc(DescDirective): - """ - A generic x-ref directive registered with Sphinx.add_description_unit(). - """ - - def parse_signature(self, sig, signode): - parse_node = additional_xref_types[self.desctype][2] - if parse_node: - name = parse_node(self.env, sig, signode) - else: - signode.clear() - signode += addnodes.desc_name(sig, sig) - # normalize whitespace like XRefRole does - name = ws_re.sub('', sig) - return name - - def add_target_and_index(self, name, sig, signode): - rolename, indextemplate = additional_xref_types[self.desctype][:2] - targetname = '%s-%s' % (rolename, name) - signode['ids'].append(targetname) - self.state.document.note_explicit_target(signode) - if indextemplate: - indexentry = indextemplate % (name,) - indextype = 'single' - colon = indexentry.find(':') - if colon != -1: - indextype = indexentry[:colon].strip() - indexentry = indexentry[colon+1:].strip() - self.indexnode['entries'].append((indextype, indexentry, - targetname, targetname)) - self.env.note_reftarget(rolename, name, targetname) - - -class Target(Directive): - """ - Generic target for user-defined cross-reference types. - """ - - has_content = False - required_arguments = 1 - optional_arguments = 0 - final_argument_whitespace = True - option_spec = {} - - def run(self): - env = self.state.document.settings.env - rolename, indextemplate, foo = additional_xref_types[self.name] - # normalize whitespace in fullname like XRefRole does - fullname = ws_re.sub('', self.arguments[0].strip()) - targetname = '%s-%s' % (rolename, fullname) - node = nodes.target('', '', ids=[targetname]) - self.state.document.note_explicit_target(node) - ret = [node] - if indextemplate: - indexentry = indextemplate % (fullname,) - indextype = 'single' - colon = indexentry.find(':') - if colon != -1: - indextype = indexentry[:colon].strip() - indexentry = indexentry[colon+1:].strip() - inode = addnodes.index(entries=[(indextype, indexentry, - targetname, targetname)]) - ret.insert(0, inode) - env.note_reftarget(rolename, fullname, targetname) - return ret +# backwards compatible old name +DescDirective = ObjectDescription class DefaultDomain(Directive): @@ -372,18 +270,7 @@ class DefaultDomain(Directive): env.default_domain = env.domains.get(domain_name) -# Note: the target directive is not registered here, it is used by the -# application when registering additional xref types. - -# Generic cross-reference types; they can be registered in the application; -# the directives are either desc_directive or target_directive. -additional_xref_types = { - # directive name: (role name, index text, function to parse the desc node) - 'envvar': ('envvar', l_('environment variable; %s'), None), -} - - directives.register_directive('default-domain', directive_dwim(DefaultDomain)) -directives.register_directive('describe', directive_dwim(DescDirective)) -directives.register_directive('cmdoption', directive_dwim(CmdoptionDesc)) -directives.register_directive('envvar', directive_dwim(GenericDesc)) +directives.register_directive('describe', directive_dwim(ObjectDescription)) +# new, more consistent, name +directives.register_directive('object', directive_dwim(ObjectDescription)) diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py index 225c1816d..969a15044 100644 --- a/sphinx/directives/other.py +++ b/sphinx/directives/other.py @@ -136,27 +136,6 @@ class Author(Directive): return [para] + messages -class Program(Directive): - """ - Directive to name the program for which options are documented. - """ - - has_content = False - required_arguments = 1 - optional_arguments = 0 - final_argument_whitespace = True - option_spec = {} - - def run(self): - env = self.state.document.settings.env - program = ws_re.sub('-', self.arguments[0].strip()) - if program == 'None': - env.currprogram = None - else: - env.currprogram = program - return [] - - class Index(Directive): """ Directive to add entries to the index. @@ -175,8 +154,7 @@ class Index(Directive): def run(self): arguments = self.arguments[0].split('\n') env = self.state.document.settings.env - targetid = 'index-%s' % env.index_num - env.index_num += 1 + targetid = 'index-%s' % env.new_serialno('index') targetnode = nodes.target('', '', ids=[targetid]) self.state.document.note_explicit_target(targetnode) indexnode = addnodes.index() @@ -231,6 +209,7 @@ class VersionChange(Directive): else: ret = [node] env = self.state.document.settings.env + # XXX replace? what with? env.note_versionchange(node['type'], node['version'], node, self.lineno) return ret @@ -261,66 +240,6 @@ class SeeAlso(Directive): return ret -token_re = re.compile('`([a-z_][a-z0-9_]*)`') - -def token_xrefs(text, env): - retnodes = [] - pos = 0 - for m in token_re.finditer(text): - if m.start() > pos: - txt = text[pos:m.start()] - retnodes.append(nodes.Text(txt, txt)) - refnode = addnodes.pending_xref(m.group(1)) - refnode['reftype'] = 'token' - refnode['reftarget'] = m.group(1) - refnode['modname'] = env.currmodule - refnode['classname'] = env.currclass - refnode += nodes.literal(m.group(1), m.group(1), classes=['xref']) - retnodes.append(refnode) - pos = m.end() - if pos < len(text): - retnodes.append(nodes.Text(text[pos:], text[pos:])) - return retnodes - -class ProductionList(Directive): - """ - Directive to list grammar productions. - """ - - has_content = False - required_arguments = 1 - optional_arguments = 0 - final_argument_whitespace = True - option_spec = {} - - def run(self): - env = self.state.document.settings.env - node = addnodes.productionlist() - messages = [] - i = 0 - - for rule in self.arguments[0].split('\n'): - if i == 0 and ':' not in rule: - # production group - continue - i += 1 - try: - name, tokens = rule.split(':', 1) - except ValueError: - break - subnode = addnodes.production() - subnode['tokenname'] = name.strip() - if subnode['tokenname']: - idname = 'grammar-token-%s' % subnode['tokenname'] - if idname not in self.state.document.ids: - subnode['ids'].append(idname) - self.state.document.note_implicit_target(subnode, subnode) - env.note_reftarget('token', subnode['tokenname'], idname) - subnode.extend(token_xrefs(tokens, env)) - node.append(subnode) - return [node] + messages - - class TabularColumns(Directive): """ Directive to give an explicit tabulary column definition to LaTeX. @@ -338,57 +257,6 @@ class TabularColumns(Directive): return [node] -class Glossary(Directive): - """ - Directive to create a glossary with cross-reference targets - for :term: roles. - """ - - has_content = True - required_arguments = 0 - optional_arguments = 0 - final_argument_whitespace = False - option_spec = { - 'sorted': directives.flag, - } - - def run(self): - env = self.state.document.settings.env - node = addnodes.glossary() - node.document = self.state.document - self.state.nested_parse(self.content, self.content_offset, node) - - # the content should be definition lists - dls = [child for child in node - if isinstance(child, nodes.definition_list)] - # now, extract definition terms to enable cross-reference creation - new_dl = nodes.definition_list() - new_dl['classes'].append('glossary') - items = [] - for dl in dls: - for li in dl.children: - if not li.children or not isinstance(li[0], nodes.term): - continue - termtext = li.children[0].astext() - new_id = 'term-' + nodes.make_id(termtext) - if new_id in env.gloss_entries: - new_id = 'term-' + str(len(env.gloss_entries)) - env.gloss_entries.add(new_id) - li[0]['names'].append(new_id) - li[0]['ids'].append(new_id) - env.note_reftarget('term', termtext.lower(), new_id) - # add an index entry too - indexnode = addnodes.index() - indexnode['entries'] = [('single', termtext, new_id, termtext)] - li.insert(0, indexnode) - items.append((termtext, li)) - if 'sorted' in self.options: - items.sort(key=lambda x: x[0].lower()) - new_dl.extend(item[1] for item in items) - node.children = [new_dl] - return [node] - - class Centered(Directive): """ Directive to create a centered line of bold text. @@ -494,22 +362,19 @@ class Only(Directive): directives.register_directive('toctree', directive_dwim(TocTree)) directives.register_directive('sectionauthor', directive_dwim(Author)) directives.register_directive('moduleauthor', directive_dwim(Author)) -directives.register_directive('program', directive_dwim(Program)) directives.register_directive('index', directive_dwim(Index)) directives.register_directive('deprecated', directive_dwim(VersionChange)) directives.register_directive('versionadded', directive_dwim(VersionChange)) directives.register_directive('versionchanged', directive_dwim(VersionChange)) directives.register_directive('seealso', directive_dwim(SeeAlso)) -directives.register_directive('productionlist', directive_dwim(ProductionList)) directives.register_directive('tabularcolumns', directive_dwim(TabularColumns)) -directives.register_directive('glossary', directive_dwim(Glossary)) directives.register_directive('centered', directive_dwim(Centered)) directives.register_directive('acks', directive_dwim(Acks)) directives.register_directive('hlist', directive_dwim(HList)) directives.register_directive('only', directive_dwim(Only)) # register the standard rst class directive under a different name - +# only for backwards compatibility now try: # docutils 0.4 from docutils.parsers.rst.directives.misc import class_directive diff --git a/sphinx/domains/__init__.py b/sphinx/domains/__init__.py new file mode 100644 index 000000000..7691d2639 --- /dev/null +++ b/sphinx/domains/__init__.py @@ -0,0 +1,176 @@ +# -*- coding: utf-8 -*- +""" + sphinx.domains + ~~~~~~~~~~~~~~ + + 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. + :license: BSD, see LICENSE for details. +""" + + +class ObjType(object): + """ + XXX add docstring + """ + + known_attrs = { + 'search': True, + } + + def __init__(self, lname, *roles, **attrs): + self.lname = lname + self.roles = roles + self.attrs = self.known_attrs.copy() + self.attrs.update(attrs) + + +class Domain(object): + """ + A Domain is meant to be a group of "object" description directives for + objects of a similar nature, and corresponding roles to create references to + them. Examples would be Python modules, classes, functions etc., elements + 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 + for objects in a domain-agnostic way. + + About `self.data`: since all object and cross-referencing information is + stored on a BuildEnvironment instance, the `domain.data` object is also + stored in the `env.domaindata` dict under the key `domain.name`. Before the + 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. + """ + + #: domain name: should be short, but unique + name = '' + #: domain label: longer, more descriptive (used in messages) + label = '' + #: type (usually directive) name -> ObjType instance + object_types = {} + #: directive name -> directive class + directives = {} + #: role name -> role callable + roles = {} + + #: data value for a fresh environment + initial_data = {} + #: data version + data_version = 0 + + def __init__(self, env): + self.env = env + if self.name not in env.domaindata: + assert isinstance(self.initial_data, dict) + new_data = self.initial_data.copy() + new_data['version'] = self.data_version + self.data = env.domaindata[self.name] = new_data + else: + self.data = env.domaindata[self.name] + if self.data['version'] != self.data_version: + raise IOError('data of %r domain out of date' % self.label) + self._role_cache = {} + self._directive_cache = {} + self._role2type = {} + for name, obj in self.object_types.iteritems(): + for rolename in obj.roles: + self._role2type[rolename] = name + self.object_type = self._role2type.get # XXX necessary? + + def role(self, name): + """ + Return a role adapter function that always gives the registered + role its full name ('domain:name') as the first argument. + """ + if name in self._role_cache: + return self._role_cache[name] + if name not in self.roles: + return None + fullname = '%s:%s' % (self.name, name) + def role_adapter(typ, rawtext, text, lineno, inliner, + options={}, content=[]): + return self.roles[name](fullname, rawtext, text, lineno, + inliner, options, content) + self._role_cache[name] = role_adapter + return role_adapter + + def directive(self, name): + """ + Return a directive adapter class that always gives the registered + directive its full name ('domain:name') as ``self.name``. + """ + if name in self._directive_cache: + return self._directive_cache[name] + if name not in self.directives: + return None + fullname = '%s:%s' % (self.name, name) + # XXX what about function-style directives? + BaseDirective = self.directives[name] + class DirectiveAdapter(BaseDirective): + def run(self): + self.name = fullname + return BaseDirective.run(self) + self._directive_cache[name] = DirectiveAdapter + return DirectiveAdapter + + # methods that should be overwritten + + def clear_doc(self, docname): + """ + Remove traces of a document in the domain-specific inventories. + """ + pass + + # XXX way for individual docs to override methods in an existing domain? + + def resolve_xref(self, env, fromdocname, builder, + typ, target, node, contnode): + """ + Resolve the ``pending_xref`` *node* with the given *typ* and *target*. + + This method should return a new node, to replace the xref node, + containing the *contnode* which is the markup content of the + cross-reference. + + If no resolution can be found, None can be returned; the xref node will + 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. + """ + pass + + def get_objects(self): + """ + Return an iterable of "object descriptions", which are tuples with + five items: + + * `name` -- fully qualified name + * `type` -- object type, a key in ``self.object_types`` + * `docname` -- the document where it is to be found + * `anchor` -- the anchor name for the object + * `priority` -- how "important" the object is; an integer + (XXX assign meanings) + """ + return [] + + +from sphinx.domains.c import CDomain +from sphinx.domains.std import StandardDomain +from sphinx.domains.python import PythonDomain + +# this contains all registered domains +all_domains = { + 'std': StandardDomain, + 'py': PythonDomain, + 'c': CDomain, +} diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py new file mode 100644 index 000000000..216ae05d9 --- /dev/null +++ b/sphinx/domains/c.py @@ -0,0 +1,203 @@ +# -*- coding: utf-8 -*- +""" + sphinx.domains.c + ~~~~~~~~~~~~~~~~ + + The C language domain. + + :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re +import string + +from docutils import nodes +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.directives import ObjectDescription +from sphinx.util import make_refnode +from sphinx.util.compat import Directive + + +# RE to split at word boundaries +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) + (?: \((.*)\) )? # optionally arguments + (\s+const)? $ # const specifier + ''', re.VERBOSE) +c_funcptr_sig_re = re.compile( + r'''^([^(]+?) # return type + (\( [^()]+ \)) \s* # name in parentheses + \( (.*) \) # arguments + (\s+const)? $ # const specifier + ''', re.VERBOSE) +c_funcptr_name_re = re.compile(r'^\(\s*\*\s*(.*?)\s*\)$') + + +class CObject(ObjectDescription): + """ + Description of a C language object. + """ + + # These C types aren't described anywhere, so don't try to create + # a cross-reference to them + stopwords = set(('const', 'void', 'char', 'int', 'long', 'FILE', 'struct')) + + def _parse_type(self, node, ctype): + # add cross-ref nodes for all words + for part in filter(None, wsplit_re.split(ctype)): + tnode = nodes.Text(part, part) + if part[0] in string.ascii_letters+'_' and \ + part not in self.stopwords: + pnode = addnodes.pending_xref( + '', refdomain='c', reftype='type', reftarget=part, + modname=None, classname=None) + pnode += tnode + node += pnode + else: + node += tnode + + def parse_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) + if m is None: + m = c_sig_re.match(sig) + if m is None: + raise ValueError('no match') + rettype, name, arglist, const = m.groups() + + signode += addnodes.desc_type('', '') + self._parse_type(signode[-1], rettype) + try: + classname, funcname = name.split('::', 1) + classname += '::' + signode += addnodes.desc_addname(classname, classname) + signode += addnodes.desc_name(funcname, funcname) + # name (the full name) is still both parts + except ValueError: + signode += addnodes.desc_name(name, name) + # clean up parentheses from canonical name + m = c_funcptr_name_re.match(name) + if m: + name = m.group(1) + if not arglist: + if self.objtype == 'cfunction': + # for functions, add an empty parameter list + signode += addnodes.desc_parameterlist() + if const: + signode += addnodes.desc_addname(const, const) + return name + + paramlist = addnodes.desc_parameterlist() + arglist = arglist.replace('`', '').replace('\\ ', '') # remove markup + # this messes up function pointer types, but not too badly ;) + args = arglist.split(',') + for arg in args: + arg = arg.strip() + param = addnodes.desc_parameter('', '', noemph=True) + try: + ctype, argname = arg.rsplit(' ', 1) + except ValueError: + # no argument name given, only the type + self._parse_type(param, arg) + else: + self._parse_type(param, ctype) + param += nodes.emphasis(' '+argname, ' '+argname) + paramlist += param + signode += paramlist + if const: + signode += addnodes.desc_addname(const, const) + return name + + def get_index_text(self, name): + if self.objtype == 'cfunction': + return _('%s (C function)') % name + elif self.objtype == 'cmember': + return _('%s (C member)') % name + elif self.objtype == 'cmacro': + return _('%s (C macro)') % name + elif self.objtype == 'ctype': + return _('%s (C type)') % name + elif self.objtype == 'cvar': + return _('%s (C variable)') % name + else: + return '' + + def add_target_and_index(self, name, sig, signode): + # note target + if name not in self.state.document.ids: + signode['names'].append(name) + signode['ids'].append(name) + signode['first'] = (not self.names) + self.state.document.note_explicit_target(signode) + inv = self.env.domaindata['c']['objects'] + if name in inv: + self.env.warn( + self.env.docname, + 'duplicate C object description of %s, ' % name + + 'other instance in ' + self.env.doc2path(inv[name][0]), + self.lineno) + inv[name] = (self.env.docname, self.objtype) + + indextext = self.get_index_text(name) + if indextext: + self.indexnode['entries'].append(('single', indextext, name, name)) + + +class CDomain(Domain): + """C language domain.""" + name = 'c' + label = 'C' + object_types = { + '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 = { + 'function': CObject, + 'member': CObject, + 'macro': CObject, + 'type': CObject, + 'var': CObject, + } + roles = { + '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: + return None + obj = self.data[target] + return make_refnode(builder, fromdocname, obj[0], contnode, target) + + def get_objects(self): + for refname, (docname, type) in self.data['objects'].iteritems(): + yield (refname, type, docname, refname, 0) diff --git a/sphinx/domains.py b/sphinx/domains/python.py similarity index 53% rename from sphinx/domains.py rename to sphinx/domains/python.py index 6e00d5402..dfefe24cb 100644 --- a/sphinx/domains.py +++ b/sphinx/domains/python.py @@ -1,135 +1,28 @@ # -*- coding: utf-8 -*- """ - sphinx.domains - ~~~~~~~~~~~~~~ + sphinx.domains.python + ~~~~~~~~~~~~~~~~~~~~~ - Support for domains, which are groupings of description directives - and roles describing e.g. constructs of one programming language. + The Python domain. :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. """ import re -import string from docutils import nodes from docutils.parsers.rst import directives from sphinx import addnodes from sphinx.roles import XRefRole -from sphinx.directives import DescDirective +from sphinx.locale import l_ +from sphinx.domains import Domain, ObjType +from sphinx.directives import ObjectDescription from sphinx.util import make_refnode from sphinx.util.compat import Directive -class Domain(object): - """ - A Domain is meant to be a group of "object" description directives for - objects of a similar nature, and corresponding roles to create references to - them. Examples would be Python modules, classes, functions etc., elements - 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 - for objects in a domain-agnostic way. - - About `self.data`: since all object and cross-referencing information is - stored on a BuildEnvironment instance, the `domain.data` object is also - stored in the `env.domaindata` dict under the key `domain.name`. Before the - 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. - """ - - name = '' - directives = {} - roles = {} - label = '' - - # data value for a fresh environment - initial_data = {} - # data version - data_version = 0 - - def __init__(self, env): - self.env = env - if self.name not in env.domaindata: - assert isinstance(self.initial_data, dict) - new_data = self.initial_data.copy() - new_data['version'] = self.data_version - self.data = env.domaindata[self.name] = new_data - else: - self.data = env.domaindata[self.name] - if self.data['version'] != self.data_version: - raise IOError('data of %r domain out of date' % self.label) - self._role_cache = {} - self._directive_cache = {} - - def clear_doc(self, docname): - """ - Remove traces of a document in the domain-specific inventories. - """ - pass - - def role(self, name): - """ - Return a role adapter function that always gives the registered - role its full name ('domain:name') as the first argument. - """ - if name in self._role_cache: - return self._role_cache[name] - if name not in self.roles: - return None - fullname = '%s:%s' % (self.name, name) - def role_adapter(typ, rawtext, text, lineno, inliner, - options={}, content=[]): - return self.roles[name](fullname, rawtext, text, lineno, - inliner, options, content) - self._role_cache[name] = role_adapter - return role_adapter - - def directive(self, name): - """ - Return a directive adapter class that always gives the registered - directive its full name ('domain:name') as ``self.name``. - """ - if name in self._directive_cache: - return self._directive_cache[name] - if name not in self.directives: - return None - fullname = '%s:%s' % (self.name, name) - # XXX what about function-style directives? - BaseDirective = self.directives[name] - class DirectiveAdapter(BaseDirective): - def run(self): - self.name = fullname - return BaseDirective.run(self) - self._directive_cache[name] = DirectiveAdapter - return DirectiveAdapter - - def resolve_xref(self, typ, target, node, contnode): - """ - Resolve the ``pending_xref`` *node* with the given *typ* and *target*. - - This method should return a new node, to replace the xref node, - containing the *contnode* which is the markup content of the - cross-reference. - - If no resolution can be found, None can be returned; the xref node will - 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. - """ - pass - - # REs for Python signatures py_sig_re = re.compile( r'''^ ([\w.]*\.)? # class name(s) @@ -142,7 +35,7 @@ py_sig_re = re.compile( py_paramlist_re = re.compile(r'([\[\],])') # split at '[', ']' and ',' -class PythonDesc(DescDirective): +class PyObject(ObjectDescription): """ Description of a general Python object. """ @@ -174,19 +67,20 @@ class PythonDesc(DescDirective): raise ValueError classname, name, arglist, retann = m.groups() - if self.env.currclass: + currclass = self.env.doc_read_data.get('py_class') + if currclass: add_module = False - if classname and classname.startswith(self.env.currclass): + if classname and classname.startswith(currclass): fullname = classname + name # class name is given again in the signature - classname = classname[len(self.env.currclass):].lstrip('.') + classname = classname[len(currclass):].lstrip('.') elif classname: # class name is given in the signature, but different # (shouldn't happen) - fullname = self.env.currclass + '.' + classname + name + fullname = currclass + '.' + classname + name else: # class name is not given in the signature - fullname = self.env.currclass + '.' + name + fullname = currclass + '.' + name else: add_module = True fullname = classname and classname + name or name @@ -200,7 +94,8 @@ class PythonDesc(DescDirective): # exceptions are a special case, since they are documented in the # 'exceptions' module. elif add_module and self.env.config.add_module_names: - modname = self.options.get('module', self.env.currmodule) + modname = self.options.get( + 'module', self.env.doc_read_data.get('py_module')) if modname and modname != 'exceptions': nodetext = modname + '.' signode += addnodes.desc_addname(nodetext, nodetext) @@ -244,7 +139,8 @@ class PythonDesc(DescDirective): raise NotImplementedError('must be implemented in subclasses') def add_target_and_index(self, name_cls, sig, signode): - modname = self.options.get('module', self.env.currmodule) + modname = self.options.get( + '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: @@ -260,7 +156,7 @@ class PythonDesc(DescDirective): 'other instance in ' + self.env.doc2path(objects[fullname][0]), self.lineno) - objects[fullname] = (self.env.docname, self.desctype) + objects[fullname] = (self.env.docname, self.objtype) indextext = self.get_index_text(modname, name_cls) if indextext: @@ -273,23 +169,23 @@ class PythonDesc(DescDirective): def after_content(self): if self.clsname_set: - self.env.currclass = None + self.env.doc_read_data['py_class'] = None -class ModulelevelDesc(PythonDesc): +class PyModulelevel(PyObject): """ Description of an object on module level (functions, data). """ def needs_arglist(self): - return self.desctype == 'function' + return self.objtype == 'function' def get_index_text(self, modname, name_cls): - if self.desctype == 'function': + if self.objtype == 'function': if not modname: return _('%s() (built-in function)') % name_cls[0] return _('%s() (in module %s)') % (name_cls[0], modname) - elif self.desctype == 'data': + elif self.objtype == 'data': if not modname: return _('%s (built-in variable)') % name_cls[0] return _('%s (in module %s)') % (name_cls[0], modname) @@ -297,50 +193,50 @@ class ModulelevelDesc(PythonDesc): return '' -class ClasslikeDesc(PythonDesc): +class PyClasslike(PyObject): """ Description of a class-like object (classes, interfaces, exceptions). """ def get_signature_prefix(self, sig): - return self.desctype + ' ' + return self.objtype + ' ' def get_index_text(self, modname, name_cls): - if self.desctype == 'class': + if self.objtype == 'class': if not modname: return _('%s (built-in class)') % name_cls[0] return _('%s (class in %s)') % (name_cls[0], modname) - elif self.desctype == 'exception': + elif self.objtype == 'exception': return name_cls[0] else: return '' def before_content(self): - PythonDesc.before_content(self) + PyObject.before_content(self) if self.names: - self.env.currclass = self.names[0][0] + self.env.doc_read_data['py_class'] = self.names[0][0] self.clsname_set = True -class ClassmemberDesc(PythonDesc): +class PyClassmember(PyObject): """ Description of a class member (methods, attributes). """ def needs_arglist(self): - return self.desctype.endswith('method') + return self.objtype.endswith('method') def get_signature_prefix(self, sig): - if self.desctype == 'staticmethod': + if self.objtype == 'staticmethod': return 'static ' - elif self.desctype == 'classmethod': + elif self.objtype == 'classmethod': return 'classmethod ' return '' def get_index_text(self, modname, name_cls): name, cls = name_cls add_modules = self.env.config.add_module_names - if self.desctype == 'method': + if self.objtype == 'method': try: clsname, methname = name.rsplit('.', 1) except ValueError: @@ -352,7 +248,7 @@ class ClassmemberDesc(PythonDesc): return _('%s() (%s.%s method)') % (methname, modname, clsname) else: return _('%s() (%s method)') % (methname, clsname) - elif self.desctype == 'staticmethod': + elif self.objtype == 'staticmethod': try: clsname, methname = name.rsplit('.', 1) except ValueError: @@ -365,7 +261,7 @@ class ClassmemberDesc(PythonDesc): clsname) else: return _('%s() (%s static method)') % (methname, clsname) - elif self.desctype == 'classmethod': + elif self.objtype == 'classmethod': try: clsname, methname = name.rsplit('.', 1) except ValueError: @@ -378,7 +274,7 @@ class ClassmemberDesc(PythonDesc): clsname) else: return _('%s() (%s class method)') % (methname, clsname) - elif self.desctype == 'attribute': + elif self.objtype == 'attribute': try: clsname, attrname = name.rsplit('.', 1) except ValueError: @@ -394,9 +290,10 @@ class ClassmemberDesc(PythonDesc): return '' def before_content(self): - PythonDesc.before_content(self) - if self.names and self.names[-1][1] and not self.env.currclass: - self.env.currclass = self.names[-1][1].strip('.') + 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('.') self.clsname_set = True @@ -420,7 +317,7 @@ class PyModule(Directive): env = self.state.document.settings.env modname = self.arguments[0].strip() noindex = 'noindex' in self.options - env.currmodule = 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) @@ -463,16 +360,16 @@ class PyCurrentModule(Directive): env = self.state.document.settings.env modname = self.arguments[0].strip() if modname == 'None': - env.currmodule = None + env.doc_read_data['py_module'] = None else: - env.currmodule = modname + env.doc_read_data['py_module'] = modname return [] class PyXRefRole(XRefRole): def process_link(self, env, pnode, has_explicit_title, title, target): - pnode['modname'] = env.currmodule - pnode['classname'] = env.currclass + pnode['modname'] = env.doc_read_data.get('py_module') + pnode['classname'] = 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 @@ -495,31 +392,41 @@ class PythonDomain(Domain): """Python language domain.""" name = 'py' label = 'Python' + object_types = { + 'function': ObjType(l_('function'), 'func', 'obj'), + 'data': ObjType(l_('data'), 'data', 'obj'), + 'class': ObjType(l_('class'), 'class', 'obj'), + 'exception': ObjType(l_('exception'), 'exc', 'obj'), + 'method': ObjType(l_('method'), 'meth', 'obj'), + 'attribute': ObjType(l_('attribute'), 'attr', 'obj'), + 'module': ObjType(l_('module'), 'mod', 'obj'), + } + directives = { - 'function': ModulelevelDesc, - 'data': ModulelevelDesc, - 'class': ClasslikeDesc, - 'exception': ClasslikeDesc, - 'method': ClassmemberDesc, - 'classmethod': ClassmemberDesc, - 'staticmethod': ClassmemberDesc, - 'attribute': ClassmemberDesc, - 'module': PyModule, + 'function': PyModulelevel, + 'data': PyModulelevel, + 'class': PyClasslike, + 'exception': PyClasslike, + 'method': PyClassmember, + 'classmethod': PyClassmember, + 'staticmethod': PyClassmember, + 'attribute': PyClassmember, + 'module': PyModule, 'currentmodule': PyCurrentModule, } roles = { - 'data': PyXRefRole(), - 'exc': PyXRefRole(), - 'func': PyXRefRole(fix_parens=True), + 'data': PyXRefRole(), + 'exc': PyXRefRole(), + 'func': PyXRefRole(fix_parens=True), 'class': PyXRefRole(), 'const': PyXRefRole(), - 'attr': PyXRefRole(), - 'meth': PyXRefRole(fix_parens=True), - 'mod': PyXRefRole(), - 'obj': PyXRefRole(), + 'attr': PyXRefRole(), + 'meth': PyXRefRole(fix_parens=True), + 'mod': PyXRefRole(), + 'obj': PyXRefRole(), } initial_data = { - 'objects': {}, # fullname -> docname, desctype + 'objects': {}, # fullname -> docname, objtype 'modules': {}, # modname -> docname, synopsis, platform, deprecated } @@ -531,10 +438,10 @@ class PythonDomain(Domain): if fn == docname: del self.data['modules'][modname] - def find_desc(self, env, modname, classname, name, type, searchorder=0): + def find_obj(self, env, modname, classname, name, type, searchorder=0): """ - Find a Python object description for "name", perhaps using the given - module and/or classname. + Find a Python object for "name", perhaps using the given module and/or + classname. """ # skip parens if name[-2:] == '()': @@ -592,188 +499,19 @@ class PythonDomain(Domain): return make_refnode(builder, fromdocname, docname, 'module-' + target, contnode, title) else: - modname = node['modname'] - clsname = node['classname'] + modname = node.get('modname') + clsname = node.get('classname') searchorder = node.hasattr('refspecific') and 1 or 0 - name, desc = self.find_desc(env, modname, clsname, - target, typ, searchorder) - if not desc: + name, obj = self.find_obj(env, modname, clsname, + target, typ, searchorder) + if not obj: return None else: - return make_refnode(builder, fromdocname, desc[0], name, + return make_refnode(builder, fromdocname, obj[0], name, contnode, name) - - -# RE to split at word boundaries -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) - (?: \((.*)\) )? # optionally arguments - (\s+const)? $ # const specifier - ''', re.VERBOSE) -c_funcptr_sig_re = re.compile( - r'''^([^(]+?) # return type - (\( [^()]+ \)) \s* # name in parentheses - \( (.*) \) # arguments - (\s+const)? $ # const specifier - ''', re.VERBOSE) -c_funcptr_name_re = re.compile(r'^\(\s*\*\s*(.*?)\s*\)$') - - -class CDesc(DescDirective): - """ - Description of a C language object. - """ - - # These C types aren't described anywhere, so don't try to create - # a cross-reference to them - stopwords = set(('const', 'void', 'char', 'int', 'long', 'FILE', 'struct')) - - def _parse_type(self, node, ctype): - # add cross-ref nodes for all words - for part in filter(None, wsplit_re.split(ctype)): - tnode = nodes.Text(part, part) - if part[0] in string.ascii_letters+'_' and \ - part not in self.stopwords: - pnode = addnodes.pending_xref( - '', refdomain='c', reftype='type', reftarget=part, - modname=None, classname=None) - pnode += tnode - node += pnode - else: - node += tnode - - def parse_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) - if m is None: - m = c_sig_re.match(sig) - if m is None: - raise ValueError('no match') - rettype, name, arglist, const = m.groups() - - signode += addnodes.desc_type('', '') - self._parse_type(signode[-1], rettype) - try: - classname, funcname = name.split('::', 1) - classname += '::' - signode += addnodes.desc_addname(classname, classname) - signode += addnodes.desc_name(funcname, funcname) - # name (the full name) is still both parts - except ValueError: - signode += addnodes.desc_name(name, name) - # clean up parentheses from canonical name - m = c_funcptr_name_re.match(name) - if m: - name = m.group(1) - if not arglist: - if self.desctype == 'cfunction': - # for functions, add an empty parameter list - signode += addnodes.desc_parameterlist() - if const: - signode += addnodes.desc_addname(const, const) - return name - - paramlist = addnodes.desc_parameterlist() - arglist = arglist.replace('`', '').replace('\\ ', '') # remove markup - # this messes up function pointer types, but not too badly ;) - args = arglist.split(',') - for arg in args: - arg = arg.strip() - param = addnodes.desc_parameter('', '', noemph=True) - try: - ctype, argname = arg.rsplit(' ', 1) - except ValueError: - # no argument name given, only the type - self._parse_type(param, arg) - else: - self._parse_type(param, ctype) - param += nodes.emphasis(' '+argname, ' '+argname) - paramlist += param - signode += paramlist - if const: - signode += addnodes.desc_addname(const, const) - return name - - def get_index_text(self, name): - if self.desctype == 'cfunction': - return _('%s (C function)') % name - elif self.desctype == 'cmember': - return _('%s (C member)') % name - elif self.desctype == 'cmacro': - return _('%s (C macro)') % name - elif self.desctype == 'ctype': - return _('%s (C type)') % name - elif self.desctype == 'cvar': - return _('%s (C variable)') % name - else: - return '' - - def add_target_and_index(self, name, sig, signode): - # note target - if name not in self.state.document.ids: - signode['names'].append(name) - signode['ids'].append(name) - signode['first'] = (not self.names) - self.state.document.note_explicit_target(signode) - inv = self.env.domaindata['c']['objects'] - if name in inv: - self.env.warn( - self.env.docname, - 'duplicate C object description of %s, ' % name + - 'other instance in ' + self.env.doc2path(inv[name][0]), - self.lineno) - inv[name] = (self.env.docname, self.desctype) - - indextext = self.get_index_text(name) - if indextext: - self.indexnode['entries'].append(('single', indextext, name, name)) - - -class CDomain(Domain): - """C language domain.""" - name = 'c' - label = 'C' - directives = { - 'function': CDesc, - 'member': CDesc, - 'macro': CDesc, - 'type': CDesc, - 'var': CDesc, - } - roles = { - 'member': XRefRole(), - 'macro': XRefRole(), - 'func' : XRefRole(fix_parens=True), - 'data': XRefRole(), - 'type': XRefRole(), - } - initial_data = { - 'objects': {}, # fullname -> docname, desctype - } - - 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: - return None - desc = self.data[target] - return make_refnode(builder, fromdocname, desc[0], contnode, target) - - -# this contains all registered domains -all_domains = { - 'py': PythonDomain, - 'c': CDomain, -} + def get_objects(self): + for modname, info in self.data['modules'].iteritems(): + yield (modname, 'module', info[0], 'module-' + modname, 1) + for refname, (docname, type) in self.data['objects'].iteritems(): + yield (refname, type, docname, refname, 0) diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py new file mode 100644 index 000000000..a040dedae --- /dev/null +++ b/sphinx/domains/std.py @@ -0,0 +1,361 @@ +# -*- coding: utf-8 -*- +""" + sphinx.domains.std + ~~~~~~~~~~~~~~~~~~ + + The standard domain. + + :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import re + +from docutils import nodes +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.directives import ObjectDescription +from sphinx.util import make_refnode, ws_re +from sphinx.util.compat import Directive + + +# RE for option descriptions +option_desc_re = re.compile( + r'((?:/|-|--)[-_a-zA-Z0-9]+)(\s*.*?)(?=,\s+(?:/|-|--)|$)') + + +class GenericObject(ObjectDescription): + """ + A generic x-ref directive registered with Sphinx.add_description_unit(). + """ + indextemplate = '' + parse_node = None + + def parse_signature(self, sig, signode): + if self.parse_node: + name = self.parse_node(self.env, sig, signode) + else: + signode.clear() + signode += addnodes.desc_name(sig, sig) + # normalize whitespace like XRefRole does + name = ws_re.sub('', sig) + return name + + def add_target_and_index(self, name, sig, signode): + targetname = '%s-%s' % (self.objtype, name) + signode['ids'].append(targetname) + self.state.document.note_explicit_target(signode) + if self.indextemplate: + colon = self.indextemplate.find(':') + if colon != -1: + indextype = self.indextemplate[:colon].strip() + indexentry = self.indextemplate[colon+1:].strip() % (name,) + else: + indextype = 'single' + indexentry = self.indextemplate % (name,) + self.indexnode['entries'].append((indextype, indexentry, + targetname, targetname)) + self.env.domaindata['std']['objects'][self.objtype, name] = \ + self.env.docname, targetname + + +class EnvVar(GenericObject): + indextemplate = l_('environment variable; %s') + + +class Target(Directive): + """ + Generic target for user-defined cross-reference types. + """ + indextemplate = '' + + has_content = False + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + option_spec = {} + + def run(self): + env = self.state.document.settings.env + # normalize whitespace in fullname like XRefRole does + fullname = ws_re.sub('', self.arguments[0].strip()) + targetname = '%s-%s' % (self.name, fullname) + node = nodes.target('', '', ids=[targetname]) + self.state.document.note_explicit_target(node) + ret = [node] + if self.indextemplate: + indexentry = self.indextemplate % (fullname,) + indextype = 'single' + colon = indexentry.find(':') + if colon != -1: + indextype = indexentry[:colon].strip() + indexentry = indexentry[colon+1:].strip() + inode = addnodes.index(entries=[(indextype, indexentry, + targetname, targetname)]) + ret.insert(0, inode) + env.domaindata['std']['objects'][self.name, fullname] = \ + env.docname, targetname + return ret + + +class Cmdoption(ObjectDescription): + """ + Description of a command-line option (.. cmdoption). + """ + + def parse_signature(self, sig, signode): + """Transform an option description into RST nodes.""" + count = 0 + firstname = '' + for m in option_desc_re.finditer(sig): + optname, args = m.groups() + if count: + signode += addnodes.desc_addname(', ', ', ') + signode += addnodes.desc_name(optname, optname) + signode += addnodes.desc_addname(args, args) + if not count: + firstname = optname + count += 1 + if not firstname: + raise ValueError + return firstname + + def add_target_and_index(self, name, sig, signode): + targetname = name.replace('/', '-') + currprogram = self.env.doc_read_data.get('std_program') + if currprogram: + targetname = '-' + currprogram + targetname + targetname = 'cmdoption' + targetname + signode['ids'].append(targetname) + self.state.document.note_explicit_target(signode) + self.indexnode['entries'].append( + ('pair', _('%scommand line option; %s') % + ((currprogram and currprogram + ' ' or ''), sig), + targetname, targetname)) + self.env.domaindata['std']['progoptions'][currprogram, name] = \ + self.env.docname, targetname + + +class Program(Directive): + """ + Directive to name the program for which options are documented. + """ + + has_content = False + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + option_spec = {} + + def run(self): + env = self.state.document.settings.env + program = ws_re.sub('-', self.arguments[0].strip()) + if program == 'None': + env.doc_read_data['std_program'] = None + else: + env.doc_read_data['std_program'] = program + return [] + + +class OptionXRefRole(XRefRole): + innernodeclass = addnodes.literal_emphasis + + def process_link(self, env, pnode, has_explicit_title, title, target): + program = env.doc_read_data.get('std_program') + if not has_explicit_title: + if ' ' in title and not (title.startswith('/') or + title.startswith('-')): + program, target = re.split(' (?=-|--|/)', title, 1) + program = ws_re.sub('-', program) + target = target.strip() + elif ' ' in target: + program, target = re.split(' (?=-|--|/)', target, 1) + program = ws_re.sub('-', program) + pnode['refprogram'] = program + return title, target + + +class Glossary(Directive): + """ + Directive to create a glossary with cross-reference targets + for :term: roles. + """ + + has_content = True + required_arguments = 0 + optional_arguments = 0 + final_argument_whitespace = False + option_spec = { + 'sorted': directives.flag, + } + + def run(self): + env = self.state.document.settings.env + objects = env.domaindata['std']['objects'] + gloss_entries = env.doc_read_data.setdefault('gloss_entries', set()) + node = addnodes.glossary() + node.document = self.state.document + self.state.nested_parse(self.content, self.content_offset, node) + + # the content should be definition lists + dls = [child for child in node + if isinstance(child, nodes.definition_list)] + # now, extract definition terms to enable cross-reference creation + new_dl = nodes.definition_list() + new_dl['classes'].append('glossary') + items = [] + for dl in dls: + for li in dl.children: + if not li.children or not isinstance(li[0], nodes.term): + continue + termtext = li.children[0].astext() + new_id = 'term-' + nodes.make_id(termtext) + if new_id in gloss_entries: + new_id = 'term-' + str(len(gloss_entries)) + gloss_entries.add(new_id) + li[0]['names'].append(new_id) + li[0]['ids'].append(new_id) + objects['term', termtext.lower()] = env.docname, new_id + # add an index entry too + indexnode = addnodes.index() + indexnode['entries'] = [('single', termtext, new_id, termtext)] + li.insert(0, indexnode) + items.append((termtext, li)) + if 'sorted' in self.options: + items.sort(key=lambda x: x[0].lower()) + new_dl.extend(item[1] for item in items) + node.children = [new_dl] + return [node] + + +token_re = re.compile('`([a-z_][a-z0-9_]*)`') + +def token_xrefs(text): + retnodes = [] + pos = 0 + for m in token_re.finditer(text): + if m.start() > pos: + txt = text[pos:m.start()] + retnodes.append(nodes.Text(txt, txt)) + refnode = addnodes.pending_xref( + m.group(1), reftype='token', refdomain='std', reftarget=m.group(1)) + refnode += nodes.literal(m.group(1), m.group(1), classes=['xref']) + retnodes.append(refnode) + pos = m.end() + if pos < len(text): + retnodes.append(nodes.Text(text[pos:], text[pos:])) + return retnodes + + +class ProductionList(Directive): + """ + Directive to list grammar productions. + """ + + has_content = False + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + option_spec = {} + + def run(self): + env = self.state.document.settings.env + objects = env.domaindata['std']['objects'] + node = addnodes.productionlist() + messages = [] + i = 0 + + for rule in self.arguments[0].split('\n'): + if i == 0 and ':' not in rule: + # production group + continue + i += 1 + try: + name, tokens = rule.split(':', 1) + except ValueError: + break + subnode = addnodes.production() + subnode['tokenname'] = name.strip() + if subnode['tokenname']: + idname = 'grammar-token-%s' % subnode['tokenname'] + if idname not in self.state.document.ids: + subnode['ids'].append(idname) + self.state.document.note_implicit_target(subnode, subnode) + objects['token', subnode['tokenname']] = env.docname, idname + subnode.extend(token_xrefs(tokens)) + node.append(subnode) + return [node] + messages + + +class StandardDomain(Domain): + """ + Domain for all objects that don't fit into another domain or are added + via the application interface. + """ + + name = 'std' + label = 'Default' + + object_types = { + 'term': ObjType(l_('glossary term'), 'term', search=False), + 'token': ObjType(l_('grammar token'), 'token', search=False), + 'envvar': ObjType(l_('environment variable'), 'envvar'), + 'cmdoption': ObjType(l_('program option'), 'option'), + } + + directives = { + 'program': Program, + 'cmdoption': Cmdoption, + 'envvar': EnvVar, + 'glossary': Glossary, + 'productionlist': ProductionList, + } + roles = { + 'option': OptionXRefRole(innernodeclass=addnodes.literal_emphasis), + 'envvar': XRefRole(), # XXX add index entries + 'token': XRefRole(), + 'term': XRefRole(lowercase=True, innernodeclass=nodes.emphasis), + } + + initial_data = { + 'progoptions': {}, # (program, name) -> docname, labelid + 'objects': {}, # (type, name) -> docname, labelid + } + + def clear_doc(self, docname): + for key, (fn, _) in self.data['progoptions'].items(): + if fn == docname: + del self.data['progoptions'][key] + for key, (fn, _) in self.data['objects'].items(): + if fn == docname: + del self.data['objects'][key] + + def resolve_xref(self, env, fromdocname, builder, + typ, target, node, contnode): + if typ == 'option': + progname = node['refprogram'] + docname, labelid = self.data['progoptions'].get((progname, target), + ('', '')) + if not docname: + return None + else: + return make_refnode(builder, fromdocname, docname, + labelid, contnode) + else: + docname, labelid = self.data['objects'].get((typ, target), + ('', '')) + if not docname: + if typ == 'term': + env.warn(fromdocname, 'term not in glossary: %s' % target, + node.line) + return None + else: + return make_refnode(builder, fromdocname, docname, + labelid, contnode) + + def get_objects(self): + return [] # XXX implement diff --git a/sphinx/environment.py b/sphinx/environment.py index bd93c91c4..db48acec1 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -39,7 +39,6 @@ from sphinx import addnodes from sphinx.util import movefile, get_matching_docs, SEP, ustrftime, \ docname_join, FilenameUniqDict, url_re, make_refnode from sphinx.errors import SphinxError, ExtensionError -from sphinx.directives import additional_xref_types orig_role_function = roles.role orig_directive_function = directives.directive @@ -56,7 +55,7 @@ default_settings = { # This is increased every time an environment attribute is added # or changed to properly invalidate pickle files. -ENV_VERSION = 31 +ENV_VERSION = 32 default_substitutions = set([ @@ -292,14 +291,12 @@ class BuildEnvironment: self.numbered_toctrees = set() # docnames that have :numbered: toctrees # domain-specific inventories, here to be pickled - self.domaindata = {} # domainname -> domain-specific object + self.domaindata = {} # domainname -> domain-specific dict # X-ref target inventory self.labels = {} # labelname -> docname, labelid, sectionname self.anonlabels = {} # labelname -> docname, labelid - self.progoptions = {} # (program, name) -> docname, labelid - self.reftargets = {} # (type, name) -> docname, labelid - # type: term, token, envvar, citation + self.citations = {} # citation name -> docname, labelid # Other inventories self.indexentries = {} # docname -> list of @@ -311,15 +308,8 @@ class BuildEnvironment: self.images = FilenameUniqDict() self.dlfiles = FilenameUniqDict() - # These are set while parsing a file - self.docname = None # current document name - # XXX remove currmodule and currclass from here - self.currmodule = None # current module name - self.currclass = None # current class name - self.currdesc = None # current descref name - self.currprogram = None # current program name - self.index_num = 0 # autonumber for index targets - self.gloss_entries = set() # existing definition labels + # temporary data storage while reading a document + self.doc_read_data = {} # Some magically present labels def add_magic_label(name, description): @@ -366,12 +356,9 @@ class BuildEnvironment: for labelname, (fn, _, _) in self.labels.items(): if fn == docname: del self.labels[labelname] - for key, (fn, _) in self.reftargets.items(): + for key, (fn, _) in self.citations.items(): if fn == docname: - del self.reftargets[key] - for key, (fn, _) in self.progoptions.items(): - if fn == docname: - del self.progoptions[key] + del self.citations[key] for version, changes in self.versionchanges.items(): new = [change for change in changes if change[1] != docname] changes[:] = new @@ -574,7 +561,7 @@ class BuildEnvironment: self.warn(docname, 'default role %s not found' % self.config.default_role) - self.docname = docname + self.doc_read_data['docname'] = docname self.settings['input_encoding'] = self.config.source_encoding self.settings['trim_footnote_reference_space'] = \ self.config.trim_footnote_reference_space @@ -603,7 +590,9 @@ class BuildEnvironment: # monkey-patch, so that domain directives take precedence def directive(directive_name, language_module, document): + """Lookup a directive.""" directive_name = directive_name.lower() + # explicit domain given? if ':' in directive_name: domain_name, directive_name = directive_name.split(':', 1) if domain_name in self.domains: @@ -611,16 +600,25 @@ class BuildEnvironment: directive = domain.directive(directive_name) if directive is not None: return directive, [] + # else look in the default domain elif self.default_domain is not None: directive = self.default_domain.directive(directive_name) if directive is not None: return directive, [] + # always look in the std domain + # (XXX or register them in the docutils namespace?) + directive = self.domains['std'].directive(directive_name) + if directive is not None: + return directive, [] + # last, look in the default docutils namespace return orig_directive_function(directive_name, language_module, document) directives.directive = directive def role(role_name, language_module, lineno, reporter): + """Lookup a role name.""" role_name = role_name.lower() + # explicit domain given? if ':' in role_name: domain_name, role_name = role_name.split(':', 1) if domain_name in self.domains: @@ -628,10 +626,16 @@ class BuildEnvironment: role = domain.role(role_name) if role is not None: return role, [] + # else look in the default domain elif self.default_domain is not None: role = self.default_domain.role(role_name) if role is not None: return role, [] + # always look in the std domain + role = self.domains['std'].role(role_name) + if role is not None: + return role, [] + # last, look in the default docutils namespace return orig_role_function(role_name, language_module, lineno, reporter) roles.role = role @@ -678,11 +682,8 @@ class BuildEnvironment: metanode.__class__ = addnodes.meta # cleanup - self.docname = None - self.currmodule = None - self.currclass = None + self.doc_read_data.clear() self.default_domain = None - self.gloss_entries = set() if save_parsed: # save the parsed doctree @@ -699,6 +700,33 @@ class BuildEnvironment: else: return doctree + # utilities to use while reading a document + + @property + def docname(self): + """Backwards compatible alias.""" + return self.doc_read_data['docname'] + + @property + def currmodule(self): + """Backwards compatible alias.""" + return self.doc_read_data.get('py_module') + + @property + def currclass(self): + """Backwards compatible alias.""" + return self.doc_read_data.get('py_class') + + def new_serialno(self, category=''): + """Return a serial number, e.g. for index entry targets.""" + key = category + 'serialno' + cur = self.doc_read_data.get(key, 0) + self.doc_read_data[key] = cur + 1 + return cur + + def note_dependency(self, filename): + self.dependencies.setdefault(self.docname, set()).add(filename) + def filter_messages(self, doctree): """ Filter system messages from a doctree. @@ -887,11 +915,11 @@ class BuildEnvironment: def note_citations_from(self, docname, document): for node in document.traverse(nodes.citation): label = node[0].astext() - if ('citation', label) in self.reftargets: + if label in self.citations: self.warn(docname, 'duplicate citation %s, ' % label + 'other instance in %s' % self.doc2path( - self.reftargets['citation', label][0]), node.line) - self.reftargets['citation', label] = (docname, node['ids'][0]) + self.citations[label][0]), node.line) + self.citations[label] = (docname, node['ids'][0]) def note_toctree(self, docname, toctreenode): """Note a TOC tree directive in a document and gather information about @@ -988,24 +1016,14 @@ class BuildEnvironment: if result is not None: return result - # ------- - # these are called from docutils directives and therefore use self.docname - # - def note_progoption(self, optname, labelid): - self.progoptions[self.currprogram, optname] = (self.docname, labelid) - - def note_reftarget(self, type, name, labelid): - self.reftargets[type, name] = (self.docname, labelid) - + # XXX remove def note_versionchange(self, type, version, node, lineno): self.versionchanges.setdefault(version, []).append( - (type, self.docname, lineno, self.currmodule, self.currdesc, + (type, self.docname, lineno, + self.doc_read_data.get('py_module'), + self.doc_read_data.get('object'), node.astext())) - def note_dependency(self, filename): - self.dependencies.setdefault(self.docname, set()).add(filename) - # ------- - def get_domain(self, domainname): """Return the domain instance with the specified name. Raises an ExtensionError if the domain is not registered.""" @@ -1197,11 +1215,6 @@ class BuildEnvironment: return newnode def resolve_references(self, doctree, fromdocname, builder): - # XXX remove this - reftarget_roles = set(('token', 'term', 'citation')) - # add all custom xref types too - reftarget_roles.update(i[0] for i in additional_xref_types.values()) - for node in doctree.traverse(addnodes.pending_xref): contnode = node[0].deepcopy() newnode = None @@ -1218,6 +1231,7 @@ class BuildEnvironment: raise NoUri newnode = domain.resolve_xref(self, fromdocname, builder, typ, target, node, contnode) + # really hardwired reference types elif typ == 'ref': if node['refcaption']: # reference to anonymous label; the reference uses @@ -1276,37 +1290,21 @@ class BuildEnvironment: newnode['refuri'] = builder.get_relative_uri( fromdocname, docname) newnode.append(innernode) + elif typ == 'citation': + docname, labelid = self.citations.get(target, ('', '')) + if not docname: + self.warn(fromdocname, + 'citation not found: %s' % target, node.line) + newnode = None + else: + newnode = make_refnode(builder, fromdocname, docname, + labelid, contnode) elif typ == 'keyword': - # keywords are referenced by named labels + # keywords are oddballs: they are referenced by named labels docname, labelid, _ = self.labels.get(target, ('','','')) if not docname: #self.warn(fromdocname, 'unknown keyword: %s' % target) - newnode = contnode - else: - newnode = make_refnode(builder, fromdocname, docname, - labelid, contnode) - elif typ == 'option': - progname = node['refprogram'] - docname, labelid = self.progoptions.get((progname, target), - ('', '')) - if not docname: - newnode = contnode - else: - newnode = make_refnode(builder, fromdocname, docname, - labelid, contnode) - elif typ in reftarget_roles: - docname, labelid = self.reftargets.get((typ, target), - ('', '')) - if not docname: - if typ == 'term': - self.warn(fromdocname, - 'term not in glossary: %s' % target, - node.line) - elif typ == 'citation': - self.warn(fromdocname, - 'citation not found: %s' % target, - node.line) - newnode = contnode + newnode = None else: newnode = make_refnode(builder, fromdocname, docname, labelid, contnode) diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc.py index 705853c20..3501e15b9 100644 --- a/sphinx/ext/autodoc.py +++ b/sphinx/ext/autodoc.py @@ -546,9 +546,9 @@ class Documenter(object): do all members, else those given by *self.options.members*. """ # set current namespace for finding members - self.env.autodoc_current_module = self.modname + self.env.doc_read_data['autodoc_module'] = self.modname if self.objpath: - self.env.autodoc_current_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 @@ -589,8 +589,8 @@ class Documenter(object): check_module=members_check_module) # reset current objects - self.env.autodoc_current_module = None - self.env.autodoc_current_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): @@ -748,11 +748,10 @@ class ModuleLevelDocumenter(Documenter): else: # if documenting a toplevel object without explicit module, # it can be contained in another auto directive ... - if hasattr(self.env, 'autodoc_current_module'): - modname = self.env.autodoc_current_module + modname = self.env.doc_read_data.get('autodoc_module') # ... or in the scope of a module directive if not modname: - modname = self.env.currmodule + modname = self.env.doc_read_data.get('py_module') # ... else, it stays None, which means invalid return modname, parents + [base] @@ -771,21 +770,20 @@ class ClassLevelDocumenter(Documenter): # if documenting a class-level object without path, # there must be a current class, either from a parent # auto directive ... - if hasattr(self.env, 'autodoc_current_class'): - mod_cls = self.env.autodoc_current_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.currclass + 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, [] modname, cls = rpartition(mod_cls, '.') parents = [cls] # if the module name is still missing, get it like above - if not modname and hasattr(self.env, 'autodoc_current_module'): - modname = self.env.autodoc_current_module if not modname: - modname = self.env.currmodule + modname = self.env.doc_read_data.get('autodoc_module') + if not modname: + modname = self.env.doc_read_data.get('py_module') # ... else, it stays None, which means invalid return modname, parents + [base] @@ -1082,7 +1080,7 @@ class AutoDirective(Directive): # record all filenames as dependencies -- this will at least # partially make automatic invalidation possible for fn in self.filename_set: - self.env.note_dependency(fn) + self.state.document.settings.record_dependencies.add(fn) # use a custom reporter that correctly assigns lines to source # filename/description and lineno diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index 09212eed0..0569b75d9 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -228,8 +228,9 @@ class Autosummary(Directive): env = self.state.document.settings.env prefixes = [''] - if env.currmodule: - prefixes.insert(0, env.currmodule) + currmodule = env.doc_read_data.get('py_module') + if currmodule: + prefixes.insert(0, currmodule) items = [] diff --git a/sphinx/ext/inheritance_diagram.py b/sphinx/ext/inheritance_diagram.py index e464adb57..dc3afa4dc 100644 --- a/sphinx/ext/inheritance_diagram.py +++ b/sphinx/ext/inheritance_diagram.py @@ -283,7 +283,8 @@ class InheritanceDiagram(Directive): # Create a graph starting with the list of classes try: - graph = InheritanceGraph(class_names, env.currmodule) + graph = InheritanceGraph(class_names, + env.doc_read_data.get('py_module')) except InheritanceException, err: return [node.document.reporter.warning(err.args[0], line=self.lineno)] diff --git a/sphinx/ext/intersphinx.py b/sphinx/ext/intersphinx.py index 3c97a7342..7756ae707 100644 --- a/sphinx/ext/intersphinx.py +++ b/sphinx/ext/intersphinx.py @@ -3,17 +3,17 @@ sphinx.ext.intersphinx ~~~~~~~~~~~~~~~~~~~~~~ - Insert links to Python objects documented in remote Sphinx documentation. + Insert links to objects documented in remote Sphinx documentation. This works as follows: - * Each Sphinx HTML build creates a file named "objects.inv" that contains - a mapping from Python identifiers to URIs relative to the HTML set's root. + * Each Sphinx HTML build creates a file named "objects.inv" that contains a + mapping from object names to URIs relative to the HTML set's root. * Projects using the Intersphinx extension can specify links to such mapping files in the `intersphinx_mapping` config value. The mapping will then be - used to resolve otherwise missing references to Python objects into links - to the other documentation. + used to resolve otherwise missing references to objects into links to the + other documentation. * By default, the mapping file is assumed to be at the same location as the rest of the documentation; however, the location of the mapping file can @@ -41,12 +41,44 @@ if hasattr(urllib2, 'HTTPSHandler'): urllib2.install_opener(urllib2.build_opener(*handlers)) +def fetch_inventory_v1(f, uri, join): + invdata = {} + line = f.next() + projname = line.rstrip()[11:].decode('utf-8') + line = f.next() + version = line.rstrip()[11:] + for line in f: + name, type, location = line.rstrip().split(None, 2) + location = join(uri, location) + # version 1 did not add anchors to the location + if type == 'mod': + type = 'py:module' + location += '#module-' + name + else: + location += '#' + name + invdata.setdefault(type, {})[name] = (projname, version, location) + return invdata + + +def fetch_inventory_v2(f, uri, join): + invdata = {} + line = f.next() + projname = line.rstrip()[11:].decode('utf-8') + line = f.next() + version = line.rstrip()[11:] + for line in f: + name, type, prio, location = line.rstrip().split(None, 3) + location = join(uri, location) + invdata.setdefault(type, {})[name] = (projname, version, location) + return invdata + + def fetch_inventory(app, uri, inv): """Fetch, parse and return an intersphinx inventory file.""" - invdata = {} # both *uri* (base URI of the links to generate) and *inv* (actual # location of the inventory file) can be local or remote URIs localuri = uri.find('://') == -1 + join = localuri and path.join or posixpath.join try: if inv.find('://') != -1: f = urllib2.urlopen(inv) @@ -57,24 +89,18 @@ def fetch_inventory(app, uri, inv): '%s: %s' % (inv, err.__class__, err)) return try: - line = f.next() - if line.rstrip() != '# Sphinx inventory version 1': + line = f.next().rstrip() + if line == '# Sphinx inventory version 1': + invdata = fetch_inventory_v1(f, uri, join) + elif line == '# Sphinx inventory version 2': + invdata = fetch_inventory_v2(f, uri, join) + else: + f.close() raise ValueError('unknown or unsupported inventory version') - line = f.next() - projname = line.rstrip()[11:].decode('utf-8') - line = f.next() - version = line.rstrip()[11:] - for line in f: - name, type, location = line.rstrip().split(None, 2) - if localuri: - location = path.join(uri, location) - else: - location = posixpath.join(uri, location) - invdata[name] = (type, projname, version, location) f.close() except Exception, err: app.warn('intersphinx inventory %r not readable due to ' - '%s: %s' % (inv, err.__class__, err)) + '%s: %s' % (inv, err.__class__.__name__, err)) else: return invdata @@ -111,27 +137,29 @@ def load_mappings(app): def missing_reference(app, env, node, contnode): """Attempt to resolve a missing reference via intersphinx references.""" type = node['reftype'] + domain = node['refdomain'] + fulltype = domain + ':' + type target = node['reftarget'] - if type == 'mod': - type, proj, version, uri = env.intersphinx_inventory.get(target, - ('','','','')) - if type != 'mod': - return None - target = 'module-' + target # for link anchor - else: - if target[-2:] == '()': - target = target[:-2] - target = target.rstrip(' *') - # special case: exceptions and object methods - if type == 'exc' and '.' not in target and \ - 'exceptions.' + target in env.intersphinx_inventory: - target = 'exceptions.' + target - elif type in ('func', 'meth') and '.' not in target and \ - 'object.' + target in env.intersphinx_inventory: - target = 'object.' + target - if target not in env.intersphinx_inventory: - return None - type, proj, version, uri = env.intersphinx_inventory[target] + if type not in env.intersphinx_inventory: + return + if target not in env.intersphinx_inventory[type]: + return + proj, version, uri = env.intersphinx_inventory[type][target] + + # XXX + if target[-2:] == '()': + target = target[:-2] + target = target.rstrip(' *') + # special case: exceptions and object methods + if type == 'exc' and '.' not in target and \ + 'exceptions.' + target in env.intersphinx_inventory: + target = 'exceptions.' + target + elif type in ('func', 'meth') and '.' not in target and \ + 'object.' + target in env.intersphinx_inventory: + target = 'object.' + target + if target not in env.intersphinx_inventory: + return None + type, proj, version, uri = env.intersphinx_inventory[target] # print "Intersphinx hit:", target, uri newnode = nodes.reference('', '') newnode['refuri'] = uri + '#' + target diff --git a/sphinx/ext/refcounting.py b/sphinx/ext/refcounting.py index cad9d7f1a..1845cfaba 100644 --- a/sphinx/ext/refcounting.py +++ b/sphinx/ext/refcounting.py @@ -69,7 +69,7 @@ class Refcounts(dict): def add_refcount_annotations(self, app, doctree): for node in doctree.traverse(addnodes.desc_content): par = node.parent - if par['desctype'] != 'cfunction': + if par['domain'] != 'c' or par['objtype'] != 'function': continue if not par[0].has_key('names') or not par[0]['names']: continue diff --git a/sphinx/ext/todo.py b/sphinx/ext/todo.py index 249fb3611..5ab97f40a 100644 --- a/sphinx/ext/todo.py +++ b/sphinx/ext/todo.py @@ -34,9 +34,7 @@ class Todo(Directive): def run(self): env = self.state.document.settings.env - - targetid = "todo-%s" % env.index_num - env.index_num += 1 + targetid = 'index-%s' % env.new_serialno('index') targetnode = nodes.target('', '', ids=[targetid]) ad = make_admonition(todo_node, self.name, [_('Todo')], self.options, diff --git a/sphinx/roles.py b/sphinx/roles.py index db231463f..7c5e8ed35 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -41,7 +41,7 @@ for rolename, nodeclass in generic_docroles.iteritems(): # -- generic cross-reference roles --------------------------------------------- class XRefRole(object): - """XXX add docstring""" + """XXX add docstrings""" nodeclass = addnodes.pending_xref innernodeclass = nodes.literal @@ -101,24 +101,6 @@ class XRefRole(object): return [pnode], [] -class OptionXRefRole(XRefRole): - innernodeclass = addnodes.literal_emphasis - - def process_link(self, env, pnode, has_explicit_title, title, target): - program = env.currprogram - if not has_explicit_title: - if ' ' in title and not (title.startswith('/') or - title.startswith('-')): - program, target = re.split(' (?=-|--|/)', title, 1) - program = ws_re.sub('-', program) - target = target.strip() - elif ' ' in target: - program, target = re.split(' (?=-|--|/)', target, 1) - program = ws_re.sub('-', program) - pnode['refprogram'] = program - return title, target - - _EnvvarXrefRole = XRefRole() def indexmarkup_role(typ, rawtext, etext, lineno, inliner, @@ -129,11 +111,11 @@ def indexmarkup_role(typ, rawtext, etext, lineno, inliner, else: typ = typ.lower() text = utils.unescape(etext) - targetid = 'index-%s' % env.index_num - env.index_num += 1 + targetid = 'index-%s' % env.new_serialno('index') indexnode = addnodes.index() targetnode = nodes.target('', '', ids=[targetid]) inliner.document.note_explicit_target(targetnode) + # XXX remove if typ == 'envvar': indexnode['entries'] = [('single', text, targetid, text), ('single', _('environment variable; %s') % text, @@ -214,9 +196,6 @@ def abbr_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): specific_docroles = { 'keyword': XRefRole(), 'ref': XRefRole(lowercase=True, innernodeclass=nodes.emphasis), - 'token': XRefRole(), - 'term': XRefRole(lowercase=True, innernodeclass=nodes.emphasis), - 'option': OptionXRefRole(innernodeclass=addnodes.literal_emphasis), 'doc': XRefRole(), 'download': XRefRole(nodeclass=addnodes.download_reference), @@ -233,7 +212,7 @@ for rolename, func in specific_docroles.iteritems(): roles.register_local_role(rolename, func) -# compatibility alias +# backwards compatibility alias def xfileref_role(*args, **kwds): warnings.warn('xfileref_role is deprecated, use XRefRole', DeprecationWarning, stacklevel=2) diff --git a/sphinx/search.py b/sphinx/search.py index 7f1a87a97..888d3ad69 100644 --- a/sphinx/search.py +++ b/sphinx/search.py @@ -119,8 +119,8 @@ class IndexBuilder(object): self._titles = {} # stemmed word -> set(filenames) self._mapping = {} - # desctypes -> index - self._desctypes = {} + # objtype -> index + self._objtypes = {} def load(self, stream, format): """Reconstruct from frozen data.""" @@ -138,7 +138,7 @@ class IndexBuilder(object): self._mapping[k] = set([index2fn[v]]) else: self._mapping[k] = set(index2fn[i] for i in v) - # no need to load keywords/desctypes + # no need to load keywords/objtypes def dump(self, stream, format): """Dump the frozen index to a stream.""" @@ -157,7 +157,7 @@ class IndexBuilder(object): def get_descrefs(self, fn2index): rv = {} - dt = self._desctypes + dt = self._objtypes # XXX implement search capability return rv for fullname, (doc, desctype) in self.env.descrefs.iteritems(): @@ -193,9 +193,10 @@ class IndexBuilder(object): filenames=filenames, titles=titles, terms=self.get_terms(fn2index), - descrefs=self.get_descrefs(fn2index), - modules=self.get_modules(fn2index), - desctypes=dict((v, k) for (k, v) in self._desctypes.items()), + # XXX + #descrefs=self.get_descrefs(fn2index), + #modules=self.get_modules(fn2index), + objtypes=dict((v, k) for (k, v) in self._objtypes.items()), ) def prune(self, filenames): diff --git a/sphinx/themes/basic/static/searchtools.js b/sphinx/themes/basic/static/searchtools.js index 36acb12ee..83909f205 100644 --- a/sphinx/themes/basic/static/searchtools.js +++ b/sphinx/themes/basic/static/searchtools.js @@ -330,9 +330,8 @@ var Search = { var filenames = this._index.filenames; var titles = this._index.titles; var terms = this._index.terms; - var descrefs = this._index.descrefs; - var modules = this._index.modules; - var desctypes = this._index.desctypes; + var objects = this._index.objects; + var objtypes = this._index.objtypes; var fileMap = {}; var files = null; var objectResults = []; @@ -341,6 +340,7 @@ var Search = { // lookup as object if (object != null) { + // XXX must be adapted for (var module in modules) { if (module.indexOf(object) > -1) { fn = modules[module]; @@ -348,12 +348,12 @@ var Search = { objectResults.push([filenames[fn], module, '#module-'+module, descr]); } } - for (var prefix in descrefs) { - for (var name in descrefs[prefix]) { + for (var prefix in objects) { + for (var name in objects[prefix]) { var fullname = (prefix ? prefix + '.' : '') + name; if (fullname.toLowerCase().indexOf(object) > -1) { - match = descrefs[prefix][name]; - descr = desctypes[match[1]] + _(', in ') + titles[match[0]]; + match = objects[prefix][name]; + descr = objtypes[match[1]] + _(', in ') + titles[match[0]]; objectResults.push([filenames[match[0]], fullname, '#'+fullname, descr]); } } diff --git a/sphinx/writers/html.py b/sphinx/writers/html.py index c15e5ce5b..5a47b8276 100644 --- a/sphinx/writers/html.py +++ b/sphinx/writers/html.py @@ -61,7 +61,7 @@ class HTMLTranslator(BaseTranslator): self.add_permalinks = builder.config.html_add_permalinks def visit_desc(self, node): - self.body.append(self.starttag(node, 'dl', CLASS=node['desctype'])) + self.body.append(self.starttag(node, 'dl', CLASS=node['objtype'])) def depart_desc(self, node): self.body.append('\n\n') @@ -69,7 +69,7 @@ class HTMLTranslator(BaseTranslator): # the id is set automatically self.body.append(self.starttag(node, 'dt')) # anchor for per-desc interactive data - if node.parent['desctype'] != 'describe' \ + if node.parent['objtype'] != 'describe' \ and node['ids'] and node['first']: self.body.append('' % node['ids'][0]) def depart_desc_signature(self, node): diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 700867796..7e796270c 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -123,7 +123,7 @@ class Table(object): class Desc(object): def __init__(self, node): - self.env = LaTeXTranslator.desc_map.get(node['desctype'], 'describe') + self.env = LaTeXTranslator.desc_map.get(node['objtype'], 'describe') self.type = self.cls = self.name = self.params = \ self.annotation = self.returns = '' self.count = 0 @@ -462,7 +462,7 @@ class LaTeXTranslator(nodes.NodeVisitor): def depart_desc_signature(self, node): d = self.descstack[-1] d.cls = d.cls.rstrip('.') - if node.parent['desctype'] != 'describe' and node['ids']: + if node.parent['objtype'] != 'describe' and node['ids']: hyper = '\\hypertarget{%s}{}' % node['ids'][0] else: hyper = '' diff --git a/sphinx/writers/text.py b/sphinx/writers/text.py index 9e7bb63d2..5177e6089 100644 --- a/sphinx/writers/text.py +++ b/sphinx/writers/text.py @@ -174,8 +174,8 @@ class TextTranslator(nodes.NodeVisitor): def visit_desc_signature(self, node): self.new_state(0) - if node.parent['desctype'] in ('class', 'exception'): - self.add_text('%s ' % node.parent['desctype']) + if node.parent['objtype'] in ('class', 'exception'): + self.add_text('%s ' % node.parent['objtype']) def depart_desc_signature(self, node): # XXX: wrap signatures in a way that makes sense self.end_state(wrap=False, end=None) diff --git a/tests/root/contents.txt b/tests/root/contents.txt index 9891c64ef..e1468ad04 100644 --- a/tests/root/contents.txt +++ b/tests/root/contents.txt @@ -16,7 +16,7 @@ Contents: subdir/includes includes markup - desc + objects bom math autodoc diff --git a/tests/root/desc.txt b/tests/root/objects.txt similarity index 100% rename from tests/root/desc.txt rename to tests/root/objects.txt diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index 8e0114386..0820e0d22 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.autodoc_current_module = 'util' + directive.env.doc_read_data['autodoc_module'] = 'util' verify('function', 'raises', ('util', ['raises'], None, None)) - directive.env.autodoc_current_module = None - directive.env.currmodule = '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.currmodule = 'foo' + directive.env.doc_read_data['py_module'] = 'foo' verify('method', 'util.TestApp.cleanup', ('util', ['TestApp', 'cleanup'], None, None)) - directive.env.currmodule = 'util' - directive.env.currclass = 'Foo' - directive.env.autodoc_current_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 - directive.env.currmodule = None - directive.env.currclass = None - directive.env.autodoc_current_class = None + 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.currmodule = '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) @@ -428,7 +428,7 @@ def test_generate(): 'class', 'Outer', all_members=True) # test generation for C modules (which have no source file) - directive.env.currmodule = 'time' + directive.env.doc_read_data['py_module'] = 'time' assert_processes([('function', 'time.asctime')], 'function', 'asctime') assert_processes([('function', 'time.asctime')], 'function', 'asctime') diff --git a/tests/test_build.py b/tests/test_build.py index e0a59e192..0d72017c6 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -111,11 +111,11 @@ HTML_XPATH = { ".//li/tt/em/span[@class='pre']": '^i$', ".//a[@href='http://www.python.org/dev/peps/pep-0008']/strong": 'PEP 8', ".//a[@href='http://tools.ietf.org/html/rfc1.html']/strong": 'RFC 1', - ".//a[@href='desc.html#envvar-HOME']/tt/span[@class='pre']": 'HOME', + ".//a[@href='objects.html#envvar-HOME']/tt/span[@class='pre']": 'HOME', ".//a[@href='#with']/tt/span[@class='pre']": '^with$', ".//a[@href='#grammar-token-try_stmt']/tt/span": '^statement$', ".//a[@href='subdir/includes.html']/em": 'Including in subdir', - ".//a[@href='desc.html#cmdoption-python-c']/em": 'Python -c option', + ".//a[@href='objects.html#cmdoption-python-c']/em": 'Python -c option', # abbreviations ".//abbr[@title='abbreviation']": '^abbr$', # version stuff @@ -141,13 +141,13 @@ HTML_XPATH = { ".//p": 'In both.', ".//p": 'Always present', }, - 'desc.html': { + 'objects.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']": '', + ".//dt[@id='userdesc-myobj']": '', + ".//a[@href='#userdesc-myobj']": '', ".//span[@class='pre']": 'CFunction()', }, 'contents.html': { diff --git a/tests/test_env.py b/tests/test_env.py index e9118a633..660d0e3be 100644 --- a/tests/test_env.py +++ b/tests/test_env.py @@ -95,7 +95,7 @@ def test_object_inventory(): refs = env.domaindata['py']['objects'] assert 'func_without_module' in refs - assert refs['func_without_module'] == ('desc', 'function') + assert refs['func_without_module'] == ('objects', 'function') assert 'func_without_module2' in refs assert 'mod.func_in_module' in refs assert 'mod.Cls' in refs @@ -110,7 +110,7 @@ def test_object_inventory(): assert 'func_noindex' not in refs assert env.domaindata['py']['modules']['mod'] == \ - ('desc', 'Module synopsis.', 'UNIX', False) + ('objects', 'Module synopsis.', 'UNIX', False) assert env.domains['py'].data is env.domaindata['py'] assert env.domains['c'].data is env.domaindata['c'] From 9101593b0183a566248bfea40232bd9328fb8004 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 7 Sep 2009 22:57:46 +0200 Subject: [PATCH 52/85] Remove almost empty sphinx.directives.desc module. --- sphinx/directives/__init__.py | 266 +++++++++++++++++++++++++++++++- sphinx/directives/desc.py | 276 ---------------------------------- 2 files changed, 265 insertions(+), 277 deletions(-) delete mode 100644 sphinx/directives/desc.py diff --git a/sphinx/directives/__init__.py b/sphinx/directives/__init__.py index 2de29010c..dbaa9ad55 100644 --- a/sphinx/directives/__init__.py +++ b/sphinx/directives/__init__.py @@ -9,11 +9,17 @@ :license: BSD, see LICENSE for details. """ +import re + +from docutils import nodes from docutils.parsers.rst import directives from docutils.parsers.rst.directives import images +from sphinx import addnodes +from sphinx.locale import l_ +from sphinx.util.compat import Directive, directive_dwim + # import and register directives -from sphinx.directives.desc import * from sphinx.directives.code import * from sphinx.directives.other import * @@ -25,3 +31,261 @@ try: except AttributeError: images.figure.options['figwidth'] = \ directives.length_or_percentage_or_unitless + + +def _is_only_paragraph(node): + """True if the node only contains one paragraph (and system messages).""" + if len(node) == 0: + return False + elif len(node) > 1: + for subnode in node[1:]: + if not isinstance(subnode, nodes.system_message): + return False + if isinstance(node[0], nodes.paragraph): + return True + return False + + +# RE to strip backslash escapes +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. + """ + + has_content = True + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = True + option_spec = { + 'noindex': directives.flag, + 'module': directives.unchanged, + } + + doc_fields_with_arg = { + 'param': '%param', + 'parameter': '%param', + 'arg': '%param', + 'argument': '%param', + 'keyword': '%param', + 'kwarg': '%param', + 'kwparam': '%param', + 'type': '%type', + 'raises': l_('Raises'), + 'raise': l_('Raises'), + 'exception': l_('Raises'), + 'except': l_('Raises'), + 'var': l_('Variable'), + 'ivar': l_('Variable'), + 'cvar': l_('Variable'), + 'returns': l_('Returns'), + 'return': l_('Returns'), + } + + doc_fields_with_linked_arg = ('raises', 'raise', 'exception', 'except') + + doc_fields_without_arg = { + 'returns': l_('Returns'), + 'return': l_('Returns'), + 'rtype': l_('Return type'), + } + + def handle_doc_fields(self, node): + """ + Convert field lists with known keys inside the description content into + better-looking equivalents. + """ + # don't traverse, only handle field lists that are immediate children + for child in node.children: + if not isinstance(child, nodes.field_list): + continue + params = [] + pfield = None + param_nodes = {} + param_types = {} + new_list = nodes.field_list() + for field in child: + fname, fbody = field + try: + typ, obj = fname.astext().split(None, 1) + typdesc = self.doc_fields_with_arg[typ] + if _is_only_paragraph(fbody): + children = fbody.children[0].children + else: + children = fbody.children + if typdesc == '%param': + if not params: + # add the field that later gets all the parameters + pfield = nodes.field() + new_list += pfield + dlitem = nodes.list_item() + dlpar = nodes.paragraph() + dlpar += nodes.emphasis(obj, obj) + dlpar += nodes.Text(' -- ', ' -- ') + dlpar += children + param_nodes[obj] = dlpar + dlitem += dlpar + params.append(dlitem) + elif typdesc == '%type': + typenodes = fbody.children + if _is_only_paragraph(fbody): + typenodes = ([nodes.Text(' (')] + + typenodes[0].children + + [nodes.Text(')')]) + param_types[obj] = typenodes + else: + fieldname = typdesc + ' ' + nfield = nodes.field() + nfieldname = nodes.field_name(fieldname, fieldname) + nfield += nfieldname + node = nfieldname + if typ in self.doc_fields_with_linked_arg: + # XXX currmodule/currclass + node = addnodes.pending_xref( + obj, reftype='obj', refcaption=False, + reftarget=obj) + #, modname=self.env.currmodule + #, classname=self.env.currclass + nfieldname += node + node += nodes.Text(obj, obj) + nfield += nodes.field_body() + nfield[1] += fbody.children + new_list += nfield + except (KeyError, ValueError): + fnametext = fname.astext() + try: + typ = self.doc_fields_without_arg[fnametext] + except KeyError: + # at least capitalize the field name + typ = fnametext.capitalize() + fname[0] = nodes.Text(typ) + new_list += field + if params: + if len(params) == 1: + pfield += nodes.field_name('', _('Parameter')) + pfield += nodes.field_body() + pfield[1] += params[0][0] + else: + pfield += nodes.field_name('', _('Parameters')) + pfield += nodes.field_body() + pfield[1] += nodes.bullet_list() + pfield[1][0].extend(params) + + for param, type in param_types.iteritems(): + if param in param_nodes: + param_nodes[param][1:1] = type + child.replace_self(new_list) + + def get_signatures(self): + """ + Retrieve the signatures to document from the directive arguments. + """ + # remove backslashes to support (dummy) escapes; helps Vim highlighting + return [strip_backslash_re.sub('', sig.strip()) + for sig in self.arguments[0].split('\n')] + + def parse_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 + *sig* is put into a single desc_name node. + """ + raise ValueError + + def add_target_and_index(self, name, sig, signode): + """ + Add cross-reference IDs and entries to self.indexnode, if applicable. + """ + return # do nothing by default + + def before_content(self): + """ + Called before parsing content. Used to set information about the current + directive context on the build environment. + """ + pass + + def after_content(self): + """ + Called after parsing content. Used to reset information about the + current directive context on the build environment. + """ + pass + + def run(self): + if ':' in self.name: + self.domain, self.objtype = self.name.split(':', 1) + else: + self.domain, self.objtype = '', self.name + self.env = self.state.document.settings.env + self.indexnode = addnodes.index(entries=[]) + + node = addnodes.desc() + node.document = self.state.document + node['domain'] = self.domain + # 'desctype' is a backwards compatible attribute + node['objtype'] = node['desctype'] = self.objtype + node['noindex'] = noindex = ('noindex' in self.options) + + self.names = [] + signatures = self.get_signatures() + for i, sig in enumerate(signatures): + # add a signature node for each signature in the current unit + # and add a reference target for it + signode = addnodes.desc_signature(sig, '') + signode['first'] = False + node.append(signode) + try: + # name can also be a tuple, e.g. (classname, objname) + name = self.parse_signature(sig, signode) + except ValueError, err: + # signature parsing failed + signode.clear() + signode += addnodes.desc_name(sig, sig) + continue # we don't want an index entry here + if not noindex and name not in self.names: + # only add target and index entry if this is the first + # description of the object with this name in this desc block + self.names.append(name) + self.add_target_and_index(name, sig, signode) + + contentnode = addnodes.desc_content() + node.append(contentnode) + if self.names: + # needed for association of version{added,changed} directives + self.env.doc_read_data['object'] = self.names[0] + self.before_content() + self.state.nested_parse(self.content, self.content_offset, contentnode) + self.handle_doc_fields(contentnode) + self.env.doc_read_data['object'] = None + self.after_content() + return [self.indexnode, node] + +# backwards compatible old name +DescDirective = ObjectDescription + + +class DefaultDomain(Directive): + """ + Directive to (re-)set the default domain for this source file. + """ + + has_content = False + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = False + option_spec = {} + + def run(self): + env = self.state.document.settings.env + domain_name = arguments[0] + env.default_domain = env.domains.get(domain_name) + + +directives.register_directive('default-domain', directive_dwim(DefaultDomain)) +directives.register_directive('describe', directive_dwim(ObjectDescription)) +# new, more consistent, name +directives.register_directive('object', directive_dwim(ObjectDescription)) diff --git a/sphinx/directives/desc.py b/sphinx/directives/desc.py deleted file mode 100644 index 2d888930c..000000000 --- a/sphinx/directives/desc.py +++ /dev/null @@ -1,276 +0,0 @@ -# -*- coding: utf-8 -*- -""" - sphinx.directives.desc - ~~~~~~~~~~~~~~~~~~~~~~ - - :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" - -import re -import string - -from docutils import nodes -from docutils.parsers.rst import directives - -from sphinx import addnodes -from sphinx.locale import l_ -from sphinx.util.compat import Directive, directive_dwim - - -def _is_only_paragraph(node): - """True if the node only contains one paragraph (and system messages).""" - if len(node) == 0: - return False - elif len(node) > 1: - for subnode in node[1:]: - if not isinstance(subnode, nodes.system_message): - return False - if isinstance(node[0], nodes.paragraph): - return True - return False - - -# RE to strip backslash escapes -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. - """ - - has_content = True - required_arguments = 1 - optional_arguments = 0 - final_argument_whitespace = True - option_spec = { - 'noindex': directives.flag, - 'module': directives.unchanged, - } - - doc_fields_with_arg = { - 'param': '%param', - 'parameter': '%param', - 'arg': '%param', - 'argument': '%param', - 'keyword': '%param', - 'kwarg': '%param', - 'kwparam': '%param', - 'type': '%type', - 'raises': l_('Raises'), - 'raise': l_('Raises'), - 'exception': l_('Raises'), - 'except': l_('Raises'), - 'var': l_('Variable'), - 'ivar': l_('Variable'), - 'cvar': l_('Variable'), - 'returns': l_('Returns'), - 'return': l_('Returns'), - } - - doc_fields_with_linked_arg = ('raises', 'raise', 'exception', 'except') - - doc_fields_without_arg = { - 'returns': l_('Returns'), - 'return': l_('Returns'), - 'rtype': l_('Return type'), - } - - def handle_doc_fields(self, node): - """ - Convert field lists with known keys inside the description content into - better-looking equivalents. - """ - # don't traverse, only handle field lists that are immediate children - for child in node.children: - if not isinstance(child, nodes.field_list): - continue - params = [] - pfield = None - param_nodes = {} - param_types = {} - new_list = nodes.field_list() - for field in child: - fname, fbody = field - try: - typ, obj = fname.astext().split(None, 1) - typdesc = self.doc_fields_with_arg[typ] - if _is_only_paragraph(fbody): - children = fbody.children[0].children - else: - children = fbody.children - if typdesc == '%param': - if not params: - # add the field that later gets all the parameters - pfield = nodes.field() - new_list += pfield - dlitem = nodes.list_item() - dlpar = nodes.paragraph() - dlpar += nodes.emphasis(obj, obj) - dlpar += nodes.Text(' -- ', ' -- ') - dlpar += children - param_nodes[obj] = dlpar - dlitem += dlpar - params.append(dlitem) - elif typdesc == '%type': - typenodes = fbody.children - if _is_only_paragraph(fbody): - typenodes = ([nodes.Text(' (')] + - typenodes[0].children + - [nodes.Text(')')]) - param_types[obj] = typenodes - else: - fieldname = typdesc + ' ' - nfield = nodes.field() - nfieldname = nodes.field_name(fieldname, fieldname) - nfield += nfieldname - node = nfieldname - if typ in self.doc_fields_with_linked_arg: - # XXX currmodule/currclass - node = addnodes.pending_xref( - obj, reftype='obj', refcaption=False, - reftarget=obj) - #, modname=self.env.currmodule - #, classname=self.env.currclass - nfieldname += node - node += nodes.Text(obj, obj) - nfield += nodes.field_body() - nfield[1] += fbody.children - new_list += nfield - except (KeyError, ValueError): - fnametext = fname.astext() - try: - typ = self.doc_fields_without_arg[fnametext] - except KeyError: - # at least capitalize the field name - typ = fnametext.capitalize() - fname[0] = nodes.Text(typ) - new_list += field - if params: - if len(params) == 1: - pfield += nodes.field_name('', _('Parameter')) - pfield += nodes.field_body() - pfield[1] += params[0][0] - else: - pfield += nodes.field_name('', _('Parameters')) - pfield += nodes.field_body() - pfield[1] += nodes.bullet_list() - pfield[1][0].extend(params) - - for param, type in param_types.iteritems(): - if param in param_nodes: - param_nodes[param][1:1] = type - child.replace_self(new_list) - - def get_signatures(self): - """ - Retrieve the signatures to document from the directive arguments. - """ - # remove backslashes to support (dummy) escapes; helps Vim highlighting - return [strip_backslash_re.sub('', sig.strip()) - for sig in self.arguments[0].split('\n')] - - def parse_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 - *sig* is put into a single desc_name node. - """ - raise ValueError - - def add_target_and_index(self, name, sig, signode): - """ - Add cross-reference IDs and entries to self.indexnode, if applicable. - """ - return # do nothing by default - - def before_content(self): - """ - Called before parsing content. Used to set information about the current - directive context on the build environment. - """ - pass - - def after_content(self): - """ - Called after parsing content. Used to reset information about the - current directive context on the build environment. - """ - pass - - def run(self): - if ':' in self.name: - self.domain, self.objtype = self.name.split(':', 1) - else: - self.domain, self.objtype = '', self.name - self.env = self.state.document.settings.env - self.indexnode = addnodes.index(entries=[]) - - node = addnodes.desc() - node.document = self.state.document - node['domain'] = self.domain - # 'desctype' is a backwards compatible attribute - node['objtype'] = node['desctype'] = self.objtype - node['noindex'] = noindex = ('noindex' in self.options) - - self.names = [] - signatures = self.get_signatures() - for i, sig in enumerate(signatures): - # add a signature node for each signature in the current unit - # and add a reference target for it - signode = addnodes.desc_signature(sig, '') - signode['first'] = False - node.append(signode) - try: - # name can also be a tuple, e.g. (classname, objname) - name = self.parse_signature(sig, signode) - except ValueError, err: - # signature parsing failed - signode.clear() - signode += addnodes.desc_name(sig, sig) - continue # we don't want an index entry here - if not noindex and name not in self.names: - # only add target and index entry if this is the first - # description of the object with this name in this desc block - self.names.append(name) - self.add_target_and_index(name, sig, signode) - - contentnode = addnodes.desc_content() - node.append(contentnode) - if self.names: - # needed for association of version{added,changed} directives - self.env.doc_read_data['object'] = self.names[0] - self.before_content() - self.state.nested_parse(self.content, self.content_offset, contentnode) - self.handle_doc_fields(contentnode) - self.env.doc_read_data['object'] = None - self.after_content() - return [self.indexnode, node] - -# backwards compatible old name -DescDirective = ObjectDescription - - -class DefaultDomain(Directive): - """ - Directive to (re-)set the default domain for this source file. - """ - - has_content = False - required_arguments = 1 - optional_arguments = 0 - final_argument_whitespace = False - option_spec = {} - - def run(self): - env = self.state.document.settings.env - domain_name = arguments[0] - env.default_domain = env.domains.get(domain_name) - - -directives.register_directive('default-domain', directive_dwim(DefaultDomain)) -directives.register_directive('describe', directive_dwim(ObjectDescription)) -# new, more consistent, name -directives.register_directive('object', directive_dwim(ObjectDescription)) From 6769daa80a8ce873072a88a751b4ecb05f66467b Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 7 Sep 2009 23:18:17 +0200 Subject: [PATCH 53/85] autodoc: generate directives including domain name. --- sphinx/ext/autodoc.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sphinx/ext/autodoc.py b/sphinx/ext/autodoc.py index 3501e15b9..579dcc194 100644 --- a/sphinx/ext/autodoc.py +++ b/sphinx/ext/autodoc.py @@ -390,9 +390,11 @@ class Documenter(object): def add_directive_header(self, sig): """Add the directive header and options to the generated content.""" + domain = getattr(self, 'domain', 'py') directive = getattr(self, 'directivetype', self.objtype) name = self.format_name() - self.add_line(u'.. %s:: %s%s' % (directive, name, sig), '') + self.add_line(u'.. %s:%s:: %s%s' % (domain, directive, name, sig), + '') if self.options.noindex: self.add_line(u' :noindex:', '') if self.objpath: From a5e68f5f75d34c67bbe31361b7788ff7593a7514 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 7 Sep 2009 23:18:32 +0200 Subject: [PATCH 54/85] C domain: fix reference generation. --- sphinx/domains/c.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index 216ae05d9..278319ae9 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -193,10 +193,11 @@ class CDomain(Domain): typ, target, node, contnode): # strip pointer asterisk target = target.rstrip(' *') - if target not in self.data: + if target not in self.data['objects']: return None - obj = self.data[target] - return make_refnode(builder, fromdocname, obj[0], contnode, target) + 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(): From 9b0dd2c115114ee90baaa4f091999f6f7fa96afe Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 7 Sep 2009 23:35:37 +0200 Subject: [PATCH 55/85] HTML builder: fix inventory generation. --- sphinx/builders/html.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index 26f351157..a8cb462c4 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -716,9 +716,9 @@ class StandaloneHTMLBuilder(Builder): f.write('# Sphinx inventory version 2\n') f.write('# Project: %s\n' % self.config.project.encode('utf-8')) f.write('# Version: %s\n' % self.config.version) - for domain in self.env.domains.itervalues(): + for domainname, domain in self.env.domains.iteritems(): for name, type, docname, anchor, prio in domain.get_objects(): - f.write('%s %s:%s %s %s\n' % (name, domain, type, prio, + f.write('%s %s:%s %s %s\n' % (name, domainname, type, prio, self.get_target_uri(docname) + '#' + anchor)) finally: f.close() From a47e1c4e17fabad90fb096e6c91fffc3adf0de8a Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 7 Sep 2009 23:37:22 +0200 Subject: [PATCH 56/85] util: fix explicit_title_re to allow newlines. --- sphinx/util/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index 87b08095f..2b5630188 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -28,7 +28,7 @@ import sphinx # Generally useful regular expressions. ws_re = re.compile(r'\s+') -explicit_title_re = re.compile('^(.+?)\s*<(.*?)>$') +explicit_title_re = re.compile('^(.+?)\s*<(.*?)>$', re.DOTALL) caption_ref_re = explicit_title_re # b/w compat alias url_re = re.compile(r'(?P.+)://.*') From b4ef4bd670225165c49523effa5273b5622307c2 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 8 Sep 2009 00:04:05 +0200 Subject: [PATCH 57/85] app: allow giving the localized object name when adding new object types. --- doc/conf.py | 3 ++- sphinx/application.py | 10 ++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/doc/conf.py b/doc/conf.py index e3952a0b2..e8db04500 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -132,5 +132,6 @@ def setup(app): parse_directive) app.add_description_unit('role', 'role', 'pair: %s; role', parse_role) app.add_description_unit('confval', 'confval', - 'pair: %s; configuration value') + objname='configuration value', + indextemplate='pair: %s; configuration value') app.add_description_unit('event', 'event', 'pair: %s; event', parse_event) diff --git a/sphinx/application.py b/sphinx/application.py index e50248729..14aee1bea 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -379,9 +379,10 @@ class Sphinx(object): all_domains[domain].roles[name] = role def add_object_type(self, directivename, rolename, indextemplate='', - parse_node=None, ref_nodeclass=None): + parse_node=None, ref_nodeclass=None, objname=''): + # XXX document objname StandardDomain.object_types[directivename] = \ - ObjType(directivename, rolename) + ObjType(objname or directivename, rolename) # create a subclass of GenericObject as the new directive new_directive = type(directivename, (GenericObject, object), {'indextemplate': indextemplate, @@ -394,9 +395,10 @@ class Sphinx(object): add_description_unit = add_object_type def add_crossref_type(self, directivename, rolename, indextemplate='', - ref_nodeclass=None): + ref_nodeclass=None, objname=''): + # XXX document objname StandardDomain.object_types[directivename] = \ - ObjType(directivename, rolename) + ObjType(objname or directivename, rolename) # create a subclass of Target as the new directive new_directive = type(directivename, (Target, object), {'indextemplate': indextemplate}) From 92aa430241c295ada2e753d6bc8dd04589b7a3ab Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 8 Sep 2009 00:04:25 +0200 Subject: [PATCH 58/85] search: make the search work with the new domain model. --- sphinx/domains/__init__.py | 11 ++-- sphinx/domains/c.py | 2 +- sphinx/domains/python.py | 4 +- sphinx/domains/std.py | 10 ++-- sphinx/search.py | 65 +++++++++++------------ sphinx/themes/basic/static/searchtools.js | 34 +++++++----- 6 files changed, 72 insertions(+), 54 deletions(-) diff --git a/sphinx/domains/__init__.py b/sphinx/domains/__init__.py index 7691d2639..e7933d49b 100644 --- a/sphinx/domains/__init__.py +++ b/sphinx/domains/__init__.py @@ -17,7 +17,7 @@ class ObjType(object): """ known_attrs = { - 'search': True, + 'searchprio': 1, } def __init__(self, lname, *roles, **attrs): @@ -158,8 +158,13 @@ class Domain(object): * `type` -- object type, a key in ``self.object_types`` * `docname` -- the document where it is to be found * `anchor` -- the anchor name for the object - * `priority` -- how "important" the object is; an integer - (XXX assign meanings) + * `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 """ return [] diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index 278319ae9..341fe37e0 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -201,4 +201,4 @@ class CDomain(Domain): def get_objects(self): for refname, (docname, type) in self.data['objects'].iteritems(): - yield (refname, type, docname, refname, 0) + yield (refname, type, docname, refname, 1) diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index dfefe24cb..bb06db38a 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -512,6 +512,6 @@ class PythonDomain(Domain): def get_objects(self): for modname, info in self.data['modules'].iteritems(): - yield (modname, 'module', info[0], 'module-' + modname, 1) + yield (modname, 'module', info[0], 'module-' + modname, 0) for refname, (docname, type) in self.data['objects'].iteritems(): - yield (refname, type, docname, refname, 0) + yield (refname, type, docname, refname, 1) diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index a040dedae..ef24d121f 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -301,8 +301,8 @@ class StandardDomain(Domain): label = 'Default' object_types = { - 'term': ObjType(l_('glossary term'), 'term', search=False), - 'token': ObjType(l_('grammar token'), 'token', search=False), + 'term': ObjType(l_('glossary term'), 'term', searchprio=-1), + 'token': ObjType(l_('grammar token'), 'token', searchprio=-1), 'envvar': ObjType(l_('environment variable'), 'envvar'), 'cmdoption': ObjType(l_('program option'), 'option'), } @@ -358,4 +358,8 @@ class StandardDomain(Domain): labelid, contnode) def get_objects(self): - return [] # XXX implement + for (prog, option), info in self.data['progoptions'].iteritems(): + yield (option, 'option', info[0], info[1], 1) + for (type, name), info in self.data['objects'].iteritems(): + yield (name, type, info[0], info[1], + self.object_types[type].attrs['searchprio']) diff --git a/sphinx/search.py b/sphinx/search.py index 888d3ad69..a6335a1a2 100644 --- a/sphinx/search.py +++ b/sphinx/search.py @@ -121,6 +121,8 @@ class IndexBuilder(object): self._mapping = {} # objtype -> index self._objtypes = {} + # objtype index -> objname (localized) + self._objnames = {} def load(self, stream, format): """Reconstruct from frozen data.""" @@ -146,31 +148,30 @@ class IndexBuilder(object): format = self.formats[format] format.dump(self.freeze(), stream) - def get_modules(self, fn2index): + def get_objects(self, fn2index): rv = {} - # XXX implement search capability - return rv - for name, (doc, _, _, _) in self.env.modules.iteritems(): - if doc in fn2index: - rv[name] = fn2index[doc] - return rv - - def get_descrefs(self, fn2index): - rv = {} - dt = self._objtypes - # XXX implement search capability - return rv - for fullname, (doc, desctype) in self.env.descrefs.iteritems(): - if doc not in fn2index: - continue - prefix, name = rpartition(fullname, '.') - pdict = rv.setdefault(prefix, {}) - try: - i = dt[desctype] - except KeyError: - i = len(dt) - dt[desctype] = i - pdict[name] = (fn2index[doc], i) + ot = self._objtypes + on = 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: + continue + if prio < 0: + continue + # XXX splitting at dot is kind of Python specific + prefix, name = rpartition(fullname, '.') + pdict = rv.setdefault(prefix, {}) + try: + i = ot[domainname, type] + except KeyError: + i = len(ot) + ot[domainname, type] = i + otype = domain.object_types.get(type) + if otype: + on[i] = str(otype.lname) # fire translation proxies + else: + on[i] = type + pdict[name] = (fn2index[docname], i, prio) return rv def get_terms(self, fn2index): @@ -189,15 +190,13 @@ class IndexBuilder(object): filenames = self._titles.keys() titles = self._titles.values() fn2index = dict((f, i) for (i, f) in enumerate(filenames)) - return dict( - filenames=filenames, - titles=titles, - terms=self.get_terms(fn2index), - # XXX - #descrefs=self.get_descrefs(fn2index), - #modules=self.get_modules(fn2index), - objtypes=dict((v, k) for (k, v) in self._objtypes.items()), - ) + terms = self.get_terms(fn2index) + objects = self.get_objects(fn2index) # populates _objtypes + objtypes = dict((v, k[0] + ':' + k[1]) + for (k, v) in self._objtypes.iteritems()) + objnames = self._objnames + return dict(filenames=filenames, titles=titles, terms=terms, + objects=objects, objtypes=objtypes, objnames=objnames) def prune(self, filenames): """Remove data for all filenames not in the list.""" diff --git a/sphinx/themes/basic/static/searchtools.js b/sphinx/themes/basic/static/searchtools.js index 83909f205..ede1de00b 100644 --- a/sphinx/themes/basic/static/searchtools.js +++ b/sphinx/themes/basic/static/searchtools.js @@ -332,29 +332,30 @@ var Search = { var terms = this._index.terms; var objects = this._index.objects; var objtypes = this._index.objtypes; + var objnames = this._index.objnames; var fileMap = {}; var files = null; + // different result priorities + var importantResults = []; var objectResults = []; var regularResults = []; + var unimportantResults = []; $('#search-progress').empty(); // lookup as object if (object != null) { - // XXX must be adapted - for (var module in modules) { - if (module.indexOf(object) > -1) { - fn = modules[module]; - descr = _('module, in ') + titles[fn]; - objectResults.push([filenames[fn], module, '#module-'+module, descr]); - } - } for (var prefix in objects) { for (var name in objects[prefix]) { var fullname = (prefix ? prefix + '.' : '') + name; if (fullname.toLowerCase().indexOf(object) > -1) { match = objects[prefix][name]; - descr = objtypes[match[1]] + _(', in ') + titles[match[0]]; - objectResults.push([filenames[match[0]], fullname, '#'+fullname, descr]); + descr = objnames[match[1]] + _(', in ') + titles[match[0]]; + result = [filenames[match[0]], fullname, '#'+fullname, descr]; + switch (match[2]) { + case 1: objectResults.push(result); break; + case 0: importantResults.push(result); break; + case 2: unimportantResults.push(result); break; + } } } } @@ -365,6 +366,14 @@ var Search = { return (a[1] > b[1]) ? -1 : ((a[1] < b[1]) ? 1 : 0); }); + importantResults.sort(function(a, b) { + return (a[1] > b[1]) ? -1 : ((a[1] < b[1]) ? 1 : 0); + }); + + unimportantResults.sort(function(a, b) { + return (a[1] > b[1]) ? -1 : ((a[1] < b[1]) ? 1 : 0); + }); + // perform the search on the required terms for (var i = 0; i < searchterms.length; i++) { @@ -420,8 +429,9 @@ var Search = { return (left > right) ? -1 : ((left < right) ? 1 : 0); }); - // combine both - var results = regularResults.concat(objectResults); + // combine all results + var results = unimportantResults.concat(regularResults) + .concat(objectResults).concat(importantResults); // print the results var resultCount = results.length; From 08586e677558c4a7704c2e773b578eca2435762e Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 8 Sep 2009 00:40:06 +0200 Subject: [PATCH 59/85] HTML builder: compress inventory, which makes e.g. the Python inventory 85k instead of 450k. --- sphinx/builders/html.py | 14 +++++++++--- sphinx/ext/intersphinx.py | 47 ++++++++++++++++++++++++++++++--------- 2 files changed, 48 insertions(+), 13 deletions(-) diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index a8cb462c4..d5a66fef4 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -10,6 +10,7 @@ """ import os +import zlib import codecs import posixpath import cPickle as pickle @@ -711,15 +712,22 @@ class StandaloneHTMLBuilder(Builder): self.info('done') self.info(bold('dumping object inventory... '), nonl=True) - f = open(path.join(self.outdir, INVENTORY_FILENAME), 'w') + f = open(path.join(self.outdir, INVENTORY_FILENAME), 'wb') try: f.write('# Sphinx inventory version 2\n') f.write('# Project: %s\n' % self.config.project.encode('utf-8')) f.write('# Version: %s\n' % self.config.version) + f.write('# The remainder of this file is compressed using zlib.\n') + compressor = zlib.compressobj(9) for domainname, domain in self.env.domains.iteritems(): for name, type, docname, anchor, prio in domain.get_objects(): - f.write('%s %s:%s %s %s\n' % (name, domainname, type, prio, - self.get_target_uri(docname) + '#' + anchor)) + if anchor.endswith(name): + # this can shorten the inventory by as much as 25% + anchor = anchor[:-len(name)] + '$' + f.write(compressor.compress( + '%s %s:%s %s %s\n' % (name, domainname, type, prio, + self.get_target_uri(docname) + '#' + anchor))) + f.write(compressor.flush()) finally: f.close() self.info('done') diff --git a/sphinx/ext/intersphinx.py b/sphinx/ext/intersphinx.py index 7756ae707..f5d08ee44 100644 --- a/sphinx/ext/intersphinx.py +++ b/sphinx/ext/intersphinx.py @@ -25,6 +25,7 @@ """ import time +import zlib import urllib2 import posixpath from os import path @@ -62,12 +63,35 @@ def fetch_inventory_v1(f, uri, join): def fetch_inventory_v2(f, uri, join): invdata = {} - line = f.next() + line = f.readline() projname = line.rstrip()[11:].decode('utf-8') - line = f.next() + line = f.readline() version = line.rstrip()[11:] - for line in f: + line = f.readline() + if 'zlib' not in line: + raise ValueError + + def read_chunks(): + decompressor = zlib.decompressobj() + for chunk in iter(lambda: f.read(16 * 1024), ''): + yield decompressor.decompress(chunk) + yield decompressor.flush() + + def split_lines(iter): + buf = '' + for chunk in iter: + buf += chunk + lineend = buf.find('\n') + while lineend != -1: + yield buf[:lineend] + buf = buf[lineend+1:] + lineend = buf.find('\n') + assert not buf + + for line in split_lines(read_chunks()): name, type, prio, location = line.rstrip().split(None, 3) + if location.endswith('$'): + location = location[:-1] + name location = join(uri, location) invdata.setdefault(type, {})[name] = (projname, version, location) return invdata @@ -89,15 +113,18 @@ def fetch_inventory(app, uri, inv): '%s: %s' % (inv, err.__class__, err)) return try: - line = f.next().rstrip() - if line == '# Sphinx inventory version 1': - invdata = fetch_inventory_v1(f, uri, join) - elif line == '# Sphinx inventory version 2': - invdata = fetch_inventory_v2(f, uri, join) - else: + line = f.readline().rstrip() + try: + if line == '# Sphinx inventory version 1': + invdata = fetch_inventory_v1(f, uri, join) + elif line == '# Sphinx inventory version 2': + invdata = fetch_inventory_v2(f, uri, join) + else: + raise ValueError + f.close() + except ValueError: f.close() raise ValueError('unknown or unsupported inventory version') - f.close() except Exception, err: app.warn('intersphinx inventory %r not readable due to ' '%s: %s' % (inv, err.__class__.__name__, err)) From ec5e5de95b20614a712f5a29810c962f8c81db26 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 8 Sep 2009 00:49:52 +0200 Subject: [PATCH 60/85] roles: fix implementation of XRefRole lowercase and fix_parens options. --- sphinx/roles.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sphinx/roles.py b/sphinx/roles.py index 7c5e8ed35..29a7ba155 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -49,6 +49,7 @@ class XRefRole(object): def __init__(self, fix_parens=False, lowercase=False, nodeclass=None, innernodeclass=None): self.fix_parens = fix_parens + self.lowercase = lowercase if nodeclass is not None: self.nodeclass = nodeclass if innernodeclass is not None: @@ -94,6 +95,11 @@ class XRefRole(object): refcaption=has_explicit_title) # we may need the line number for warnings pnode.line = lineno + if self.lowercase: + target = target.lower() + if self.fix_parens: + title, target = self.normalize_func_parens(env, has_explicit_title, + title, target) title, target = self.process_link(env, pnode, has_explicit_title, title, target) pnode['reftarget'] = target From efa4678cfda991cab662023a90ee2040d81b5963 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 8 Sep 2009 12:07:11 +0200 Subject: [PATCH 61/85] Add some XXX comments to places where work is needed. --- doc/markup/desc.rst | 2 ++ sphinx/writers/latex.py | 1 + tests/root/conf.py | 1 + tests/test_coverage.py | 1 + 4 files changed, 5 insertions(+) diff --git a/doc/markup/desc.rst b/doc/markup/desc.rst index 4cfba05fd..058b5137d 100644 --- a/doc/markup/desc.rst +++ b/doc/markup/desc.rst @@ -92,6 +92,8 @@ 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.:: diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 7e796270c..d21cc89d7 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -428,6 +428,7 @@ class LaTeXTranslator(nodes.NodeVisitor): def depart_subtitle(self, node): self.body.append(self.context.pop()) + # XXX update this desc_map = { 'function' : 'funcdesc', 'class': 'classdesc', diff --git a/tests/root/conf.py b/tests/root/conf.py index a82602fff..d268ae3a5 100644 --- a/tests/root/conf.py +++ b/tests/root/conf.py @@ -50,6 +50,7 @@ latex_additional_files = ['svgimg.svg'] value_from_conf_py = 84 coverage_c_path = ['special/*.h'] +# XXX cfunction? coverage_c_regexes = {'cfunction': r'^PyAPI_FUNC\(.*\)\s+([^_][\w_]+)'} autosummary_generate = ['autosummary'] diff --git a/tests/test_coverage.py b/tests/test_coverage.py index 369788a19..bc59a8152 100644 --- a/tests/test_coverage.py +++ b/tests/test_coverage.py @@ -36,6 +36,7 @@ def test_build(app): undoc_py, undoc_c = pickle.loads((app.outdir / 'undoc.pickle').text()) assert len(undoc_c) == 1 # the key is the full path to the header file, which isn't testable + # XXX this should fail right now assert undoc_c.values()[0] == [('cfunction', 'Py_SphinxTest')] assert 'test_autodoc' in undoc_py From 8f8cc0e2707143cdb1254567201d328a5c0ac067 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 8 Sep 2009 12:07:27 +0200 Subject: [PATCH 62/85] Move HTML test into own module and add some C references. --- tests/root/objects.txt | 14 +-- tests/test_build.py | 206 +--------------------------------- tests/test_build_html.py | 234 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 243 insertions(+), 211 deletions(-) create mode 100644 tests/test_build_html.py diff --git a/tests/root/objects.txt b/tests/root/objects.txt index ee70b0507..8886e535f 100644 --- a/tests/root/objects.txt +++ b/tests/root/objects.txt @@ -57,7 +57,13 @@ C items References ========== -:c:func:`CFunction`. :c:func:`!malloc`. +Referencing :class:`mod.Cls` or :Class:`mod.Cls` should be the same. + +With target: :c:func:`Sphinx_DoSomething()` (parentheses are handled), +:c:member:`SphinxStruct.member`, :c:macro:`SPHINX_USE_PYTHON`, +:c:type:`SphinxType *` (pointer is handled), :c:data:`sphinx_global`. + +Without target: :c:func:`CFunction`. :c:func:`!malloc`. Others @@ -74,12 +80,6 @@ Others .. cmdoption:: -c -Testing references -================== - -Referencing :class:`mod.Cls` or :Class:`mod.Cls` should be the same. - - User markup =========== diff --git a/tests/test_build.py b/tests/test_build.py index 0d72017c6..9d3210e75 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,204 +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']": '', - }, - 'autodoc.html': { - ".//dt[@id='test_autodoc.Class']": '', - ".//dt[@id='test_autodoc.function']/em": r'\*\*kwds', - ".//dd": r'Return spam\.', - }, - 'markup.html': { - ".//title": 'set by title directive', - ".//p/em": 'Section author: Georg Brandl', - ".//p/em": 'Module author: Georg Brandl', - # created by the meta directive - ".//meta[@name='author'][@content='Me']": '', - ".//meta[@name='keywords'][@content='docs, sphinx']": '', - # a label created by ``.. _label:`` - ".//div[@id='label']": '', - # code with standard code blocks - ".//pre": '^some code$', - # an option list - ".//span[@class='option']": '--help', - # admonitions - ".//p[@class='first admonition-title']": 'My Admonition', - ".//p[@class='last']": 'Note text.', - ".//p[@class='last']": 'Warning text.', - # inline markup - ".//li/strong": '^command$', - ".//li/strong": '^program$', - ".//li/em": '^dfn$', - ".//li/tt/span[@class='pre']": '^kbd$', - ".//li/em": u'File \N{TRIANGULAR BULLET} Close', - ".//li/tt/span[@class='pre']": '^a/$', - ".//li/tt/em/span[@class='pre']": '^varpart$', - ".//li/tt/em/span[@class='pre']": '^i$', - ".//a[@href='http://www.python.org/dev/peps/pep-0008']/strong": 'PEP 8', - ".//a[@href='http://tools.ietf.org/html/rfc1.html']/strong": 'RFC 1', - ".//a[@href='objects.html#envvar-HOME']/tt/span[@class='pre']": 'HOME', - ".//a[@href='#with']/tt/span[@class='pre']": '^with$', - ".//a[@href='#grammar-token-try_stmt']/tt/span": '^statement$', - ".//a[@href='subdir/includes.html']/em": 'Including in subdir', - ".//a[@href='objects.html#cmdoption-python-c']/em": 'Python -c option', - # abbreviations - ".//abbr[@title='abbreviation']": '^abbr$', - # version stuff - ".//span[@class='versionmodified']": 'New in version 0.6', - # footnote reference - ".//a[@class='footnote-reference']": r'\[1\]', - # created by reference lookup - ".//a[@href='contents.html#ref1']": '', - # ``seealso`` directive - ".//div/p[@class='first admonition-title']": 'See also', - # a ``hlist`` directive - ".//table[@class='hlist']/tr/td/ul/li": '^This$', - # a ``centered`` directive - ".//p[@class='centered']/strong": 'LICENSE', - # a glossary - ".//dl/dt[@id='term-boson']": 'boson', - # a production list - ".//pre/strong": 'try_stmt', - ".//pre/a[@href='#grammar-token-try1_stmt']/tt/span": 'try1_stmt', - # tests for ``only`` directive - ".//p": 'A global substitution.', - ".//p": 'In HTML.', - ".//p": 'In both.', - ".//p": 'Always present', - }, - 'objects.html': { - ".//dt[@id='mod.Cls.meth1']": '', - ".//dt[@id='errmod.Error']": '', - ".//a[@href='#mod.Cls']": '', - ".//dl[@class='userdesc']": '', - ".//dt[@id='userdesc-myobj']": '', - ".//a[@href='#userdesc-myobj']": '', - ".//span[@class='pre']": 'CFunction()', - }, - 'contents.html': { - ".//meta[@name='hc'][@content='hcval']": '', - ".//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": 'Inline markup', - ".//title": 'Sphinx ', - ".//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='search.html']/em": 'Search Page', - }, - 'bom.html': { - ".//title": " File with UTF-8 BOM", - }, - '_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, - 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 000000000..e7e4b20e3 --- /dev/null +++ b/tests/test_build_html.py @@ -0,0 +1,234 @@ +# -*- coding: utf-8 -*- +""" + test_build_html + ~~~~~~~~~~~~~~~ + + Test the HTML builder and check output against XPath. + + :copyright: Copyright 2007-2009 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']": '', + }, + 'autodoc.html': { + ".//dt[@id='test_autodoc.Class']": '', + ".//dt[@id='test_autodoc.function']/em": r'\*\*kwds', + ".//dd": r'Return spam\.', + }, + 'markup.html': { + ".//title": 'set by title directive', + ".//p/em": 'Section author: Georg Brandl', + ".//p/em": 'Module author: Georg Brandl', + # created by the meta directive + ".//meta[@name='author'][@content='Me']": '', + ".//meta[@name='keywords'][@content='docs, sphinx']": '', + # a label created by ``.. _label:`` + ".//div[@id='label']": '', + # code with standard code blocks + ".//pre": '^some code$', + # an option list + ".//span[@class='option']": '--help', + # admonitions + ".//p[@class='first admonition-title']": 'My Admonition', + ".//p[@class='last']": 'Note text.', + ".//p[@class='last']": 'Warning text.', + # inline markup + ".//li/strong": '^command$', + ".//li/strong": '^program$', + ".//li/em": '^dfn$', + ".//li/tt/span[@class='pre']": '^kbd$', + ".//li/em": u'File \N{TRIANGULAR BULLET} Close', + ".//li/tt/span[@class='pre']": '^a/$', + ".//li/tt/em/span[@class='pre']": '^varpart$', + ".//li/tt/em/span[@class='pre']": '^i$', + ".//a[@href='http://www.python.org/dev/peps/pep-0008']/strong": 'PEP 8', + ".//a[@href='http://tools.ietf.org/html/rfc1.html']/strong": 'RFC 1', + ".//a[@href='objects.html#envvar-HOME']/tt/span[@class='pre']": 'HOME', + ".//a[@href='#with']/tt/span[@class='pre']": '^with$', + ".//a[@href='#grammar-token-try_stmt']/tt/span": '^statement$', + ".//a[@href='subdir/includes.html']/em": 'Including in subdir', + ".//a[@href='objects.html#cmdoption-python-c']/em": 'Python -c option', + # abbreviations + ".//abbr[@title='abbreviation']": '^abbr$', + # version stuff + ".//span[@class='versionmodified']": 'New in version 0.6', + # footnote reference + ".//a[@class='footnote-reference']": r'\[1\]', + # created by reference lookup + ".//a[@href='contents.html#ref1']": '', + # ``seealso`` directive + ".//div/p[@class='first admonition-title']": 'See also', + # a ``hlist`` directive + ".//table[@class='hlist']/tr/td/ul/li": '^This$', + # a ``centered`` directive + ".//p[@class='centered']/strong": 'LICENSE', + # a glossary + ".//dl/dt[@id='term-boson']": 'boson', + # a production list + ".//pre/strong": 'try_stmt', + ".//pre/a[@href='#grammar-token-try1_stmt']/tt/span": 'try1_stmt', + # tests for ``only`` directive + ".//p": 'A global substitution.', + ".//p": 'In HTML.', + ".//p": 'In both.', + ".//p": 'Always present', + }, + 'objects.html': { + ".//dt[@id='mod.Cls.meth1']": '', + ".//dt[@id='errmod.Error']": '', + ".//a[@href='#mod.Cls']": '', + ".//dl[@class='userdesc']": '', + ".//dt[@id='userdesc-myobj']": '', + ".//a[@href='#userdesc-myobj']": '', + # C references + ".//span[@class='pre']": 'CFunction()', + ".//a[@href='#Sphinx_DoSomething']": '', + ".//a[@href='#SphinxStruct.member']": '', + ".//a[@href='#SPHINX_USE_PYTHON']": '', + ".//a[@href='#SphinxType']": '', + ".//a[@href='#sphinx_global']": '', + }, + 'contents.html': { + ".//meta[@name='hc'][@content='hcval']": '', + ".//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": 'Inline markup', + ".//title": 'Sphinx ', + ".//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='search.html']/em": 'Search Page', + }, + 'bom.html': { + ".//title": " File with UTF-8 BOM", + }, + '_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, + 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 From abc7597d06643ae9dcfb073f5a66587d29b91b9a Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 8 Sep 2009 12:07:49 +0200 Subject: [PATCH 63/85] Small beauty fixes. --- sphinx/domains/std.py | 3 +-- sphinx/util/__init__.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index ef24d121f..9614e6a66 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -346,8 +346,7 @@ class StandardDomain(Domain): return make_refnode(builder, fromdocname, docname, labelid, contnode) else: - docname, labelid = self.data['objects'].get((typ, target), - ('', '')) + docname, labelid = self.data['objects'].get((typ, target), ('', '')) if not docname: if typ == 'term': env.warn(fromdocname, 'term not in glossary: %s' % target, diff --git a/sphinx/util/__init__.py b/sphinx/util/__init__.py index 2b5630188..789492532 100644 --- a/sphinx/util/__init__.py +++ b/sphinx/util/__init__.py @@ -23,6 +23,8 @@ import traceback from os import path import docutils +from docutils import nodes + import sphinx @@ -447,8 +449,6 @@ def split_explicit_title(text): return False, text, text -from docutils import nodes - def make_refnode(builder, fromdocname, todocname, targetid, child, title=None): """Shortcut to create a reference node.""" node = nodes.reference('', '') From ec68f705ed8fe103f7fbda52c6b4ca05ba2fa72f Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 8 Sep 2009 12:08:34 +0200 Subject: [PATCH 64/85] Fix C domain bugs found by new tests: allow dot in member names, select index entry by correct objtype. --- sphinx/domains/c.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sphinx/domains/c.py b/sphinx/domains/c.py index 341fe37e0..0778a1d9a 100644 --- a/sphinx/domains/c.py +++ b/sphinx/domains/c.py @@ -30,7 +30,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++ class names) (?: \((.*)\) )? # optionally arguments (\s+const)? $ # const specifier ''', re.VERBOSE) @@ -91,7 +91,7 @@ class CObject(ObjectDescription): if m: name = m.group(1) if not arglist: - if self.objtype == 'cfunction': + if self.objtype == 'function': # for functions, add an empty parameter list signode += addnodes.desc_parameterlist() if const: @@ -120,15 +120,15 @@ class CObject(ObjectDescription): return name def get_index_text(self, name): - if self.objtype == 'cfunction': + if self.objtype == 'function': return _('%s (C function)') % name - elif self.objtype == 'cmember': + elif self.objtype == 'member': return _('%s (C member)') % name - elif self.objtype == 'cmacro': + elif self.objtype == 'macro': return _('%s (C macro)') % name - elif self.objtype == 'ctype': + elif self.objtype == 'type': return _('%s (C type)') % name - elif self.objtype == 'cvar': + elif self.objtype == 'var': return _('%s (C variable)') % name else: return '' From c8b977c821de0383e895e7c51d7f1ed096ba149e Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 8 Sep 2009 12:41:15 +0200 Subject: [PATCH 65/85] test_autodoc: adapt to new generation of directives with domain name. --- tests/test_autodoc.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index 0820e0d22..c1f783145 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -306,7 +306,7 @@ def test_new_documenter(): del directive.result[:] options.members = ['integer'] - assert_result_contains('.. data:: integer', 'module', 'test_autodoc') + assert_result_contains('.. py:data:: integer', 'module', 'test_autodoc') def test_generate(): @@ -393,7 +393,8 @@ def test_generate(): options.members = [] # test module flags - assert_result_contains('.. module:: test_autodoc', 'module', 'test_autodoc') + assert_result_contains('.. py:module:: test_autodoc', + 'module', 'test_autodoc') options.synopsis = 'Synopsis' assert_result_contains(' :synopsis: Synopsis', 'module', 'test_autodoc') options.deprecated = True @@ -402,9 +403,9 @@ 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('.. py:class:: Class', 'module', 'test_autodoc') try: - assert_result_contains('.. exception:: CustomEx', + assert_result_contains('.. py:exception:: CustomEx', 'module', 'test_autodoc') except AssertionError: pass @@ -418,7 +419,7 @@ def test_generate(): assert_result_contains(' :noindex:', 'class', 'Base') # okay, now let's get serious about mixing Python and C signature stuff - assert_result_contains('.. class:: CustomDict', 'class', 'CustomDict', + assert_result_contains('.. py:class:: CustomDict', 'class', 'CustomDict', all_members=True) # test inner class handling From c19eeaabcf9e1dcdc5da7d302b50e743ebd22f0c Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 8 Sep 2009 14:53:31 +0200 Subject: [PATCH 66/85] Ignore emacs dir-locals. --- .hgignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.hgignore b/.hgignore index 4e721bf2c..95e208aa7 100644 --- a/.hgignore +++ b/.hgignore @@ -1,6 +1,7 @@ .*\.pyc .*\.egg .*\.so +.dir-locals.el \.DS_Store$ ^build/ ^dist/ From 0136520b7cef41837602d7cc1de8a12c5af7e3de Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 8 Sep 2009 21:49:27 +0200 Subject: [PATCH 67/85] Add scrolls theme to the docs. --- doc/theming.rst | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/doc/theming.rst b/doc/theming.rst index 5b4b648c7..e75a1d0a7 100644 --- a/doc/theming.rst +++ b/doc/theming.rst @@ -62,7 +62,8 @@ Sphinx comes with a selection of themes to choose from: - **nosidebar** (true or false): Don't include the sidebar. Defaults to false. -* **default** -- This is the default theme. It can be customized via these +* **default** -- This is the default theme, which looks like `the Python + documentation `_. It can be customized via these options: - **rightsidebar** (true or false): Put the sidebar on the right side. @@ -100,6 +101,16 @@ Sphinx comes with a selection of themes to choose from: * **sphinxdoc** -- The theme used for this documentation. It features a sidebar 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 + available: + + - **headerbordercolor** + - **subheadlinecolor** + - **linkcolor** + - **visitedlinkcolor** + - **admonitioncolor** + * **traditional** -- A theme resembling the old Python documentation. There are currently no options beyond *nosidebar*. From 023cae4e47a97fde0ab7d8b23106478195355a5b Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 9 Sep 2009 11:00:54 +0200 Subject: [PATCH 68/85] Add link to sphinx-pypi-upload package. --- doc/faq.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/faq.rst b/doc/faq.rst index a724ddcaf..f7c329303 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -43,6 +43,12 @@ SCons Glenn Hutchings has written a SCons build script to build Sphinx documentation; it is hosted here: http://bitbucket.org/zondo/sphinx-scons +PyPI + Jannis Leidel wrote a `setuptools command + `_ that automatically uploads + Sphinx documentation to the PyPI package documentation area at + http://packages.python.org/. + github pages You can use `Michael Jones' sphinx-to-github tool `_ to prepare From eff5270219cf1e9fe25b2e00f7158d6e37dc1daa Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 9 Sep 2009 15:43:58 +0200 Subject: [PATCH 69/85] Add pointer to ``toctree()`` in FAQ. --- doc/faq.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/faq.rst b/doc/faq.rst index f7c329303..a26ae9308 100644 --- a/doc/faq.rst +++ b/doc/faq.rst @@ -19,6 +19,10 @@ How do I... ... add global substitutions or includes? Add them in the :confval:`rst_epilog` config value. +... display the whole TOC tree in the sidebar? + Use the :data:`toctree` callable in a custom layout template, probably in the + ``sidebartoc`` block. + ... write my own extension? See the :ref:`extension tutorial `. From 0792ad749899bb823a13e43b90d941d128530bd8 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 9 Sep 2009 15:56:52 +0200 Subject: [PATCH 70/85] The ``toctree()`` callable in templates now has a ``maxdepth`` keyword argument to control the depth of the generated tree. Also add tests for that callable. --- CHANGES | 3 +++ doc/templating.rst | 9 +++++++-- sphinx/builders/html.py | 2 +- sphinx/environment.py | 5 +++-- tests/root/_templates/layout.html | 7 +++++++ tests/root/objects.txt | 4 ++-- tests/test_build_html.py | 5 +++++ 7 files changed, 28 insertions(+), 7 deletions(-) diff --git a/CHANGES b/CHANGES index da99816a9..85e3f2f65 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,9 @@ Release 1.0 (in development) ============================ +* The ``toctree()`` callable in templates now has a ``maxdepth`` + keyword argument to control the depth of the generated tree. + * Added Catalan translation, thanks to Pau Fernández. * Added new minimal theme called scrolls. diff --git a/doc/templating.rst b/doc/templating.rst index 61657547d..80c290ca3 100644 --- a/doc/templating.rst +++ b/doc/templating.rst @@ -364,5 +364,10 @@ are in HTML form), these variables are also available: .. data:: toctree A callable yielding the global TOC tree containing the current page, rendered - as HTML bullet lists. If the optional keyword argument ``collapse`` is true, - all TOC entries that are not ancestors of the current page are collapsed. + as HTML bullet lists. Optional keyword arguments: + + * ``collapse`` (true by default): if true, all TOC entries that are not + ancestors of the current page are collapsed + + * ``maxdepth`` (defaults to the max depth selected in the toctree directive): + the maximum depth of the tree; set it to ``-1`` to allow unlimited depth diff --git a/sphinx/builders/html.py b/sphinx/builders/html.py index d5a66fef4..641db9c02 100644 --- a/sphinx/builders/html.py +++ b/sphinx/builders/html.py @@ -644,7 +644,7 @@ class StandaloneHTMLBuilder(Builder): if self.indexer is not None and title: self.indexer.feed(pagename, title, doctree) - def _get_local_toctree(self, docname, collapse=True): + def _get_local_toctree(self, docname, collapse=True, maxdepth=0): return self.render_partial(self.env.get_toctree_for( docname, self, collapse))['fragment'] diff --git a/sphinx/environment.py b/sphinx/environment.py index db48acec1..c06695334 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -1007,12 +1007,13 @@ class BuildEnvironment: node['refuri'] = node['anchorname'] return toc - def get_toctree_for(self, docname, builder, collapse): + def get_toctree_for(self, docname, builder, collapse, maxdepth=0): """Return the global TOC nodetree.""" doctree = self.get_doctree(self.config.master_doc) for toctreenode in doctree.traverse(addnodes.toctree): result = self.resolve_toctree(docname, builder, toctreenode, - prune=True, collapse=collapse) + prune=True, collapse=collapse, + maxdepth=maxdepth) if result is not None: return result diff --git a/tests/root/_templates/layout.html b/tests/root/_templates/layout.html index e8920025d..d312238fa 100644 --- a/tests/root/_templates/layout.html +++ b/tests/root/_templates/layout.html @@ -1,5 +1,12 @@ {% extends "!layout.html" %} + {% block extrahead %} {{ super() }} {% endblock %} + +{% block sidebartoc %} +{# display global TOC in addition to local TOC #} +{{ super() }} +{{ toctree(collapse=False, maxdepth=-1) }} +{% endblock %} \ No newline at end of file diff --git a/tests/root/objects.txt b/tests/root/objects.txt index 8886e535f..fd33a6ccf 100644 --- a/tests/root/objects.txt +++ b/tests/root/objects.txt @@ -1,5 +1,5 @@ -Testing description units -========================= +Testing object descriptions +=========================== .. function:: func_without_module(a, b, *c[, d]) diff --git a/tests/test_build_html.py b/tests/test_build_html.py index e7e4b20e3..7dfbef5b6 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -138,6 +138,11 @@ HTML_XPATH = { ".//a[@href='#SPHINX_USE_PYTHON']": '', ".//a[@href='#SphinxType']": '', ".//a[@href='#sphinx_global']": '', + # test global TOC created by toctree() + ".//ul[@class='current']/li[@class='toctree-l1 current']/a[@href='']": + 'Testing object descriptions', + ".//li[@class='toctree-l1']/a[@href='markup.html']": + 'Testing various markup', }, 'contents.html': { ".//meta[@name='hc'][@content='hcval']": '', From 6c09b9fb18d466e37f11da95df2c5464fa5facab Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 9 Sep 2009 16:39:38 +0200 Subject: [PATCH 71/85] test utils: make with_tempdir stackable. --- tests/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/util.py b/tests/util.py index 4bb6a6538..ad0f6d7ed 100644 --- a/tests/util.py +++ b/tests/util.py @@ -184,9 +184,9 @@ def gen_with_app(*args, **kwargs): def with_tempdir(func): - def new_func(): + def new_func(*args, **kwds): tempdir = path(tempfile.mkdtemp()) - func(tempdir) + func(tempdir, *args, **kwds) tempdir.rmtree() new_func.__name__ = func.__name__ return new_func From 732e2b377fc727fd58371aeea402c89df07124bd Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 9 Sep 2009 19:35:50 +0200 Subject: [PATCH 72/85] Move :envvar: index markup logic over to std domain. --- sphinx/domains/std.py | 28 +++++++++++++++--- sphinx/environment.py | 4 +-- sphinx/roles.py | 67 ++++++++++++++++++++++--------------------- 3 files changed, 61 insertions(+), 38 deletions(-) diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index 9614e6a66..c79a5d87f 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -11,7 +11,7 @@ import re -from docutils import nodes +from docutils import nodes, utils from docutils.parsers.rst import directives from sphinx import addnodes @@ -67,6 +67,26 @@ class EnvVar(GenericObject): indextemplate = l_('environment variable; %s') +class EnvVarXRefRole(XRefRole): + """ + Cross-referencing role for environment variables (adds an index entry). + """ + + def result_nodes(self, env, node, is_ref): + if not is_ref: + return [node], [] + varname = node['reftarget'] + tgtid = 'index-%s' % env.new_serialno('index') + indexnode = addnodes.index() + indexnode['entries'] = [ + ('single', varname, tgtid, varname), + ('single', _('environment variable; %s') % varname, tgtid, varname) + ] + targetnode = nodes.target('', '', ids=[tgtid]) + inliner.document.note_explicit_target(targetnode) + return [indexnode, targetnode, node], [] + + class Target(Directive): """ Generic target for user-defined cross-reference types. @@ -164,7 +184,7 @@ class Program(Directive): class OptionXRefRole(XRefRole): innernodeclass = addnodes.literal_emphasis - def process_link(self, env, pnode, has_explicit_title, title, target): + def process_link(self, env, refnode, has_explicit_title, title, target): program = env.doc_read_data.get('std_program') if not has_explicit_title: if ' ' in title and not (title.startswith('/') or @@ -175,7 +195,7 @@ class OptionXRefRole(XRefRole): elif ' ' in target: program, target = re.split(' (?=-|--|/)', target, 1) program = ws_re.sub('-', program) - pnode['refprogram'] = program + refnode['refprogram'] = program return title, target @@ -316,7 +336,7 @@ class StandardDomain(Domain): } roles = { 'option': OptionXRefRole(innernodeclass=addnodes.literal_emphasis), - 'envvar': XRefRole(), # XXX add index entries + 'envvar': EnvVarXRefRole(), 'token': XRefRole(), 'term': XRefRole(lowercase=True, innernodeclass=nodes.emphasis), } diff --git a/sphinx/environment.py b/sphinx/environment.py index c06695334..c8cb27a3b 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -1234,7 +1234,7 @@ class BuildEnvironment: typ, target, node, contnode) # really hardwired reference types elif typ == 'ref': - if node['refcaption']: + if node['refexplicit']: # reference to anonymous label; the reference uses # the supplied link caption docname, labelid = self.anonlabels.get(target, ('','')) @@ -1281,7 +1281,7 @@ class BuildEnvironment: node.line) newnode = contnode else: - if node['refcaption']: + if node['refexplicit']: # reference with explicit title caption = node.astext() else: diff --git a/sphinx/roles.py b/sphinx/roles.py index 29a7ba155..da6d03029 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -38,7 +38,7 @@ for rolename, nodeclass in generic_docroles.iteritems(): roles.register_local_role(rolename, role) -# -- generic cross-reference roles --------------------------------------------- +# -- generic cross-reference role ---------------------------------------------- class XRefRole(object): """XXX add docstrings""" @@ -55,10 +55,7 @@ class XRefRole(object): if innernodeclass is not None: self.innernodeclass = innernodeclass - def process_link(self, env, pnode, has_explicit_title, title, target): - return title, ws_re.sub(' ', target) - - def normalize_func_parens(self, env, has_explicit_title, title, target): + def _fix_parens(self, env, has_explicit_title, title, target): if not has_explicit_title: if title.endswith('()'): # remove parentheses @@ -86,28 +83,38 @@ class XRefRole(object): # if the first character is a bang, don't cross-reference at all if text[0:1] == '!': if self.fix_parens: - text, _ = self.normalize_func_parens(env, False, text[1:], "") - return [self.innernodeclass(rawtext, text, classes=['xref'])], [] + text, _ = self._fix_parens(env, False, text[1:], "") + innernode = self.innernodeclass(rawtext, text, classes=['xref']) + return self.return_nodes(env, innernode, is_ref=False) # split title and target in role content has_explicit_title, title, target = split_explicit_title(text) - # we want a cross-reference, create the reference node - pnode = self.nodeclass(rawtext, reftype=role, refdomain=domain, - refcaption=has_explicit_title) - # we may need the line number for warnings - pnode.line = lineno + # fix-up title and target if self.lowercase: target = target.lower() if self.fix_parens: - title, target = self.normalize_func_parens(env, has_explicit_title, - title, target) - title, target = self.process_link(env, pnode, - has_explicit_title, title, target) - pnode['reftarget'] = target - pnode += self.innernodeclass(rawtext, title, classes=['xref']) - return [pnode], [] + title, target = self.fix_parens( + env, has_explicit_title, title, target) + # create the reference node + refnode = self.nodeclass(rawtext, reftype=role, refdomain=domain, + refexplicit=has_explicit_title) + # we may need the line number for warnings + refnode.line = lineno + title, target = self.process_link( + 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']) + # result_nodes allow further modification of return values + return self.result_nodes(env, refnode, is_ref=True) + # methods that can be overwritten + + def process_link(self, env, refnode, has_explicit_title, title, target): + return title, ws_re.sub(' ', target) + + def result_nodes(self, env, node, is_ref): + return [node], [] -_EnvvarXrefRole = XRefRole() def indexmarkup_role(typ, rawtext, etext, lineno, inliner, options={}, content=[]): @@ -121,15 +128,7 @@ def indexmarkup_role(typ, rawtext, etext, lineno, inliner, indexnode = addnodes.index() targetnode = nodes.target('', '', ids=[targetid]) inliner.document.note_explicit_target(targetnode) - # XXX remove - if typ == 'envvar': - indexnode['entries'] = [('single', text, targetid, text), - ('single', _('environment variable; %s') % text, - targetid, text)] - xref_nodes = _EnvvarXrefRole(typ, rawtext, etext, lineno, inliner, - options, content)[0] - return [indexnode, targetnode] + xref_nodes, [] - elif typ == 'pep': + if typ == 'pep': indexnode['entries'] = [ ('single', _('Python Enhancement Proposals!PEP %s') % text, targetid, 'PEP %s' % text)] @@ -200,10 +199,14 @@ def abbr_role(typ, rawtext, text, lineno, inliner, options={}, content=[]): specific_docroles = { - 'keyword': XRefRole(), - 'ref': XRefRole(lowercase=True, innernodeclass=nodes.emphasis), - 'doc': XRefRole(), + # links to download references 'download': XRefRole(nodeclass=addnodes.download_reference), + # links to headings or arbitrary labels + 'ref': XRefRole(lowercase=True, innernodeclass=nodes.emphasis), + # links to documents + 'doc': XRefRole(), + # links to labels, without a different title + 'keyword': XRefRole(), 'envvar': indexmarkup_role, 'pep': indexmarkup_role, From bd66ba5179b0150d8d4b4ac3d076ab6425f5bf08 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 9 Sep 2009 19:36:11 +0200 Subject: [PATCH 73/85] Rename py domain specific refnode attributes. --- sphinx/domains/python.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sphinx/domains/python.py b/sphinx/domains/python.py index bb06db38a..1dd111fa2 100644 --- a/sphinx/domains/python.py +++ b/sphinx/domains/python.py @@ -367,9 +367,9 @@ class PyCurrentModule(Directive): class PyXRefRole(XRefRole): - def process_link(self, env, pnode, has_explicit_title, title, target): - pnode['modname'] = env.doc_read_data.get('py_module') - pnode['classname'] = env.doc_read_data.get('py_class') + 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') 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 @@ -384,7 +384,7 @@ class PyXRefRole(XRefRole): # else search builtins first if target[0:1] == '.': target = target[1:] - pnode['refspecific'] = True + refnode['refspecific'] = True return title, target @@ -499,11 +499,11 @@ class PythonDomain(Domain): return make_refnode(builder, fromdocname, docname, 'module-' + target, contnode, title) else: - modname = node.get('modname') - clsname = node.get('classname') + 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) + target, typ, searchorder) if not obj: return None else: From a82bc02edc8e023363d8e4844d4c71a54d8ca77c Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 9 Sep 2009 21:35:41 +0200 Subject: [PATCH 74/85] Remove envvar from specific roles list. --- sphinx/roles.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sphinx/roles.py b/sphinx/roles.py index da6d03029..d56b7a7fc 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -208,7 +208,6 @@ specific_docroles = { # links to labels, without a different title 'keyword': XRefRole(), - 'envvar': indexmarkup_role, 'pep': indexmarkup_role, 'rfc': indexmarkup_role, 'menuselection': menusel_role, From cdc94454e0dbc4e4384b2108cd601f7016c5f0c8 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Wed, 9 Sep 2009 21:56:53 +0200 Subject: [PATCH 75/85] Add test cases for intersphinx, and fix it to work with objtypes. --- sphinx/directives/__init__.py | 2 +- sphinx/domains/__init__.py | 4 +- sphinx/ext/intersphinx.py | 48 ++++++--------- tests/test_intersphinx.py | 112 ++++++++++++++++++++++++++++++++++ 4 files changed, 135 insertions(+), 31 deletions(-) create mode 100644 tests/test_intersphinx.py diff --git a/sphinx/directives/__init__.py b/sphinx/directives/__init__.py index dbaa9ad55..c905c72dd 100644 --- a/sphinx/directives/__init__.py +++ b/sphinx/directives/__init__.py @@ -145,7 +145,7 @@ class ObjectDescription(Directive): if typ in self.doc_fields_with_linked_arg: # XXX currmodule/currclass node = addnodes.pending_xref( - obj, reftype='obj', refcaption=False, + obj, reftype='obj', refexplicit=False, reftarget=obj) #, modname=self.env.currmodule #, classname=self.env.currclass diff --git a/sphinx/domains/__init__.py b/sphinx/domains/__init__.py index e7933d49b..563fd2aa0 100644 --- a/sphinx/domains/__init__.py +++ b/sphinx/domains/__init__.py @@ -82,8 +82,8 @@ class Domain(object): self._role2type = {} for name, obj in self.object_types.iteritems(): for rolename in obj.roles: - self._role2type[rolename] = name - self.object_type = self._role2type.get # XXX necessary? + self._role2type.setdefault(rolename, []).append(name) + self.objtypes_for_role = self._role2type.get def role(self, name): """ diff --git a/sphinx/ext/intersphinx.py b/sphinx/ext/intersphinx.py index f5d08ee44..78a6966b0 100644 --- a/sphinx/ext/intersphinx.py +++ b/sphinx/ext/intersphinx.py @@ -42,7 +42,7 @@ if hasattr(urllib2, 'HTTPSHandler'): urllib2.install_opener(urllib2.build_opener(*handlers)) -def fetch_inventory_v1(f, uri, join): +def read_inventory_v1(f, uri, join): invdata = {} line = f.next() projname = line.rstrip()[11:].decode('utf-8') @@ -56,12 +56,13 @@ def fetch_inventory_v1(f, uri, join): type = 'py:module' location += '#module-' + name else: + type = 'py:' + type location += '#' + name invdata.setdefault(type, {})[name] = (projname, version, location) return invdata -def fetch_inventory_v2(f, uri, join): +def read_inventory_v2(f, uri, join, bufsize=16*1024): invdata = {} line = f.readline() projname = line.rstrip()[11:].decode('utf-8') @@ -73,7 +74,7 @@ def fetch_inventory_v2(f, uri, join): def read_chunks(): decompressor = zlib.decompressobj() - for chunk in iter(lambda: f.read(16 * 1024), ''): + for chunk in iter(lambda: f.read(bufsize), ''): yield decompressor.decompress(chunk) yield decompressor.flush() @@ -116,9 +117,9 @@ def fetch_inventory(app, uri, inv): line = f.readline().rstrip() try: if line == '# Sphinx inventory version 1': - invdata = fetch_inventory_v1(f, uri, join) + invdata = read_inventory_v1(f, uri, join) elif line == '# Sphinx inventory version 2': - invdata = fetch_inventory_v2(f, uri, join) + invdata = read_inventory_v2(f, uri, join) else: raise ValueError f.close() @@ -163,33 +164,24 @@ def load_mappings(app): def missing_reference(app, env, node, contnode): """Attempt to resolve a missing reference via intersphinx references.""" - type = node['reftype'] - domain = node['refdomain'] - fulltype = domain + ':' + type + domain = node.get('refdomain') + if not domain: + # only objects in domains are in the inventory + return target = node['reftarget'] - if type not in env.intersphinx_inventory: + objtypes = env.domains[domain].objtypes_for_role(node['reftype']) + if not objtypes: return - if target not in env.intersphinx_inventory[type]: + for objtype in objtypes: + fulltype = '%s:%s' % (domain, objtype) + if fulltype in env.intersphinx_inventory and \ + target in env.intersphinx_inventory[fulltype]: + break + else: return - proj, version, uri = env.intersphinx_inventory[type][target] - - # XXX - if target[-2:] == '()': - target = target[:-2] - target = target.rstrip(' *') - # special case: exceptions and object methods - if type == 'exc' and '.' not in target and \ - 'exceptions.' + target in env.intersphinx_inventory: - target = 'exceptions.' + target - elif type in ('func', 'meth') and '.' not in target and \ - 'object.' + target in env.intersphinx_inventory: - target = 'object.' + target - if target not in env.intersphinx_inventory: - return None - type, proj, version, uri = env.intersphinx_inventory[target] - # print "Intersphinx hit:", target, uri + proj, version, uri = env.intersphinx_inventory[fulltype][target] newnode = nodes.reference('', '') - newnode['refuri'] = uri + '#' + target + newnode['refuri'] = uri newnode['reftitle'] = '(in %s v%s)' % (proj, version) newnode['class'] = 'external-xref' newnode.append(contnode) diff --git a/tests/test_intersphinx.py b/tests/test_intersphinx.py new file mode 100644 index 000000000..2d5219145 --- /dev/null +++ b/tests/test_intersphinx.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- +""" + test_intersphinx + ~~~~~~~~~~~~~~~~ + + Test the intersphinx extension. + + :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS. + :license: BSD, see LICENSE for details. +""" + +import zlib +import posixpath +from cStringIO import StringIO + +from docutils import nodes + +from sphinx import addnodes +from sphinx.ext.intersphinx import read_inventory_v1, read_inventory_v2, \ + fetch_inventory, load_mappings, missing_reference + +from util import * + + +inventory_v1 = '''\ +# Sphinx inventory version 1 +# Project: foo +# Version: 1.0 +module mod foo.html +module.cls class foo.html +''' + +inventory_v2 = '''\ +# Sphinx inventory version 2 +# Project: foo +# Version: 2.0 +# The remainder of this file is compressed with zlib. +''' + zlib.compress('''\ +module1 py:module 0 foo.html#module-module1 +module2 py:module 0 foo.html#module-$ +module1.func py:function 1 sub/foo.html#$ +CFunc c:function 2 cfunc.html#CFunc +''') + + +def test_read_inventory_v1(): + f = StringIO(inventory_v1) + f.readline() + invdata = read_inventory_v1(f, '/util', posixpath.join) + assert invdata['py:module']['module'] == \ + ('foo', '1.0', '/util/foo.html#module-module') + assert invdata['py:class']['module.cls'] == \ + ('foo', '1.0', '/util/foo.html#module.cls') + + +def test_read_inventory_v2(): + f = StringIO(inventory_v2) + f.readline() + invdata1 = read_inventory_v2(f, '/util', posixpath.join) + + # try again with a small buffer size to test the chunking algorithm + f = StringIO(inventory_v2) + f.readline() + invdata2 = read_inventory_v2(f, '/util', posixpath.join, bufsize=5) + + assert invdata1 == invdata2 + + assert len(invdata1['py:module']) == 2 + assert invdata1['py:module']['module1'] == \ + ('foo', '2.0', '/util/foo.html#module-module1') + assert invdata1['py:module']['module2'] == \ + ('foo', '2.0', '/util/foo.html#module-module2') + assert invdata1['py:function']['module1.func'][2] == \ + '/util/sub/foo.html#module1.func' + assert invdata1['c:function']['CFunc'][2] == '/util/cfunc.html#CFunc' + + +@with_app(confoverrides={'extensions': 'sphinx.ext.intersphinx'}) +@with_tempdir +def test_missing_reference(tempdir, app): + inv_file = tempdir / 'inventory' + write_file(inv_file, inventory_v2) + app.config.intersphinx_mapping = {'http://docs.python.org/': inv_file} + app.config.intersphinx_cache_limit = 0 + + # load the inventory and check if it's done correctly + load_mappings(app) + inv = app.env.intersphinx_inventory + + assert inv['py:module']['module2'] == \ + ('foo', '2.0', 'http://docs.python.org/foo.html#module-module2') + + # create fake nodes and check referencing + contnode = nodes.emphasis('foo') + refnode = addnodes.pending_xref('') + refnode['reftarget'] = 'module1.func' + refnode['reftype'] = 'func' + refnode['refdomain'] = 'py' + + rn = missing_reference(app, app.env, refnode, contnode) + assert isinstance(rn, nodes.reference) + assert rn['refuri'] == 'http://docs.python.org/sub/foo.html#module1.func' + assert rn['reftitle'] == '(in foo v2.0)' + assert rn[0] is contnode + + # create unresolvable nodes and check None return value + refnode['reftype'] = 'foo' + assert missing_reference(app, app.env, refnode, contnode) is None + + refnode['reftype'] = 'function' + refnode['reftarget'] = 'foo.func' + assert missing_reference(app, app.env, refnode, contnode) is None From 52d5411f6d1a54f9224cf1b8b87ce93b226cf4af Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Thu, 10 Sep 2009 21:58:05 +0200 Subject: [PATCH 76/85] fix bugs. --- sphinx/directives/__init__.py | 2 ++ sphinx/domains/std.py | 4 ++-- sphinx/roles.py | 9 +++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/sphinx/directives/__init__.py b/sphinx/directives/__init__.py index c905c72dd..3867ab42d 100644 --- a/sphinx/directives/__init__.py +++ b/sphinx/directives/__init__.py @@ -65,6 +65,8 @@ class ObjectDescription(Directive): 'module': directives.unchanged, } + # XXX make this more domain specific + doc_fields_with_arg = { 'param': '%param', 'parameter': '%param', diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index c79a5d87f..f2da7d116 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -72,7 +72,7 @@ class EnvVarXRefRole(XRefRole): Cross-referencing role for environment variables (adds an index entry). """ - def result_nodes(self, env, node, is_ref): + def result_nodes(self, document, env, node, is_ref): if not is_ref: return [node], [] varname = node['reftarget'] @@ -83,7 +83,7 @@ class EnvVarXRefRole(XRefRole): ('single', _('environment variable; %s') % varname, tgtid, varname) ] targetnode = nodes.target('', '', ids=[tgtid]) - inliner.document.note_explicit_target(targetnode) + document.note_explicit_target(targetnode) return [indexnode, targetnode, node], [] diff --git a/sphinx/roles.py b/sphinx/roles.py index d56b7a7fc..cc8c169ad 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -85,14 +85,15 @@ class XRefRole(object): if self.fix_parens: text, _ = self._fix_parens(env, False, text[1:], "") innernode = self.innernodeclass(rawtext, text, classes=['xref']) - return self.return_nodes(env, innernode, is_ref=False) + return self.result_nodes(inliner.document, env, innernode, + is_ref=False) # split title and target in role content has_explicit_title, title, target = split_explicit_title(text) # fix-up title and target if self.lowercase: target = target.lower() if self.fix_parens: - title, target = self.fix_parens( + title, target = self._fix_parens( env, has_explicit_title, title, target) # create the reference node refnode = self.nodeclass(rawtext, reftype=role, refdomain=domain, @@ -105,14 +106,14 @@ class XRefRole(object): refnode['reftarget'] = target refnode += self.innernodeclass(rawtext, title, classes=['xref']) # result_nodes allow further modification of return values - return self.result_nodes(env, refnode, is_ref=True) + return self.result_nodes(inliner.document, env, refnode, is_ref=True) # methods that can be overwritten def process_link(self, env, refnode, has_explicit_title, title, target): return title, ws_re.sub(' ', target) - def result_nodes(self, env, node, is_ref): + def result_nodes(self, document, env, node, is_ref): return [node], [] From 68c545cf22c783b67a501c0ba07fd1edb91bef5e Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sat, 12 Sep 2009 10:14:27 +0000 Subject: [PATCH 77/85] Refactor directive/role lookup from domains a bit to avoid code duplication. --- sphinx/directives/__init__.py | 2 +- sphinx/environment.py | 120 +++++++++++++++++----------------- tests/test_markup.py | 3 +- 3 files changed, 63 insertions(+), 62 deletions(-) diff --git a/sphinx/directives/__init__.py b/sphinx/directives/__init__.py index 3867ab42d..6183c31dc 100644 --- a/sphinx/directives/__init__.py +++ b/sphinx/directives/__init__.py @@ -284,7 +284,7 @@ class DefaultDomain(Directive): def run(self): env = self.state.document.settings.env domain_name = arguments[0] - env.default_domain = env.domains.get(domain_name) + env.doc_read_data['default_domain'] = env.domains.get(domain_name) directives.register_directive('default-domain', directive_dwim(DefaultDomain)) diff --git a/sphinx/environment.py b/sphinx/environment.py index c8cb27a3b..ce39760cc 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -40,9 +40,39 @@ from sphinx.util import movefile, get_matching_docs, SEP, ustrftime, \ docname_join, FilenameUniqDict, url_re, make_refnode from sphinx.errors import SphinxError, ExtensionError + orig_role_function = roles.role orig_directive_function = directives.directive +class ElementLookupError(Exception): pass + +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). + """ + name = name.lower() + # explicit domain given? + if ':' in name: + domain_name, name = name.split(':', 1) + if domain_name in env.domains: + domain = env.domains[domain_name] + element = getattr(domain, type)(name) + if element is not None: + return element, [] + # else look in the default domain + else: + def_domain = env.doc_read_data.get('default_domain') + if def_domain is not None: + element = getattr(def_domain, type)(name) + if element is not None: + return element, [] + # always look in the std domain + element = getattr(env.domains['std'], type)(name) + if element is not None: + return element, [] + raise ElementLookupError + + default_settings = { 'embed_stylesheet': False, 'cloak_email_addresses': True, @@ -538,6 +568,25 @@ class BuildEnvironment: error.object[error.end:lineend]), lineno) return (u'?', error.end) + def patch_lookup_functions(self): + """Monkey-patch directive and role dispatch, so that domain-specific + markup takes precedence.""" + + def directive(name, lang_module, document): + try: + return lookup_domain_element(self, 'directive', name) + except ElementLookupError: + return orig_directive_function(name, lang_module, document) + + def role(name, lang_module, lineno, reporter): + try: + return lookup_domain_element(self, 'role', name) + except ElementLookupError: + return orig_role_function(name, lang_module, lineno, reporter) + + directives.directive = directive + roles.role = role + def read_doc(self, docname, src_path=None, save_parsed=True, app=None): """ Parse a file and add/update inventory entries for the doctree. @@ -562,11 +611,15 @@ class BuildEnvironment: self.config.default_role) self.doc_read_data['docname'] = docname + # defaults to the global default, but can be re-set in a document + self.doc_read_data['default_domain'] = \ + self.domains.get(self.config.default_domain) + self.settings['input_encoding'] = self.config.source_encoding self.settings['trim_footnote_reference_space'] = \ self.config.trim_footnote_reference_space - codecs.register_error('sphinx', self.warn_and_replace) + self.patch_lookup_functions() codecs.register_error('sphinx', self.warn_and_replace) @@ -585,61 +638,6 @@ class BuildEnvironment: else: return data - # defaults to the global default, but can be re-set in a document - self.default_domain = self.domains.get(self.config.default_domain) - - # monkey-patch, so that domain directives take precedence - def directive(directive_name, language_module, document): - """Lookup a directive.""" - directive_name = directive_name.lower() - # explicit domain given? - if ':' in directive_name: - domain_name, directive_name = directive_name.split(':', 1) - if domain_name in self.domains: - domain = self.domains[domain_name] - directive = domain.directive(directive_name) - if directive is not None: - return directive, [] - # else look in the default domain - elif self.default_domain is not None: - directive = self.default_domain.directive(directive_name) - if directive is not None: - return directive, [] - # always look in the std domain - # (XXX or register them in the docutils namespace?) - directive = self.domains['std'].directive(directive_name) - if directive is not None: - return directive, [] - # last, look in the default docutils namespace - return orig_directive_function(directive_name, language_module, - document) - directives.directive = directive - - def role(role_name, language_module, lineno, reporter): - """Lookup a role name.""" - role_name = role_name.lower() - # explicit domain given? - if ':' in role_name: - domain_name, role_name = role_name.split(':', 1) - if domain_name in self.domains: - domain = self.domains[domain_name] - role = domain.role(role_name) - if role is not None: - return role, [] - # else look in the default domain - elif self.default_domain is not None: - role = self.default_domain.role(role_name) - if role is not None: - return role, [] - # always look in the std domain - role = self.domains['std'].role(role_name) - if role is not None: - return role, [] - # last, look in the default docutils namespace - return orig_role_function(role_name, language_module, - lineno, reporter) - roles.role = role - # publish manually pub = Publisher(reader=SphinxStandaloneReader(), writer=SphinxDummyWriter(), @@ -654,6 +652,8 @@ class BuildEnvironment: doctree = pub.document except UnicodeError, err: raise SphinxError(str(err)) + + # post-processing self.filter_messages(doctree) self.process_dependencies(docname, doctree) self.process_images(docname, doctree) @@ -665,12 +665,13 @@ class BuildEnvironment: self.note_citations_from(docname, doctree) self.build_toc_from(docname, doctree) - # store time of reading, used to find outdated files - self.all_docs[docname] = time.time() - + # allow extension-specific post-processing if app: app.emit('doctree-read', doctree) + # store time of reading, used to find outdated files + self.all_docs[docname] = time.time() + # make it picklable doctree.reporter = None doctree.transformer = None @@ -683,7 +684,6 @@ class BuildEnvironment: # cleanup self.doc_read_data.clear() - self.default_domain = None if save_parsed: # save the parsed doctree diff --git a/tests/test_markup.py b/tests/test_markup.py index 03c421a13..22481953a 100644 --- a/tests/test_markup.py +++ b/tests/test_markup.py @@ -29,6 +29,7 @@ def setup_module(): components=(rst.Parser, HTMLWriter, LaTeXWriter)) settings = optparser.get_default_values() settings.env = app.builder.env + settings.env.patch_lookup_functions() parser = rst.Parser() def teardown_module(): @@ -60,7 +61,7 @@ def verify_re(rst, html_expected, latex_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 + assert re.match(html_expected, html_translated), 'from ' + rst if latex_expected: latex_translator = ForgivingLaTeXTranslator(document, app.builder) From 2bbb29385ed7c2020950ec05175b9250bba08974 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sat, 12 Sep 2009 10:17:02 +0000 Subject: [PATCH 78/85] Remove env.note_versionchange(). --- sphinx/directives/other.py | 7 +++++-- sphinx/environment.py | 8 -------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py index 969a15044..735f0a40f 100644 --- a/sphinx/directives/other.py +++ b/sphinx/directives/other.py @@ -209,8 +209,11 @@ class VersionChange(Directive): else: ret = [node] env = self.state.document.settings.env - # XXX replace? what with? - env.note_versionchange(node['type'], node['version'], node, self.lineno) + env.versionchanges.setdefault(node['version'], []).append( + (node['type'], env.doc_read_data['docname'], self.lineno, + env.doc_read_data.get('py_module'), + env.doc_read_data.get('object'), + node.astext())) return ret diff --git a/sphinx/environment.py b/sphinx/environment.py index ce39760cc..62c706906 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -1017,14 +1017,6 @@ class BuildEnvironment: if result is not None: return result - # XXX remove - def note_versionchange(self, type, version, node, lineno): - self.versionchanges.setdefault(version, []).append( - (type, self.docname, lineno, - self.doc_read_data.get('py_module'), - self.doc_read_data.get('object'), - node.astext())) - def get_domain(self, domainname): """Return the domain instance with the specified name. Raises an ExtensionError if the domain is not registered.""" From 30f8dbdbf87ce243d807c1fff84120d4bad67797 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sat, 12 Sep 2009 10:19:13 +0000 Subject: [PATCH 79/85] Cosmetic changes. --- sphinx/environment.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/sphinx/environment.py b/sphinx/environment.py index 62c706906..81c044d83 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -569,9 +569,10 @@ class BuildEnvironment: return (u'?', error.end) def patch_lookup_functions(self): - """Monkey-patch directive and role dispatch, so that domain-specific - markup takes precedence.""" - + """ + Monkey-patch directive and role dispatch, so that domain-specific + markup takes precedence. + """ def directive(name, lang_module, document): try: return lookup_domain_element(self, 'directive', name) @@ -727,6 +728,8 @@ class BuildEnvironment: def note_dependency(self, filename): self.dependencies.setdefault(self.docname, set()).add(filename) + # post-processing of read doctrees + def filter_messages(self, doctree): """ Filter system messages from a doctree. From 3135d94c027e502bc746b1a3bfffd77cd4767797 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sat, 12 Sep 2009 13:39:40 +0000 Subject: [PATCH 80/85] Work on remaining XXXs. --- sphinx/roles.py | 35 ++++++++++++++++++++++- sphinx/themes/basic/static/doctools.js | 7 ++--- sphinx/themes/basic/static/searchtools.js | 1 + 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/sphinx/roles.py b/sphinx/roles.py index cc8c169ad..5809526c3 100644 --- a/sphinx/roles.py +++ b/sphinx/roles.py @@ -41,7 +41,28 @@ for rolename, nodeclass in generic_docroles.iteritems(): # -- generic cross-reference role ---------------------------------------------- class XRefRole(object): - """XXX add docstrings""" + """ + A generic cross-referencing role. To create a callable that can be used as + a role function, create an instance of this class. + + The general features of this role are: + + * Automatic creation of a reference and a content node. + * Optional separation of title and target with `title `. + * The implementation is a class rather than a function to make + customization easier. + + Customization can be done in two ways: + + * Supplying constructor parameters: + * `fix_parens` to normalize parentheses (strip from target, and add to + title if configured) + * `lowercase` to lowercase the target + * `nodeclass` and `innernodeclass` select the node classes for + the reference and the content node + + * Subclassing and overwriting `process_link()` and/or `result_nodes()`. + """ nodeclass = addnodes.pending_xref innernodeclass = nodes.literal @@ -111,14 +132,26 @@ class XRefRole(object): # methods that can be overwritten def process_link(self, env, refnode, has_explicit_title, title, target): + """ + Called after parsing title and target text, and creating the reference + node (given in *refnode*). This method can alter the reference node and + must return a new (or the same) ``(title, target)`` tuple. + """ return title, ws_re.sub(' ', target) def result_nodes(self, document, env, node, is_ref): + """ + Called before returning the finished nodes. *node* is the reference + node if one was created (*is_ref* is then true), else the content node. + This method can add other nodes and must return a ``(nodes, messages)`` + tuple (the usual return value of a role function). + """ return [node], [] def indexmarkup_role(typ, rawtext, etext, lineno, inliner, options={}, content=[]): + """Role for PEP/RFC references that generate an index entry.""" env = inliner.document.settings.env if not typ: typ = env.config.default_role diff --git a/sphinx/themes/basic/static/doctools.js b/sphinx/themes/basic/static/doctools.js index 9447678cd..12afe01b1 100644 --- a/sphinx/themes/basic/static/doctools.js +++ b/sphinx/themes/basic/static/doctools.js @@ -1,12 +1,11 @@ -/// XXX: make it cross browser - /** * 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"]; + 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() {} diff --git a/sphinx/themes/basic/static/searchtools.js b/sphinx/themes/basic/static/searchtools.js index ede1de00b..2dbda10ea 100644 --- a/sphinx/themes/basic/static/searchtools.js +++ b/sphinx/themes/basic/static/searchtools.js @@ -350,6 +350,7 @@ var Search = { if (fullname.toLowerCase().indexOf(object) > -1) { match = objects[prefix][name]; descr = objnames[match[1]] + _(', in ') + titles[match[0]]; + // XXX the generated anchors are not generally correct result = [filenames[match[0]], fullname, '#'+fullname, descr]; switch (match[2]) { case 1: objectResults.push(result); break; From f98a703ef3572ef7b4260405d0716fb60f6e01b0 Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Mon, 26 Oct 2009 14:07:13 +0100 Subject: [PATCH 81/85] Make "make check" happy. --- sphinx/ext/coverage.py | 2 +- sphinx/jinja2glue.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/sphinx/ext/coverage.py b/sphinx/ext/coverage.py index ed5d92a4f..38dabf9f3 100644 --- a/sphinx/ext/coverage.py +++ b/sphinx/ext/coverage.py @@ -119,7 +119,7 @@ class CoverageBuilder(Builder): def build_py_coverage(self): objects = self.env.domaindata['py']['objects'] modules = self.env.domaindata['py']['modules'] - + for mod_name in modules: ignore = False for exp in self.mod_ignorexps: diff --git a/sphinx/jinja2glue.py b/sphinx/jinja2glue.py index e2c96a963..0a47b66d8 100644 --- a/sphinx/jinja2glue.py +++ b/sphinx/jinja2glue.py @@ -101,7 +101,8 @@ class BuiltinTemplateLoader(TemplateBridge, BaseLoader): self.environment.globals['debug'] = contextfunction(pformat) self.environment.globals['accesskey'] = contextfunction(accesskey) if use_i18n: - self.environment.install_gettext_translations(builder.app.translator) + self.environment.install_gettext_translations( + builder.app.translator) def render(self, template, context): return self.environment.get_template(template).render(context) From 414816bb373379cf1ca8f4b6ef21a329fff1270f Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 27 Oct 2009 19:12:47 +0100 Subject: [PATCH 82/85] Remove docutils 0.4 support. --- CHANGES | 2 + sphinx/application.py | 11 +++--- sphinx/directives/__init__.py | 9 ++--- sphinx/directives/code.py | 13 +++---- sphinx/directives/other.py | 44 +++++++++------------- sphinx/util/compat.py | 69 +++++------------------------------ sphinx/writers/html.py | 19 ++-------- 7 files changed, 48 insertions(+), 119 deletions(-) diff --git a/CHANGES b/CHANGES index 119f0d5d9..cf7aaff62 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,8 @@ Release 1.0 (in development) ============================ +* Support for docutils 0.4 has been removed. + * The ``toctree()`` callable in templates now has a ``maxdepth`` keyword argument to control the depth of the generated tree. diff --git a/sphinx/application.py b/sphinx/application.py index 14aee1bea..4d861a69a 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -18,7 +18,7 @@ from os import path from cStringIO import StringIO from docutils import nodes -from docutils.parsers.rst import directives, roles +from docutils.parsers.rst import Directive, directives, roles import sphinx from sphinx import package_dir, locale @@ -31,7 +31,6 @@ from sphinx.builders import BUILTIN_BUILDERS from sphinx.environment import BuildEnvironment, SphinxStandaloneReader from sphinx.util import pycompat # imported for side-effects from sphinx.util.tags import Tags -from sphinx.util.compat import Directive, directive_dwim from sphinx.util.console import bold @@ -339,7 +338,7 @@ class Sphinx(object): if content or arguments or options: raise ExtensionError('when adding directive classes, no ' 'additional arguments may be given') - return directive_dwim(obj) + return obj else: obj.content = content obj.arguments = arguments @@ -366,11 +365,13 @@ class Sphinx(object): raise ExtensionError('domain %s already registered' % domain.name) all_domains[domain.name] = domain - def add_directive_to_domain(self, domain, name, obj): + def add_directive_to_domain(self, domain, name, obj, + content=None, arguments=None, **options): # XXX needs to be documented if domain not in all_domains: raise ExtensionError('domain %s not yet registered' % domain) - all_domains[domain].directives[name] = self._directive_helper(obj) + all_domains[domain].directives[name] = \ + self._directive_helper(obj, content, arguments, **options) def add_role_to_domain(self, domain, name, role): # XXX needs to be documented diff --git a/sphinx/directives/__init__.py b/sphinx/directives/__init__.py index 6183c31dc..75adedd42 100644 --- a/sphinx/directives/__init__.py +++ b/sphinx/directives/__init__.py @@ -12,12 +12,11 @@ import re from docutils import nodes -from docutils.parsers.rst import directives +from docutils.parsers.rst import Directive, directives from docutils.parsers.rst.directives import images from sphinx import addnodes from sphinx.locale import l_ -from sphinx.util.compat import Directive, directive_dwim # import and register directives from sphinx.directives.code import * @@ -287,7 +286,7 @@ class DefaultDomain(Directive): env.doc_read_data['default_domain'] = env.domains.get(domain_name) -directives.register_directive('default-domain', directive_dwim(DefaultDomain)) -directives.register_directive('describe', directive_dwim(ObjectDescription)) +directives.register_directive('default-domain', DefaultDomain) +directives.register_directive('describe', ObjectDescription) # new, more consistent, name -directives.register_directive('object', directive_dwim(ObjectDescription)) +directives.register_directive('object', ObjectDescription) diff --git a/sphinx/directives/code.py b/sphinx/directives/code.py index 4f44c3335..fc4a1873f 100644 --- a/sphinx/directives/code.py +++ b/sphinx/directives/code.py @@ -13,11 +13,10 @@ import codecs from os import path from docutils import nodes -from docutils.parsers.rst import directives +from docutils.parsers.rst import Directive, directives from sphinx import addnodes from sphinx.util import parselinenos -from sphinx.util.compat import Directive, directive_dwim class Highlight(Directive): @@ -184,8 +183,8 @@ class LiteralInclude(Directive): return [retnode] -directives.register_directive('highlight', directive_dwim(Highlight)) -directives.register_directive('highlightlang', directive_dwim(Highlight)) # old -directives.register_directive('code-block', directive_dwim(CodeBlock)) -directives.register_directive('sourcecode', directive_dwim(CodeBlock)) -directives.register_directive('literalinclude', directive_dwim(LiteralInclude)) +directives.register_directive('highlight', Highlight) +directives.register_directive('highlightlang', Highlight) # old +directives.register_directive('code-block', CodeBlock) +directives.register_directive('sourcecode', CodeBlock) +directives.register_directive('literalinclude', LiteralInclude) diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py index 735f0a40f..4436b306e 100644 --- a/sphinx/directives/other.py +++ b/sphinx/directives/other.py @@ -10,13 +10,13 @@ import re from docutils import nodes -from docutils.parsers.rst import directives +from docutils.parsers.rst import Directive, directives from sphinx import addnodes from sphinx.locale import pairindextypes from sphinx.util import patfilter, ws_re, url_re, docname_join, \ explicit_title_re -from sphinx.util.compat import Directive, directive_dwim, make_admonition +from sphinx.util.compat import make_admonition class TocTree(Directive): @@ -362,31 +362,21 @@ class Only(Directive): return [node] -directives.register_directive('toctree', directive_dwim(TocTree)) -directives.register_directive('sectionauthor', directive_dwim(Author)) -directives.register_directive('moduleauthor', directive_dwim(Author)) -directives.register_directive('index', directive_dwim(Index)) -directives.register_directive('deprecated', directive_dwim(VersionChange)) -directives.register_directive('versionadded', directive_dwim(VersionChange)) -directives.register_directive('versionchanged', directive_dwim(VersionChange)) -directives.register_directive('seealso', directive_dwim(SeeAlso)) -directives.register_directive('tabularcolumns', directive_dwim(TabularColumns)) -directives.register_directive('centered', directive_dwim(Centered)) -directives.register_directive('acks', directive_dwim(Acks)) -directives.register_directive('hlist', directive_dwim(HList)) -directives.register_directive('only', directive_dwim(Only)) +directives.register_directive('toctree', TocTree) +directives.register_directive('sectionauthor', Author) +directives.register_directive('moduleauthor', Author) +directives.register_directive('index', Index) +directives.register_directive('deprecated', VersionChange) +directives.register_directive('versionadded', VersionChange) +directives.register_directive('versionchanged', VersionChange) +directives.register_directive('seealso', SeeAlso) +directives.register_directive('tabularcolumns', TabularColumns) +directives.register_directive('centered', Centered) +directives.register_directive('acks', Acks) +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 -try: - # docutils 0.4 - from docutils.parsers.rst.directives.misc import class_directive - directives.register_directive('cssclass', class_directive) -except ImportError: - try: - # docutils 0.5 - from docutils.parsers.rst.directives.misc import Class - directives.register_directive('cssclass', Class) - except ImportError: - # whatever :) - pass +from docutils.parsers.rst.directives.misc import Class +directives.register_directive('cssclass', Class) diff --git a/sphinx/util/compat.py b/sphinx/util/compat.py index c00f9d4ec..76c106e72 100644 --- a/sphinx/util/compat.py +++ b/sphinx/util/compat.py @@ -11,7 +11,7 @@ from docutils import nodes -# function missing in 0.5 SVN +# function missing in docutils 0.5 def make_admonition(node_class, name, arguments, options, content, lineno, content_offset, block_text, state, state_machine): #if not content: @@ -35,64 +35,13 @@ def make_admonition(node_class, name, arguments, options, content, lineno, return [admonition_node] -# support the class-style Directive interface even when using docutils 0.4 +# backwards-compatibility aliases for helpers in older Sphinx versions that +# supported the docutils 0.4 directive function interface -try: - from docutils.parsers.rst import Directive +from docutils.parsers.rst import Directive -except ImportError: - class Directive(object): - """ - Fake Directive class to allow Sphinx directives to be written in - class style. - """ - required_arguments = 0 - optional_arguments = 0 - final_argument_whitespace = False - option_spec = None - has_content = False - - def __init__(self, name, arguments, options, content, lineno, - content_offset, block_text, state, state_machine): - self.name = name - self.arguments = arguments - self.options = options - self.content = content - self.lineno = lineno - self.content_offset = content_offset - self.block_text = block_text - self.state = state - self.state_machine = state_machine - - def run(self): - raise NotImplementedError('Must override run() is subclass.') - - def directive_dwim(obj): - """ - Return something usable with register_directive(), regardless if - class or function. For that, we need to convert classes to a - function for docutils 0.4. - """ - if isinstance(obj, type) and issubclass(obj, Directive): - def _class_directive(name, arguments, options, content, - lineno, content_offset, block_text, - state, state_machine): - return obj(name, arguments, options, content, - lineno, content_offset, block_text, - state, state_machine).run() - _class_directive.options = obj.option_spec - _class_directive.content = obj.has_content - _class_directive.arguments = (obj.required_arguments, - obj.optional_arguments, - obj.final_argument_whitespace) - return _class_directive - return obj - -else: - def directive_dwim(obj): - """ - Return something usable with register_directive(), regardless if - class or function. Nothing to do here, because docutils 0.5 takes - care of converting functions itself. - """ - return obj +def directive_dwim(obj): + import warnings + warnings.warn('directive_dwim is deprecated and no longer needed', + DeprecationWarning, stacklevel=2) + return obj diff --git a/sphinx/writers/html.py b/sphinx/writers/html.py index 701e11cd0..737710f51 100644 --- a/sphinx/writers/html.py +++ b/sphinx/writers/html.py @@ -189,21 +189,10 @@ class HTMLTranslator(BaseTranslator): numbers = self.builder.secnumbers[anchorname] self.body.append('.'.join(map(str, numbers)) + '. ') - # overwritten for docutils 0.4 - if hasattr(BaseTranslator, 'start_tag_with_title'): - def visit_section(self, node): - # the 0.5 version, to get the id attribute in the
tag - self.section_level += 1 - self.body.append(self.starttag(node, 'div', CLASS='section')) - - def visit_title(self, node): - # don't move the id attribute inside the tag - BaseTranslator.visit_title(self, node, move_ids=0) - self.add_secnumber(node) - else: - def visit_title(self, node): - BaseTranslator.visit_title(self, node) - self.add_secnumber(node) + # overwritten + def visit_title(self, node): + BaseTranslator.visit_title(self, node) + self.add_secnumber(node) # overwritten def visit_literal_block(self, node): From 213eba02250d003ebd25d2e883ffcc69b37fbd5d Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 27 Oct 2009 19:42:44 +0100 Subject: [PATCH 83/85] Automatically convert directive functions, and add a test for that. --- sphinx/application.py | 7 ++++--- sphinx/domains/__init__.py | 1 - tests/root/conf.py | 16 ++++++++++++++++ tests/root/contents.txt | 1 + tests/root/extapi.txt | 10 ++++++++++ tests/test_build_html.py | 4 ++++ 6 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 tests/root/extapi.txt diff --git a/sphinx/application.py b/sphinx/application.py index 4d861a69a..af10bb7e2 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -18,7 +18,8 @@ from os import path from cStringIO import StringIO from docutils import nodes -from docutils.parsers.rst import Directive, directives, roles +from docutils.parsers.rst import Directive, convert_directive_function, \ + directives, roles import sphinx from sphinx import package_dir, locale @@ -341,9 +342,9 @@ class Sphinx(object): return obj else: obj.content = content - obj.arguments = arguments + obj.arguments = arguments or (0, 0, False) obj.options = options - return obj + return convert_directive_function(obj) def add_directive(self, name, obj, content=None, arguments=None, **options): directives.register_directive( diff --git a/sphinx/domains/__init__.py b/sphinx/domains/__init__.py index 563fd2aa0..0ec0622fa 100644 --- a/sphinx/domains/__init__.py +++ b/sphinx/domains/__init__.py @@ -112,7 +112,6 @@ class Domain(object): if name not in self.directives: return None fullname = '%s:%s' % (self.name, name) - # XXX what about function-style directives? BaseDirective = self.directives[name] class DirectiveAdapter(BaseDirective): def run(self): diff --git a/tests/root/conf.py b/tests/root/conf.py index d268ae3a5..dbb2bca7e 100644 --- a/tests/root/conf.py +++ b/tests/root/conf.py @@ -58,7 +58,12 @@ autosummary_generate = ['autosummary'] # modify tags from conf.py tags.add('confpytag') + +# -- extension API + +from docutils import nodes from sphinx import addnodes +from sphinx.util.compat import Directive def userdesc_parse(env, sig, signode): x, y = sig.split(':') @@ -67,7 +72,18 @@ def userdesc_parse(env, sig, signode): signode[-1] += addnodes.desc_parameter(y, y) return x +def functional_directive(name, arguments, options, content, lineno, + content_offset, block_text, state, state_machine): + return [nodes.strong(text='from function: %s' % options['opt'])] + +class ClassDirective(Directive): + option_spec = {'opt': lambda x: x} + def run(self): + return [nodes.strong(text='from class: %s' % self.options['opt'])] + def setup(app): app.add_config_value('value_from_conf_py', 42, False) + app.add_directive('funcdir', functional_directive, opt=lambda x: x) + app.add_directive('clsdir', ClassDirective) app.add_description_unit('userdesc', 'userdescrole', '%s (userdesc)', userdesc_parse) diff --git a/tests/root/contents.txt b/tests/root/contents.txt index e1468ad04..2d7ba8fb0 100644 --- a/tests/root/contents.txt +++ b/tests/root/contents.txt @@ -11,6 +11,7 @@ Contents: :maxdepth: 2 :numbered: + extapi images subdir/images subdir/includes diff --git a/tests/root/extapi.txt b/tests/root/extapi.txt new file mode 100644 index 000000000..4728e3de1 --- /dev/null +++ b/tests/root/extapi.txt @@ -0,0 +1,10 @@ +Extension API tests +=================== + +Testing directives: + +.. funcdir:: + :opt: Foo + +.. clsdir:: + :opt: Bar diff --git a/tests/test_build_html.py b/tests/test_build_html.py index 7dfbef5b6..dd89ff2e0 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -66,6 +66,10 @@ HTML_XPATH = { ".//dt[@id='test_autodoc.function']/em": r'\*\*kwds', ".//dd": r'Return spam\.', }, + 'extapi.html': { + ".//strong": 'from function: Foo', + ".//strong": 'from class: Bar', + }, 'markup.html': { ".//title": 'set by title directive', ".//p/em": 'Section author: Georg Brandl', From d12ad380f28c0fccff7442035f183c2ee94e9cff Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Tue, 27 Oct 2009 19:58:40 +0100 Subject: [PATCH 84/85] Finish renaming description units to object (description)s. --- doc/concepts.rst | 2 +- doc/config.rst | 4 ++-- doc/ext/appapi.rst | 29 +++++++++++++++++------------ doc/glossary.rst | 10 +++++----- doc/markup/desc.rst | 16 +++++++++------- sphinx/addnodes.py | 2 +- sphinx/application.py | 2 -- sphinx/domains/std.py | 2 +- sphinx/environment.py | 2 +- tests/root/conf.py | 4 ++-- 10 files changed, 39 insertions(+), 34 deletions(-) diff --git a/doc/concepts.rst b/doc/concepts.rst index 277672111..212b4c7cc 100644 --- a/doc/concepts.rst +++ b/doc/concepts.rst @@ -166,7 +166,7 @@ The special document names (and pages generated for them) are: respectively. The general index is populated with entries from modules, all index-generating - :ref:`description units `, 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/config.rst b/doc/config.rst index 47fc2bdd0..529f9cf8d 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -298,8 +298,8 @@ Project information .. confval:: add_module_names A boolean that decides whether module names are prepended to all - :term:`description unit` titles, e.g. for :dir:`function` directives. - Default is ``True``. + :term:`object` names (for object types where a "module" of some kind is + defined), e.g. for :dir:`function` directives. Default is ``True``. .. confval:: show_authors diff --git a/doc/ext/appapi.rst b/doc/ext/appapi.rst index 0a3dae8fb..754f22eb4 100644 --- a/doc/ext/appapi.rst +++ b/doc/ext/appapi.rst @@ -123,26 +123,28 @@ the following public API: .. versionadded:: 0.6 -.. method:: Sphinx.add_description_unit(directivename, rolename, indextemplate='', parse_node=None, ref_nodeclass=None) +.. method:: Sphinx.add_object_type(directivename, rolename, indextemplate='', parse_node=None, ref_nodeclass=None, objname='') - This method is a very convenient way to add a new type of information that + This method is a very convenient way to add a new :term:`object` type that can be cross-referenced. It will do this: - * Create a new directive (called *directivename*) for a :term:`description - unit`. It will automatically add index entries if *indextemplate* is - nonempty; if given, it must contain exactly one instance of ``%s``. See - the example below for how the template will be interpreted. + * Create a new directive (called *directivename*) for documenting an object. + It will automatically add index entries if *indextemplate* is nonempty; if + given, it must contain exactly one instance of ``%s``. See the example + below for how the template will be interpreted. * Create a new role (called *rolename*) to cross-reference to these - description units. + object descriptions. * If you provide *parse_node*, it must be a function that takes a string and a docutils node, and it must populate the node with children parsed from the string. It must then return the name of the item to be used in cross-referencing and index entries. See the :file:`ext.py` file in the source for this documentation for an example. + * The *objname* (if not given, will default to *directivename*) names the + type of object. It is used when listing objects, e.g. in search results. For example, if you have this call in a custom Sphinx extension:: - app.add_description_unit('directive', 'dir', 'pair: %s; directive') + app.add_object_type('directive', 'dir', 'pair: %s; directive') you can use this markup in your documents:: @@ -164,12 +166,15 @@ the following public API: ``docutils.nodes.emphasis`` or ``docutils.nodes.strong`` -- you can also use ``docutils.nodes.generated`` if you want no further text decoration). - For the role content, you have the same options as for standard Sphinx roles - (see :ref:`xref-syntax`). + For the role content, you have the same syntactical possibilities as for + standard Sphinx roles (see :ref:`xref-syntax`). -.. method:: Sphinx.add_crossref_type(directivename, rolename, indextemplate='', ref_nodeclass=None) + This method is also available under the deprecated alias + :meth:`add_description_unit`. - This method is very similar to :meth:`add_description_unit` except that the +.. method:: Sphinx.add_crossref_type(directivename, rolename, indextemplate='', ref_nodeclass=None, objname='') + + This method is very similar to :meth:`add_object_type` except that the directive it generates must be empty, and will produce no output. That means that you can add semantic targets to your sources, and refer to diff --git a/doc/glossary.rst b/doc/glossary.rst index 7ec787ff5..13316900b 100644 --- a/doc/glossary.rst +++ b/doc/glossary.rst @@ -19,17 +19,17 @@ Glossary the :term:`source directory`, but can be set differently with the **-c** command-line option. - description unit - The basic building block of Sphinx documentation. Every "description - directive" (e.g. :dir:`function` or :dir:`describe`) creates such a unit; - and most units can be cross-referenced to. - environment A structure where information about all documents under the root is saved, and used for cross-referencing. The environment is pickled after the parsing stage, so that successive runs only need to read and parse new and changed documents. + object + The basic building block of Sphinx documentation. Every "object + directive" (e.g. :dir:`function` or :dir:`object`) creates such a block; + and most objects can be cross-referenced to. + source directory The directory which, including its subdirectories, contains all source files for one Sphinx project. diff --git a/doc/markup/desc.rst b/doc/markup/desc.rst index 058b5137d..4df9f6213 100644 --- a/doc/markup/desc.rst +++ b/doc/markup/desc.rst @@ -67,10 +67,12 @@ The directives you can use for module declarations are: .. _desc-units: -Object description units ------------------------- +Object descriptions +------------------- -There are a number of directives used to describe specific features provided by +.. 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 @@ -243,7 +245,7 @@ Info field lists .. versionadded:: 0.4 -Inside description unit directives, reST field lists with these fields are +Inside object description directives, reST field lists with these fields are recognized and formatted nicely: * ``param``, ``parameter``, ``arg``, ``argument``, ``key``, ``keyword``: @@ -340,8 +342,8 @@ There is a set of directives allowing documenting command-line programs: .. versionadded:: 0.5 -Custom description units -~~~~~~~~~~~~~~~~~~~~~~~~ +Custom object types +~~~~~~~~~~~~~~~~~~~ There is also a generic version of these directives: @@ -356,4 +358,4 @@ There is also a generic version of these directives: Describes a Python bytecode instruction. Extensions may add more directives like that, using the -:func:`~sphinx.application.Sphinx.add_description_unit` method. +:func:`~sphinx.application.Sphinx.add_object_type` method. diff --git a/sphinx/addnodes.py b/sphinx/addnodes.py index 71a26febc..08d6feaaf 100644 --- a/sphinx/addnodes.py +++ b/sphinx/addnodes.py @@ -14,7 +14,7 @@ from docutils import nodes # index markup class index(nodes.Invisible, nodes.Inline, nodes.TextElement): pass -# domain-specific object description units (class, function etc.) +# domain-specific object descriptions (class, function etc.) # parent node for signature and content class desc(nodes.Admonition, nodes.Element): pass diff --git a/sphinx/application.py b/sphinx/application.py index af10bb7e2..55998058e 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -382,7 +382,6 @@ class Sphinx(object): def add_object_type(self, directivename, rolename, indextemplate='', parse_node=None, ref_nodeclass=None, objname=''): - # XXX document objname StandardDomain.object_types[directivename] = \ ObjType(objname or directivename, rolename) # create a subclass of GenericObject as the new directive @@ -398,7 +397,6 @@ class Sphinx(object): def add_crossref_type(self, directivename, rolename, indextemplate='', ref_nodeclass=None, objname=''): - # XXX document objname StandardDomain.object_types[directivename] = \ ObjType(objname or directivename, rolename) # create a subclass of Target as the new directive diff --git a/sphinx/domains/std.py b/sphinx/domains/std.py index f2da7d116..37ae9378b 100644 --- a/sphinx/domains/std.py +++ b/sphinx/domains/std.py @@ -30,7 +30,7 @@ option_desc_re = re.compile( class GenericObject(ObjectDescription): """ - A generic x-ref directive registered with Sphinx.add_description_unit(). + A generic x-ref directive registered with Sphinx.add_object_type(). """ indextemplate = '' parse_node = None diff --git a/sphinx/environment.py b/sphinx/environment.py index 81c044d83..f309823d1 100644 --- a/sphinx/environment.py +++ b/sphinx/environment.py @@ -888,7 +888,7 @@ class BuildEnvironment: if name.isdigit() or node.has_key('refuri') or \ node.tagname.startswith('desc_'): # ignore footnote labels, labels automatically generated from a - # link and description units + # link and object descriptions continue if name in self.labels: self.warn(docname, 'duplicate label %s, ' % name + diff --git a/tests/root/conf.py b/tests/root/conf.py index dbb2bca7e..13b267994 100644 --- a/tests/root/conf.py +++ b/tests/root/conf.py @@ -85,5 +85,5 @@ def setup(app): app.add_config_value('value_from_conf_py', 42, False) app.add_directive('funcdir', functional_directive, opt=lambda x: x) app.add_directive('clsdir', ClassDirective) - app.add_description_unit('userdesc', 'userdescrole', '%s (userdesc)', - userdesc_parse) + app.add_object_type('userdesc', 'userdescrole', '%s (userdesc)', + userdesc_parse, objname='user desc') From ae0c16a3d3de66325d0e4b826c8988142986fb9e Mon Sep 17 00:00:00 2001 From: Georg Brandl Date: Sun, 8 Nov 2009 16:29:31 +0100 Subject: [PATCH 85/85] Update message catalog. --- sphinx/locale/sphinx.pot | 396 ++++++++++++++++++++++----------------- 1 file changed, 224 insertions(+), 172 deletions(-) diff --git a/sphinx/locale/sphinx.pot b/sphinx/locale/sphinx.pot index 438d247d7..64bc69dda 100644 --- a/sphinx/locale/sphinx.pot +++ b/sphinx/locale/sphinx.pot @@ -6,9 +6,9 @@ #, fuzzy msgid "" msgstr "" -"Project-Id-Version: Sphinx 1.0pre/4bacd76a2cdc+a18846601c6e+\n" +"Project-Id-Version: Sphinx 1.0pre/[?1034h2e1ab15e035e\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2009-08-09 22:45+0200\n" +"POT-Creation-Date: 2009-11-08 16:28+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,117 +17,12 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Generated-By: Babel 0.9.4\n" -#: sphinx/domains.py:290 -#, python-format -msgid "%s() (built-in function)" -msgstr "" - -#: sphinx/domains.py:291 sphinx/domains.py:348 sphinx/domains.py:360 -#: sphinx/domains.py:373 -#, python-format -msgid "%s() (in module %s)" -msgstr "" - -#: sphinx/domains.py:294 -#, python-format -msgid "%s (built-in variable)" -msgstr "" - -#: sphinx/domains.py:295 sphinx/domains.py:386 -#, python-format -msgid "%s (in module %s)" -msgstr "" - -#: sphinx/domains.py:311 -#, python-format -msgid "%s (built-in class)" -msgstr "" - -#: sphinx/domains.py:312 -#, python-format -msgid "%s (class in %s)" -msgstr "" - -#: sphinx/domains.py:352 -#, python-format -msgid "%s() (%s.%s method)" -msgstr "" - -#: sphinx/domains.py:354 -#, python-format -msgid "%s() (%s method)" -msgstr "" - -#: sphinx/domains.py:364 -#, python-format -msgid "%s() (%s.%s static method)" -msgstr "" - -#: sphinx/domains.py:367 -#, python-format -msgid "%s() (%s static method)" -msgstr "" - -#: sphinx/domains.py:377 -#, python-format -msgid "%s() (%s.%s class method)" -msgstr "" - -#: sphinx/domains.py:380 -#, python-format -msgid "%s() (%s class method)" -msgstr "" - -#: sphinx/domains.py:390 -#, python-format -msgid "%s (%s.%s attribute)" -msgstr "" - -#: sphinx/domains.py:392 -#, python-format -msgid "%s (%s attribute)" -msgstr "" - -#: sphinx/domains.py:437 -msgid "Platforms: " -msgstr "" - -#: sphinx/domains.py:443 -#, python-format -msgid "%s (module)" -msgstr "" - -#: sphinx/domains.py:705 -#, python-format -msgid "%s (C function)" -msgstr "" - -#: sphinx/domains.py:707 -#, python-format -msgid "%s (C member)" -msgstr "" - -#: sphinx/domains.py:709 -#, python-format -msgid "%s (C macro)" -msgstr "" - -#: sphinx/domains.py:711 -#, python-format -msgid "%s (C type)" -msgstr "" - -#: sphinx/domains.py:713 -#, python-format -msgid "%s (C variable)" -msgstr "" - -#: sphinx/environment.py:106 sphinx/writers/latex.py:184 +#: sphinx/environment.py:130 sphinx/writers/latex.py:184 #, python-format msgid "%B %d, %Y" msgstr "" -#: sphinx/environment.py:333 sphinx/themes/basic/genindex-single.html:2 +#: sphinx/environment.py:348 sphinx/themes/basic/genindex-single.html:2 #: sphinx/themes/basic/genindex-split.html:2 #: sphinx/themes/basic/genindex-split.html:5 #: sphinx/themes/basic/genindex.html:2 sphinx/themes/basic/genindex.html:5 @@ -136,61 +31,56 @@ msgstr "" msgid "Index" msgstr "" -#: sphinx/environment.py:334 sphinx/writers/latex.py:189 +#: sphinx/environment.py:349 sphinx/writers/latex.py:189 msgid "Module Index" msgstr "" -#: sphinx/environment.py:335 sphinx/themes/basic/defindex.html:16 +#: sphinx/environment.py:350 sphinx/themes/basic/defindex.html:16 msgid "Search Page" msgstr "" -#: sphinx/roles.py:139 sphinx/directives/desc.py:382 -#, python-format -msgid "environment variable; %s" -msgstr "" - -#: sphinx/roles.py:146 +#: sphinx/roles.py:167 #, python-format msgid "Python Enhancement Proposals!PEP %s" msgstr "" -#: sphinx/builders/changes.py:71 +#: sphinx/builders/changes.py:70 msgid "Builtins" msgstr "" -#: sphinx/builders/changes.py:73 +#: sphinx/builders/changes.py:72 msgid "Module level" msgstr "" -#: sphinx/builders/html.py:223 +#: sphinx/builders/html.py:224 #, python-format msgid "%b %d, %Y" msgstr "" -#: sphinx/builders/html.py:242 sphinx/themes/basic/defindex.html:21 +#: sphinx/builders/html.py:243 sphinx/themes/basic/defindex.html:21 msgid "General Index" msgstr "" -#: sphinx/builders/html.py:242 +#: sphinx/builders/html.py:243 msgid "index" msgstr "" -#: sphinx/builders/html.py:246 sphinx/builders/htmlhelp.py:219 +#: sphinx/builders/html.py:247 sphinx/builders/htmlhelp.py:220 #: sphinx/builders/qthelp.py:133 sphinx/themes/basic/defindex.html:19 #: sphinx/themes/basic/modindex.html:2 sphinx/themes/basic/modindex.html:13 #: sphinx/themes/scrolls/modindex.html:2 sphinx/themes/scrolls/modindex.html:13 msgid "Global Module Index" msgstr "" -#: sphinx/builders/html.py:247 +#: sphinx/builders/html.py:248 msgid "modules" msgstr "" -#: sphinx/builders/html.py:303 +#: sphinx/builders/html.py:304 msgid "next" msgstr "" -#: sphinx/builders/html.py:312 +#: sphinx/builders/html.py:313 msgid "previous" msgstr "" @@ -198,74 +88,248 @@ msgstr "" msgid " (in " msgstr "" -#: sphinx/directives/desc.py:67 sphinx/directives/desc.py:68 -#: sphinx/directives/desc.py:69 sphinx/directives/desc.py:70 +#: sphinx/directives/__init__.py:78 sphinx/directives/__init__.py:79 +#: sphinx/directives/__init__.py:80 sphinx/directives/__init__.py:81 msgid "Raises" msgstr "" -#: sphinx/directives/desc.py:71 sphinx/directives/desc.py:72 -#: sphinx/directives/desc.py:73 +#: sphinx/directives/__init__.py:82 sphinx/directives/__init__.py:83 +#: sphinx/directives/__init__.py:84 msgid "Variable" msgstr "" -#: sphinx/directives/desc.py:74 sphinx/directives/desc.py:75 -#: sphinx/directives/desc.py:81 sphinx/directives/desc.py:82 +#: sphinx/directives/__init__.py:85 sphinx/directives/__init__.py:86 +#: sphinx/directives/__init__.py:92 sphinx/directives/__init__.py:93 msgid "Returns" msgstr "" -#: sphinx/directives/desc.py:83 +#: sphinx/directives/__init__.py:94 msgid "Return type" msgstr "" -#: sphinx/directives/desc.py:156 +#: sphinx/directives/__init__.py:169 msgid "Parameter" msgstr "" -#: sphinx/directives/desc.py:160 +#: sphinx/directives/__init__.py:173 msgid "Parameters" msgstr "" -#: sphinx/directives/desc.py:284 +#: sphinx/directives/other.py:127 +msgid "Section author: " +msgstr "" + +#: sphinx/directives/other.py:129 +msgid "Module author: " +msgstr "" + +#: sphinx/directives/other.py:131 +msgid "Author: " +msgstr "" + +#: sphinx/directives/other.py:233 +msgid "See also" +msgstr "" + +#: sphinx/domains/c.py:124 +#, python-format +msgid "%s (C function)" +msgstr "" + +#: sphinx/domains/c.py:126 +#, python-format +msgid "%s (C member)" +msgstr "" + +#: sphinx/domains/c.py:128 +#, python-format +msgid "%s (C macro)" +msgstr "" + +#: sphinx/domains/c.py:130 +#, python-format +msgid "%s (C type)" +msgstr "" + +#: sphinx/domains/c.py:132 +#, python-format +msgid "%s (C variable)" +msgstr "" + +#: sphinx/domains/c.py:162 +msgid "C function" +msgstr "" + +#: sphinx/domains/c.py:163 +msgid "C member" +msgstr "" + +#: sphinx/domains/c.py:164 +msgid "C macro" +msgstr "" + +#: sphinx/domains/c.py:165 +msgid "C type" +msgstr "" + +#: sphinx/domains/c.py:166 +msgid "C variable" +msgstr "" + +#: sphinx/domains/python.py:186 +#, python-format +msgid "%s() (built-in function)" +msgstr "" + +#: sphinx/domains/python.py:187 sphinx/domains/python.py:244 +#: sphinx/domains/python.py:256 sphinx/domains/python.py:269 +#, python-format +msgid "%s() (in module %s)" +msgstr "" + +#: sphinx/domains/python.py:190 +#, python-format +msgid "%s (built-in variable)" +msgstr "" + +#: sphinx/domains/python.py:191 sphinx/domains/python.py:282 +#, python-format +msgid "%s (in module %s)" +msgstr "" + +#: sphinx/domains/python.py:207 +#, python-format +msgid "%s (built-in class)" +msgstr "" + +#: sphinx/domains/python.py:208 +#, python-format +msgid "%s (class in %s)" +msgstr "" + +#: sphinx/domains/python.py:248 +#, python-format +msgid "%s() (%s.%s method)" +msgstr "" + +#: sphinx/domains/python.py:250 +#, python-format +msgid "%s() (%s method)" +msgstr "" + +#: sphinx/domains/python.py:260 +#, python-format +msgid "%s() (%s.%s static method)" +msgstr "" + +#: sphinx/domains/python.py:263 +#, python-format +msgid "%s() (%s static method)" +msgstr "" + +#: sphinx/domains/python.py:273 +#, python-format +msgid "%s() (%s.%s class method)" +msgstr "" + +#: sphinx/domains/python.py:276 +#, python-format +msgid "%s() (%s class method)" +msgstr "" + +#: sphinx/domains/python.py:286 +#, python-format +msgid "%s (%s.%s attribute)" +msgstr "" + +#: sphinx/domains/python.py:288 +#, python-format +msgid "%s (%s attribute)" +msgstr "" + +#: sphinx/domains/python.py:334 +msgid "Platforms: " +msgstr "" + +#: sphinx/domains/python.py:340 +#, python-format +msgid "%s (module)" +msgstr "" + +#: sphinx/domains/python.py:396 +msgid "function" +msgstr "" + +#: sphinx/domains/python.py:397 +msgid "data" +msgstr "" + +#: sphinx/domains/python.py:398 +msgid "class" +msgstr "" + +#: sphinx/domains/python.py:399 sphinx/locale/__init__.py:161 +msgid "exception" +msgstr "" + +#: sphinx/domains/python.py:400 +msgid "method" +msgstr "" + +#: sphinx/domains/python.py:401 +msgid "attribute" +msgstr "" + +#: sphinx/domains/python.py:402 sphinx/locale/__init__.py:157 +msgid "module" +msgstr "" + +#: sphinx/domains/std.py:67 sphinx/domains/std.py:83 +#, python-format +msgid "environment variable; %s" +msgstr "" + +#: sphinx/domains/std.py:156 #, python-format msgid "%scommand line option; %s" msgstr "" -#: sphinx/directives/other.py:126 -msgid "Section author: " +#: sphinx/domains/std.py:324 +msgid "glossary term" msgstr "" -#: sphinx/directives/other.py:128 -msgid "Module author: " +#: sphinx/domains/std.py:325 +msgid "grammar token" msgstr "" -#: sphinx/directives/other.py:130 -msgid "Author: " +#: sphinx/domains/std.py:326 +msgid "environment variable" msgstr "" -#: sphinx/directives/other.py:250 -msgid "See also" +#: sphinx/domains/std.py:327 +msgid "program option" msgstr "" -#: sphinx/ext/autodoc.py:889 +#: sphinx/ext/autodoc.py:892 #, python-format msgid " Bases: %s" msgstr "" -#: sphinx/ext/autodoc.py:922 +#: sphinx/ext/autodoc.py:925 #, python-format msgid "alias of :class:`%s`" msgstr "" -#: sphinx/ext/todo.py:41 +#: sphinx/ext/todo.py:40 msgid "Todo" msgstr "" -#: sphinx/ext/todo.py:99 +#: sphinx/ext/todo.py:98 #, python-format msgid "(The original entry is located in %s, line %d and can be found " msgstr "" -#: sphinx/ext/todo.py:105 +#: sphinx/ext/todo.py:104 msgid "here" msgstr "" @@ -324,10 +388,6 @@ msgstr "" msgid "Deprecated since version %s" msgstr "" -#: sphinx/locale/__init__.py:157 -msgid "module" -msgstr "" - #: sphinx/locale/__init__.py:158 msgid "keyword" msgstr "" @@ -340,10 +400,6 @@ msgstr "" msgid "object" msgstr "" -#: sphinx/locale/__init__.py:161 -msgid "exception" -msgstr "" - #: sphinx/locale/__init__.py:162 msgid "statement" msgstr "" @@ -512,7 +568,7 @@ msgid "search" msgstr "" #: sphinx/themes/basic/search.html:25 -#: sphinx/themes/basic/static/searchtools.js:462 +#: sphinx/themes/basic/static/searchtools.js:473 msgid "Search Results" msgstr "" @@ -548,16 +604,16 @@ msgstr "" msgid "Other changes" msgstr "" -#: sphinx/themes/basic/static/doctools.js:139 sphinx/writers/html.py:473 -#: sphinx/writers/html.py:478 +#: sphinx/themes/basic/static/doctools.js:138 sphinx/writers/html.py:462 +#: sphinx/writers/html.py:467 msgid "Permalink to this headline" msgstr "" -#: sphinx/themes/basic/static/doctools.js:145 sphinx/writers/html.py:80 +#: sphinx/themes/basic/static/doctools.js:144 sphinx/writers/html.py:80 msgid "Permalink to this definition" msgstr "" -#: sphinx/themes/basic/static/doctools.js:174 +#: sphinx/themes/basic/static/doctools.js:173 msgid "Hide Search Matches" msgstr "" @@ -569,21 +625,17 @@ msgstr "" msgid "Preparing search..." msgstr "" -#: sphinx/themes/basic/static/searchtools.js:347 -msgid "module, in " -msgstr "" - -#: sphinx/themes/basic/static/searchtools.js:356 +#: sphinx/themes/basic/static/searchtools.js:352 msgid ", in " msgstr "" -#: sphinx/themes/basic/static/searchtools.js:464 +#: sphinx/themes/basic/static/searchtools.js:475 msgid "" "Your search did not match any documents. Please make sure that all words " "are spelled correctly and that you've selected enough categories." msgstr "" -#: sphinx/themes/basic/static/searchtools.js:466 +#: sphinx/themes/basic/static/searchtools.js:477 #, python-format msgid "Search finished, found %s page(s) matching the search query." msgstr "" @@ -592,15 +644,15 @@ msgstr "" msgid "Release" msgstr "" -#: sphinx/writers/latex.py:578 +#: sphinx/writers/latex.py:579 msgid "Footnotes" msgstr "" -#: sphinx/writers/latex.py:646 +#: sphinx/writers/latex.py:647 msgid "continued from previous page" msgstr "" -#: sphinx/writers/latex.py:651 +#: sphinx/writers/latex.py:652 msgid "Continued on next page" msgstr ""