From 9bb7e4779182490abc6e1784b0ee63d22b91b11e Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 28 May 2008 11:26:59 +0200 Subject: [PATCH] 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 --- docs/_static/implementation.png | Bin 0 -> 3628 bytes docs/_static/note.png | Bin 0 -> 2135 bytes docs/_static/style.css | 22 +++++--- docs/api.rst | 92 ++++++++++++++++++++++++++++++-- docs/sandbox.rst | 15 ++++++ docs/switching.rst | 7 +++ docs/templates.rst | 36 ++++++++----- ext/jinja.el | 6 +-- jinja2/__init__.py | 4 +- jinja2/_speedups.c | 2 +- jinja2/filters.py | 11 ++-- jinja2/nodes.py | 3 +- jinja2/parser.py | 7 +-- jinja2/runtime.py | 66 +++++++++++++---------- jinja2/utils.py | 44 ++++++++++++--- tests/test_syntax.py | 8 +++ 16 files changed, 249 insertions(+), 74 deletions(-) create mode 100644 docs/_static/implementation.png create mode 100644 docs/_static/note.png diff --git a/docs/_static/implementation.png b/docs/_static/implementation.png new file mode 100644 index 0000000000000000000000000000000000000000..21deae6850f6747a8d9394b3b28266b85d55e7b6 GIT binary patch literal 3628 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 N002ovPDHLkV1gi8?biSR literal 0 HcmV?d00001 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) -- 2.26.2