Merged revisions 65283,65303,65316-65317,65372-65375,65377,65380,65483-65485,65494 via svnmerge from

svn+ssh://pythondev@svn.python.org/doctools/branches/0.4.x

........
  r65283 | georg.brandl | 2008-07-29 10:07:26 +0000 (Tue, 29 Jul 2008) | 2 lines

  Update ez_setup.py.
........
  r65303 | benjamin.peterson | 2008-07-30 12:35:34 +0000 (Wed, 30 Jul 2008) | 1 line

  add a with_testapp decorator for test functions that passes the TestApp instance in a cleans up after it
........
  r65316 | benjamin.peterson | 2008-07-30 23:12:07 +0000 (Wed, 30 Jul 2008) | 1 line

  make the app for test_markup global to the module
........
  r65317 | benjamin.peterson | 2008-07-30 23:31:29 +0000 (Wed, 30 Jul 2008) | 1 line

  make TestApp.cleanup more aggressive
........
  r65372 | georg.brandl | 2008-08-01 19:11:22 +0000 (Fri, 01 Aug 2008) | 2 lines

  Add more tests, fix a few bugs in image handling.
........
  r65373 | georg.brandl | 2008-08-01 19:28:33 +0000 (Fri, 01 Aug 2008) | 2 lines

  Fix oversight.
........
  r65374 | benjamin.peterson | 2008-08-01 19:36:32 +0000 (Fri, 01 Aug 2008) | 1 line

  fix one broken test
........
  r65375 | georg.brandl | 2008-08-01 19:41:11 +0000 (Fri, 01 Aug 2008) | 2 lines

  Fix the handling of non-ASCII input in quickstart.
........
  r65377 | georg.brandl | 2008-08-01 19:48:24 +0000 (Fri, 01 Aug 2008) | 2 lines

  Allow REs in markup checks.
........
  r65380 | georg.brandl | 2008-08-01 20:31:18 +0000 (Fri, 01 Aug 2008) | 2 lines

  Don't rely on mtimes being different for changed files.
........
  r65483 | georg.brandl | 2008-08-04 09:01:40 +0000 (Mon, 04 Aug 2008) | 4 lines

  Add an "encoding" option to literalinclude.

  Add tests for include directives.
........
  r65484 | georg.brandl | 2008-08-04 09:11:17 +0000 (Mon, 04 Aug 2008) | 2 lines

  Add changelog entry.
........
  r65485 | georg.brandl | 2008-08-04 09:21:58 +0000 (Mon, 04 Aug 2008) | 2 lines

  Fix markup.
........
  r65494 | georg.brandl | 2008-08-04 16:34:59 +0000 (Mon, 04 Aug 2008) | 2 lines

  Correctly use HTML file suffix in templates.
........
This commit is contained in:
Georg Brandl
2008-08-04 17:01:15 +00:00
parent 1f608000d8
commit b4f71aa642
31 changed files with 2496 additions and 89 deletions

View File

@@ -20,6 +20,7 @@ to be included, please mail to sphinx-dev@googlegroups.com.
* Py on Windows: http://timgolden.me.uk/python-on-windows/ * Py on Windows: http://timgolden.me.uk/python-on-windows/
* mpmath: http://mpmath.googlecode.com/svn/trunk/doc/build/index.html * mpmath: http://mpmath.googlecode.com/svn/trunk/doc/build/index.html
* Zope 3: e.g. http://docs.carduner.net/z3c-tutorial/ * Zope 3: e.g. http://docs.carduner.net/z3c-tutorial/
* Glashammer: http://glashammer.welterde.de/
* SymPy: http://docs.sympy.org/ * SymPy: http://docs.sympy.org/
* Grok (upcoming) * Grok (upcoming)
* Django (upcoming) * Django (upcoming)

View File

@@ -27,4 +27,4 @@ reindent:
@$(PYTHON) utils/reindent.py -r -B . @$(PYTHON) utils/reindent.py -r -B .
test: test:
@cd tests; $(PYTHON) run.py -d @cd tests; $(PYTHON) run.py -d -m '^[tT]est' $(TEST)

View File

@@ -100,6 +100,15 @@ Includes
:language: ruby :language: ruby
:linenos: :linenos:
Include files are assumed to be encoded in UTF-8. If the file has a different
encoding, you can specify it with the ``encoding`` option::
.. literalinclude:: example.py
:encoding: latin-1
.. versionadded:: 0.4.3
The ``encoding`` option.
.. rubric:: Footnotes .. rubric:: Footnotes

View File

