getargspec moved to sphinx.util.inspect

This commit is contained in:
Łukasz Langa 2011-01-08 18:33:29 +01:00
commit 0964f4f993
18 changed files with 178 additions and 125 deletions

10
CHANGES
View File

@ -5,6 +5,10 @@ Release 1.1 (in development)
* Added a Texinfo builder.
* Incompatibility: The :rst:dir:`py:module` directive doesn't output
its ``platform`` option value anymore. (It was the only thing that
the directive did output, and therefore quite inconsistent.)
* Added i18n support for content, a ``gettext`` builder and
related utilities.
@ -85,6 +89,12 @@ Release 1.1 (in development)
Release 1.0.7 (in development)
==============================
* #572: Show warnings by default when reference labels cannot be
found.
* #536: Include line number when complaining about missing reference
targets in nitpicky mode.
* #590: Fix inline display of graphviz diagrams in LaTeX output.
* #589: Build using app.build() in setup command.

View File

@ -8,8 +8,9 @@ PAPER =
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) \
$(SPHINXOPTS) $(O) .
ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) \
$(SPHINXOPTS) $(O) .
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) $(O) .
.PHONY: help clean html dirhtml singlehtml text man pickle json htmlhelp \
qthelp devhelp epub latex latexpdf changes linkcheck doctest
@ -116,7 +117,7 @@ latexpdf:
@echo "pdflatex finished; the PDF files are in _build/latex."
gettext:
$(SPHINXBUILD) -b gettext $(ALLSPHINXOPTS) _build/locale
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) _build/locale
@echo
@echo "Build finished. The message catalogs are in _build/locale."

View File

@ -240,7 +240,7 @@ Note that a direct PDF builder using ReportLab is available in `rst2pdf
.. versionadded:: 0.5
.. module:: sphinx.builders.intl
.. module:: sphinx.builders.gettext
.. class:: MessageCatalogBuilder
This builder produces gettext-style message catalos. Each top-level file or

View File

@ -32,7 +32,7 @@ task to split up paragraphs which are too large as there is no sane automated
way to do that.
After Sphinx successfully ran the
:class:`~sphinx.builders.intl.MessageCatalogBuilder` you will find a collection
:class:`~sphinx.builders.gettext.MessageCatalogBuilder` you will find a collection
of ``.pot`` files in your output directory. These are **catalog templates**
and contain messages in your original language *only*.

View File

@ -179,7 +179,7 @@ These themes are:
*nosidebar*.
* **pyramid** -- A theme from the Pyramid web framework project, designed by
Blais Laflamme. THere are currently no options beyond *nosidebar*.
Blaise Laflamme. THere are currently no options beyond *nosidebar*.
* **haiku** -- A theme without sidebar inspired by the `Haiku OS user guide
<http://www.haiku-os.org/docs/userguide/en/contents.html>`_. The following

View File

@ -31,9 +31,12 @@ class Builder(object):
name = ''
# builder's output format, or '' if no document output is produced
format = ''
# doctree versioning method
versioning_method = 'none'
def __init__(self, app):
self.env = app.env
self.env.set_versioning_method(self.versioning_method)
self.srcdir = app.srcdir
self.confdir = app.confdir
self.outdir = app.outdir
@ -330,5 +333,5 @@ BUILTIN_BUILDERS = {
'changes': ('changes', 'ChangesBuilder'),
'linkcheck': ('linkcheck', 'CheckExternalLinksBuilder'),
'websupport': ('websupport', 'WebSupportBuilder'),
'gettext': ('intl', 'MessageCatalogBuilder'),
'gettext': ('gettext', 'MessageCatalogBuilder'),
}

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""
sphinx.builders.intl
~~~~~~~~~~~~~~~~~~~~
sphinx.builders.gettext
~~~~~~~~~~~~~~~~~~~~~~~
The MessageCatalogBuilder class.
@ -48,6 +48,7 @@ class I18nBuilder(Builder):
General i18n builder.
"""
name = 'i18n'
versioning_method = 'text'
def init(self):
Builder.init(self)

View File

@ -26,6 +26,7 @@ class WebSupportBuilder(PickleHTMLBuilder):
Builds documents for the web support package.
"""
name = 'websupport'
versioning_method = 'commentable'
def init(self):
PickleHTMLBuilder.init(self)

View File

@ -131,6 +131,8 @@ class Domain(object):
roles = {}
#: a list of Index subclasses
indices = []
#: role name -> a warning message if reference is missing
dangling_warnings = {}
#: data value for a fresh environment
initial_data = {}

