Merge pull request #922 from CleoQc/feature/support_scientific_notation

Feature/support scientific notation
This commit is contained in:
David Lord 2019-07-23 08:35:15 -07:00 committed by GitHub
commit 278a0574f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 46 additions and 12 deletions

View File

@ -21,6 +21,8 @@ unreleased
- Added a ``default`` parameter for the ``map`` filter. (`#557`_) - Added a ``default`` parameter for the ``map`` filter. (`#557`_)
- Exclude environment globals from - Exclude environment globals from
:func:`meta.find_undeclared_variables`. #931 :func:`meta.find_undeclared_variables`. #931
- Float literals can be written with scientific notation, like
``{{ 2.56e-3 }}``. #912, #922
.. _#557: https://github.com/pallets/jinja/issues/557 .. _#557: https://github.com/pallets/jinja/issues/557
.. _#765: https://github.com/pallets/jinja/issues/765 .. _#765: https://github.com/pallets/jinja/issues/765

View File

@ -396,7 +396,7 @@ this template, it first locates the parent. The extends tag should be the
first tag in the template. Everything before it is printed out normally and first tag in the template. Everything before it is printed out normally and
may cause confusion. For details about this behavior and how to take may cause confusion. For details about this behavior and how to take
advantage of it, see :ref:`null-master-fallback`. Also a block will always be advantage of it, see :ref:`null-master-fallback`. Also a block will always be
filled in regardless of whether the surrounding condition is evaluated to be true filled in regardless of whether the surrounding condition is evaluated to be true
or false. or false.
The filename of the template depends on the template loader. For example, the The filename of the template depends on the template loader. For example, the
@ -1177,11 +1177,13 @@ for Python objects such as strings and numbers. The following literals exist:
arguments to function calls and filters, or just to extend or include a arguments to function calls and filters, or just to extend or include a
template). template).
42 / 42.23: 42:
Integers and floating point numbers are created by just writing the Integers are whole numbers without a decimal part.
number down. If a dot is present, the number is a float, otherwise an
integer. Keep in mind that, in Python, ``42`` and ``42.0`` 42.23 / 42.1e2:
are different (``int`` and ``float``, respectively). Floating point numbers can be written using a '.' as a decimal mark.
They can also be written in scientific notation with an upper or
lower case 'e' to indicate the exponent part.
['list', 'of', 'objects']: ['list', 'of', 'objects']:
Everything between two brackets is a list. Lists are useful for storing Everything between two brackets is a list. Lists are useful for storing

View File

@ -15,6 +15,7 @@
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
import re import re
from ast import literal_eval
from collections import deque from collections import deque
from operator import itemgetter from operator import itemgetter
@ -52,7 +53,7 @@ else:
del jinja2._identifier del jinja2._identifier
del _identifier del _identifier
float_re = re.compile(r'(?<!\.)\d+\.\d+') float_re = re.compile(r'(?<!\.)\d+(?:\.\d+)?(?:e[+\-]?\d+)?', re.IGNORECASE)
newline_re = re.compile(r'(\r\n|\r|\n)') newline_re = re.compile(r'(\r\n|\r|\n)')
# internal the tokens and keep references to them # internal the tokens and keep references to them
@ -601,7 +602,7 @@ class Lexer(object):
elif token == 'integer': elif token == 'integer':
value = int(value) value = int(value)
elif token == 'float': elif token == 'float':
value = float(value) value = literal_eval(value)
elif token == 'operator': elif token == 'operator':
token = operators[value] token = operators[value]
yield Token(lineno, token, value) yield Token(lineno, token, value)

View File

@ -136,9 +136,11 @@ class TestFilter(object):
def test_float(self, env): def test_float(self, env):
tmpl = env.from_string('{{ "42"|float }}|' tmpl = env.from_string('{{ "42"|float }}|'
'{{ "ajsghasjgd"|float }}|' '{{ "ajsghasjgd"|float }}|'
'{{ "10e1"|float }}|'
'{{ "10.5e-10"|float }}|'
'{{ "32.32"|float }}') '{{ "32.32"|float }}')
out = tmpl.render() out = tmpl.render()
assert out == '42.0|0.0|32.32' assert out == '42.0|0.0|100.0|1.05e-09|32.32'
def test_format(self, env): def test_format(self, env):
tmpl = env.from_string('''{{ "%s|%s"|format("a", "b") }}''') tmpl = env.from_string('''{{ "%s|%s"|format("a", "b") }}''')

View File

@ -336,9 +336,36 @@ class TestSyntax(object):
tmpl = env.from_string('{{ 1 in [1, 2, 3] }}|{{ 1 not in [1, 2, 3] }}') tmpl = env.from_string('{{ 1 in [1, 2, 3] }}|{{ 1 not in [1, 2, 3] }}')
assert tmpl.render() == 'True|False' assert tmpl.render() == 'True|False'
def test_literals(self, env): @pytest.mark.parametrize("value", ("[]", "{}", "()"))
tmpl = env.from_string('{{ [] }}|{{ {} }}|{{ () }}') def test_collection_literal(self, env, value):
assert tmpl.render().lower() == '[]|{}|()' t = env.from_string("{{ %s }}" % value)
assert t.render() == value
@pytest.mark.parametrize("value", ("1", "123"))
def test_int_literal(self, env, value):
t = env.from_string("{{ %s }}" % value)
assert t.render() == value
@pytest.mark.parametrize(
"value",
(
"1.2",
"34.56",
("1e0", "1.0"),
("10e1", "100.0"),
("2.5e100", "2.5e+100"),
"2.5e+100",
("25.6e-10", "2.56e-09"),
),
)
def test_float_literal(self, env, value):
if isinstance(value, tuple):
value, expect = value
else:
expect = value
t = env.from_string("{{ %s }}" % value)
assert t.render() == expect
def test_bool(self, env): def test_bool(self, env):
tmpl = env.from_string('{{ true and false }}|{{ false ' tmpl = env.from_string('{{ true and false }}|{{ false '