@@ -14,8 +14,8 @@ the appropriate options to ``use_setuptools()``.
This file can also be run as a script to install or upgrade setuptools. This file can also be run as a script to install or upgrade setuptools.
""" """
import sys import sys
DEFAULT_VERSION = "0.6c5" DEFAULT_VERSION = "0.6c8"
DEFAULT_URL = "http://cheeseshop.python.org/packages/%s/s/setuptools/" % sys.version[:3] DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3]
md5_data = { md5_data = {
'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca', 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca',
@@ -39,6 +39,15 @@ md5_data = {
'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167', 'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167',
'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64', 'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64',
'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d', 'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d',
'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20',
'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab',
'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53',
'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2',
'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e',
'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372',
'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902',
'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de',
'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b',
} }
import sys, os import sys, os
@@ -71,31 +80,31 @@ def use_setuptools(
this routine will print a message to ``sys.stderr`` and raise SystemExit in this routine will print a message to ``sys.stderr`` and raise SystemExit in
an attempt to abort the calling script. an attempt to abort the calling script.
""" """
try: was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules
import setuptools def do_download():
if setuptools.__version__ == '0.0.1':
print >>sys.stderr, (
"You have an obsolete version of setuptools installed. Please\n"
"remove it from your system entirely before rerunning this script."
)
sys.exit(2)
except ImportError:
egg = download_setuptools(version, download_base, to_dir, download_delay) egg = download_setuptools(version, download_base, to_dir, download_delay)
sys.path.insert(0, egg) sys.path.insert(0, egg)
import setuptools; setuptools.bootstrap_install_from = egg import setuptools; setuptools.bootstrap_install_from = egg
import pkg_resources
try: try:
pkg_resources.require("setuptools>="+version) import pkg_resources
except ImportError:
return do_download()
try:
pkg_resources.require("setuptools>="+version); return
except pkg_resources.VersionConflict, e: except pkg_resources.VersionConflict, e:
# XXX could we install in a subprocess here? if was_imported:
print >>sys.stderr, ( print >>sys.stderr, (
"The required version of setuptools (>=%s) is not available, and\n" "The required version of setuptools (>=%s) is not available, and\n"
"can't be installed while this script is running. Please install\n" "can't be installed while this script is running. Please install\n"
" a more recent version first.\n\n(Currently using %r)" " a more recent version first, using 'easy_install -U setuptools'."
) % (version, e.args[0]) "\n\n(Currently using %r)"
sys.exit(2) ) % (version, e.args[0])
sys.exit(2)
else:
del pkg_resources, sys.modules['pkg_resources'] # reload ok
return do_download()
except pkg_resources.DistributionNotFound:
return do_download()
def download_setuptools( def download_setuptools(
version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
@@ -144,9 +153,43 @@ and place it in this directory before rerunning this script.)
if dst: dst.close() if dst: dst.close()
return os.path.realpath(saveto) return os.path.realpath(saveto)
def main(argv, version=DEFAULT_VERSION): def main(argv, version=DEFAULT_VERSION):
"""Install or upgrade setuptools and EasyInstall""" """Install or upgrade setuptools and EasyInstall"""
try: try:
import setuptools import setuptools
except ImportError: except ImportError:
@@ -161,8 +204,11 @@ def main(argv, version=DEFAULT_VERSION):
os.unlink(egg) os.unlink(egg)
else: else:
if setuptools.__version__ == '0.0.1': if setuptools.__version__ == '0.0.1':
# tell the user to uninstall obsolete version print >>sys.stderr, (
use_setuptools(version) "You have an obsolete version of setuptools installed. Please\n"
"remove it from your system entirely before rerunning this script."
)
sys.exit(2)
req = "setuptools>="+version req = "setuptools>="+version
import pkg_resources import pkg_resources
@@ -183,8 +229,6 @@ def main(argv, version=DEFAULT_VERSION):
print "Setuptools version",version,"or greater has been installed." print "Setuptools version",version,"or greater has been installed."
print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)' print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
def update_md5(filenames): def update_md5(filenames):
"""Update our built-in md5 registry""" """Update our built-in md5 registry"""
@@ -221,3 +265,8 @@ if __name__=='__main__':
update_md5(sys.argv[2:]) update_md5(sys.argv[2:])
else: else:
main(sys.argv[1:]) main(sys.argv[1:])

View File

@@ -145,6 +145,9 @@ class Builder(object):
node['uri'] = candidate node['uri'] = candidate
else: else:
candidate = node['uri'] candidate = node['uri']
if candidate not in self.env.images:
# non-existing URI; let it alone
continue
self.images[candidate] = self.env.images[candidate][1] self.images[candidate] = self.env.images[candidate][1]
# build methods # build methods

View File

@@ -8,6 +8,7 @@
""" """
import sys import sys
import codecs
from os import path from os import path
from docutils import nodes from docutils import nodes
@@ -67,13 +68,19 @@ def literalinclude_directive(name, arguments, options, content, lineno,
lineno - state_machine.input_offset - 1))) lineno - state_machine.input_offset - 1)))
fn = path.normpath(path.join(source_dir, rel_fn)) fn = path.normpath(path.join(source_dir, rel_fn))
encoding = options.get('encoding', 'utf-8')
try: try:
f = open(fn) f = codecs.open(fn, 'r', encoding)
text = f.read() text = f.read()
f.close() f.close()
except (IOError, OSError): except (IOError, OSError):
retnode = state.document.reporter.warning( retnode = state.document.reporter.warning(
'Include file %r not found or reading it failed' % arguments[0], line=lineno) 'Include file %r not found or reading it failed' % arguments[0], line=lineno)
except UnicodeError:
retnode = state.document.reporter.warning(
'Encoding %r used for reading included file %r seems to '
'be wrong, try giving an :encoding: option' %
(encoding, arguments[0]))
else: else:
retnode = nodes.literal_block(text, text, source=fn) retnode = nodes.literal_block(text, text, source=fn)
retnode.line = 1 retnode.line = 1
@@ -85,7 +92,8 @@ def literalinclude_directive(name, arguments, options, content, lineno,
return [retnode] return [retnode]
literalinclude_directive.options = {'linenos': directives.flag, literalinclude_directive.options = {'linenos': directives.flag,
'language': directives.unchanged} 'language': directives.unchanged,
'encoding': directives.encoding}
literalinclude_directive.content = 0 literalinclude_directive.content = 0
literalinclude_directive.arguments = (1, 0, 0) literalinclude_directive.arguments = (1, 0, 0)
directives.register_directive('literalinclude', literalinclude_directive) directives.register_directive('literalinclude', literalinclude_directive)

View File

@@ -559,12 +559,12 @@ class BuildEnvironment:
imgpath = path.normpath(path.join(docdir, imguri)) imgpath = path.normpath(path.join(docdir, imguri))
node['uri'] = imgpath node['uri'] = imgpath
if imgpath.endswith(os.extsep + '*'): if imgpath.endswith(os.extsep + '*'):
for filename in glob(imgpath): for filename in glob(path.join(self.srcdir, imgpath)):
basename, ext = os.path.splitext(filename) dir, base = path.split(filename)
if ext == '.pdf': if base.lower().endswith('.pdf'):
candidates['application/pdf'] = filename candidates['application/pdf'] = path.join(docdir, base)
elif ext == '.svg': elif base.lower().endswith('.svg'):
candidates['image/svg+xml'] = filename candidates['image/svg+xml'] = path.join(docdir, base)
else: else:
f = open(filename, 'rb') f = open(filename, 'rb')
try: try:
@@ -572,23 +572,23 @@ class BuildEnvironment:
finally: finally:
f.close() f.close()
if imgtype: if imgtype:
candidates['image/' + imgtype] = filename candidates['image/' + imgtype] = path.join(docdir, base)
else: else:
candidates['*'] = imgpath candidates['*'] = imgpath
for img in candidates.itervalues(): for imgpath in candidates.itervalues():
self.dependencies.setdefault(docname, set()).add(img) self.dependencies.setdefault(docname, set()).add(imgpath)
if not os.access(path.join(self.srcdir, img), os.R_OK): if not os.access(path.join(self.srcdir, imgpath), os.R_OK):
self.warn(docname, 'Image file not readable: %s' % img, node.line) self.warn(docname, 'Image file not readable: %s' % imgpath, node.line)
if img in self.images: if imgpath in self.images:
self.images[img][0].add(docname) self.images[imgpath][0].add(docname)
continue continue
uniquename = path.basename(img) uniquename = path.basename(imgpath)
base, ext = path.splitext(uniquename) base, ext = path.splitext(uniquename)
i = 0 i = 0
while uniquename in existing_names: while uniquename in existing_names:
i += 1 i += 1
uniquename = '%s%s%s' % (base, i, ext) uniquename = '%s%s%s' % (base, i, ext)
self.images[img] = (set([docname]), uniquename) self.images[imgpath] = (set([docname]), uniquename)
existing_names.add(uniquename) existing_names.add(uniquename)
def process_metadata(self, docname, doctree): def process_metadata(self, docname, doctree):

View File

@@ -128,6 +128,13 @@ class PygmentsBridge(object):
if sys.version_info >= (2, 5): if sys.version_info >= (2, 5):
src = 'from __future__ import with_statement\n' + src src = 'from __future__ import with_statement\n' + src
if isinstance(src, unicode):
# Non-ASCII chars will only occur in string literals
# and comments. If we wanted to give them to the parser
# correctly, we'd have to find out the correct source
# encoding. Since it may not even be given in a snippet,
# just replace all non-ASCII characters.
src = src.encode('ascii', 'replace')
try: try:
parser.suite(src) parser.suite(src)
except parsing_exceptions: except parsing_exceptions:

View File

@@ -12,8 +12,10 @@
import sys, os, time import sys, os, time
from os import path from os import path
TERM_ENCODING = getattr(sys.stdin, 'encoding', None)
from sphinx.util import make_filename from sphinx.util import make_filename
from sphinx.util.console import purple, bold, red, nocolor from sphinx.util.console import purple, bold, red, turquoise, nocolor
PROMPT_PREFIX = '> ' PROMPT_PREFIX = '> '
@@ -56,8 +58,8 @@ source_suffix = '%(suffix)s'
master_doc = '%(master)s' master_doc = '%(master)s'
# General substitutions. # General substitutions.
project = %(project)r project = u'%(project)s'
copyright = '%(year)s, %(author)s' copyright = u'%(copyright)s'
# The default replacements for |version| and |release|, also used in various # The default replacements for |version| and |release|, also used in various
# other places throughout the built documents. # other places throughout the built documents.
@@ -178,8 +180,8 @@ htmlhelp_basename = '%(project_fn)sdoc'
# Grouping the document tree into LaTeX files. List of tuples # Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, document class [howto/manual]). # (source start file, target name, title, author, document class [howto/manual]).
latex_documents = [ latex_documents = [
('%(master)s', '%(project_fn)s.tex', '%(project)s Documentation', ('%(master)s', '%(project_fn)s.tex', u'%(project_doc)s',
'%(author)s', 'manual'), u'%(author)s', 'manual'),
] ]
# The name of an image file (relative to this directory) to place at the top of # The name of an image file (relative to this directory) to place at the top of
@@ -343,8 +345,18 @@ def do_prompt(d, key, text, default=None, validator=nonempty):
x = raw_input(prompt) x = raw_input(prompt)
if default and not x: if default and not x:
x = default x = default
if x.decode('ascii', 'replace').encode('ascii', 'replace') != x:
if TERM_ENCODING:
x = x.decode(TERM_ENCODING)
else:
print turquoise('* Note: non-ASCII characters entered and terminal '
'encoding unknown -- assuming UTF-8 or Latin-1.')
try:
x = x.decode('utf-8')
except UnicodeDecodeError:
x = x.decode('latin1')
if validator and not validator(x): if validator and not validator(x):
print red(" * " + validator.__doc__) print red('* ' + validator.__doc__)
continue continue
break break
d[key] = x d[key] = x
@@ -414,12 +426,13 @@ directly.'''
os.name == 'posix' and 'y' or 'n', boolean) os.name == 'posix' and 'y' or 'n', boolean)
d['project_fn'] = make_filename(d['project']) d['project_fn'] = make_filename(d['project'])
d['year'] = time.strftime('%Y')
d['now'] = time.asctime() d['now'] = time.asctime()
d['underline'] = len(d['project']) * '=' d['underline'] = len(d['project']) * '='
d['extensions'] = ', '.join( d['extensions'] = ', '.join(
repr('sphinx.ext.' + name) for name in ('autodoc', 'doctest') repr('sphinx.ext.' + name) for name in ('autodoc', 'doctest')
if d['ext_' + name].upper() in ('Y', 'YES')) if d['ext_' + name].upper() in ('Y', 'YES'))
d['copyright'] = time.strftime('%Y') + ', ' + d['author']
d['project_doc'] = d['project'] + ' Documentation'
if not path.isdir(d['path']): if not path.isdir(d['path']):
mkdir_p(d['path']) mkdir_p(d['path'])
@@ -437,12 +450,12 @@ directly.'''
mkdir_p(path.join(srcdir, d['dot'] + 'static')) mkdir_p(path.join(srcdir, d['dot'] + 'static'))
f = open(path.join(srcdir, 'conf.py'), 'w') f = open(path.join(srcdir, 'conf.py'), 'w')
f.write(QUICKSTART_CONF % d) f.write((QUICKSTART_CONF % d).encode('utf-8'))
f.close() f.close()
masterfile = path.join(srcdir, d['master'] + d['suffix']) masterfile = path.join(srcdir, d['master'] + d['suffix'])
f = open(masterfile, 'w') f = open(masterfile, 'w')
f.write(MASTER_FILE % d) f.write((MASTER_FILE % d).encode('utf-8'))
f.close() f.close()
create_makefile = d['makefile'].upper() in ('Y', 'YES') create_makefile = d['makefile'].upper() in ('Y', 'YES')
@@ -450,7 +463,7 @@ directly.'''
d['rsrcdir'] = separate and 'source' or '.' d['rsrcdir'] = separate and 'source' or '.'
d['rbuilddir'] = separate and 'build' or d['dot'] + 'build' d['rbuilddir'] = separate and 'build' or d['dot'] + 'build'
f = open(path.join(d['path'], 'Makefile'), 'w') f = open(path.join(d['path'], 'Makefile'), 'w')
f.write(MAKEFILE % d) f.write((MAKEFILE % d).encode('utf-8'))
f.close() f.close()
print print

View File

@@ -256,7 +256,7 @@ def patfilter(names, pat):
return filter(match, names) return filter(match, names)
no_fn_re = re.compile(r'[:/\\?*%|"\'<>. \t]') no_fn_re = re.compile(r'[^a-zA-Z0-9_-]')
def make_filename(string): def make_filename(string):
return no_fn_re.sub('', string) return no_fn_re.sub('', string)

View File

@@ -0,0 +1,226 @@
#
# ElementTree
# $Id$
#
# limited xpath support for element trees
#
# history:
# 2003-05-23 fl created
# 2003-05-28 fl added support for // etc
# 2003-08-27 fl fixed parsing of periods in element names
# 2007-09-10 fl new selection engine
#
# Copyright (c) 2003-2007 by Fredrik Lundh. All rights reserved.
#
# fredrik@pythonware.com
# http://www.pythonware.com
#
# --------------------------------------------------------------------
# The ElementTree toolkit is
#
# Copyright (c) 1999-2007 by Fredrik Lundh
#
# By obtaining, using, and/or copying this software and/or its
# associated documentation, you agree that you have read, understood,
# and will comply with the following terms and conditions:
#
# Permission to use, copy, modify, and distribute this software and
# its associated documentation for any purpose and without fee is
# hereby granted, provided that the above copyright notice appears in
# all copies, and that both that copyright notice and this permission
# notice appear in supporting documentation, and that the name of
# Secret Labs AB or the author not be used in advertising or publicity
# pertaining to distribution of the software without specific, written
# prior permission.
#
# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
# OF THIS SOFTWARE.
# --------------------------------------------------------------------
##
# Implementation module for XPath support. There's usually no reason
# to import this module directly; the <b>ElementTree</b> does this for
# you, if needed.
##
import re
xpath_tokenizer = re.compile(
"("
"'[^']*'|\"[^\"]*\"|"
"::|"
"//?|"
"\.\.|"
"\(\)|"
"[/.*:\[\]\(\)@=])|"
"((?:\{[^}]+\})?[^/:\[\]\(\)@=\s]+)|"
"\s+"
).findall
def prepare_tag(next, token):
tag = token[1]
def select(context, result):
for elem in result:
for e in elem:
if e.tag == tag:
yield e
return select
def prepare_star(next, token):
def select(context, result):
for elem in result:
for e in elem:
yield e
return select
def prepare_dot(next, token):
def select(context, result):
for elem in result:
yield elem
return select
def prepare_iter(next, token):
token = next()
if token[0] == "*":
tag = "*"
elif not token[0]:
tag = token[1]
else:
raise SyntaxError
def select(context, result):
for elem in result:
for e in elem.iter(tag):
if e is not elem:
yield e
return select
def prepare_dot_dot(next, token):
def select(context, result):
parent_map = context.parent_map
if parent_map is None:
context.parent_map = parent_map = {}
for p in context.root.iter():
for e in p:
parent_map[e] = p
for elem in result:
if elem in parent_map:
yield parent_map[elem]
return select
def prepare_predicate(next, token):
# this one should probably be refactored...
token = next()
if token[0] == "@":
# attribute
token = next()
if token[0]:
raise SyntaxError("invalid attribute predicate")
key = token[1]
token = next()
if token[0] == "]":
def select(context, result):
for elem in result:
if elem.get(key) is not None:
yield elem
elif token[0] == "=":
value = next()[0]
if value[:1] == "'" or value[:1] == '"':
value = value[1:-1]
else:
raise SyntaxError("invalid comparision target")
token = next()
def select(context, result):
for elem in result:
if elem.get(key) == value:
yield elem
if token[0] != "]":
raise SyntaxError("invalid attribute predicate")
elif not token[0]:
tag = token[1]
token = next()
if token[0] != "]":
raise SyntaxError("invalid node predicate")
def select(context, result):
for elem in result:
if elem.find(tag) is not None:
yield elem
else:
raise SyntaxError("invalid predicate")
return select
ops = {
"": prepare_tag,
"*": prepare_star,
".": prepare_dot,
"..": prepare_dot_dot,
"//": prepare_iter,
"[": prepare_predicate,
}
_cache = {}
class _SelectorContext:
parent_map = None
def __init__(self, root):
self.root = root
# --------------------------------------------------------------------
##
# Find first matching object.
def find(elem, path):
try:
return findall(elem, path).next()
except StopIteration:
return None
##
# Find all matching objects.
def findall(elem, path):
# compile selector pattern
try:
selector = _cache[path]
except KeyError:
if len(_cache) > 100:
_cache.clear()
if path[:1] == "/":
raise SyntaxError("cannot use absolute path on element")
stream = iter(xpath_tokenizer(path))
next = stream.next; token = next()
selector = []
while 1:
try:
selector.append(ops[token[0]](next, token))
except StopIteration:
raise SyntaxError("invalid path")
try:
token = next()
if token[0] == "/":
token = next()
except StopIteration:
break
_cache[path] = selector
# execute selector pattern
result = [elem]
context = _SelectorContext(elem)
for select in selector:
result = select(context, result)
return result
##
# Find text for first matching object.
def findtext(elem, path, default=None):
try:
elem = findall(elem, path).next()
return elem.text
except StopIteration:
return default

1542
tests/etree13/ElementTree.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,230 @@
#
# ElementTree
# $Id$
#
# a simple tree builder, for HTML input
#
# history:
# 2002-04-06 fl created
# 2002-04-07 fl ignore IMG and HR end tags
# 2002-04-07 fl added support for 1.5.2 and later
# 2003-04-13 fl added HTMLTreeBuilder alias
# 2004-12-02 fl don't feed non-ASCII charrefs/entities as 8-bit strings
# 2004-12-05 fl don't feed non-ASCII CDATA as 8-bit strings
#
# Copyright (c) 1999-2004 by Fredrik Lundh. All rights reserved.
#
# fredrik@pythonware.com
# http://www.pythonware.com
#
# --------------------------------------------------------------------
# The ElementTree toolkit is
#
# Copyright (c) 1999-2007 by Fredrik Lundh
#
# By obtaining, using, and/or copying this software and/or its
# associated documentation, you agree that you have read, understood,
# and will comply with the following terms and conditions:
#
# Permission to use, copy, modify, and distribute this software and
# its associated documentation for any purpose and without fee is
# hereby granted, provided that the above copyright notice appears in
# all copies, and that both that copyright notice and this permission
# notice appear in supporting documentation, and that the name of
# Secret Labs AB or the author not be used in advertising or publicity
# pertaining to distribution of the software without specific, written
# prior permission.
#
# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
# OF THIS SOFTWARE.
# --------------------------------------------------------------------
##
# Tools to build element trees from HTML files.
##
import htmlentitydefs
import re, string, sys
import mimetools, StringIO
import ElementTree
AUTOCLOSE = "p", "li", "tr", "th", "td", "head", "body"
IGNOREEND = "img", "hr", "meta", "link", "br"
if sys.version[:3] == "1.5":
is_not_ascii = re.compile(r"[\x80-\xff]").search # 1.5.2
else:
is_not_ascii = re.compile(eval(r'u"[\u0080-\uffff]"')).search
try:
from HTMLParser import HTMLParser
except ImportError:
from sgmllib import SGMLParser
# hack to use sgmllib's SGMLParser to emulate 2.2's HTMLParser
class HTMLParser(SGMLParser):
# the following only works as long as this class doesn't
# provide any do, start, or end handlers
def unknown_starttag(self, tag, attrs):
self.handle_starttag(tag, attrs)
def unknown_endtag(self, tag):
self.handle_endtag(tag)
##
# ElementTree builder for HTML source code. This builder converts an
# HTML document or fragment to an ElementTree.
# <p>
# The parser is relatively picky, and requires balanced tags for most
# elements. However, elements belonging to the following group are
# automatically closed: P, LI, TR, TH, and TD. In addition, the
# parser automatically inserts end tags immediately after the start
# tag, and ignores any end tags for the following group: IMG, HR,
# META, and LINK.
#
# @keyparam builder Optional builder object. If omitted, the parser
# uses the standard <b>elementtree</b> builder.
# @keyparam encoding Optional character encoding, if known. If omitted,
# the parser looks for META tags inside the document. If no tags
# are found, the parser defaults to ISO-8859-1. Note that if your
# document uses a non-ASCII compatible encoding, you must decode
# the document before parsing.
#
# @see elementtree.ElementTree
class HTMLTreeBuilder(HTMLParser):
# FIXME: shouldn't this class be named Parser, not Builder?
def __init__(self, builder=None, encoding=None):
self.__stack = []
if builder is None:
builder = ElementTree.TreeBuilder()
self.__builder = builder
self.encoding = encoding or "iso-8859-1"
HTMLParser.__init__(self)
##
# Flushes parser buffers, and return the root element.
#
# @return An Element instance.
def close(self):
HTMLParser.close(self)
return self.__builder.close()
##
# (Internal) Handles start tags.
def handle_starttag(self, tag, attrs):
if tag == "meta":
# look for encoding directives
http_equiv = content = None
for k, v in attrs:
if k == "http-equiv":
http_equiv = string.lower(v)
elif k == "content":
content = v
if http_equiv == "content-type" and content:
# use mimetools to parse the http header
header = mimetools.Message(
StringIO.StringIO("%s: %s\n\n" % (http_equiv, content))
)
encoding = header.getparam("charset")
if encoding:
self.encoding = encoding
if tag in AUTOCLOSE:
if self.__stack and self.__stack[-1] == tag:
self.handle_endtag(tag)
self.__stack.append(tag)
attrib = {}
if attrs:
for k, v in attrs:
attrib[string.lower(k)] = v
self.__builder.start(tag, attrib)
if tag in IGNOREEND:
self.__stack.pop()
self.__builder.end(tag)
##
# (Internal) Handles end tags.
def handle_endtag(self, tag):
if tag in IGNOREEND:
return
lasttag = self.__stack.pop()
if tag != lasttag and lasttag in AUTOCLOSE:
self.handle_endtag(lasttag)
self.__builder.end(tag)
##
# (Internal) Handles character references.
def handle_charref(self, char):
if char[:1] == "x":
char = int(char[1:], 16)
else:
char = int(char)
if 0 <= char < 128:
self.__builder.data(chr(char))
else:
self.__builder.data(unichr(char))
##
# (Internal) Handles entity references.
def handle_entityref(self, name):
entity = htmlentitydefs.entitydefs.get(name)
if entity:
if len(entity) == 1:
entity = ord(entity)
else:
entity = int(entity[2:-1])
if 0 <= entity < 128:
self.__builder.data(chr(entity))
else:
self.__builder.data(unichr(entity))
else:
self.unknown_entityref(name)
##
# (Internal) Handles character data.
def handle_data(self, data):
if isinstance(data, type('')) and is_not_ascii(data):
# convert to unicode, but only if necessary
data = unicode(data, self.encoding, "ignore")
self.__builder.data(data)
##
# (Hook) Handles unknown entity references. The default action
# is to ignore unknown entities.
def unknown_entityref(self, name):
pass # ignore by default; override if necessary
##
# An alias for the <b>HTMLTreeBuilder</b> class.
TreeBuilder = HTMLTreeBuilder
##
# Parse an HTML document or document fragment.
#
# @param source A filename or file object containing HTML data.
# @param encoding Optional character encoding, if known. If omitted,
# the parser looks for META tags inside the document. If no tags
# are found, the parser defaults to ISO-8859-1.
# @return An ElementTree instance
def parse(source, encoding=None):
return ElementTree.parse(source, HTMLTreeBuilder(encoding=encoding))
if __name__ == "__main__":
import sys
ElementTree.dump(parse(open(sys.argv[1])))

30
tests/etree13/__init__.py Normal file
View File

@@ -0,0 +1,30 @@
# $Id$
# elementtree package
# --------------------------------------------------------------------
# The ElementTree toolkit is
#
# Copyright (c) 1999-2007 by Fredrik Lundh
#
# By obtaining, using, and/or copying this software and/or its
# associated documentation, you agree that you have read, understood,
# and will comply with the following terms and conditions:
#
# Permission to use, copy, modify, and distribute this software and
# its associated documentation for any purpose and without fee is
# hereby granted, provided that the above copyright notice appears in
# all copies, and that both that copyright notice and this permission
# notice appear in supporting documentation, and that the name of
# Secret Labs AB or the author not be used in advertising or publicity
# pertaining to distribution of the software without specific, written
# prior permission.
#
# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
# OF THIS SOFTWARE.
# --------------------------------------------------------------------

View File

@@ -32,7 +32,7 @@ templates_path = ['_templates']
source_suffix = '.txt' source_suffix = '.txt'
# The master toctree document. # The master toctree document.
master_doc = 'index' master_doc = 'contents'
# General substitutions. # General substitutions.
project = 'Sphinx Tests' project = 'Sphinx Tests'

View File

@@ -10,10 +10,12 @@ Contents:
.. toctree:: .. toctree::
:maxdepth: 2 :maxdepth: 2
images
includes
Indices and tables Indices and tables
================== ==================
* :ref:`genindex` * :ref:`genindex`
* :ref:`modindex` * :ref:`modindex`
* :ref:`search` * :ref:`search`

20
tests/root/images.txt Normal file
View File

@@ -0,0 +1,20 @@
Sphinx image handling
=====================
.. first, a simple test with direct filename
.. image:: img.png
.. a non-existing image with direct filename
.. image:: foo.png
.. an image with path name (relative to this directory!)
.. image:: subdir/img.png
.. an image with unspecified extension
.. image:: img.*
.. a non-existing image with .*
.. image:: foo.*
.. a non-local image URI
.. image:: http://www.python.org/logo.png

BIN
tests/root/img.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
tests/root/img.pdf Normal file

Binary file not shown.

BIN
tests/root/img.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

16
tests/root/includes.txt Normal file
View File

@@ -0,0 +1,16 @@
Test file and literal inclusion
===============================
.. include:: subdir/include.inc
.. literalinclude:: literal.inc
:language: python
.. should give a warning
.. literalinclude:: wrongenc.inc
.. should succeed
.. literalinclude:: wrongenc.inc
:encoding: latin-1
.. include:: wrongenc.inc
:encoding: latin-1

4
tests/root/literal.inc Normal file
View File

@@ -0,0 +1,4 @@
# Literally included file using Python highlighting
# -*- coding: utf-8 -*-
foo = u"Including Unicode characters: üöä"

BIN
tests/root/subdir/img.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@@ -0,0 +1,5 @@
.. This file is included by contents.txt.
.. Paths in included files are relative to the file that
includes them
.. image:: ../root/img.png

3
tests/root/wrongenc.inc Normal file
View File

@@ -0,0 +1,3 @@
This file is encoded in latin-1 but at first read as utf-8.
Max Strau<EFBFBD> a<EFBFBD> in M<EFBFBD>nchen eine Leberk<EFBFBD>ssemmel.

95
tests/test_build.py Normal file
View File

@@ -0,0 +1,95 @@
# -*- coding: utf-8 -*-
"""
test_build
~~~~~~~~~~
Test the entire build process with the test root.
:copyright: 2008 by Georg Brandl.
:license: BSD.
"""
import os
import htmlentitydefs
from StringIO import StringIO
from etree13 import ElementTree as ET
from util import *
from sphinx.builder import StandaloneHTMLBuilder, LaTeXBuilder
html_warnfile = StringIO()
latex_warnfile = StringIO()
ENV_WARNINGS = """\
WARNING: %(root)s/images.txt:9: Image file not readable: foo.png
WARNING: %(root)s/images.txt:20: Nonlocal image URI found: http://www.python.org/logo.png
WARNING: %(root)s/includes.txt:: (WARNING/2) Encoding 'utf-8' used for reading included file u'wrongenc.inc' seems to be wrong, try giving an :encoding: option
"""
HTML_WARNINGS = ENV_WARNINGS + """\
WARNING: %(root)s/images.txt:: no matching candidate for image URI u'foo.*'
"""
LATEX_WARNINGS = ENV_WARNINGS + """\
WARNING: None:: no matching candidate for image URI u'foo.*'
"""
HTML_XPATH = {
'images.html': {
".//img[@src='_images/img.png']": '',
".//img[@src='_images/img1.png']": '',
},
'includes.html': {
".//pre/span[@class='s']": u'üöä',
".//pre": u'Max Strauß',
},
}
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
@with_testapp(buildername='html', warning=html_warnfile)
def test_html(app):
app.builder.build_all()
html_warnings = html_warnfile.getvalue().replace(os.sep, '/')
assert html_warnings == HTML_WARNINGS % {'root': app.srcdir}
if not ET:
return
for fname, paths in HTML_XPATH.iteritems():
parser = NslessParser()
parser.entity.update(htmlentitydefs.entitydefs)
etree = ET.parse(app.outdir / fname, parser)
for path, text in paths.iteritems():
nodes = list(etree.findall(path))
assert nodes != []
if not text:
# only check for node presence
continue
for node in nodes:
if node.text and text in node.text:
break
else:
assert False, ('%r not found in any node matching '
'path %s in %s' % (text, path, fname))
@with_testapp(buildername='latex', warning=latex_warnfile)
def test_latex(app):
app.builder.build_all()
latex_warnings = latex_warnfile.getvalue().replace(os.sep, '/')
assert latex_warnings == LATEX_WARNINGS % {'root': app.srcdir}

View File

@@ -15,9 +15,9 @@ from util import *
from sphinx.application import ExtensionError from sphinx.application import ExtensionError
def test_core_config(): @with_testapp(confoverrides={'master_doc': 'master', 'nonexisting_value': 'True'})
overrides = {'master_doc': 'master', 'nonexisting_value': 'True'} def test_core_config(app):
cfg = TestApp(confoverrides=overrides).config cfg = app.config
# simple values # simple values
assert 'project' in cfg.__dict__ assert 'project' in cfg.__dict__
@@ -61,8 +61,8 @@ def test_core_config():
assert cfg['project'] == cfg.project == 'Sphinx Tests' assert cfg['project'] == cfg.project == 'Sphinx Tests'
def test_extension_values(): @with_testapp()
app = TestApp() def test_extension_values(app):
cfg = app.config cfg = app.config
# default value # default value

81
tests/test_env.py Normal file
View File

@@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
"""
test_env
~~~~~~~~
Test the BuildEnvironment class.
:copyright: 2008 by Georg Brandl.
:license: BSD.
"""
from util import *
from sphinx.environment import BuildEnvironment
from sphinx.builder import StandaloneHTMLBuilder, LaTeXBuilder
app = env = None
warnings = []
def setup_module():
global app, env
app = TestApp(srcdir='(temp)')
env = BuildEnvironment(app.srcdir, app.doctreedir, app.config)
env.set_warnfunc(warnings.append)
def teardown_module():
app.cleanup()
def warning_emitted(file, text):
for warning in warnings:
if file+':' in warning and text in warning:
return True
return False
# Tests are run in the order they appear in the file, therefore we can
# afford to not run update() in the setup but in its own test
def test_first_update():
it = env.update(app.config, app.srcdir, app.doctreedir, app)
msg = it.next()
assert msg.endswith('%d added, 0 changed, 0 removed' % len(env.found_docs))
docnames = set()
for docname in it: # the generator does all the work
docnames.add(docname)
assert docnames == env.found_docs == set(env.all_docs)
def test_images():
assert warning_emitted('images.txt', 'Image file not readable: foo.png')
assert warning_emitted('images.txt', 'Nonlocal image URI found: '
'http://www.python.org/logo.png')
tree = env.get_doctree('images')
app._warning.reset()
htmlbuilder = StandaloneHTMLBuilder(app, env)
htmlbuilder.post_process_images(tree)
assert "no matching candidate for image URI u'foo.*'" in app._warning.content[-1]
assert set(htmlbuilder.images.keys()) == set(['subdir/img.png', 'img.png'])
assert set(htmlbuilder.images.values()) == set(['img.png', 'img1.png'])
app._warning.reset()
latexbuilder = LaTeXBuilder(app, env)
latexbuilder.post_process_images(tree)
assert "no matching candidate for image URI u'foo.*'" in app._warning.content[-1]
assert set(latexbuilder.images.keys()) == set(['subdir/img.png', 'img.png', 'img.pdf'])
assert set(latexbuilder.images.values()) == set(['img.pdf', 'img.png', 'img1.png'])
def test_second_update():
# delete, add and "edit" (change saved mtime) some files and update again
env.all_docs['contents'] = 0
root = path(app.srcdir)
(root / 'images.txt').unlink()
(root / 'new.txt').write_text('New file\n========\n')
it = env.update(app.config, app.srcdir, app.doctreedir, app)
msg = it.next()
assert '1 added, 1 changed, 1 removed' in msg
docnames = set()
for docname in it:
docnames.add(docname)
assert docnames == set(['contents', 'new'])
assert 'images' not in env.all_docs
assert 'images' not in env.found_docs

View File

@@ -9,6 +9,8 @@
:license: BSD. :license: BSD.
""" """
import re
from util import * from util import *
from docutils import frontend, utils, nodes from docutils import frontend, utils, nodes
@@ -18,11 +20,16 @@ from sphinx import addnodes
from sphinx.htmlwriter import HTMLWriter, SmartyPantsHTMLTranslator from sphinx.htmlwriter import HTMLWriter, SmartyPantsHTMLTranslator
from sphinx.latexwriter import LaTeXWriter, LaTeXTranslator from sphinx.latexwriter import LaTeXWriter, LaTeXTranslator
app = TestApp() def setup_module():
optparser = frontend.OptionParser(components=(rst.Parser, HTMLWriter, LaTeXWriter)) global app, settings, parser
settings = optparser.get_default_values() app = TestApp()
settings.env = app.builder.env optparser = frontend.OptionParser(components=(rst.Parser, HTMLWriter, LaTeXWriter))
parser = rst.Parser() settings = optparser.get_default_values()
settings.env = app.builder.env
parser = rst.Parser()
def teardown_module():
app.cleanup()
# since we're not resolving the markup afterwards, these nodes may remain # since we're not resolving the markup afterwards, these nodes may remain
class ForgivingTranslator: class ForgivingTranslator:
@@ -38,7 +45,7 @@ class ForgivingLaTeXTranslator(LaTeXTranslator, ForgivingTranslator):
pass pass
def verify(rst, html_expected, latex_expected): def verify_re(rst, html_expected, latex_expected):
document = utils.new_document('test data', settings) document = utils.new_document('test data', settings)
parser.parse(rst, document) parser.parse(rst, document)
for msg in document.traverse(nodes.system_message): for msg in document.traverse(nodes.system_message):
@@ -49,14 +56,17 @@ def verify(rst, html_expected, latex_expected):
html_translator = ForgivingHTMLTranslator(app.builder, document) html_translator = ForgivingHTMLTranslator(app.builder, document)
document.walkabout(html_translator) document.walkabout(html_translator)
html_translated = ''.join(html_translator.fragment).strip() html_translated = ''.join(html_translator.fragment).strip()
assert html_translated == html_expected, 'from ' + rst assert re.match(html_expected, html_translated), 'from' + rst
if latex_expected: if latex_expected:
latex_translator = ForgivingLaTeXTranslator(document, app.builder) latex_translator = ForgivingLaTeXTranslator(document, app.builder)
latex_translator.first_document = -1 # don't write \begin{document} latex_translator.first_document = -1 # don't write \begin{document}
document.walkabout(latex_translator) document.walkabout(latex_translator)
latex_translated = ''.join(latex_translator.body).strip() latex_translated = ''.join(latex_translator.body).strip()
assert latex_translated == latex_expected, 'from ' + rst assert re.match(latex_expected, latex_translated), 'from ' + rst
def verify(rst, html_expected, latex_expected):
verify_re(rst, re.escape(html_expected) + '$', re.escape(latex_expected) + '$')
def test_inline(): def test_inline():
@@ -78,9 +88,9 @@ def test_inline():
'\\emph{a $\\rightarrow$ b}') '\\emph{a $\\rightarrow$ b}')
# non-interpolation of dashes in option role # non-interpolation of dashes in option role
verify(':option:`--with-option`', verify_re(':option:`--with-option`',
'<p><em>--with-option</em></p>', '<p><em( class="xref")?>--with-option</em></p>$',
r'\emph{\texttt{--with-option}}') r'\\emph{\\texttt{--with-option}}$')
# verify smarty-pants quotes # verify smarty-pants quotes
verify('"John"', '<p>&#8220;John&#8221;</p>', "``John''") verify('"John"', '<p>&#8220;John&#8221;</p>', "``John''")

View File

@@ -38,6 +38,7 @@ def mock_raw_input(answers, needanswer=False):
def teardown_module(): def teardown_module():
qs.raw_input = __builtin__.raw_input qs.raw_input = __builtin__.raw_input
qs.TERM_ENCODING = getattr(sys.stdin, 'encoding', None)
coloron() coloron()
@@ -108,10 +109,10 @@ def test_quickstart_all_answers(tempdir):
'Root path': tempdir, 'Root path': tempdir,
'Separate source and build': 'y', 'Separate source and build': 'y',
'Name prefix for templates': '_', 'Name prefix for templates': '_',
'Project name': 'Sphinx Test', 'Project name': 'STASI\xe2\x84\xa2',
'Author name': 'Georg Brandl', 'Author name': 'Wolfgang Sch\xc3\xa4uble',
'Project version': '0.1', 'Project version': '2.0',
'Project release': '0.1.1', 'Project release': '2.0.1',
'Source file suffix': '.txt', 'Source file suffix': '.txt',
'Name of your master document': 'contents', 'Name of your master document': 'contents',
'autodoc': 'y', 'autodoc': 'y',
@@ -119,6 +120,7 @@ def test_quickstart_all_answers(tempdir):
'Create Makefile': 'no', 'Create Makefile': 'no',
} }
qs.raw_input = mock_raw_input(answers, needanswer=True) qs.raw_input = mock_raw_input(answers, needanswer=True)
qs.TERM_ENCODING = 'utf-8'
qs.inner_main([]) qs.inner_main([])
conffile = tempdir / 'source' / 'conf.py' conffile = tempdir / 'source' / 'conf.py'
@@ -129,14 +131,14 @@ def test_quickstart_all_answers(tempdir):
assert ns['templates_path'] == ['_templates'] assert ns['templates_path'] == ['_templates']
assert ns['source_suffix'] == '.txt' assert ns['source_suffix'] == '.txt'
assert ns['master_doc'] == 'contents' assert ns['master_doc'] == 'contents'
assert ns['project'] == 'Sphinx Test' assert ns['project'] == u'STASI™'
assert ns['copyright'] == '%s, Georg Brandl' % time.strftime('%Y') assert ns['copyright'] == u'%s, Wolfgang Schäuble' % time.strftime('%Y')
assert ns['version'] == '0.1' assert ns['version'] == '2.0'
assert ns['release'] == '0.1.1' assert ns['release'] == '2.0.1'
assert ns['html_static_path'] == ['_static'] assert ns['html_static_path'] == ['_static']
assert ns['latex_documents'] == [ assert ns['latex_documents'] == [
('contents', 'SphinxTest.tex', 'Sphinx Test Documentation', ('contents', 'STASI.tex', u'STASI™ Documentation',
'Georg Brandl', 'manual')] u'Wolfgang Schäuble', 'manual')]
assert (tempdir / 'build').isdir() assert (tempdir / 'build').isdir()
assert (tempdir / 'source' / '_static').isdir() assert (tempdir / 'source' / '_static').isdir()

View File

@@ -8,19 +8,26 @@
""" """
import sys import sys
import os
import StringIO import StringIO
import tempfile import tempfile
import shutil
from functools import wraps
from sphinx import application, builder from sphinx import application, builder
from path import path from path import path
from nose import tools
__all__ = [ __all__ = [
'test_root', 'test_root',
'raises', 'raises_msg', 'raises', 'raises_msg',
'ErrorOutput', 'TestApp', 'ListOutput', 'TestApp', 'with_testapp',
'path', 'with_tempdir', 'write_file', 'path', 'with_tempdir', 'write_file',
'sprint',
] ]
@@ -59,15 +66,19 @@ def raises_msg(exc, msg, func, *args, **kwds):
(func.__name__, _excstr(exc))) (func.__name__, _excstr(exc)))
class ErrorOutput(object): class ListOutput(object):
""" """
File-like object that raises :exc:`AssertionError` on ``write()``. File-like object that collects written text in a list.
""" """
def __init__(self, name): def __init__(self, name):
self.name = name self.name = name
self.content = []
def reset(self):
del self.content[:]
def write(self, text): def write(self, text):
assert False, 'tried to write %r to %s' % (text, self.name) self.content.append(text)
class TestApp(application.Sphinx): class TestApp(application.Sphinx):
@@ -84,20 +95,32 @@ class TestApp(application.Sphinx):
if srcdir is None: if srcdir is None:
srcdir = test_root srcdir = test_root
if srcdir == '(temp)':
tempdir = path(tempfile.mkdtemp()) / 'root'
test_root.copytree(tempdir)
srcdir = tempdir
else: else:
srcdir = path(srcdir) srcdir = path(srcdir)
self.builddir = srcdir.joinpath('_build')
if not self.builddir.isdir():
self.builddir.makedirs()
self.made_builddir = True
else:
self.made_builddir = False
if confdir is None: if confdir is None:
confdir = srcdir confdir = srcdir
if outdir is None: if outdir is None:
outdir = srcdir.joinpath('_build', buildername) outdir = srcdir.joinpath(self.builddir, buildername)
if not outdir.isdir():
outdir.makedirs()
if doctreedir is None: if doctreedir is None:
doctreedir = srcdir.joinpath(srcdir, '_build', 'doctrees') doctreedir = srcdir.joinpath(srcdir, self.builddir, 'doctrees')
if confoverrides is None: if confoverrides is None:
confoverrides = {} confoverrides = {}
if status is None: if status is None:
status = StringIO.StringIO() status = StringIO.StringIO()
if warning is None: if warning is None:
warning = ErrorOutput('stderr') warning = ListOutput('stderr')
if freshenv is None: if freshenv is None:
freshenv = True freshenv = True
@@ -105,6 +128,30 @@ class TestApp(application.Sphinx):
buildername, confoverrides, status, warning, buildername, confoverrides, status, warning,
freshenv) freshenv)
def cleanup(self):
trees = [self.outdir, self.doctreedir]
#f self.made_builddir:
# trees.append(self.builddir)
#for tree in trees:
# shutil.rmtree(tree, True)
def with_testapp(*args, **kwargs):
"""
Make a TestApp with args and kwargs, pass it to the test and clean up
properly.
"""
def generator(func):
@wraps(func)
def deco(*args2, **kwargs2):
app = TestApp(*args, **kwargs)
try:
func(app, *args2, **kwargs2)
finally:
app.cleanup()
return deco
return generator
def with_tempdir(func): def with_tempdir(func):
def new_func(): def new_func():
@@ -119,3 +166,7 @@ def write_file(name, contents):
f = open(str(name), 'wb') f = open(str(name), 'wb')
f.write(contents) f.write(contents)
f.close() f.close()
def sprint(*args):
sys.stderr.write(' '.join(map(str, args)) + '\n')