View File

@ -1090,13 +1090,15 @@ class CPPDomain(Domain):
contnode, name)
parser = DefinitionParser(target)
# XXX: warn?
try:
expr = parser.parse_type().get_name()
parser.skip_ws()
if not parser.eof or expr is None:
return None
raise DefinitionError('')
except DefinitionError:
refdoc = node.get('refdoc', fromdocname)
env.warn(refdoc, 'unparseable C++ definition: %r' % target,
node.line)
return None
parent = node['cpp:parent']

View File

@ -419,15 +419,8 @@ class PyModule(Directive):
targetnode = nodes.target('', '', ids=['module-' + modname], ismod=True)
self.state.document.note_explicit_target(targetnode)
ret = [targetnode]
# XXX this behavior of the module directive is a mess...
if 'platform' in self.options:
platform = self.options['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
# the platform and synopsis aren't printed; in fact, they are only used
# in the modindex currently
if not noindex:
indextext = _('%s (module)') % modname
inode = addnodes.index(entries=[('single', indextext,

View File

@ -411,11 +411,13 @@ class StandardDomain(Domain):
# links to tokens in grammar productions
'token': XRefRole(),
# links to terms in glossary
'term': XRefRole(lowercase=True, innernodeclass=nodes.emphasis),
'term': XRefRole(lowercase=True, innernodeclass=nodes.emphasis,
warn_dangling=True),
# links to headings or arbitrary labels
'ref': XRefRole(lowercase=True, innernodeclass=nodes.emphasis),
'ref': XRefRole(lowercase=True, innernodeclass=nodes.emphasis,
warn_dangling=True),
# links to labels, without a different title
'keyword': XRefRole(),
'keyword': XRefRole(warn_dangling=True),
}
initial_data = {
@ -433,6 +435,13 @@ class StandardDomain(Domain):
},
}
dangling_warnings = {
'term': 'term not in glossary: %(target)s',
'ref': 'undefined label: %(target)s (if the link has no caption '
'the label must precede a section header)',
'keyword': 'unknown keyword: %(target)s',
}
def clear_doc(self, docname):
for key, (fn, _) in self.data['progoptions'].items():
if fn == docname:
@ -490,27 +499,16 @@ class StandardDomain(Domain):
def resolve_xref(self, env, fromdocname, builder,
typ, target, node, contnode):
if typ == 'ref':
#refdoc = node.get('refdoc', fromdocname)
if node['refexplicit']:
# reference to anonymous label; the reference uses
# the supplied link caption
docname, labelid = self.data['anonlabels'].get(target, ('',''))
sectname = node.astext()
# XXX warn somehow if not resolved by intersphinx
#if not docname:
# env.warn(refdoc, 'undefined label: %s' %
# target, node.line)
else:
# reference to named label; the final node will
# contain the section name after the label
docname, labelid, sectname = self.data['labels'].get(target,
('','',''))
# XXX warn somehow if not resolved by intersphinx
#if not docname:
# env.warn(refdoc,
# 'undefined label: %s' % target + ' -- if you '
# 'don\'t give a link caption the label must '
# 'precede a section header.', node.line)
if not docname:
return None
newnode = nodes.reference('', '', internal=True)
@ -534,20 +532,17 @@ class StandardDomain(Domain):
# keywords are oddballs: they are referenced by named labels
docname, labelid, _ = self.data['labels'].get(target, ('','',''))
if not docname:
#env.warn(refdoc, 'unknown keyword: %s' % target)
return None
else:
return make_refnode(builder, fromdocname, docname,
labelid, contnode)
return make_refnode(builder, fromdocname, docname,
labelid, contnode)
elif 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)
return make_refnode(builder, fromdocname, docname,
labelid, contnode)
else:
objtypes = self.objtypes_for_role(typ) or []
for objtype in objtypes:
@ -557,13 +552,9 @@ class StandardDomain(Domain):
else:
docname, labelid = '', ''
if not docname:
if typ == 'term':
env.warn(node.get('refdoc', fromdocname),
'term not in glossary: %s' % target, node.line)
return None
else:
return make_refnode(builder, fromdocname, docname,
labelid, contnode)
return make_refnode(builder, fromdocname, docname,
labelid, contnode)
def get_objects(self):
for (prog, option), info in self.data['progoptions'].iteritems():

View File

@ -43,6 +43,7 @@ from sphinx.util.nodes import clean_astext, make_refnode, extract_messages
from sphinx.util.osutil import movefile, SEP, ustrftime
from sphinx.util.matching import compile_matchers
from sphinx.util.pycompat import all, class_types
from sphinx.util.websupport import is_commentable
from sphinx.errors import SphinxError, ExtensionError
from sphinx.locale import _, init as init_locale
from sphinx.versioning import add_uids, merge_doctrees
@ -68,7 +69,7 @@ default_settings = {
# This is increased every time an environment attribute is added
# or changed to properly invalidate pickle files.
ENV_VERSION = 39
ENV_VERSION = 40
default_substitutions = set([
@ -79,6 +80,12 @@ default_substitutions = set([
dummy_reporter = Reporter('', 4, 4)
versioning_conditions = {
'none': False,
'text': nodes.TextElement,
'commentable': is_commentable,
}
class WarningStream(object):
def __init__(self, warnfunc):
@ -182,7 +189,8 @@ class CitationReferences(Transform):
for citnode in self.document.traverse(nodes.citation_reference):
cittext = citnode.astext()
refnode = addnodes.pending_xref(cittext, reftype='citation',
reftarget=cittext)
reftarget=cittext, refwarn=True)
refnode.line = citnode.line or citnode.parent.line
refnode += nodes.Text('[' + cittext + ']')
citnode.parent.replace(citnode, refnode)
@ -313,6 +321,9 @@ class BuildEnvironment:
self.srcdir = srcdir
self.config = config
# the method of doctree versioning; see set_versioning_method
self.versioning_condition = None
# the application object; only set while update() runs
self.app = None
@ -380,6 +391,23 @@ class BuildEnvironment:
self._warnfunc = func
self.settings['warning_stream'] = WarningStream(func)
def set_versioning_method(self, method):
"""This sets the doctree versioning method for this environment.
Versioning methods are a builder property; only builders with the same
versioning method can share the same doctree directory. Therefore, we
raise an exception if the user tries to use an environment with an
incompatible versioning method.
"""
if method not in versioning_conditions:
raise ValueError('invalid versioning method: %r' % method)
condition = versioning_conditions[method]
if self.versioning_condition not in (None, condition):
raise SphinxError('This environment is incompatible with the '
'selected builder, please choose another '
'doctree directory.')
self.versioning_condition = condition
def warn(self, docname, msg, lineno=None):
# strange argument order is due to backwards compatibility
self._warnfunc(msg, (docname, lineno))
@ -754,25 +782,24 @@ class BuildEnvironment:
# store time of build, for outdated files detection
self.all_docs[docname] = time.time()
# get old doctree
old_doctree_path = self.doc2path(docname, self.doctreedir, '.doctree')
try:
f = open(old_doctree_path, 'rb')
if self.versioning_condition:
# get old doctree
try:
old_doctree = pickle.load(f)
finally:
f.close()
old_doctree.settings.env = self
old_doctree.reporter = Reporter(self.doc2path(docname), 2, 5,
stream=WarningStream(self._warnfunc))
except EnvironmentError:
old_doctree = None
f = open(self.doc2path(docname,
self.doctreedir, '.doctree'), 'rb')
try:
old_doctree = pickle.load(f)
finally:
f.close()
except EnvironmentError:
old_doctree = None
# add uids for versioning
if old_doctree is None:
list(add_uids(doctree, nodes.TextElement))
else:
list(merge_doctrees(old_doctree, doctree, nodes.TextElement))
# add uids for versioning
if old_doctree is None:
list(add_uids(doctree, self.versioning_condition))
else:
list(merge_doctrees(
old_doctree, doctree, self.versioning_condition))
# make it picklable
doctree.reporter = None
@ -1385,10 +1412,10 @@ class BuildEnvironment:
typ = node['reftype']
target = node['reftarget']
refdoc = node.get('refdoc', fromdocname)
warned = False
domain = None
try:
if node.has_key('refdomain') and node['refdomain']:
if 'refdomain' in node and node['refdomain']:
# let the domain try to resolve the reference
try:
domain = self.domains[node['refdomain']]
@ -1401,11 +1428,7 @@ class BuildEnvironment:
# directly reference to document by source name;
# can be absolute or relative
docname = docname_join(refdoc, target)
if docname not in self.all_docs:
self.warn(refdoc,
'unknown document: %s' % docname, node.line)
warned = True
else:
if docname in self.all_docs:
if node['refexplicit']:
# reference with explicit title
caption = node.astext()
@ -1418,11 +1441,7 @@ class BuildEnvironment:
newnode.append(innernode)
elif typ == 'citation':
docname, labelid = self.citations.get(target, ('', ''))
if not docname:
self.warn(refdoc,
'citation not found: %s' % target, node.line)
warned = True
else:
if docname:
newnode = make_refnode(builder, fromdocname, docname,
labelid, contnode)
# no new node found? try the missing-reference event
@ -1430,16 +1449,40 @@ class BuildEnvironment:
newnode = builder.app.emit_firstresult(
'missing-reference', self, node, contnode)
# still not found? warn if in nit-picky mode
if newnode is None and not warned and self.config.nitpicky:
self.warn(refdoc,
'reference target not found: %stype %s, target %s'
% (node.get('refdomain') and
'domain %s, ' % node['refdomain'] or '',
typ, target))
if newnode is None:
self._warn_missing_reference(
fromdocname, typ, target, node, domain)
except NoUri:
newnode = contnode
node.replace_self(newnode or contnode)
# remove only-nodes that do not belong to our builder
self.process_only_nodes(doctree, fromdocname, builder)
# allow custom references to be resolved
builder.app.emit('doctree-resolved', doctree, fromdocname)
def _warn_missing_reference(self, fromdoc, typ, target, node, domain):
warn = node.get('refwarn')
if self.config.nitpicky:
warn = True # XXX process exceptions here
if not warn:
return
refdoc = node.get('refdoc', fromdoc)
if domain and typ in domain.dangling_warnings:
msg = domain.dangling_warnings[typ]
elif typ == 'doc':
msg = 'unknown document: %(target)s'
elif typ == 'citation':
msg = 'citation not found: %(target)s'
elif node.get('refdomain', 'std') != 'std':
msg = '%s:%s reference target not found: %%(target)s' % \
(node['refdomain'], typ)
else:
msg = '%s reference target not found: %%(target)s' % typ
self.warn(refdoc, msg % {'target': target}, node.line)
def process_only_nodes(self, doctree, fromdocname, builder):
for node in doctree.traverse(addnodes.only):
try:
ret = builder.tags.eval_condition(node['expr'])
@ -1455,9 +1498,6 @@ class BuildEnvironment:
# if there is a target node before the only node
node.replace_self(nodes.comment())
# allow custom references to be resolved
builder.app.emit('doctree-resolved', doctree, fromdocname)
def assign_section_numbers(self):
"""Assign a section number to each heading under a numbered toctree."""
# a list of all docnames whose section numbers changed

View File

@ -27,7 +27,8 @@ from sphinx.pycode import ModuleAnalyzer, PycodeError
from sphinx.application import ExtensionError
from sphinx.util.nodes import nested_parse_with_titles
from sphinx.util.compat import Directive
from sphinx.util.inspect import isdescriptor, safe_getmembers, safe_getattr
from sphinx.util.inspect import (getargspec, isdescriptor, safe_getmembers, \
safe_getattr)
from sphinx.util.pycompat import base_exception, class_types
from sphinx.util.docstrings import prepare_docstring
@ -1284,36 +1285,6 @@ def add_documenter(cls):
AutoDirective._registry[cls.objtype] = cls
if sys.version_info >= (2, 5):
from functools import partial
def getargspec(func):
"""Like inspect.getargspec but supports functools.partial as well."""
if inspect.ismethod(func):
func = func.im_func
parts = 0, ()
if type(func) is partial:
parts = len(func.args), func.keywords.keys()
func = func.func
if not inspect.isfunction(func):
raise TypeError('{!r} is not a Python function'.format(func))
args, varargs, varkw = inspect.getargs(func.func_code)
func_defaults = func.func_defaults
if func_defaults:
func_defaults = list(func_defaults)
if parts[0]:
args = args[parts[0]:]
if parts[1]:
for arg in parts[1]:
i = args.index(arg) - len(args)
del args[i]
try:
del func_defaults[i]
except IndexError:
pass
return inspect.ArgSpec(args, varargs, varkw, func_defaults)
else:
getargspec = inspect.getargspec
def setup(app):
app.add_autodocumenter(ModuleDocumenter)
app.add_autodocumenter(ClassDocumenter)

View File

@ -361,6 +361,8 @@ PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) \
$(SPHINXOPTS) %(rsrcdir)s
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) %(rsrcdir)s
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp \
epub latex latexpdf text man changes linkcheck doctest gettext
@ -483,7 +485,7 @@ info:
\t@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
\t$(SPHINXBUILD) -b gettext $(ALLSPHINXOPTS) $(BUILDDIR)/locale
\t$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
\t@echo
\t@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
@ -514,8 +516,10 @@ if "%%SPHINXBUILD%%" == "" (
)
set BUILDDIR=%(rbuilddir)s
set ALLSPHINXOPTS=-d %%BUILDDIR%%/doctrees %%SPHINXOPTS%% %(rsrcdir)s
set I18NSPHINXOPTS=%%SPHINXOPTS%% %(rsrcdir)s
if NOT "%%PAPER%%" == "" (
\tset ALLSPHINXOPTS=-D latex_paper_size=%%PAPER%% %%ALLSPHINXOPTS%%
\tset I18NSPHINXOPTS=-D latex_paper_size=%%PAPER%% %%I18NSPHINXOPTS%%
)
if "%%1" == "" goto help
@ -659,7 +663,7 @@ if "%%1" == "texinfo" (
)
if "%%1" == "gettext" (
\t%%SPHINXBUILD%% -b gettext %%ALLSPHINXOPTS%% %%BUILDDIR%%/locale
\t%%SPHINXBUILD%% -b gettext %%I18NSPHINXOPTS%% %%BUILDDIR%%/locale
\tif errorlevel 1 exit /b 1
\techo.
\techo.Build finished. The message catalogs are in %%BUILDDIR%%/locale.
@ -991,4 +995,3 @@ def main(argv=sys.argv):
print
print '[Interrupted.]'
return

View File

@ -69,9 +69,10 @@ class XRefRole(object):
innernodeclass = nodes.literal
def __init__(self, fix_parens=False, lowercase=False,
nodeclass=None, innernodeclass=None):
nodeclass=None, innernodeclass=None, warn_dangling=False):
self.fix_parens = fix_parens
self.lowercase = lowercase
self.warn_dangling = warn_dangling
if nodeclass is not None:
self.nodeclass = nodeclass
if innernodeclass is not None:
@ -133,6 +134,7 @@ class XRefRole(object):
refnode += self.innernodeclass(rawtext, title, classes=classes)
# we also need the source document
refnode['refdoc'] = env.docname
refnode['refwarn'] = self.warn_dangling
# result_nodes allow further modification of return values
return self.result_nodes(inliner.document, env, refnode, is_ref=True)
@ -298,7 +300,7 @@ specific_docroles = {
# links to download references
'download': XRefRole(nodeclass=addnodes.download_reference),
# links to documents
'doc': XRefRole(),
'doc': XRefRole(warn_dangling=True),
'pep': indexmarkup_role,
'rfc': indexmarkup_role,

View File

@ -9,6 +9,40 @@
:license: BSD, see LICENSE for details.
"""
inspect = __import__('inspect')
import sys
if sys.version_info >= (2, 5):
from functools import partial
def getargspec(func):
"""Like inspect.getargspec but supports functools.partial as well."""
if inspect.ismethod(func):
func = func.im_func
parts = 0, ()
if type(func) is partial:
parts = len(func.args), func.keywords.keys()
func = func.func
if not inspect.isfunction(func):
raise TypeError('%r is not a Python function' % func)
args, varargs, varkw = inspect.getargs(func.func_code)
func_defaults = func.func_defaults
if func_defaults:
func_defaults = list(func_defaults)
if parts[0]:
args = args[parts[0]:]
if parts[1]:
for arg in parts[1]:
i = args.index(arg) - len(args)
del args[i]
try:
del func_defaults[i]
except IndexError:
pass
return inspect.ArgSpec(args, varargs, varkw, func_defaults)
else:
getargspec = inspect.getargspec
def isdescriptor(x):
"""Check if the object is some kind of descriptor."""
for item in '__get__', '__set__', '__delete__':

View File

@ -11,7 +11,6 @@
"""
from uuid import uuid4
from operator import itemgetter
from collections import defaultdict
from sphinx.util.pycompat import product, zip_longest
@ -49,7 +48,7 @@ def merge_doctrees(old, new, condition):
new_iter = new.traverse(condition)
old_nodes = []
new_nodes = []
ratios = defaultdict(list)
ratios = {}
seen = set()
# compare the nodes each doctree in order
for old_node, new_node in zip_longest(old_iter, new_iter):