From: Armin Ronacher Date: Wed, 28 May 2008 09:26:59 +0000 (+0200) Subject: some more documentation updates and minor code cleanups. Additionally True and true... X-Git-Tag: 2.0rc1~12 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=9bb7e4779182490abc6e1784b0ee63d22b91b11e;p=jinja2.git some more documentation updates and minor code cleanups. Additionally True and true in the template are the same now, same for false/False and none/None. --HG-- branch : trunk --- diff --git a/docs/_static/implementation.png b/docs/_static/implementation.png new file mode 100644 index 0000000..21deae6 Binary files /dev/null and b/docs/_static/implementation.png differ diff --git a/docs/_static/note.png b/docs/_static/note.png new file mode 100644 index 0000000..c484da6 Binary files /dev/null and b/docs/_static/note.png differ diff --git a/docs/_static/style.css b/docs/_static/style.css index 8bfa23e..c5894f6 100644 --- a/docs/_static/style.css +++ b/docs/_static/style.css @@ -209,20 +209,25 @@ cite { div.admonition { margin: 10px 0 10px 0; - padding: 10px; + padding: 10px 10px 10px 60px; border: 1px solid #ccc; - background-color: #f8f8f8; } div.admonition p.admonition-title { - margin: -3px 0 5px 0; + background-color: #b41717; + color: white; + margin: -10px -10px 10px -60px; + padding: 4px 10px 4px 10px; font-weight: bold; - color: #b41717; - font-size: 16px; + font-size: 15px; +} + +div.admonition-note { + background: url(note.png) no-repeat 10px 40px; } -div.admonition p { - margin: 0 0 0 40px; +div.admonition-implementation { + background: url(implementation.png) no-repeat 10px 40px; } #toc { @@ -296,7 +301,8 @@ table.indextable dl dd a { dl.function dt, dl.class dt, dl.exception dt, -dl.method dt { +dl.method dt, +dl.attribute dt { font-weight: normal; } diff --git a/docs/api.rst b/docs/api.rst index cc9e9d8..0dce618 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -95,6 +95,11 @@ For more details about unicode in Python have a look at the excellent High Level API -------------- +The high-level API is the API you will use in the application to load and +render Jinja2 templates. The :ref:`low-level-api` on the other side is only +useful if you want to dig deeper into Jinja2 or :ref:`develop extensions +`. + .. autoclass:: Environment([options]) :members: from_string, get_template, join_path, extend @@ -171,7 +176,7 @@ High Level API possibility to enhance the error message. .. autoclass:: Template - :members: make_module, module, new_context + :members: module, make_module .. attribute:: globals @@ -233,12 +238,62 @@ disallows all operations beside testing if it's an undefined object. .. autoclass:: jinja2.runtime.Undefined() + .. attribute:: _undefined_hint + + Either `None` or an unicode string with the error message for + the undefined object. + + .. attribute:: _undefined_obj + + Either `None` or the owner object that caused the undefined object + to be created (for example because an attribute does not exist). + + .. attribute:: _undefined_name + + The name for the undefined variable / attribute or just `None` + if no such information exists. + + .. attribute:: _undefined_exception + + The exception that the undefined object wants to raise. This + is usually one of :exc:`UndefinedError` or :exc:`SecurityError`. + + .. method:: _fail_with_undefined_error(\*args, \**kwargs) + + When called with any arguments this method raises + :attr:`_undefined_exception` with an error message generated + from the undefined hints stored on the undefined object. + .. autoclass:: jinja2.runtime.DebugUndefined() .. autoclass:: jinja2.runtime.StrictUndefined() Undefined objects are created by calling :attr:`undefined`. +.. admonition:: Implementation + + :class:`Undefined` objects are implemented by overriding the special + `__underscore__` methods. For example the default :class:`Undefined` + class implements `__unicode__` in a way that it returns an empty + string, however `__int__` and others still fail with an exception. To + allow conversion to int by returning ``0`` you can implement your own:: + + class NullUndefined(Undefined): + def __int__(self): + return 0 + def __float__(self): + return 0.0 + + To disallow a method, just override it and raise + :attr:`_undefined_exception`. Because this is a very common idom in + undefined objects there is the helper method + :meth:`_fail_with_undefined_error`. That does that automatically. Here + a class that works like the regular :class:`UndefinedError` but chokes + on iteration:: + + class NonIterableUndefined(Undefined): + __iter__ = Undefined._fail_with_undefined_error + The Context ----------- @@ -283,6 +338,20 @@ The Context blocks registered. The last item in each list is the current active block (latest in the inheritance chain). + .. automethod:: jinja2.runtime.Context.call(callable, \*args, \**kwargs) + + +.. admonition:: Implementation + + Context is immutable for the same reason Python's frame locals are + immutable inside functions. Both Jinja2 and Python are not using the + context / frame locals as data storage for variables but only as primary + data source. + + When a template accesses a variable the template does not define, Jinja2 + looks up the variable in the context, after that the variable is treated + as if it was defined in the template. + .. _loaders: @@ -330,13 +399,17 @@ functions to a Jinja2 environment. .. function:: escape(s) - Convert the characters &, <, >, and " in string s to HTML-safe sequences. - Use this if you need to display text that might contain such characters - in HTML. This function will not escaped objects that do have an HTML - representation such as already escaped data. + Convert the characters ``&``, ``<``, ``>``, ``'``, and ``"`` in string `s` + to HTML-safe sequences. Use this if you need to display text that might + contain such characters in HTML. This function will not escaped objects + that do have an HTML representation such as already escaped data. + + The return value is a :class:`Markup` string. .. autofunction:: jinja2.utils.clear_caches +.. autofunction:: jinja2.utils.is_undefined + .. autoclass:: jinja2.utils.Markup @@ -482,6 +555,8 @@ exist that are variables available to a specific template that are available to all :meth:`~Template.render` calls. +.. _low-level-api: + Low Level API ------------- @@ -518,3 +593,10 @@ don't recommend using any of those. This attribute is `False` if there is a newer version of the template available, otherwise `True`. + +.. admonition:: Note + + The low-level API is fragile. Future Jinja2 versions will not change it + in a backwards incompatible way but modifications in the Jinja core may + shine through. For example if Jinja2 introduces a new AST node in later + versions that may be returned by :meth:`~Environment.parse`. diff --git a/docs/sandbox.rst b/docs/sandbox.rst index f6ec78c..bb0ca9f 100644 --- a/docs/sandbox.rst +++ b/docs/sandbox.rst @@ -29,3 +29,18 @@ SecurityError: access to attribute 'func_code' of 'function' object is unsafe. .. autofunction:: is_internal_attribute .. autofunction:: modifies_known_mutable + +.. admonition:: Note + + The Jinja2 sandbox alone is no solution for perfect security. Especially + for web applications you have to keep in mind that users may create + templates with arbitrary HTML in so it's crucial to ensure that (if you + are running multiple users on the same server) they can't harm each other + via JavaScript insertions and much more. + + Also the sandbox is only as good as the configuration. We stronly + recommend only passing non-shared resources to the template and use + some sort of whitelisting for attributes. + + Also keep in mind that templates may raise runtime or compile time errors, + so make sure to catch them. diff --git a/docs/switching.rst b/docs/switching.rst index f521f08..0c49631 100644 --- a/docs/switching.rst +++ b/docs/switching.rst @@ -174,6 +174,13 @@ operator. Here are some examples:: hmm. {{ user.username|e }} looks pretty normal {% endif %} +Loops +~~~~~ + +For loops work very similar to Django, the only incompatibility is that in +Jinja2 the special variable for the loop context is called `loop` and not +`forloop` like in Django. + Mako ---- diff --git a/docs/templates.rst b/docs/templates.rst index ca30721..182ef94 100644 --- a/docs/templates.rst +++ b/docs/templates.rst @@ -147,19 +147,21 @@ that block:: This will yield all elements without whitespace between them. If `seq` was a list of numbers from ``1`` to ``9`` the output would be ``123456789``. -Note that you must not use a whitespace between the tag and the minus sign: +If :ref:`line-statements` are enabled they strip leading whitespace +automatically up to the beginning of the line. + +.. admonition:: Note - valid:: + You must not use a whitespace between the tag and the minus sign. + + **valid**:: {%- if foo -%}...{% endif %} - invalid:: + **invalid**:: {% - if foo - %}...{% endif %} -If :ref:`line-statements` are enabled they strip leading whitespace -automatically up to the beginning of the line. - Escaping -------- @@ -797,8 +799,16 @@ for Python objects such as strings and numbers. The following literals exist: filter. true / false: - true is always true and false is always false. Keep in mind that those - literals are lowercase! + true is always true and false is always false. + +.. admonition:: Note + + The special constants `true`, `false` and `none` are indeed lowercase. + Because that caused confusion in the past, when writing `True` expands + to an undefined variable that is considered false, all three of them can + be written in title case too (`True`, `False`, and `None`). However for + consistency (all Jinja identifiers are lowercase) you should use the + lowercase versions. Math ~~~~ @@ -854,12 +864,12 @@ not (expr) group an expression. -Note that there is no support for any bit operations or something similar. +.. admonition:: Note -- special note regarding ``not``: The ``is`` and ``in`` operators support - negation using an infix notation too: ``foo is not bar`` and - ``foo not in bar`` instead of ``not foo is bar`` and ``not foo in bar``. - All other expressions require a prefix notation: ``not (foo and bar).`` + The ``is`` and ``in`` operators support negation using an infix notation + too: ``foo is not bar`` and ``foo not in bar`` instead of ``not foo is bar`` + and ``not foo in bar``. All other expressions require a prefix notation: + ``not (foo and bar).`` Other Operators diff --git a/ext/jinja.el b/ext/jinja.el index a224c30..bff9e83 100644 --- a/ext/jinja.el +++ b/ext/jinja.el @@ -43,12 +43,12 @@ '("\\({{ ?\\)\\([^|]*?\\)\\(|.*?\\)? ?}}" . (1 font-lock-variable-name-face)) ;; keywords and builtins (cons (rx word-start - (or "in" "as" "reversed" "recursive" "not" "and" "or" - "if" "else" "import" "with" "without" "context") + (or "in" "as" "recursive" "not" "and" "or" "if" "else" + "import" "with" "without" "context") word-end) font-lock-keyword-face) (cons (rx word-start - (or "true" "false" "null" "loop" "block" "super" "forloop") + (or "true" "false" "none" "loop" "self" "super") word-end) font-lock-builtin-face) ;; tests diff --git a/jinja2/__init__.py b/jinja2/__init__.py index cd720a6..194390a 100644 --- a/jinja2/__init__.py +++ b/jinja2/__init__.py @@ -50,7 +50,7 @@ from jinja2.exceptions import TemplateError, UndefinedError, \ # decorators and public utilities from jinja2.filters import environmentfilter, contextfilter from jinja2.utils import Markup, escape, clear_caches, \ - environmentfunction, contextfunction + environmentfunction, contextfunction, is_undefined __all__ = [ 'Environment', 'Template', 'BaseLoader', 'FileSystemLoader', @@ -59,5 +59,5 @@ __all__ = [ 'TemplateError', 'UndefinedError', 'TemplateNotFound', 'TemplateSyntaxError', 'TemplateAssertionError', 'environmentfilter', 'contextfilter', 'Markup', 'escape', 'environmentfunction', - 'contextfunction', 'clear_caches' + 'contextfunction', 'clear_caches', 'is_undefined' ] diff --git a/jinja2/_speedups.c b/jinja2/_speedups.c index f691c78..8a9a108 100644 --- a/jinja2/_speedups.c +++ b/jinja2/_speedups.c @@ -187,7 +187,7 @@ tb_set_next(PyObject *self, PyObject *args) static PyMethodDef module_methods[] = { {"escape", (PyCFunction)escape, METH_O, "escape(s) -> markup\n\n" - "Convert the characters &, <, >, and \" in string s to HTML-safe\n" + "Convert the characters &, <, >, ', and \" in string s to HTML-safe\n" "sequences. Use this if you need to display text that might contain\n" "such characters in HTML. Marks return value as markup string."}, {"soft_unicode", (PyCFunction)soft_unicode, METH_O, diff --git a/jinja2/filters.py b/jinja2/filters.py index 0a32f9a..762b06d 100644 --- a/jinja2/filters.py +++ b/jinja2/filters.py @@ -23,8 +23,8 @@ _word_re = re.compile(r'\w+') def contextfilter(f): - """Decorator for marking context dependent filters. The current context - argument will be passed as first argument. + """Decorator for marking context dependent filters. The current + :class:`Context` will be passed as first argument. """ if getattr(f, 'environmentfilter', False): raise TypeError('filter already marked as environment filter') @@ -33,8 +33,8 @@ def contextfilter(f): def environmentfilter(f): - """Decorator for marking evironment dependent filters. The environment - used for the template is passed to the filter as first argument. + """Decorator for marking evironment dependent filters. The current + :class:`Environment` is passed to the filter as first argument. """ if getattr(f, 'contextfilter', False): raise TypeError('filter already marked as context filter') @@ -578,7 +578,8 @@ def do_groupby(environment, value, attribute): class _GroupTuple(tuple): __slots__ = () - grouper, list = (property(itemgetter(x)) for x in xrange(2)) + grouper = property(itemgetter(0)) + list = property(itemgetter(1)) def __new__(cls, (key, value)): return tuple.__new__(cls, (key, list(value))) diff --git a/jinja2/nodes.py b/jinja2/nodes.py index 0cccddf..f4b1f32 100644 --- a/jinja2/nodes.py +++ b/jinja2/nodes.py @@ -400,7 +400,8 @@ class Name(Expr): fields = ('name', 'ctx') def can_assign(self): - return self.name not in ('true', 'false', 'none') + return self.name not in ('true', 'false', 'none', + 'True', 'False', 'None') class Literal(Expr): diff --git a/jinja2/parser.py b/jinja2/parser.py index 0ce4a29..7efe79c 100644 --- a/jinja2/parser.py +++ b/jinja2/parser.py @@ -454,9 +454,10 @@ class Parser(object): def parse_primary(self, with_postfix=True): token = self.stream.current if token.type is 'name': - if token.value in ('true', 'false'): - node = nodes.Const(token.value == 'true', lineno=token.lineno) - elif token.value == 'none': + if token.value in ('true', 'false', 'True', 'False'): + node = nodes.Const(token.value in ('true', 'True'), + lineno=token.lineno) + elif token.value in ('none', 'None'): node = nodes.Const(None, lineno=token.lineno) else: node = nodes.Name(token.value, 'load', lineno=token.lineno) diff --git a/jinja2/runtime.py b/jinja2/runtime.py index babc3c9..2dbd569 100644 --- a/jinja2/runtime.py +++ b/jinja2/runtime.py @@ -117,6 +117,11 @@ class Context(object): return dict(self.parent, **self.vars) def call(__self, __obj, *args, **kwargs): + """Call the callable with the arguments and keyword arguments + provided but inject the active context or environment as first + argument if the callable is a :func:`contextfunction` or + :func:`environmentfunction`. + """ if __debug__: __traceback_hide__ = True if isinstance(__obj, _context_function_types): @@ -160,6 +165,14 @@ class Context(object): ) +# register the context as mutable mapping if possible +try: + from collections import MutableMapping + MutableMapping.register(Context) +except ImportError: + pass + + class TemplateReference(object): """The `self` in templates.""" @@ -321,28 +334,6 @@ class Macro(object): ) -def fail_with_undefined_error(self, *args, **kwargs): - """Regular callback function for undefined objects that raises an - `UndefinedError` on call. - """ - if self._undefined_hint is None: - if self._undefined_obj is None: - hint = '%r is undefined' % self._undefined_name - elif not isinstance(self._undefined_name, basestring): - hint = '%r object has no element %r' % ( - self._undefined_obj.__class__.__name__, - self._undefined_name - ) - else: - hint = '%r object has no attribute %r' % ( - self._undefined_obj.__class__.__name__, - self._undefined_name - ) - else: - hint = self._undefined_hint - raise self._undefined_exception(hint) - - class Undefined(object): """The default undefined type. This undefined type can be printed and iterated over, but every other access will raise an :exc:`UndefinedError`: @@ -366,18 +357,36 @@ class Undefined(object): self._undefined_name = name self._undefined_exception = exc + def _fail_with_undefined_error(self, *args, **kwargs): + """Regular callback function for undefined objects that raises an + `UndefinedError` on call. + """ + if self._undefined_hint is None: + if self._undefined_obj is None: + hint = '%r is undefined' % self._undefined_name + elif not isinstance(self._undefined_name, basestring): + hint = '%r object has no element %r' % ( + self._undefined_obj.__class__.__name__, + self._undefined_name + ) + else: + hint = '%r object has no attribute %r' % ( + self._undefined_obj.__class__.__name__, + self._undefined_name + ) + else: + hint = self._undefined_hint + raise self._undefined_exception(hint) + __add__ = __radd__ = __mul__ = __rmul__ = __div__ = __rdiv__ = \ __realdiv__ = __rrealdiv__ = __floordiv__ = __rfloordiv__ = \ __mod__ = __rmod__ = __pos__ = __neg__ = __call__ = \ __getattr__ = __getitem__ = __lt__ = __le__ = __gt__ = __ge__ = \ - fail_with_undefined_error + __int__ = __float__ = __complex__ = _fail_with_undefined_error def __str__(self): return self.__unicode__().encode('utf-8') - def __repr__(self): - return 'Undefined' - def __unicode__(self): return u'' @@ -391,6 +400,9 @@ class Undefined(object): def __nonzero__(self): return False + def __repr__(self): + return 'Undefined' + class DebugUndefined(Undefined): """An undefined that returns the debug info when printed. @@ -439,7 +451,7 @@ class StrictUndefined(Undefined): """ __slots__ = () __iter__ = __unicode__ = __len__ = __nonzero__ = __eq__ = __ne__ = \ - fail_with_undefined_error + Undefined._fail_with_undefined_error # remove remaining slots attributes, after the metaclass did the magic they diff --git a/jinja2/utils.py b/jinja2/utils.py index e064a25..6d1c958 100644 --- a/jinja2/utils.py +++ b/jinja2/utils.py @@ -63,23 +63,47 @@ except TypeError, _error: def contextfunction(f): - """This decorator can be used to mark a callable as context callable. A - context callable is passed the active context as first argument when - called from the template. + """This decorator can be used to mark a function or method context callable. + A context callable is passed the active :class:`Context` as first argument when + called from the template. This is useful if a function wants to get access + to the context or functions provided on the context object. For example + a function that returns a sorted list of template variables the current + template exports could look like this:: + + @contextcallable + def get_exported_names(context): + return sorted(context.exported_vars) """ f.contextfunction = True return f def environmentfunction(f): - """This decorator can be used to mark a callable as environment callable. - A environment callable is passed the current environment as first argument - when called from the template. + """This decorator can be used to mark a function or method as environment + callable. This decorator works exactly like the :func:`contextfunction` + decorator just that the first argument is the active :class:`Environment` + and not context. """ f.environmentfunction = True return f +def is_undefined(obj): + """Check if the object passed is undefined. This does nothing more than + performing an instance check against :class:`Undefined` but looks nicer. + This can be used for custom filters or tests that want to react to + undefined variables. For example a custom default filter can look like + this:: + + def default(var, default=''): + if is_undefined(var): + return default + return var + """ + from jinja2.runtime import Undefined + return isinstance(obj, Undefined) + + def clear_caches(): """Jinja2 keeps internal caches for environments and lexers. These are used so that Jinja2 doesn't have to recreate environments and lexers all @@ -532,6 +556,14 @@ class LRUCache(object): __copy__ = copy +# register the LRU cache as mutable mapping if possible +try: + from collections import MutableMapping + MutableMapping.register(LRUCache) +except ImportError: + pass + + # we have to import it down here as the speedups module imports the # markup type which is define above. try: diff --git a/tests/test_syntax.py b/tests/test_syntax.py index 717e165..a126b5f 100644 --- a/tests/test_syntax.py +++ b/tests/test_syntax.py @@ -163,3 +163,11 @@ def test_trailing_comma(env): def test_block_end_name(env): env.from_string('{% block foo %}...{% endblock foo %}') raises(TemplateSyntaxError, env.from_string, '{% block x %}{% endblock y %}') + + +def test_contant_casing(env): + for const in True, False, None: + tmpl = env.from_string('{{ %s }}|{{ %s }}|{{ %s }}' % ( + str(const), str(const).lower(), str(const).upper() + )) + assert tmpl.render() == '%s|%s|' % (const, const)