diff --git a/CHANGES b/CHANGES index afd529900..4f984dca8 100644 --- a/CHANGES +++ b/CHANGES @@ -23,6 +23,14 @@ Release 1.0 (in development) Release 0.6.2 (in development) ============================== +* #158: Allow '..' in template names, and absolute template paths; + Jinja 2 by default disables both. + +* When highlighting Python code, ignore extra indentation before + trying to parse it as Python. + +* #191: Don't escape the tilde in URIs in LaTeX. + * Don't consider contents of source comments for the search index. * Set the default encoding to ``utf-8-sig`` to handle files with a diff --git a/sphinx/highlighting.py b/sphinx/highlighting.py index c41720ad3..5bd0fe00a 100644 --- a/sphinx/highlighting.py +++ b/sphinx/highlighting.py @@ -13,6 +13,7 @@ import sys import cgi import re import parser +import textwrap from sphinx.util.texescape import tex_hl_escape_map @@ -129,6 +130,10 @@ class PygmentsBridge(object): # Make sure it ends in a newline src += '\n' + # Ignore consistent indentation. + if src.lstrip('\n').startswith(' '): + src = textwrap.dedent(src) + # Replace "..." by a mark which is also a valid python expression # (Note, the highlighter gets the original source, this is only done # to allow "..." in code and still highlight it as Python code.) diff --git a/sphinx/jinja2glue.py b/sphinx/jinja2glue.py index 679447a81..c584a0cde 100644 --- a/sphinx/jinja2glue.py +++ b/sphinx/jinja2glue.py @@ -15,6 +15,7 @@ from pprint import pformat from jinja2 import FileSystemLoader, BaseLoader, TemplateNotFound, \ contextfunction +from jinja2.utils import open_if_exists from jinja2.sandbox import SandboxedEnvironment from sphinx.util import mtimes_of_files @@ -36,6 +37,32 @@ def accesskey(context, key): return '' +class SphinxFileSystemLoader(FileSystemLoader): + """FileSystemLoader subclass that is not so strict about '..' + entries in template names.""" + + def get_source(self, environment, template): + for searchpath in self.searchpath: + filename = path.join(searchpath, template) + f = open_if_exists(filename) + if f is None: + continue + try: + contents = f.read().decode(self.encoding) + finally: + f.close() + + mtime = path.getmtime(filename) + def uptodate(): + try: + return path.getmtime(filename) == mtime + except OSError: + return False + return contents, filename, uptodate + raise TemplateNotFound(template) + + + class BuiltinTemplateLoader(TemplateBridge, BaseLoader): """ Interfaces the rendering environment of jinja2 for use in Sphinx. @@ -65,7 +92,7 @@ class BuiltinTemplateLoader(TemplateBridge, BaseLoader): self.pathchain = chain # make the paths into loaders - self.loaders = map(FileSystemLoader, chain) + self.loaders = map(SphinxFileSystemLoader, chain) use_i18n = builder.translator is not None extensions = use_i18n and ['jinja2.ext.i18n'] or [] diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index b917358cc..f9b78793f 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -835,12 +835,16 @@ class LaTeXTranslator(nodes.NodeVisitor): res = "%.3f\\linewidth" % (float(amount) / 100.0) return res + def is_inline(self, node): + """Check whether a node represents an inline element.""" + return isinstance(node.parent, nodes.TextElement) + def visit_image(self, node): attrs = node.attributes pre = [] # in reverse order post = [] include_graphics_options = [] - inline = isinstance(node.parent, nodes.TextElement) + is_inline = self.is_inline(node) if attrs.has_key('scale'): # Could also be done with ``scale`` option to # ``\includegraphics``; doing it this way for consistency. @@ -867,11 +871,11 @@ class LaTeXTranslator(nodes.NodeVisitor): (0, 'left'): ('{', '\\hfill}'), (0, 'right'): ('{\\hfill', '}'),} try: - pre.append(align_prepost[inline, attrs['align']][0]) - post.append(align_prepost[inline, attrs['align']][1]) + pre.append(align_prepost[is_inline, attrs['align']][0]) + post.append(align_prepost[is_inline, attrs['align']][1]) except KeyError: pass # XXX complain here? - if not inline: + if not is_inline: pre.append('\n') post.append('\n') pre.reverse() @@ -1029,7 +1033,7 @@ class LaTeXTranslator(nodes.NodeVisitor): self.context.append('') elif uri.startswith('mailto:') or uri.startswith('http:') or \ uri.startswith('https:') or uri.startswith('ftp:'): - self.body.append('\\href{%s}{' % self.encode(uri)) + self.body.append('\\href{%s}{' % self.encode_uri(uri)) self.context.append('}') elif uri.startswith('#'): self.body.append('\\hyperlink{%s}{' % uri[1:]) @@ -1330,6 +1334,10 @@ class LaTeXTranslator(nodes.NodeVisitor): text = text.replace('--', u'-{-}') return text + def encode_uri(self, text): + # in \href, the tilde is allowed and must be represented literally + return self.encode(text).replace('\\textasciitilde{}', '~') + def visit_Text(self, node): if self.verbatim is not None: self.verbatim += node.astext() diff --git a/tests/root/includes.txt b/tests/root/includes.txt index b8a6bee8b..da36503b3 100644 --- a/tests/root/includes.txt +++ b/tests/root/includes.txt @@ -1,3 +1,10 @@ +Testing downloadable files +========================== + +Download :download:`img.png` here. +Download :download:`this ` there. +Don't download :download:`this `. + Test file and literal inclusion =============================== @@ -42,9 +49,10 @@ Literalinclude options :prepend: START CODE :append: END CODE -Testing downloadable files -========================== +Test if dedenting before parsing works. -Download :download:`img.png` here. -Download :download:`this ` there. -Don't download :download:`this `. +.. highlight:: python + +.. cssclass:: inc-pyobj-dedent +.. literalinclude:: literal.inc + :pyobject: Bar.baz diff --git a/tests/test_build.py b/tests/test_build.py index d81e496eb..6668e2453 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -43,7 +43,7 @@ ENV_WARNINGS = """\ http://www.python.org/logo.png %(root)s/includes.txt:: (WARNING/2) Encoding 'utf-8-sig' used for reading \ included file u'wrongenc.inc' seems to be wrong, try giving an :encoding: option -%(root)s/includes.txt:60: WARNING: download file not readable: nonexisting.png +%(root)s/includes.txt:4: WARNING: download file not readable: nonexisting.png """ HTML_WARNINGS = ENV_WARNINGS + """\ @@ -136,6 +136,8 @@ if pygments: 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', diff --git a/tests/test_markup.py b/tests/test_markup.py index 0c5e74680..03c421a13 100644 --- a/tests/test_markup.py +++ b/tests/test_markup.py @@ -118,3 +118,6 @@ def test_latex_escaping(): u'\\begin{Verbatim}[commandchars=@\\[\\]]\n' u'@PYGZat[]@(@Gamma@)\\@(@infty@)@$@PYGZlb[]@PYGZrb[]\n' u'\\end{Verbatim}') + # in URIs + yield (verify, u'`test `_', None, + u'\\href{http://example.com/~me/}{test}')