Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add missing jinja2 filters #25

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
34 changes: 20 additions & 14 deletions jinja2schema/visitors/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,8 +283,8 @@ def visit_test(ast, ctx, macroses=None, config=default_config):
if ast.name in ('divisibleby', 'escaped', 'even', 'lower', 'odd', 'upper'):
# TODO
predicted_struct = Scalar.from_ast(ast.node, order_nr=config.ORDER_OBJECT.get_next())
elif ast.name in ('defined', 'undefined', 'equalto', 'iterable', 'mapping',
'none', 'number', 'sameas', 'sequence', 'string'):
elif ast.name in ('defined', 'equalto', 'iterable', 'mapping', 'none', 'number', 'sameas',
'sequence', 'string', 'undefined'):
predicted_struct = Unknown.from_ast(ast.node, order_nr=config.ORDER_OBJECT.get_next())
if ast.name == 'defined':
predicted_struct.checked_as_defined = True
Expand Down Expand Up @@ -431,20 +431,20 @@ def visit_call(ast, ctx, macroses=None, config=default_config):
@visits_expr(nodes.Filter)
def visit_filter(ast, ctx, macroses=None, config=default_config):
return_struct_cls = None
if ast.name in ('abs', 'striptags', 'capitalize', 'center', 'escape', 'filesizeformat',
'float', 'forceescape', 'format', 'indent', 'int', 'replace', 'round',
'safe', 'string', 'striptags', 'title', 'trim', 'truncate', 'upper',
'urlencode', 'urlize', 'wordcount', 'wordwrap', 'e'):
if ast.name in ('abs', 'capitalize', 'center', 'e', 'escape', 'filesizeformat', 'float', 'forceescape',
'format', 'indent', 'int', 'lower', 'replace', 'round', 'safe', 'string', 'striptags',
'striptags', 'title', 'trim', 'truncate', 'upper', 'urlencode', 'urlize',
'wordcount', 'wordwrap'):
ctx.meet(Scalar(), ast)
if ast.name in ('abs', 'round'):
node_struct = Number.from_ast(ast.node, order_nr=config.ORDER_OBJECT.get_next())
return_struct_cls = Number
elif ast.name in ('float', 'int'):
node_struct = Scalar.from_ast(ast.node, order_nr=config.ORDER_OBJECT.get_next())
return_struct_cls = Number
elif ast.name in ('striptags', 'capitalize', 'center', 'escape', 'forceescape', 'format', 'indent',
'replace', 'safe', 'title', 'trim', 'truncate', 'upper', 'urlencode',
'urlize', 'wordwrap', 'e'):
elif ast.name in ('capitalize', 'center', 'e', 'escape', 'forceescape', 'format', 'indent',
'lower', 'replace', 'safe', 'striptags', 'title', 'trim', 'truncate',
'upper', 'urlencode', 'urlize', 'wordwrap'):
node_struct = String.from_ast(ast.node, order_nr=config.ORDER_OBJECT.get_next())
return_struct_cls = String
elif ast.name == 'filesizeformat':
Expand All @@ -468,7 +468,7 @@ def visit_filter(ast, ctx, macroses=None, config=default_config):
predicted_struct=node_struct
), macroses, config=config)
return rtype, struct
elif ast.name == 'default':
elif ast.name in ('d', 'default'):
default_value_rtype, default_value_struct = visit_expr(
ast.args[0],
Context(predicted_struct=Unknown.from_ast(ast.args[0], order_nr=config.ORDER_OBJECT.get_next())),
Expand All @@ -494,18 +494,18 @@ def visit_filter(ast, ctx, macroses=None, config=default_config):
order_nr=config.ORDER_OBJECT.get_next())),
macroses, config=config)
return rtype, merge(struct, arg_struct)
elif ast.name in ('first', 'last', 'random', 'length', 'sum'):
if ast.name in ('first', 'last', 'random'):
elif ast.name in ('count', 'first', 'last', 'length', 'max', 'min', 'random', 'sum'):
if ast.name in ('first', 'last', 'max', 'min', 'random'):
el_struct = ctx.get_predicted_struct()
elif ast.name == 'length':
elif ast.name in ('count', 'length'):
ctx.meet(Scalar(), ast)
return_struct_cls = Number
el_struct = Unknown()
else:
ctx.meet(Scalar(), ast)
el_struct = Scalar()
node_struct = List.from_ast(ast.node, el_struct, order_nr=config.ORDER_OBJECT.get_next())
elif ast.name in ('groupby', 'map', 'reject', 'rejectattr', 'select', 'selectattr', 'sort'):
elif ast.name in ('groupby', 'map', 'reject', 'rejectattr', 'select', 'selectattr', 'sort', 'unique'):
ctx.meet(List(Unknown()), ast)
node_struct = merge(
List(Unknown()),
Expand All @@ -520,6 +520,12 @@ def visit_filter(ast, ctx, macroses=None, config=default_config):
elif ast.name == 'pprint':
ctx.meet(Scalar(), ast)
node_struct = ctx.get_predicted_struct()
elif ast.name == 'reverse':
node_struct = Unknown.from_ast(ast.node, order_nr=config.ORDER_OBJECT.get_next())
return_struct_cls = Unknown
elif ast.name == 'tojson':
node_struct = Unknown.from_ast(ast.node, order_nr=config.ORDER_OBJECT.get_next())
return_struct_cls = String
elif ast.name == 'xmlattr':
ctx.meet(Scalar(), ast)
node_struct = Dictionary.from_ast(ast.node, order_nr=config.ORDER_OBJECT.get_next())
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
author_email='anthony.romanovich@gmail.com',
url='https://jinja2schema.readthedocs.io',
packages=find_packages(exclude=['tests']),
install_requires=['Jinja2>=2.2'],
install_requires=['Jinja2>=2.10'],
classifiers=[
'Development Status :: 4 - Beta',
'Intended Audience :: Developers',
Expand Down
77 changes: 64 additions & 13 deletions tests/unit_tests/test_filter_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def get_scalar_context(ast):


def test_string_filters():
for filter in ('striptags', 'capitalize', 'title', 'upper', 'urlize'):
for filter in ('capitalize', 'lower', 'striptags', 'title', 'upper', 'urlize'):
template = '{{ x|' + filter + ' }}'
ast = parse(template).find(nodes.Filter)

Expand Down Expand Up @@ -51,14 +51,16 @@ def test_batch_and_slice_filters():


def test_default_filter():
template = '''{{ x|default('g') }}'''
ast = parse(template).find(nodes.Filter)
rtype, struct = visit_filter(ast, get_scalar_context(ast))
for filter in ('d', 'default'):
template = '''{{ x|''' + filter + '''('g') }}'''

expected_struct = Dictionary({
'x': String(label='x', linenos=[1], used_with_default=True, value='g'),
})
assert struct == expected_struct
ast = parse(template).find(nodes.Filter)
rtype, struct = visit_filter(ast, get_scalar_context(ast))

expected_struct = Dictionary({
'x': String(label='x', linenos=[1], used_with_default=True, value='g'),
})
assert struct == expected_struct


def test_filter_chaining():
Expand Down Expand Up @@ -140,9 +142,58 @@ def test_join_filter():


def test_length_filter():
ast = parse('{{ xs|length }}').find(nodes.Filter)
rtype, struct = visit_filter(ast, get_scalar_context(ast))
assert rtype == Number(label='xs', linenos=[1])
for filter in ('count', 'length'):
template = '{{ xs|' + filter + ' }}'

ast = parse(template).find(nodes.Filter)
rtype, struct = visit_filter(ast, get_scalar_context(ast))
assert rtype == Number(label='xs', linenos=[1])
assert struct == Dictionary({
'xs': List(Unknown(), label='xs', linenos=[1]),
})

def test_max_min_filter():
for filter in ('max', 'min'):
template = '{{ values|' + filter + ' }}'
ast = parse(template).find(nodes.Filter)

rtype, struct = visit_filter(ast, get_scalar_context(ast))
assert rtype == Scalar(label='values', linenos=[1])
assert struct == Dictionary({
'values': List(Scalar(linenos=[1]), label='values', linenos=[1]),
})

def test_unique_filter():
template = '{{ values|unique }}'
ast = parse(template).find(nodes.Filter)

unknown_ctx = Context(predicted_struct=Unknown.from_ast(ast))
rtype, struct = visit_filter(ast, unknown_ctx)
assert rtype == Unknown(label='values', linenos=[1])
assert struct == Dictionary({
'xs': List(Unknown(), label='xs', linenos=[1]),
})
'values': List(Unknown(), label='values', linenos=[1]),
})

def test_reverse_filter():
template = '{{ x|reverse }}'

ast = parse(template).find(nodes.Filter)
rtype, struct = visit_filter(ast, get_scalar_context(ast))

assert rtype == Unknown(label='x', linenos=[1])
expected_struct = Dictionary({
'x': Unknown(label='x', linenos=[1]),
})
assert struct == expected_struct

def test_tojson_filter():
template = '{{ x|tojson }}'

ast = parse(template).find(nodes.Filter)
rtype, struct = visit_filter(ast, get_scalar_context(ast))

assert rtype == String(label='x', linenos=[1])
expected_struct = Dictionary({
'x': Unknown(label='x', linenos=[1]),
})
assert struct == expected_struct