From abd3657a64bb7f5c47572d621fea4f47f71b9bb6 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 27 Jun 2008 08:45:19 +0200 Subject: [PATCH] Added non-babel output mode to extract_from_ast, integreated jinja2 doctests directly into the py.test suite (ugh, that's an ugly hack) --HG-- branch : trunk --- docs/Makefile | 13 +++++++++---- jinja2/ext.py | 42 +++++++++++++++++++++++++++++++++++----- jinja2/loaders.py | 2 +- jinja2/runtime.py | 10 +++++----- tests/conftest.py | 49 +++++++++++++++++++++++++++++++++++++++++++---- 5 files changed, 97 insertions(+), 19 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index e9c11d0..5e24ec1 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -16,7 +16,8 @@ ALLSPHINXOPTS = -d _build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" - @echo " pickle to make pickle files (usable by e.g. sphinx-web)" + @echo " pickle to make pickle files" + @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " changes to make an overview over all changed/added/deprecated items" @@ -35,9 +36,13 @@ pickle: mkdir -p _build/pickle _build/doctrees $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) _build/pickle @echo - @echo "Build finished; now you can process the pickle files or run" - @echo " sphinx-web _build/pickle" - @echo "to start the sphinx-web server." + @echo "Build finished; now you can process the pickle files" + +json: + mkdir -p _build/json _build/doctrees + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) _build/json + @echo + @echo "Build finished; now you can process the json files" web: pickle diff --git a/jinja2/ext.py b/jinja2/ext.py index f60aade..4d68983 100644 --- a/jinja2/ext.py +++ b/jinja2/ext.py @@ -321,8 +321,28 @@ class LoopControlExtension(Extension): return nodes.Continue(lineno=token.lineno) -def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS): - """Extract localizable strings from the given template node. +def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS, + babel_style=True): + """Extract localizable strings from the given template node. Per + default this function returns matches in babel style that means non string + parameters as well as keyword arguments are returned as `None`. This + allows Babel to figure out what you really meant if you are using + gettext functions that allow keyword arguments for placeholder expansion. + If you don't want that behavior set the `babel_style` parameter to `False` + which causes only strings to be returned and parameters are always stored + in tuples. As a consequence invalid gettext calls (calls without a single + string parameter or string parameters after non-string parameters) are + skipped. + + This example explains the behavior: + + >>> from jinja2 import Environment + >>> env = Environment() + >>> node = env.parse('{{ (_("foo"), _(), ngettext("foo", "bar", 42)) }}') + >>> list(extract_from_ast(node)) + [(1, '_', 'foo'), (1, '_', ()), (1, 'ngettext', ('foo', 'bar', None))] + >>> list(extract_from_ast(node, babel_style=False)) + [(1, '_', ('foo',)), (1, 'ngettext', ('foo', 'bar'))] For every string found this function yields a ``(lineno, function, message)`` tuple, where: @@ -346,10 +366,22 @@ def extract_from_ast(node, gettext_functions=GETTEXT_FUNCTIONS): else: strings.append(None) - if len(strings) == 1: - strings = strings[0] + for arg in node.kwargs: + strings.append(None) + if node.dyn_args is not None: + strings.append(None) + if node.dyn_kwargs is not None: + strings.append(None) + + if not babel_style: + strings = tuple(x for x in strings if x is not None) + if not strings: + continue else: - strings = tuple(strings) + if len(strings) == 1: + strings = strings[0] + else: + strings = tuple(strings) yield node.lineno, node.node.name, strings diff --git a/jinja2/loaders.py b/jinja2/loaders.py index ccd6ec1..e964fdc 100644 --- a/jinja2/loaders.py +++ b/jinja2/loaders.py @@ -257,7 +257,7 @@ class ChoiceLoader(BaseLoader): >>> loader = ChoiceLoader([ ... FileSystemLoader('/path/to/user/templates'), - ... PackageLoader('myapplication') + ... PackageLoader('mypackage') ... ]) This is useful if you want to allow users to override builtin templates diff --git a/jinja2/runtime.py b/jinja2/runtime.py index 2dbd569..0417f61 100644 --- a/jinja2/runtime.py +++ b/jinja2/runtime.py @@ -346,7 +346,7 @@ class Undefined(object): >>> foo + 42 Traceback (most recent call last): ... - jinja2.exceptions.UndefinedError: 'foo' is undefined + UndefinedError: 'foo' is undefined """ __slots__ = ('_undefined_hint', '_undefined_obj', '_undefined_name', '_undefined_exception') @@ -415,7 +415,7 @@ class DebugUndefined(Undefined): >>> foo + 42 Traceback (most recent call last): ... - jinja2.exceptions.UndefinedError: 'foo' is undefined + UndefinedError: 'foo' is undefined """ __slots__ = () @@ -439,15 +439,15 @@ class StrictUndefined(Undefined): >>> str(foo) Traceback (most recent call last): ... - jinja2.exceptions.UndefinedError: 'foo' is undefined + UndefinedError: 'foo' is undefined >>> not foo Traceback (most recent call last): ... - jinja2.exceptions.UndefinedError: 'foo' is undefined + UndefinedError: 'foo' is undefined >>> foo + 42 Traceback (most recent call last): ... - jinja2.exceptions.UndefinedError: 'foo' is undefined + UndefinedError: 'foo' is undefined """ __slots__ = () __iter__ = __unicode__ = __len__ = __nonzero__ = __eq__ = __ne__ = \ diff --git a/tests/conftest.py b/tests/conftest.py index 8a24673..fa60acd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,6 +18,7 @@ from jinja2 import Environment from jinja2.loaders import BaseLoader from jinja2.exceptions import TemplateNotFound + try: # This code adds support for coverage.py (see # http://nedbatchelder.com/code/modules/coverage.html). @@ -67,6 +68,20 @@ loader = GlobalLoader() simple_env = Environment(trim_blocks=True, loader=loader, cache_size=0) +class Directory(py.test.collect.Directory): + + def run(self): + rv = super(Directory, self).run() + if self.fspath.basename == 'tests': + rv.append('doctests') + return rv + + def join(self, name): + if name == 'doctests': + return JinjaDocTestModule(name, parent=self) + return super(Directory, self).join(name) + + class Module(py.test.collect.Module): def __init__(self, *args, **kwargs): @@ -94,15 +109,41 @@ class JinjaTestFunction(py.test.collect.Function): class JinjaDocTest(py.test.collect.Item): + def __init__(self, *args, **kwargs): + realmod = kwargs.pop('realmod', False) + super(JinjaDocTest, self).__init__(*args, **kwargs) + self.realmod = realmod + def run(self): - mod = py.std.types.ModuleType(self.name) - mod.__doc__ = self.obj + if self.realmod: + mod = __import__(self.name, None, None, ['']) + else: + mod = py.std.types.ModuleType(self.name) + mod.__doc__ = self.obj + mod.env = self.parent.env + mod.MODULE = self.parent.obj self.execute(mod) def execute(self, mod): - mod.env = self.parent.env - mod.MODULE = self.parent.obj failed, tot = py.compat.doctest.testmod(mod, verbose=True) if failed: py.test.fail('doctest %s: %s failed out of %s' % ( self.fspath, failed, tot)) + + +class JinjaDocTestModule(py.test.collect.Module): + + def __init__(self, *args, **kwargs): + super(JinjaDocTestModule, self).__init__(*args, **kwargs) + self.doctest_modules = [ + 'jinja2.environment', 'jinja2.compiler', 'jinja2.parser', + 'jinja2.lexer', 'jinja2.ext', 'jinja2.sandbox', + 'jinja2.filters', 'jinja2.tests', 'jinja2.utils', + 'jinja2.runtime' + ] + + def run(self): + return self.doctest_modules + + def join(self, name): + return JinjaDocTest(name, parent=self, realmod=True) -- 2.26.2