From 89754079b48791ab3d064c2c4a80b2ff64115909 Mon Sep 17 00:00:00 2001 From: TayHobbs Date: Tue, 29 Dec 2015 07:42:10 -0600 Subject: [PATCH 01/16] Add Python 3 support --- .gitignore | 3 +++ jinja2schema/visitors/stmt.py | 10 +++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 1003006..07c59ed 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ .coverage docs/build +**/__pycache__/* +*.pyc +*.egg-info* diff --git a/jinja2schema/visitors/stmt.py b/jinja2schema/visitors/stmt.py index 83946fa..e715e46 100644 --- a/jinja2schema/visitors/stmt.py +++ b/jinja2schema/visitors/stmt.py @@ -15,7 +15,7 @@ from ..macro import Macro from ..mergers import merge, merge_many from ..exceptions import InvalidExpression -from .. import _compat +from .._compat import iteritems, izip, zip_longest from .expr import Context, visit_expr from .util import visit_many @@ -46,7 +46,7 @@ def visit_stmt(ast, macroses=None, config=default_config): """ visitor = stmt_visitors.get(type(ast)) if not visitor: - for node_cls, visitor_ in stmt_visitors.iteritems(): + for node_cls, visitor_ in iteritems(stmt_visitors): if isinstance(ast, node_cls): visitor = visitor_ if not visitor: @@ -95,7 +95,7 @@ def visit_if(ast, macroses=None, config=default_config): else_struct = visit_many(ast.else_, macroses, config, predicted_struct_cls=Scalar) if ast.else_ else Dictionary() struct = merge_many(test_struct, if_struct, else_struct) - for var_name, var_struct in test_struct.iteritems(): + for var_name, var_struct in iteritems(test_struct): if var_struct.checked_as_defined or var_struct.checked_as_undefined: if var_struct.checked_as_undefined: lookup_struct = if_struct @@ -125,7 +125,7 @@ def visit_assign(ast, macroses=None, config=default_config): if len(ast.target.items) != len(ast.node.items): raise InvalidExpression(ast, 'number of items in left side is different ' 'from right side') - for name_ast, var_ast in _compat.izip(ast.target.items, ast.node.items): + for name_ast, var_ast in izip(ast.target.items, ast.node.items): variables.append((name_ast.name, var_ast)) for var_name, var_ast in variables: var_rtype, var_struct = visit_expr(var_ast, Context(predicted_struct=Unknown.from_ast(var_ast)), macroses, config) @@ -160,7 +160,7 @@ def visit_macro(ast, macroses=None, config=default_config): kwargs = [] body_struct = visit_many(ast.body, macroses, config, predicted_struct_cls=Scalar) - for i, (arg, default_value_ast) in enumerate(reversed(list(_compat.zip_longest(reversed(ast.args), + for i, (arg, default_value_ast) in enumerate(reversed(list(zip_longest(reversed(ast.args), reversed(ast.defaults)))), start=1): has_default_value = bool(default_value_ast) if has_default_value: From 08c457cd4305a88c394e52dc031e5bad264a4e5f Mon Sep 17 00:00:00 2001 From: TayHobbs Date: Tue, 29 Dec 2015 07:46:44 -0600 Subject: [PATCH 02/16] Allow blocks to be used in templates --- jinja2schema/visitors/stmt.py | 5 +++++ tests/func_tests/test_basics.py | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/jinja2schema/visitors/stmt.py b/jinja2schema/visitors/stmt.py index 83946fa..49968ce 100644 --- a/jinja2schema/visitors/stmt.py +++ b/jinja2schema/visitors/stmt.py @@ -188,3 +188,8 @@ def visit_macro(ast, macroses=None, config=default_config): for arg in args_struct.iterkeys(): body_struct.pop(arg, None) return body_struct + + +@visits_stmt(nodes.Block) +def visit_block(ast, macroses=None, config=default_config): + return visit_many(ast.body, macroses, config) diff --git a/tests/func_tests/test_basics.py b/tests/func_tests/test_basics.py index afad078..a104b3d 100644 --- a/tests/func_tests/test_basics.py +++ b/tests/func_tests/test_basics.py @@ -413,3 +413,18 @@ def test_boolean_conditions_setting_2(): }) assert struct == expected_struct +def test_block_1(): + config = Config() + + template = ''' + {% block test %} + {{ x }} + {{ y }} + {% endblock %} + ''' + struct = infer(template, config) + expected_struct = Dictionary({ + 'x': Scalar(label='x', linenos=[3]), + 'y': Scalar(label='y', linenos=[4]), + }) + assert struct == expected_struct From f25cf4d0721630febfad37fc21bc92cda9fd71a9 Mon Sep 17 00:00:00 2001 From: TayHobbs Date: Tue, 29 Dec 2015 13:26:46 -0600 Subject: [PATCH 03/16] Allow includes and extend tags to be used in a template --- .DS_Store | Bin 0 -> 6148 bytes jinja2schema/config.py | 24 +++- jinja2schema/visitors/stmt.py | 50 +++++-- jinja2schema/visitors/util.py | 7 +- scratch.py | 9 ++ tests/.DS_Store | Bin 0 -> 8196 bytes tests/func_tests/test_inheritance.py | 130 ++++++++++++++++++ tests/templates/extend_override_1.html | 3 + tests/templates/extend_override_2.html | 9 ++ tests/templates/extend_override_3.html | 6 + tests/templates/extend_override_4.html | 10 ++ tests/templates/include_1.html | 1 + tests/templates/include_2.html | 4 + tests/templates/include_extend.html | 3 + tests/templates/include_override_1.html | 3 + tests/templates/include_override_2.html | 9 ++ tests/templates/include_override_3.html | 6 + tests/templates/include_override_4.html | 10 ++ tests/templates/inner_extend.html | 2 + tests/templates/inner_extend_override_1.html | 4 + tests/templates/inner_extend_override_2.html | 7 + tests/templates/inner_extend_override_3.html | 10 ++ tests/templates/inner_extend_override_4.html | 10 ++ tests/templates/inner_include_1.html | 2 + tests/templates/inner_include_2.html | 4 + tests/templates/inner_include_override_1.html | 4 + tests/templates/inner_include_override_2.html | 7 + tests/templates/inner_include_override_3.html | 10 ++ tests/templates/inner_include_override_4.html | 10 ++ 29 files changed, 341 insertions(+), 13 deletions(-) create mode 100644 .DS_Store create mode 100644 scratch.py create mode 100644 tests/.DS_Store create mode 100644 tests/func_tests/test_inheritance.py create mode 100644 tests/templates/extend_override_1.html create mode 100644 tests/templates/extend_override_2.html create mode 100644 tests/templates/extend_override_3.html create mode 100644 tests/templates/extend_override_4.html create mode 100644 tests/templates/include_1.html create mode 100644 tests/templates/include_2.html create mode 100644 tests/templates/include_extend.html create mode 100644 tests/templates/include_override_1.html create mode 100644 tests/templates/include_override_2.html create mode 100644 tests/templates/include_override_3.html create mode 100644 tests/templates/include_override_4.html create mode 100644 tests/templates/inner_extend.html create mode 100644 tests/templates/inner_extend_override_1.html create mode 100644 tests/templates/inner_extend_override_2.html create mode 100644 tests/templates/inner_extend_override_3.html create mode 100644 tests/templates/inner_extend_override_4.html create mode 100644 tests/templates/inner_include_1.html create mode 100644 tests/templates/inner_include_2.html create mode 100644 tests/templates/inner_include_override_1.html create mode 100644 tests/templates/inner_include_override_2.html create mode 100644 tests/templates/inner_include_override_3.html create mode 100644 tests/templates/inner_include_override_4.html diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..39e566f84f3ff54ef00e8f15607e2f0e08a35414 GIT binary patch literal 6148 zcmeHK%}T>S5Z-O8rh18}Q1G%>p@;h0YpnGs6e@~XsR;>mp(LeAEm8_O`xyQ|h9LS5 zK8iPIcGps=UOb4DnK1LsPIflzx3H68jPXj(UuVo_j9H+FnLLg%w+P}3QAG?FC-h=H#RWcAR-`oI5k|Nk|KM#KOyFjWljT(w@U zz?MvHo!A`KS^;_vih}b}jiVHBR4InJSc(gvN+2%K0dx$e8X*EgKLUydGQ_~2GVlRJ CbYBzz literal 0 HcmV?d00001 diff --git a/jinja2schema/config.py b/jinja2schema/config.py index 6de2c93..7f48cad 100644 --- a/jinja2schema/config.py +++ b/jinja2schema/config.py @@ -31,10 +31,28 @@ class Config(object): unknown structure. If this variable is set, ``xs`` will be a boolean. """ + PACKAGE_NAME = '' + """Name of the package where you want to load templates from. + + This configuration is for if you are using includes in your jinja templates. This tells jinja + where to look to be able to load the included template from. If you do not plan on using ``includes`` + this configuration is not needed. + """ + + TEMPLATE_DIR = 'templates' + """Name of the directory where you want to load templates from. Defaulted to ``templates`` + + This configuration is for if you are using includes in your jinja templates. This tells jinja + which directoy to look to be able to load the included template from. If you do not plan on using ``includes`` + this configuration is not needed. + """ + def __init__(self, TYPE_OF_VARIABLE_INDEXED_WITH_VARIABLE_TYPE='dictionary', TYPE_OF_VARIABLE_INDEXED_WITH_INTEGER_TYPE='list', - BOOLEAN_CONDITIONS=False): + BOOLEAN_CONDITIONS=False, + PACKAGE_NAME='', + TEMPLATE_DIR='templates'): if TYPE_OF_VARIABLE_INDEXED_WITH_VARIABLE_TYPE not in ('dictionary', 'list'): raise ValueError('TYPE_OF_VARIABLE_INDEXED_WITH_VARIABLE_TYPE must be' 'either "dictionary" or "list"') @@ -44,6 +62,8 @@ def __init__(self, self.TYPE_OF_VARIABLE_INDEXED_WITH_INTEGER_TYPE = TYPE_OF_VARIABLE_INDEXED_WITH_INTEGER_TYPE self.TYPE_OF_VARIABLE_INDEXED_WITH_VARIABLE_TYPE = TYPE_OF_VARIABLE_INDEXED_WITH_VARIABLE_TYPE self.BOOLEAN_CONDITIONS = BOOLEAN_CONDITIONS + self.PACKAGE_NAME = PACKAGE_NAME + self.TEMPLATE_DIR = TEMPLATE_DIR -default_config = Config() \ No newline at end of file +default_config = Config() diff --git a/jinja2schema/visitors/stmt.py b/jinja2schema/visitors/stmt.py index fb1a8f1..815bc4e 100644 --- a/jinja2schema/visitors/stmt.py +++ b/jinja2schema/visitors/stmt.py @@ -8,7 +8,7 @@ """ import functools -from jinja2 import nodes +from jinja2 import nodes, Environment, PackageLoader from jinja2schema.config import default_config from ..model import Scalar, Dictionary, List, Unknown, Tuple, Boolean @@ -31,14 +31,14 @@ def visits_stmt(node_cls): def decorator(func): stmt_visitors[node_cls] = func @functools.wraps(func) - def wrapped_func(ast, macroses=None, config=default_config): + def wrapped_func(ast, macroses=None, config=default_config, child_blocks=None): assert isinstance(ast, node_cls) - return func(ast, macroses, config) + return func(ast, macroses, config, child_blocks) return wrapped_func return decorator -def visit_stmt(ast, macroses=None, config=default_config): +def visit_stmt(ast, macroses=None, config=default_config, child_blocks=None): """Returns a structure of ``ast``. :param ast: instance of :class:`jinja2.nodes.Stmt` @@ -55,7 +55,7 @@ def visit_stmt(ast, macroses=None, config=default_config): @visits_stmt(nodes.For) -def visit_for(ast, macroses=None, config=default_config): +def visit_for(ast, macroses=None, config=default_config, child_blocks=None): body_struct = visit_many(ast.body, macroses, config, predicted_struct_cls=Scalar) else_struct = visit_many(ast.else_, macroses, config, predicted_struct_cls=Scalar) @@ -84,7 +84,7 @@ def visit_for(ast, macroses=None, config=default_config): @visits_stmt(nodes.If) -def visit_if(ast, macroses=None, config=default_config): +def visit_if(ast, macroses=None, config=default_config, child_blocks=None): if config.BOOLEAN_CONDITIONS: test_predicted_struct = Boolean.from_ast(ast.test) else: @@ -114,7 +114,7 @@ def visit_if(ast, macroses=None, config=default_config): @visits_stmt(nodes.Assign) -def visit_assign(ast, macroses=None, config=default_config): +def visit_assign(ast, macroses=None, config=default_config, child_blocks=None): struct = Dictionary() if (isinstance(ast.target, nodes.Name) or (isinstance(ast.target, nodes.Tuple) and isinstance(ast.node, nodes.Tuple))): @@ -149,12 +149,12 @@ def visit_assign(ast, macroses=None, config=default_config): @visits_stmt(nodes.Output) -def visit_output(ast, macroses=None, config=default_config): +def visit_output(ast, macroses=None, config=default_config, child_blocks=None): return visit_many(ast.nodes, macroses, config, predicted_struct_cls=Scalar) @visits_stmt(nodes.Macro) -def visit_macro(ast, macroses=None, config=default_config): +def visit_macro(ast, macroses=None, config=default_config, child_blocks=None): # XXX the code needs to be refactored args = [] kwargs = [] @@ -193,3 +193,35 @@ def visit_macro(ast, macroses=None, config=default_config): @visits_stmt(nodes.Block) def visit_block(ast, macroses=None, config=default_config): return visit_many(ast.body, macroses, config) + + +@visits_stmt((nodes.Extends, nodes.Include)) +def visit_inheritance(ast, macroses=None, config=default_config, child_blocks=None): + template = get_inherited_template(config, ast) + if not child_blocks: + return visit_many(template.body, macroses, config) + return visit_many(get_correct_nodes(child_blocks, template.body), None, config) + + +def get_inherited_template(config, ast): + env = Environment(loader=PackageLoader(config.PACKAGE_NAME, config.TEMPLATE_DIR)) + return env.parse(env.loader.get_source(env, ast.template.value)[0]) + + +def separate_template_blocks(template, blocks, template_nodes): + for node in template: + if isinstance(node, nodes.Block): + blocks.append(node) + else: + template_nodes.append(node) + return blocks, template_nodes + + +def get_correct_nodes(child_blocks, template): + parent_blocks, nodes = separate_template_blocks(template, [], []) + child_block_names = [c.name for c in child_blocks] + blocks = child_blocks + parent_blocks + for parent_block in parent_blocks: + if parent_block.name in child_block_names: + blocks.remove(parent_block) + return blocks + nodes diff --git a/jinja2schema/visitors/util.py b/jinja2schema/visitors/util.py index f44ed55..ad1c0a2 100644 --- a/jinja2schema/visitors/util.py +++ b/jinja2schema/visitors/util.py @@ -30,11 +30,14 @@ def visit_many(nodes, macroses, config, predicted_struct_cls=Scalar, return_stru """ rv = Dictionary() for node in nodes: - structure = visit(node, macroses, config, predicted_struct_cls=predicted_struct_cls, return_struct_cls=return_struct_cls) + if isinstance(node, (jinja2.nodes.Include, jinja2.nodes.Extends)): + structure = visit_inheritance(node, macroses, config, [x for x in nodes if isinstance(x, jinja2.nodes.Block)]) + else: + structure = visit(node, macroses, config, predicted_struct_cls, return_struct_cls) rv = merge(rv, structure) return rv # keep these at the end of file to avoid circular imports from .expr import Context, visit_expr -from .stmt import visit_stmt +from .stmt import visit_stmt, visit_inheritance diff --git a/scratch.py b/scratch.py new file mode 100644 index 0000000..99e20bb --- /dev/null +++ b/scratch.py @@ -0,0 +1,9 @@ +python +from random import randint + +y = [1, '2', 3, '4'] +a = [] +z = [lambda x: randint(0,9) if isinstance(x, int) else x for x in y] +# z = list(map(lambda x: x if isinstance(x, (str)) else a.append(x), y)) +print(z) +print(a) diff --git a/tests/.DS_Store b/tests/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..448628f44ac7a910c7fe32ca76659792e9b83eca GIT binary patch literal 8196 zcmeHMTWl0n7(U-pV1|xxS_+h98&-;`H4T&&S`dNlg*&7Ty#un$?u;@zomqEgTeMai z-&E9ijnQa~iWg9y5cI_ujKs)`3O-VP!^JxQ!z~vc9Ex~Sg4sn66DXA&2;87lX;oZDZL{_gg}Hq zgg}Hqgg}JAt$+aC*&-=7xc8+sDkB6U1a39aID%0Ld~^2Z`zw z2LwLJP^LmTA!Xo7W6J0Op)0}=1Ja%BNzt8TDwGpaN_Ph7&JfOwFhW5vJNYGp?hGj@ zqcTDuLSQNaB6e4Un0=V#CdcnyE@QhHvR=*PY|HZ-8!sR>qip7^*@`-+Tzq)NocntY zxuZS4?iajvJ-;t#S%#Sj>)pD4s95iHUHe*H&-7d7Xqs1bxQ?$|j+vk2x_`iO40p8M zEjWg^zekQYU>W|QG+)r2H%G{|TX)QU%d;|;ZTXKXs`#XN_1IWb)0&24b4$y3LvpOO zwXq?&y18|HoPVt-OX}CO?HrURwJnavOSe|s@{s7?W*DZSC`xUS1+}TWWwmzG zU$UasrW!-5!fIF(>twswL6&D@>^b&2JI+q9lk5~b&CarO>;n6PU1XQo-|RBG0w9Jm z%tR%YAb}dxqY2GegBGkuCpKd%wqZB=F@PcL#X(qj2sVaMz!P{96L=ag;bpvv*Kib{ z;RL?I8T^1B@e|JBSNw^KxFlB=E3$b&WH?DIkIWr^j2id>P+xk265Cu4v)aY;}{RP`V^kU5j>9<@FL#8n|KRv;~jjAV>pgaaT2HS1-`^- zoW*(kT*BI&C9M7J`gYX1nFxrxpHj7m|2Nd@0Kv`!`XFKh!`0h}; zO%ZD+DIcUPlIS-fW#B?Z5RQ`!!f}$<{xGC^lDZ0?R46B;Bn_qi{D*+zD`|B9PkLvK L?*D*{w^8vg$u%-# literal 0 HcmV?d00001 diff --git a/tests/func_tests/test_inheritance.py b/tests/func_tests/test_inheritance.py new file mode 100644 index 0000000..ce3b11d --- /dev/null +++ b/tests/func_tests/test_inheritance.py @@ -0,0 +1,130 @@ +# coding: utf-8 +import pytest +from jinja2 import PackageLoader, Template, nodes, Environment +from jinja2schema.config import Config + +from jinja2schema.core import infer +from jinja2schema.exceptions import MergeException, UnexpectedExpression +from jinja2schema.model import List, Dictionary, Scalar, Unknown, String, Boolean, Tuple, Number +from jinja2schema.util import debug_repr + +def test_include_1(): + env = Environment(loader=PackageLoader('tests', 'templates')) + struct = infer(env.loader.get_source(env, 'inner_include_1.html')[0], Config(PACKAGE_NAME='tests')) + expected_struct = Dictionary({ + 'var': Dictionary( + {'x': Scalar(label='x', linenos=[1]), 'y': Scalar(label='y', linenos=[1])}, + label='var', + linenos=[1] + ), + 'more': Scalar(label='more', linenos=[2]), + }) + assert struct == expected_struct + +def test_include_override_1(): + env = Environment(loader=PackageLoader('tests', 'templates')) + struct = infer(env.loader.get_source(env, 'inner_include_override_1.html')[0], Config(PACKAGE_NAME='tests')) + expected_struct = Dictionary({ + 'name': Scalar(label='name', linenos=[3]), + }) + assert struct == expected_struct + +def test_include_override_2(): + env = Environment(loader=PackageLoader('tests', 'templates')) + struct = infer(env.loader.get_source(env, 'inner_include_override_2.html')[0], Config(PACKAGE_NAME='tests')) + expected_struct = Dictionary({ + 'name': Scalar(label='name', linenos=[3]), + 'location': Scalar(label='location', linenos=[6]), + 'default_mood': Scalar(label='default_mood', linenos=[8]), + }) + assert struct == expected_struct + +def test_include_override_3(): + env = Environment(loader=PackageLoader('tests', 'templates')) + struct = infer(env.loader.get_source(env, 'inner_include_override_3.html')[0], Config(PACKAGE_NAME='tests')) + expected_struct = Dictionary({ + 'location': Scalar(label='location', linenos=[6]), + 'mood': Scalar(label='mood', linenos=[9]), + 'name': Scalar(label='name', linenos=[3]), + }) + assert struct == expected_struct + +def test_include_override_4(): + env = Environment(loader=PackageLoader('tests', 'templates')) + struct = infer(env.loader.get_source(env, 'inner_include_override_4.html')[0], Config(PACKAGE_NAME='tests')) + expected_struct = Dictionary({ + 'noblock': Scalar(label='noblock', linenos=[1]), + 'brake': Scalar(label='brake', linenos=[3]), + 'location': Scalar(label='location', linenos=[6]), + 'mood': Scalar(label='mood', linenos=[9]), + 'name': Scalar(label='name', linenos=[3]), + }) + assert struct == expected_struct + +def test_extend_1(): + env = Environment(loader=PackageLoader('tests', 'templates')) + struct = infer(env.loader.get_source(env, 'inner_extend.html')[0], Config(PACKAGE_NAME='tests')) + expected_struct = Dictionary({ + 'var': Dictionary( + {'a': Scalar(label='a', linenos=[1])}, + label='var', + linenos=[1] + ), + 'some': Scalar(label='some', linenos=[2]), + 'extended': Scalar(label='extended', linenos=[2]), + }) + assert struct == expected_struct + +def test_include_extend_1(): + env = Environment(loader=PackageLoader('tests', 'templates')) + struct = infer(env.loader.get_source(env, 'include_extend.html')[0], Config(PACKAGE_NAME='tests')) + expected_struct = Dictionary({ + 'var': Dictionary( + {'x': Scalar(label='x', linenos=[1]), 'y': Scalar(label='y', linenos=[1]), 'a': Scalar(label='a', linenos=[1])}, + label='var', + linenos=[1] + ), + 'also': Scalar(label='also', linenos=[3]), + 'extended': Scalar(label='extended', linenos=[2]), + }) + assert struct == expected_struct + +def test_extend_with_block_override_1(): + env = Environment(loader=PackageLoader('tests', 'templates')) + struct = infer(env.loader.get_source(env, 'inner_extend_override_1.html')[0], Config(PACKAGE_NAME='tests')) + expected_struct = Dictionary({ + 'name': Scalar(label='name', linenos=[3]), + }) + assert struct == expected_struct + +def test_extend_with_block_override_2(): + env = Environment(loader=PackageLoader('tests', 'templates')) + struct = infer(env.loader.get_source(env, 'inner_extend_override_2.html')[0], Config(PACKAGE_NAME='tests')) + expected_struct = Dictionary({ + 'name': Scalar(label='name', linenos=[3]), + 'location': Scalar(label='location', linenos=[6]), + 'default_mood': Scalar(label='default_mood', linenos=[8]), + }) + assert struct == expected_struct + +def test_extend_with_block_override_3(): + env = Environment(loader=PackageLoader('tests', 'templates')) + struct = infer(env.loader.get_source(env, 'inner_extend_override_3.html')[0], Config(PACKAGE_NAME='tests')) + expected_struct = Dictionary({ + 'location': Scalar(label='location', linenos=[6]), + 'mood': Scalar(label='mood', linenos=[9]), + 'name': Scalar(label='name', linenos=[3]), + }) + assert struct == expected_struct + +def test_extend_with_block_override_4(): + env = Environment(loader=PackageLoader('tests', 'templates')) + struct = infer(env.loader.get_source(env, 'inner_extend_override_4.html')[0], Config(PACKAGE_NAME='tests')) + expected_struct = Dictionary({ + 'noblock': Scalar(label='noblock', linenos=[1]), + 'brake': Scalar(label='brake', linenos=[3]), + 'location': Scalar(label='location', linenos=[6]), + 'mood': Scalar(label='mood', linenos=[9]), + 'name': Scalar(label='name', linenos=[3]), + }) + assert struct == expected_struct diff --git a/tests/templates/extend_override_1.html b/tests/templates/extend_override_1.html new file mode 100644 index 0000000..3bc4614 --- /dev/null +++ b/tests/templates/extend_override_1.html @@ -0,0 +1,3 @@ +{% block head %} +

I am the default, my name is {{ default_name }}

+{% endblock %} diff --git a/tests/templates/extend_override_2.html b/tests/templates/extend_override_2.html new file mode 100644 index 0000000..75723aa --- /dev/null +++ b/tests/templates/extend_override_2.html @@ -0,0 +1,9 @@ +{% block head %} +

Kylo Ren is {{ default_name }}

+{% endblock %} +{% block body %} +

He lives at {{ default_location }}

+{% endblock %} +{% block footer %} +

He is {{ default_mood }}

+{% endblock %} diff --git a/tests/templates/extend_override_3.html b/tests/templates/extend_override_3.html new file mode 100644 index 0000000..c190c53 --- /dev/null +++ b/tests/templates/extend_override_3.html @@ -0,0 +1,6 @@ +{% block head %} +

Kylo Ren is {{ default_name }}

+{% endblock %} +{% block body %} +

He lives at {{ default_location }}

+{% endblock %} diff --git a/tests/templates/extend_override_4.html b/tests/templates/extend_override_4.html new file mode 100644 index 0000000..65b79bb --- /dev/null +++ b/tests/templates/extend_override_4.html @@ -0,0 +1,10 @@ +{{ noblock }} +{% block brake %} +

Inners name is {{ brake }}

+{% endblock %} +{% block head %} +

Kylo Ren is {{ default_name }}

+{% endblock %} +{% block body %} +

He lives at {{ default_location }}

+{% endblock %} diff --git a/tests/templates/include_1.html b/tests/templates/include_1.html new file mode 100644 index 0000000..6abc693 --- /dev/null +++ b/tests/templates/include_1.html @@ -0,0 +1 @@ +{{ var.y }} {{ var.x }} diff --git a/tests/templates/include_2.html b/tests/templates/include_2.html new file mode 100644 index 0000000..0fe0d44 --- /dev/null +++ b/tests/templates/include_2.html @@ -0,0 +1,4 @@ +{{ parent }} +{% block head %} + {{ also_parent }} +{% endblock %} diff --git a/tests/templates/include_extend.html b/tests/templates/include_extend.html new file mode 100644 index 0000000..30cf2b9 --- /dev/null +++ b/tests/templates/include_extend.html @@ -0,0 +1,3 @@ +{% extends "extend.html" %} +{% include "include_1.html" %} +{{ also }} diff --git a/tests/templates/include_override_1.html b/tests/templates/include_override_1.html new file mode 100644 index 0000000..3bc4614 --- /dev/null +++ b/tests/templates/include_override_1.html @@ -0,0 +1,3 @@ +{% block head %} +

I am the default, my name is {{ default_name }}

+{% endblock %} diff --git a/tests/templates/include_override_2.html b/tests/templates/include_override_2.html new file mode 100644 index 0000000..75723aa --- /dev/null +++ b/tests/templates/include_override_2.html @@ -0,0 +1,9 @@ +{% block head %} +

Kylo Ren is {{ default_name }}

+{% endblock %} +{% block body %} +

He lives at {{ default_location }}

+{% endblock %} +{% block footer %} +

He is {{ default_mood }}

+{% endblock %} diff --git a/tests/templates/include_override_3.html b/tests/templates/include_override_3.html new file mode 100644 index 0000000..c190c53 --- /dev/null +++ b/tests/templates/include_override_3.html @@ -0,0 +1,6 @@ +{% block head %} +

Kylo Ren is {{ default_name }}

+{% endblock %} +{% block body %} +

He lives at {{ default_location }}

+{% endblock %} diff --git a/tests/templates/include_override_4.html b/tests/templates/include_override_4.html new file mode 100644 index 0000000..65b79bb --- /dev/null +++ b/tests/templates/include_override_4.html @@ -0,0 +1,10 @@ +{{ noblock }} +{% block brake %} +

Inners name is {{ brake }}

+{% endblock %} +{% block head %} +

Kylo Ren is {{ default_name }}

+{% endblock %} +{% block body %} +

He lives at {{ default_location }}

+{% endblock %} diff --git a/tests/templates/inner_extend.html b/tests/templates/inner_extend.html new file mode 100644 index 0000000..b1b9b2e --- /dev/null +++ b/tests/templates/inner_extend.html @@ -0,0 +1,2 @@ +{% extends "extend.html" %} +{{ some }} diff --git a/tests/templates/inner_extend_override_1.html b/tests/templates/inner_extend_override_1.html new file mode 100644 index 0000000..27874bb --- /dev/null +++ b/tests/templates/inner_extend_override_1.html @@ -0,0 +1,4 @@ +{% extends "extend_override_1.html" %} +{% block head %} +

Hello, {{ name }}

+{% endblock %} diff --git a/tests/templates/inner_extend_override_2.html b/tests/templates/inner_extend_override_2.html new file mode 100644 index 0000000..ea8b49c --- /dev/null +++ b/tests/templates/inner_extend_override_2.html @@ -0,0 +1,7 @@ +{% extends "extend_override_2.html" %} +{% block head %} +

Inners name is {{ name }}

+{% endblock %} +{% block body %} +

Inner lives at {{ location }}

+{% endblock %} diff --git a/tests/templates/inner_extend_override_3.html b/tests/templates/inner_extend_override_3.html new file mode 100644 index 0000000..1f9357d --- /dev/null +++ b/tests/templates/inner_extend_override_3.html @@ -0,0 +1,10 @@ +{% extends "extend_override_3.html" %} +{% block head %} +

Inners name is {{ name }}

+{% endblock %} +{% block body %} +

Inner lives at {{ location }}

+{% endblock %} +{% block footer %} +

Inner is {{ mood }}

+{% endblock %} diff --git a/tests/templates/inner_extend_override_4.html b/tests/templates/inner_extend_override_4.html new file mode 100644 index 0000000..af29299 --- /dev/null +++ b/tests/templates/inner_extend_override_4.html @@ -0,0 +1,10 @@ +{% extends "extend_override_4.html" %} +{% block head %} +

Inners name is {{ name }}

+{% endblock %} +{% block body %} +

Inner lives at {{ location }}

+{% endblock %} +{% block footer %} +

Inner is {{ mood }}

+{% endblock %} diff --git a/tests/templates/inner_include_1.html b/tests/templates/inner_include_1.html new file mode 100644 index 0000000..d6d7a5b --- /dev/null +++ b/tests/templates/inner_include_1.html @@ -0,0 +1,2 @@ +{% include "include_1.html" %} +{{ more }} diff --git a/tests/templates/inner_include_2.html b/tests/templates/inner_include_2.html new file mode 100644 index 0000000..6718d38 --- /dev/null +++ b/tests/templates/inner_include_2.html @@ -0,0 +1,4 @@ +{% include "include_2.html" %} +{% block head %} + {{ child }} +{% endblock %} diff --git a/tests/templates/inner_include_override_1.html b/tests/templates/inner_include_override_1.html new file mode 100644 index 0000000..9a60de2 --- /dev/null +++ b/tests/templates/inner_include_override_1.html @@ -0,0 +1,4 @@ +{% include "include_override_1.html" %} +{% block head %} +

Hello, {{ name }}

+{% endblock %} diff --git a/tests/templates/inner_include_override_2.html b/tests/templates/inner_include_override_2.html new file mode 100644 index 0000000..b9e7f28 --- /dev/null +++ b/tests/templates/inner_include_override_2.html @@ -0,0 +1,7 @@ +{% include "include_override_2.html" %} +{% block head %} +

Inners name is {{ name }}

+{% endblock %} +{% block body %} +

Inner lives at {{ location }}

+{% endblock %} diff --git a/tests/templates/inner_include_override_3.html b/tests/templates/inner_include_override_3.html new file mode 100644 index 0000000..b340e08 --- /dev/null +++ b/tests/templates/inner_include_override_3.html @@ -0,0 +1,10 @@ +{% include "include_override_3.html" %} +{% block head %} +

Inners name is {{ name }}

+{% endblock %} +{% block body %} +

Inner lives at {{ location }}

+{% endblock %} +{% block footer %} +

Inner is {{ mood }}

+{% endblock %} diff --git a/tests/templates/inner_include_override_4.html b/tests/templates/inner_include_override_4.html new file mode 100644 index 0000000..42b37a1 --- /dev/null +++ b/tests/templates/inner_include_override_4.html @@ -0,0 +1,10 @@ +{% include "include_override_4.html" %} +{% block head %} +

Inners name is {{ name }}

+{% endblock %} +{% block body %} +

Inner lives at {{ location }}

+{% endblock %} +{% block footer %} +

Inner is {{ mood }}

+{% endblock %} From 27e821ffb9f72ef0bd9dec933b00bb6a093fcffa Mon Sep 17 00:00:00 2001 From: TayHobbs Date: Wed, 30 Dec 2015 14:16:04 -0600 Subject: [PATCH 04/16] Include missing test template --- tests/templates/extend.html | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 tests/templates/extend.html diff --git a/tests/templates/extend.html b/tests/templates/extend.html new file mode 100644 index 0000000..4d46470 --- /dev/null +++ b/tests/templates/extend.html @@ -0,0 +1,2 @@ +{{ var.a }} +{{ extended }} From beb6b955c8afee1a9b1059cfd2ee539ed8c4463a Mon Sep 17 00:00:00 2001 From: TayHobbs Date: Wed, 30 Dec 2015 14:31:46 -0600 Subject: [PATCH 05/16] Remove support for overriding blocks when using includes Jinja2 doesn't support this, so support for it in jinja2schema is incorrect --- jinja2schema/visitors/stmt.py | 10 ++++-- jinja2schema/visitors/util.py | 6 ++-- tests/func_tests/test_inheritance.py | 33 +------------------ tests/templates/include_override_2.html | 9 ----- tests/templates/include_override_3.html | 6 ---- tests/templates/include_override_4.html | 10 ------ tests/templates/inner_include_override_2.html | 7 ---- tests/templates/inner_include_override_3.html | 10 ------ tests/templates/inner_include_override_4.html | 10 ------ 9 files changed, 12 insertions(+), 89 deletions(-) delete mode 100644 tests/templates/include_override_2.html delete mode 100644 tests/templates/include_override_3.html delete mode 100644 tests/templates/include_override_4.html delete mode 100644 tests/templates/inner_include_override_2.html delete mode 100644 tests/templates/inner_include_override_3.html delete mode 100644 tests/templates/inner_include_override_4.html diff --git a/jinja2schema/visitors/stmt.py b/jinja2schema/visitors/stmt.py index 815bc4e..782b8a5 100644 --- a/jinja2schema/visitors/stmt.py +++ b/jinja2schema/visitors/stmt.py @@ -195,8 +195,14 @@ def visit_block(ast, macroses=None, config=default_config): return visit_many(ast.body, macroses, config) -@visits_stmt((nodes.Extends, nodes.Include)) -def visit_inheritance(ast, macroses=None, config=default_config, child_blocks=None): +@visits_stmt(nodes.Include) +def visit_include(ast, macroses=None, config=default_config, child_blocks=None): + template = get_inherited_template(config, ast) + return visit_many(template.body, macroses, config) + + +@visits_stmt(nodes.Extends) +def visit_extends(ast, macroses=None, config=default_config, child_blocks=None): template = get_inherited_template(config, ast) if not child_blocks: return visit_many(template.body, macroses, config) diff --git a/jinja2schema/visitors/util.py b/jinja2schema/visitors/util.py index ad1c0a2..c3f3677 100644 --- a/jinja2schema/visitors/util.py +++ b/jinja2schema/visitors/util.py @@ -30,8 +30,8 @@ def visit_many(nodes, macroses, config, predicted_struct_cls=Scalar, return_stru """ rv = Dictionary() for node in nodes: - if isinstance(node, (jinja2.nodes.Include, jinja2.nodes.Extends)): - structure = visit_inheritance(node, macroses, config, [x for x in nodes if isinstance(x, jinja2.nodes.Block)]) + if isinstance(node, jinja2.nodes.Extends): + structure = visit_extends(node, macroses, config, [x for x in nodes if isinstance(x, jinja2.nodes.Block)]) else: structure = visit(node, macroses, config, predicted_struct_cls, return_struct_cls) rv = merge(rv, structure) @@ -40,4 +40,4 @@ def visit_many(nodes, macroses, config, predicted_struct_cls=Scalar, return_stru # keep these at the end of file to avoid circular imports from .expr import Context, visit_expr -from .stmt import visit_stmt, visit_inheritance +from .stmt import visit_stmt, visit_extends diff --git a/tests/func_tests/test_inheritance.py b/tests/func_tests/test_inheritance.py index ce3b11d..ab386e8 100644 --- a/tests/func_tests/test_inheritance.py +++ b/tests/func_tests/test_inheritance.py @@ -25,38 +25,7 @@ def test_include_override_1(): env = Environment(loader=PackageLoader('tests', 'templates')) struct = infer(env.loader.get_source(env, 'inner_include_override_1.html')[0], Config(PACKAGE_NAME='tests')) expected_struct = Dictionary({ - 'name': Scalar(label='name', linenos=[3]), - }) - assert struct == expected_struct - -def test_include_override_2(): - env = Environment(loader=PackageLoader('tests', 'templates')) - struct = infer(env.loader.get_source(env, 'inner_include_override_2.html')[0], Config(PACKAGE_NAME='tests')) - expected_struct = Dictionary({ - 'name': Scalar(label='name', linenos=[3]), - 'location': Scalar(label='location', linenos=[6]), - 'default_mood': Scalar(label='default_mood', linenos=[8]), - }) - assert struct == expected_struct - -def test_include_override_3(): - env = Environment(loader=PackageLoader('tests', 'templates')) - struct = infer(env.loader.get_source(env, 'inner_include_override_3.html')[0], Config(PACKAGE_NAME='tests')) - expected_struct = Dictionary({ - 'location': Scalar(label='location', linenos=[6]), - 'mood': Scalar(label='mood', linenos=[9]), - 'name': Scalar(label='name', linenos=[3]), - }) - assert struct == expected_struct - -def test_include_override_4(): - env = Environment(loader=PackageLoader('tests', 'templates')) - struct = infer(env.loader.get_source(env, 'inner_include_override_4.html')[0], Config(PACKAGE_NAME='tests')) - expected_struct = Dictionary({ - 'noblock': Scalar(label='noblock', linenos=[1]), - 'brake': Scalar(label='brake', linenos=[3]), - 'location': Scalar(label='location', linenos=[6]), - 'mood': Scalar(label='mood', linenos=[9]), + 'default_name': Scalar(label='default_name', linenos=[2]), 'name': Scalar(label='name', linenos=[3]), }) assert struct == expected_struct diff --git a/tests/templates/include_override_2.html b/tests/templates/include_override_2.html deleted file mode 100644 index 75723aa..0000000 --- a/tests/templates/include_override_2.html +++ /dev/null @@ -1,9 +0,0 @@ -{% block head %} -

Kylo Ren is {{ default_name }}

-{% endblock %} -{% block body %} -

He lives at {{ default_location }}

-{% endblock %} -{% block footer %} -

He is {{ default_mood }}

-{% endblock %} diff --git a/tests/templates/include_override_3.html b/tests/templates/include_override_3.html deleted file mode 100644 index c190c53..0000000 --- a/tests/templates/include_override_3.html +++ /dev/null @@ -1,6 +0,0 @@ -{% block head %} -

Kylo Ren is {{ default_name }}

-{% endblock %} -{% block body %} -

He lives at {{ default_location }}

-{% endblock %} diff --git a/tests/templates/include_override_4.html b/tests/templates/include_override_4.html deleted file mode 100644 index 65b79bb..0000000 --- a/tests/templates/include_override_4.html +++ /dev/null @@ -1,10 +0,0 @@ -{{ noblock }} -{% block brake %} -

Inners name is {{ brake }}

-{% endblock %} -{% block head %} -

Kylo Ren is {{ default_name }}

-{% endblock %} -{% block body %} -

He lives at {{ default_location }}

-{% endblock %} diff --git a/tests/templates/inner_include_override_2.html b/tests/templates/inner_include_override_2.html deleted file mode 100644 index b9e7f28..0000000 --- a/tests/templates/inner_include_override_2.html +++ /dev/null @@ -1,7 +0,0 @@ -{% include "include_override_2.html" %} -{% block head %} -

Inners name is {{ name }}

-{% endblock %} -{% block body %} -

Inner lives at {{ location }}

-{% endblock %} diff --git a/tests/templates/inner_include_override_3.html b/tests/templates/inner_include_override_3.html deleted file mode 100644 index b340e08..0000000 --- a/tests/templates/inner_include_override_3.html +++ /dev/null @@ -1,10 +0,0 @@ -{% include "include_override_3.html" %} -{% block head %} -

Inners name is {{ name }}

-{% endblock %} -{% block body %} -

Inner lives at {{ location }}

-{% endblock %} -{% block footer %} -

Inner is {{ mood }}

-{% endblock %} diff --git a/tests/templates/inner_include_override_4.html b/tests/templates/inner_include_override_4.html deleted file mode 100644 index 42b37a1..0000000 --- a/tests/templates/inner_include_override_4.html +++ /dev/null @@ -1,10 +0,0 @@ -{% include "include_override_4.html" %} -{% block head %} -

Inners name is {{ name }}

-{% endblock %} -{% block body %} -

Inner lives at {{ location }}

-{% endblock %} -{% block footer %} -

Inner is {{ mood }}

-{% endblock %} From 2a01c86c143636fdd18ebc2345042597ece2634c Mon Sep 17 00:00:00 2001 From: Anton Romanovich Date: Thu, 31 Dec 2015 17:34:10 +0500 Subject: [PATCH 06/16] Minor cleanup --- .DS_Store | Bin 6148 -> 0 bytes scratch.py | 9 --- tests/.DS_Store | Bin 8196 -> 0 bytes tests/func_tests/test_inheritance.py | 116 +++++++++++++++------------ 4 files changed, 66 insertions(+), 59 deletions(-) delete mode 100644 .DS_Store delete mode 100644 scratch.py delete mode 100644 tests/.DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 39e566f84f3ff54ef00e8f15607e2f0e08a35414..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}T>S5Z-O8rh18}Q1G%>p@;h0YpnGs6e@~XsR;>mp(LeAEm8_O`xyQ|h9LS5 zK8iPIcGps=UOb4DnK1LsPIflzx3H68jPXj(UuVo_j9H+FnLLg%w+P}3QAG?FC-h=H#RWcAR-`oI5k|Nk|KM#KOyFjWljT(w@U zz?MvHo!A`KS^;_vih}b}jiVHBR4InJSc(gvN+2%K0dx$e8X*EgKLUydGQ_~2GVlRJ CbYBzz diff --git a/scratch.py b/scratch.py deleted file mode 100644 index 99e20bb..0000000 --- a/scratch.py +++ /dev/null @@ -1,9 +0,0 @@ -python -from random import randint - -y = [1, '2', 3, '4'] -a = [] -z = [lambda x: randint(0,9) if isinstance(x, int) else x for x in y] -# z = list(map(lambda x: x if isinstance(x, (str)) else a.append(x), y)) -print(z) -print(a) diff --git a/tests/.DS_Store b/tests/.DS_Store deleted file mode 100644 index 448628f44ac7a910c7fe32ca76659792e9b83eca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 zcmeHMTWl0n7(U-pV1|xxS_+h98&-;`H4T&&S`dNlg*&7Ty#un$?u;@zomqEgTeMai z-&E9ijnQa~iWg9y5cI_ujKs)`3O-VP!^JxQ!z~vc9Ex~Sg4sn66DXA&2;87lX;oZDZL{_gg}Hq zgg}Hqgg}JAt$+aC*&-=7xc8+sDkB6U1a39aID%0Ld~^2Z`zw z2LwLJP^LmTA!Xo7W6J0Op)0}=1Ja%BNzt8TDwGpaN_Ph7&JfOwFhW5vJNYGp?hGj@ zqcTDuLSQNaB6e4Un0=V#CdcnyE@QhHvR=*PY|HZ-8!sR>qip7^*@`-+Tzq)NocntY zxuZS4?iajvJ-;t#S%#Sj>)pD4s95iHUHe*H&-7d7Xqs1bxQ?$|j+vk2x_`iO40p8M zEjWg^zekQYU>W|QG+)r2H%G{|TX)QU%d;|;ZTXKXs`#XN_1IWb)0&24b4$y3LvpOO zwXq?&y18|HoPVt-OX}CO?HrURwJnavOSe|s@{s7?W*DZSC`xUS1+}TWWwmzG zU$UasrW!-5!fIF(>twswL6&D@>^b&2JI+q9lk5~b&CarO>;n6PU1XQo-|RBG0w9Jm z%tR%YAb}dxqY2GegBGkuCpKd%wqZB=F@PcL#X(qj2sVaMz!P{96L=ag;bpvv*Kib{ z;RL?I8T^1B@e|JBSNw^KxFlB=E3$b&WH?DIkIWr^j2id>P+xk265Cu4v)aY;}{RP`V^kU5j>9<@FL#8n|KRv;~jjAV>pgaaT2HS1-`^- zoW*(kT*BI&C9M7J`gYX1nFxrxpHj7m|2Nd@0Kv`!`XFKh!`0h}; zO%ZD+DIcUPlIS-fW#B?Z5RQ`!!f}$<{xGC^lDZ0?R46B;Bn_qi{D*+zD`|B9PkLvK L?*D*{w^8vg$u%-# diff --git a/tests/func_tests/test_inheritance.py b/tests/func_tests/test_inheritance.py index ab386e8..cb5c51c 100644 --- a/tests/func_tests/test_inheritance.py +++ b/tests/func_tests/test_inheritance.py @@ -1,99 +1,115 @@ # coding: utf-8 import pytest -from jinja2 import PackageLoader, Template, nodes, Environment -from jinja2schema.config import Config +from jinja2 import PackageLoader, Environment +from jinja2schema.config import Config from jinja2schema.core import infer -from jinja2schema.exceptions import MergeException, UnexpectedExpression -from jinja2schema.model import List, Dictionary, Scalar, Unknown, String, Boolean, Tuple, Number -from jinja2schema.util import debug_repr +from jinja2schema.model import Dictionary, Scalar + + +@pytest.fixture +def env(): + loader = PackageLoader('tests', 'templates') + return Environment(loader=loader) + + +@pytest.fixture +def config(): + return Config(PACKAGE_NAME='tests') + -def test_include_1(): - env = Environment(loader=PackageLoader('tests', 'templates')) - struct = infer(env.loader.get_source(env, 'inner_include_1.html')[0], Config(PACKAGE_NAME='tests')) +def test_include_1(env, config): + struct = infer(env.loader.get_source(env, 'inner_include_1.html')[0], config) expected_struct = Dictionary({ 'var': Dictionary( - {'x': Scalar(label='x', linenos=[1]), 'y': Scalar(label='y', linenos=[1])}, + { + 'x': Scalar(label='x', linenos=[1]), + 'y': Scalar(label='y', linenos=[1]), + }, label='var', linenos=[1] ), - 'more': Scalar(label='more', linenos=[2]), + 'more': Scalar(label='more', linenos=[2]), }) assert struct == expected_struct -def test_include_override_1(): - env = Environment(loader=PackageLoader('tests', 'templates')) - struct = infer(env.loader.get_source(env, 'inner_include_override_1.html')[0], Config(PACKAGE_NAME='tests')) + +def test_include_override_1(env, config): + struct = infer(env.loader.get_source(env, 'inner_include_override_1.html')[0], config) expected_struct = Dictionary({ - 'default_name': Scalar(label='default_name', linenos=[2]), - 'name': Scalar(label='name', linenos=[3]), + 'default_name': Scalar(label='default_name', linenos=[2]), + 'name': Scalar(label='name', linenos=[3]), }) assert struct == expected_struct -def test_extend_1(): - env = Environment(loader=PackageLoader('tests', 'templates')) - struct = infer(env.loader.get_source(env, 'inner_extend.html')[0], Config(PACKAGE_NAME='tests')) + +def test_extend_1(env, config): + struct = infer(env.loader.get_source(env, 'inner_extend.html')[0], config) expected_struct = Dictionary({ 'var': Dictionary( {'a': Scalar(label='a', linenos=[1])}, label='var', linenos=[1] ), - 'some': Scalar(label='some', linenos=[2]), - 'extended': Scalar(label='extended', linenos=[2]), + 'some': Scalar(label='some', linenos=[2]), + 'extended': Scalar(label='extended', linenos=[2]), }) assert struct == expected_struct -def test_include_extend_1(): - env = Environment(loader=PackageLoader('tests', 'templates')) - struct = infer(env.loader.get_source(env, 'include_extend.html')[0], Config(PACKAGE_NAME='tests')) + +def test_include_extend_1(env, config): + struct = infer(env.loader.get_source(env, 'include_extend.html')[0], config) expected_struct = Dictionary({ 'var': Dictionary( - {'x': Scalar(label='x', linenos=[1]), 'y': Scalar(label='y', linenos=[1]), 'a': Scalar(label='a', linenos=[1])}, + { + 'x': Scalar(label='x', linenos=[1]), + 'y': Scalar(label='y', linenos=[1]), + 'a': Scalar(label='a', linenos=[1]), + }, label='var', linenos=[1] ), - 'also': Scalar(label='also', linenos=[3]), - 'extended': Scalar(label='extended', linenos=[2]), + 'also': Scalar(label='also', linenos=[3]), + 'extended': Scalar(label='extended', linenos=[2]), }) assert struct == expected_struct -def test_extend_with_block_override_1(): - env = Environment(loader=PackageLoader('tests', 'templates')) - struct = infer(env.loader.get_source(env, 'inner_extend_override_1.html')[0], Config(PACKAGE_NAME='tests')) + +def test_extend_with_block_override_1(env, config): + struct = infer(env.loader.get_source(env, 'inner_extend_override_1.html')[0], config) expected_struct = Dictionary({ - 'name': Scalar(label='name', linenos=[3]), + 'name': Scalar(label='name', linenos=[3]), }) assert struct == expected_struct -def test_extend_with_block_override_2(): - env = Environment(loader=PackageLoader('tests', 'templates')) - struct = infer(env.loader.get_source(env, 'inner_extend_override_2.html')[0], Config(PACKAGE_NAME='tests')) + +def test_extend_with_block_override_2(env, config): + struct = infer(env.loader.get_source(env, 'inner_extend_override_2.html')[0], config) expected_struct = Dictionary({ - 'name': Scalar(label='name', linenos=[3]), - 'location': Scalar(label='location', linenos=[6]), - 'default_mood': Scalar(label='default_mood', linenos=[8]), + 'name': Scalar(label='name', linenos=[3]), + 'location': Scalar(label='location', linenos=[6]), + 'default_mood': Scalar(label='default_mood', linenos=[8]), }) assert struct == expected_struct -def test_extend_with_block_override_3(): - env = Environment(loader=PackageLoader('tests', 'templates')) - struct = infer(env.loader.get_source(env, 'inner_extend_override_3.html')[0], Config(PACKAGE_NAME='tests')) + +def test_extend_with_block_override_3(env, config): + struct = infer(env.loader.get_source(env, 'inner_extend_override_3.html')[0], config) expected_struct = Dictionary({ - 'location': Scalar(label='location', linenos=[6]), - 'mood': Scalar(label='mood', linenos=[9]), - 'name': Scalar(label='name', linenos=[3]), + 'location': Scalar(label='location', linenos=[6]), + 'mood': Scalar(label='mood', linenos=[9]), + 'name': Scalar(label='name', linenos=[3]), }) assert struct == expected_struct -def test_extend_with_block_override_4(): - env = Environment(loader=PackageLoader('tests', 'templates')) - struct = infer(env.loader.get_source(env, 'inner_extend_override_4.html')[0], Config(PACKAGE_NAME='tests')) + +def test_extend_with_block_override_4(env, config): + struct = infer(env.loader.get_source(env, 'inner_extend_override_4.html')[0], config) expected_struct = Dictionary({ - 'noblock': Scalar(label='noblock', linenos=[1]), - 'brake': Scalar(label='brake', linenos=[3]), - 'location': Scalar(label='location', linenos=[6]), - 'mood': Scalar(label='mood', linenos=[9]), - 'name': Scalar(label='name', linenos=[3]), + 'noblock': Scalar(label='noblock', linenos=[1]), + 'brake': Scalar(label='brake', linenos=[3]), + 'location': Scalar(label='location', linenos=[6]), + 'mood': Scalar(label='mood', linenos=[9]), + 'name': Scalar(label='name', linenos=[3]), }) assert struct == expected_struct From 126ad427833421a9cfdcf01100758efca2a01cb5 Mon Sep 17 00:00:00 2001 From: Anton Romanovich Date: Thu, 31 Dec 2015 17:37:53 +0500 Subject: [PATCH 07/16] Bump version to 0.1.2 --- jinja2schema/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jinja2schema/__init__.py b/jinja2schema/__init__.py index 2ced7c6..ab29e77 100644 --- a/jinja2schema/__init__.py +++ b/jinja2schema/__init__.py @@ -17,7 +17,7 @@ __author__ = 'Anton Romanovich' __license__ = 'BSD' __copyright__ = 'Copyright 2014 Anton Romanovich' -__version__ = '0.1.1' +__version__ = '0.1.2' __version_info__ = tuple(int(i) for i in __version__.split('.')) From 5b0ca0e941f33b304af5f3c1a54527613ad52071 Mon Sep 17 00:00:00 2001 From: Adam Chainz Date: Mon, 13 Jun 2016 21:12:32 +0100 Subject: [PATCH 08/16] Convert readthedocs links for their .org -> .io migration for hosted projects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As per [their blog post of the 27th April](https://blog.readthedocs.com/securing-subdomains/) ‘Securing subdomains’: > Starting today, Read the Docs will start hosting projects from subdomains on the domain readthedocs.io, instead of on readthedocs.org. This change addresses some security concerns around site cookies while hosting user generated data on the same domain as our dashboard. Test Plan: Manually visited all the links I’ve modified. --- README.rst | 2 +- jinja2schema/__init__.py | 2 +- setup.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 4c81061..e174f7a 100644 --- a/README.rst +++ b/README.rst @@ -73,7 +73,7 @@ License .. _Jinja2: http://jinja.pocoo.org/docs/ .. _Demo: http://jinja2schema.aromanovich.ru/ .. _demo page: http://jinja2schema.aromanovich.ru/ -.. _Documentation: http://jinja2schema.rtfd.org/ +.. _Documentation: https://jinja2schema.readthedocs.io/ .. _GitHub: https://github.com/aromanovich/jinja2schema .. _PyPI: https://pypi.python.org/pypi/jinja2schema .. _BSD license: https://github.com/aromanovich/jinja2schema/blob/master/LICENSE diff --git a/jinja2schema/__init__.py b/jinja2schema/__init__.py index ab29e77..84d015a 100644 --- a/jinja2schema/__init__.py +++ b/jinja2schema/__init__.py @@ -6,7 +6,7 @@ Type inference for Jinja2 templates. -See http://jinja2schema.rtfd.org/ for documentation. +See https://jinja2schema.readthedocs.io/ for documentation. :copyright: (c) 2014 Anton Romanovich :license: BSD diff --git a/setup.py b/setup.py index 9973460..c6a3c08 100644 --- a/setup.py +++ b/setup.py @@ -33,7 +33,7 @@ license=open('LICENSE').read(), author='Anton Romanovich', author_email='anthony.romanovich@gmail.com', - url='https://jinja2schema.readthedocs.org', + url='https://jinja2schema.readthedocs.io', packages=find_packages(exclude=['tests']), install_requires=['Jinja2>=2.2'], classifiers=[ From c78186d34991580cfb5b58fcd622340daa308a03 Mon Sep 17 00:00:00 2001 From: ubaumann Date: Mon, 20 Feb 2017 12:12:13 +0100 Subject: [PATCH 09/16] Fix build error. Jinja2 2.9 change: Restricted test arguments --- tests/unit_tests/test_expr_visitors.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit_tests/test_expr_visitors.py b/tests/unit_tests/test_expr_visitors.py index 22a8ff3..312cd26 100644 --- a/tests/unit_tests/test_expr_visitors.py +++ b/tests/unit_tests/test_expr_visitors.py @@ -186,7 +186,7 @@ def test_slice(): def test_test_1(): - template = '''{{ x is divisibleby data.field }}''' + template = '''{{ x is divisibleby (data.field) }}''' ast = parse(template).find(nodes.Test) rtype, struct = visit_test(ast, get_scalar_context(ast)) From d3a7414112f0d52f0f518b098d0d5c2ac66eff7b Mon Sep 17 00:00:00 2001 From: ubaumann Date: Mon, 20 Feb 2017 12:20:16 +0100 Subject: [PATCH 10/16] Add value to model --- jinja2schema/core.py | 2 ++ jinja2schema/mergers.py | 2 ++ jinja2schema/model.py | 11 +++++++++-- jinja2schema/visitors/expr.py | 1 + tests/func_tests/test_basics.py | 2 +- tests/unit_tests/test_call_visitor.py | 4 ++-- tests/unit_tests/test_expr_visitors.py | 2 +- tests/unit_tests/test_filter_visitor.py | 4 ++-- tests/unit_tests/test_stmt_visitors.py | 4 ++-- 9 files changed, 22 insertions(+), 10 deletions(-) diff --git a/jinja2schema/core.py b/jinja2schema/core.py index 844c0f2..5863c79 100644 --- a/jinja2schema/core.py +++ b/jinja2schema/core.py @@ -76,6 +76,8 @@ def encode_common_attrs(self, var): rv = {} if var.label: rv['title'] = var.label + if var.value: + rv['value'] = var.value return rv def encode(self, var): diff --git a/jinja2schema/mergers.py b/jinja2schema/mergers.py index 6f6ee07..0589db5 100644 --- a/jinja2schema/mergers.py +++ b/jinja2schema/mergers.py @@ -71,6 +71,8 @@ def merge(fst, snd, custom_merger=None): result.used_with_default = fst.used_with_default and snd.used_with_default result.checked_as_defined = fst.checked_as_defined and snd.checked_as_defined result.checked_as_undefined = fst.checked_as_undefined and snd.checked_as_undefined + if fst.value == snd.value: + result.value = fst.value if callable(custom_merger): result = custom_merger(fst, snd, result) return result diff --git a/jinja2schema/model.py b/jinja2schema/model.py index 4da979c..d2c138c 100644 --- a/jinja2schema/model.py +++ b/jinja2schema/model.py @@ -47,10 +47,14 @@ class Variable(object): Is true if the variable occurs within ``{% if %}`` block which condition checks if the variable is defined. + + .. attribute:: value + + Value of the variable in template. Set by default filter or assignment. """ def __init__(self, label=None, linenos=None, constant=False, may_be_defined=False, used_with_default=False, - checked_as_undefined=False, checked_as_defined=False): + checked_as_undefined=False, checked_as_defined=False, value=None): self.label = label self.linenos = linenos if linenos is not None else [] self.constant = constant @@ -58,6 +62,7 @@ def __init__(self, label=None, linenos=None, constant=False, self.used_with_default = used_with_default self.checked_as_undefined = checked_as_undefined self.checked_as_defined = checked_as_defined + self.value = value def clone(self): cls = type(self) @@ -68,6 +73,7 @@ def _get_kwargs_from_ast(cls, ast): return { 'linenos': [ast.lineno], 'label': ast.name if isinstance(ast, nodes.Name) else None, + 'value': ast.value if hasattr(ast, 'value') else None, } @classmethod @@ -97,7 +103,8 @@ def __eq__(self, other): self.used_with_default == other.used_with_default and self.checked_as_undefined == other.checked_as_undefined and self.checked_as_defined == other.checked_as_defined and - self.required == other.required + self.required == other.required and + self.value == other.value ) def __ne__(self, other): diff --git a/jinja2schema/visitors/expr.py b/jinja2schema/visitors/expr.py index 17122f2..724d9ca 100644 --- a/jinja2schema/visitors/expr.py +++ b/jinja2schema/visitors/expr.py @@ -450,6 +450,7 @@ def visit_filter(ast, ctx, macroses=None, config=default_config): default_value_rtype, ) node_struct.used_with_default = True + node_struct.value = default_value_rtype.value elif ast.name == 'dictsort': ctx.meet(List(Tuple([Scalar(), Unknown()])), ast) node_struct = Dictionary.from_ast(ast.node) diff --git a/tests/func_tests/test_basics.py b/tests/func_tests/test_basics.py index a104b3d..06aee99 100644 --- a/tests/func_tests/test_basics.py +++ b/tests/func_tests/test_basics.py @@ -232,7 +232,7 @@ def test_basics_11(): 'a': Dictionary({ 'attr1': List(String(), label='attr1', linenos=[3]), 'attr2': List(Scalar(linenos=[4]), label='attr2', linenos=[4], used_with_default=True), - 'attr3': String(label='attr3', linenos=[5], used_with_default=True) + 'attr3': String(label='attr3', linenos=[5], used_with_default=True, value='gsom') }, label='a', linenos=[2, 3, 4, 5]), 'xs': List( Scalar(label='x', linenos=[7]), # TODO it should be Dictionary({'is_active': Unknown()}) diff --git a/tests/unit_tests/test_call_visitor.py b/tests/unit_tests/test_call_visitor.py index 0322018..6803f7f 100644 --- a/tests/unit_tests/test_call_visitor.py +++ b/tests/unit_tests/test_call_visitor.py @@ -50,8 +50,8 @@ def test_dict_call(): expected_rtype = Dictionary({ 'x': Dictionary({ - 'a': Number(linenos=[3], constant=True), - 'b': Number(linenos=[3], constant=True) + 'a': Number(linenos=[3], constant=True, value=1), + 'b': Number(linenos=[3], constant=True, value=2) }, linenos=[2], constant=True), 'y': Unknown(label='a', linenos=[3]), }, linenos=[1], constant=True) diff --git a/tests/unit_tests/test_expr_visitors.py b/tests/unit_tests/test_expr_visitors.py index 312cd26..204fa68 100644 --- a/tests/unit_tests/test_expr_visitors.py +++ b/tests/unit_tests/test_expr_visitors.py @@ -238,4 +238,4 @@ def test_const(): template = '''{{ false }}''' const_ast = parse(template).find(nodes.Const) rtype, struct = visit_const(const_ast, get_scalar_context(const_ast)) - assert rtype == Boolean(constant=True, linenos=[1]) + assert rtype == Boolean(constant=True, linenos=[1], value=False) diff --git a/tests/unit_tests/test_filter_visitor.py b/tests/unit_tests/test_filter_visitor.py index cd88871..1758ef4 100644 --- a/tests/unit_tests/test_filter_visitor.py +++ b/tests/unit_tests/test_filter_visitor.py @@ -56,7 +56,7 @@ def test_default_filter(): rtype, struct = visit_filter(ast, get_scalar_context(ast)) expected_struct = Dictionary({ - 'x': String(label='x', linenos=[1], used_with_default=True), + 'x': String(label='x', linenos=[1], used_with_default=True, value='g'), }) assert struct == expected_struct @@ -135,7 +135,7 @@ def test_join_filter(): assert rtype == String(label='xs', linenos=[1]) assert struct == Dictionary({ 'xs': List(String(), label='xs', linenos=[1]), - 'separator': String(label='separator', linenos=[1], used_with_default=True), + 'separator': String(label='separator', linenos=[1], used_with_default=True, value='|'), }) diff --git a/tests/unit_tests/test_stmt_visitors.py b/tests/unit_tests/test_stmt_visitors.py index b352b5f..92e57db 100644 --- a/tests/unit_tests/test_stmt_visitors.py +++ b/tests/unit_tests/test_stmt_visitors.py @@ -101,9 +101,9 @@ def test_assign_4(): struct = visit_assign(ast) expected_struct = Dictionary({ - 'a': Number(label='a', linenos=[1], constant=True), + 'a': Number(label='a', linenos=[1], constant=True, value=1), 'b': Dictionary(data={ - 'gsom': String(linenos=[1], constant=True), + 'gsom': String(linenos=[1], constant=True, value='gsom'), }, label='b', linenos=[1], constant=True), 'z': Scalar(label='z', linenos=[1]), }) From c76736bf308815ed11b79fb21ae016cd302f717e Mon Sep 17 00:00:00 2001 From: ubaumann Date: Fri, 24 Feb 2017 14:28:03 +0100 Subject: [PATCH 11/16] Use default keyword in schema to work smoothly with angular-schema-form --- jinja2schema/core.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jinja2schema/core.py b/jinja2schema/core.py index 5863c79..bcae8e4 100644 --- a/jinja2schema/core.py +++ b/jinja2schema/core.py @@ -76,8 +76,8 @@ def encode_common_attrs(self, var): rv = {} if var.label: rv['title'] = var.label - if var.value: - rv['value'] = var.value + if var.value and var.used_with_default: + rv['default'] = var.value return rv def encode(self, var): From 1d63d0bf9811f500ce00c019906dad95cffa2657 Mon Sep 17 00:00:00 2001 From: Anton Romanovich Date: Thu, 23 Mar 2017 23:57:57 +0500 Subject: [PATCH 12/16] Bump version to 0.1.3 --- jinja2schema/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/jinja2schema/__init__.py b/jinja2schema/__init__.py index 84d015a..80904cb 100644 --- a/jinja2schema/__init__.py +++ b/jinja2schema/__init__.py @@ -8,7 +8,7 @@ See https://jinja2schema.readthedocs.io/ for documentation. -:copyright: (c) 2014 Anton Romanovich +:copyright: (c) 2017 Anton Romanovich :license: BSD """ @@ -16,8 +16,8 @@ __title__ = 'jinja2schema' __author__ = 'Anton Romanovich' __license__ = 'BSD' -__copyright__ = 'Copyright 2014 Anton Romanovich' -__version__ = '0.1.2' +__copyright__ = 'Copyright 2017 Anton Romanovich' +__version__ = '0.1.3' __version_info__ = tuple(int(i) for i in __version__.split('.')) From 02ac9335d911d5f18c7cb2e7fdd5bbb68c6d4782 Mon Sep 17 00:00:00 2001 From: ubaumann Date: Tue, 4 Apr 2017 20:29:33 +0200 Subject: [PATCH 13/16] Add order number to make schema sortable --- jinja2schema/config.py | 20 +++- jinja2schema/core.py | 2 + jinja2schema/mergers.py | 1 + jinja2schema/model.py | 4 +- jinja2schema/order_number.py | 42 ++++++++ jinja2schema/visitors/expr.py | 136 ++++++++++++++++---------- jinja2schema/visitors/stmt.py | 25 ++--- jinja2schema/visitors/util.py | 3 +- tests/func_tests/test_basics.py | 83 ++++++++++++++++ tests/unit_tests/test_order_number.py | 29 ++++++ 10 files changed, 278 insertions(+), 67 deletions(-) create mode 100644 jinja2schema/order_number.py create mode 100644 tests/unit_tests/test_order_number.py diff --git a/jinja2schema/config.py b/jinja2schema/config.py index 7f48cad..a72b53d 100644 --- a/jinja2schema/config.py +++ b/jinja2schema/config.py @@ -3,6 +3,7 @@ jinja2schema.config ~~~~~~~~~~~~~~~~~~~ """ +from .order_number import OrderNumber class Config(object): @@ -47,12 +48,26 @@ class Config(object): this configuration is not needed. """ + ORDER_NUMBER = False + """Add a order number to each node + + Add a order number to make schema sortable. + """ + + ORDER_NUMBER_SUB_COUNTER = True + """Independent subsection order numbers + + Use a separate counter in subsections as order number creator. + """ + def __init__(self, TYPE_OF_VARIABLE_INDEXED_WITH_VARIABLE_TYPE='dictionary', TYPE_OF_VARIABLE_INDEXED_WITH_INTEGER_TYPE='list', BOOLEAN_CONDITIONS=False, PACKAGE_NAME='', - TEMPLATE_DIR='templates'): + TEMPLATE_DIR='templates', + ORDER_NUMBER=False, + ORDER_NUMBER_SUB_COUNTER=True): if TYPE_OF_VARIABLE_INDEXED_WITH_VARIABLE_TYPE not in ('dictionary', 'list'): raise ValueError('TYPE_OF_VARIABLE_INDEXED_WITH_VARIABLE_TYPE must be' 'either "dictionary" or "list"') @@ -64,6 +79,9 @@ def __init__(self, self.BOOLEAN_CONDITIONS = BOOLEAN_CONDITIONS self.PACKAGE_NAME = PACKAGE_NAME self.TEMPLATE_DIR = TEMPLATE_DIR + self.ORDER_NUMBER = ORDER_NUMBER + self.ORDER_OBJECT = OrderNumber(number=1, enabled=self.ORDER_NUMBER, + sub_counter_enabled=ORDER_NUMBER_SUB_COUNTER) default_config = Config() diff --git a/jinja2schema/core.py b/jinja2schema/core.py index bcae8e4..240b431 100644 --- a/jinja2schema/core.py +++ b/jinja2schema/core.py @@ -78,6 +78,8 @@ def encode_common_attrs(self, var): rv['title'] = var.label if var.value and var.used_with_default: rv['default'] = var.value + if var.order_nr: + rv['order_number'] = var.order_nr return rv def encode(self, var): diff --git a/jinja2schema/mergers.py b/jinja2schema/mergers.py index 0589db5..9ab0f6e 100644 --- a/jinja2schema/mergers.py +++ b/jinja2schema/mergers.py @@ -73,6 +73,7 @@ def merge(fst, snd, custom_merger=None): result.checked_as_undefined = fst.checked_as_undefined and snd.checked_as_undefined if fst.value == snd.value: result.value = fst.value + result.order_nr = fst.order_nr if callable(custom_merger): result = custom_merger(fst, snd, result) return result diff --git a/jinja2schema/model.py b/jinja2schema/model.py index d2c138c..bb7076a 100644 --- a/jinja2schema/model.py +++ b/jinja2schema/model.py @@ -54,7 +54,8 @@ class Variable(object): """ def __init__(self, label=None, linenos=None, constant=False, may_be_defined=False, used_with_default=False, - checked_as_undefined=False, checked_as_defined=False, value=None): + checked_as_undefined=False, checked_as_defined=False, + value=None, order_nr=None): self.label = label self.linenos = linenos if linenos is not None else [] self.constant = constant @@ -63,6 +64,7 @@ def __init__(self, label=None, linenos=None, constant=False, self.checked_as_undefined = checked_as_undefined self.checked_as_defined = checked_as_defined self.value = value + self.order_nr = order_nr def clone(self): cls = type(self) diff --git a/jinja2schema/order_number.py b/jinja2schema/order_number.py new file mode 100644 index 0000000..1a07fdf --- /dev/null +++ b/jinja2schema/order_number.py @@ -0,0 +1,42 @@ +# coding: utf-8 +""" +jinja2schema.order_number +~~~~~~~~~~~~~~~~~~ +""" +from contextlib import contextmanager + + +class OrderNumber(object): + """A base Order Number class. + + .. attribute:: number + + Initial counter value. + + .. attribute:: enabled + + Counter enabled or return None. + + """ + + def __init__(self, number=0, enabled=False, sub_counter_enabled=True): + self.start = number + self.number = self.start + self.order_enabled = enabled + self.sub_counter_enabled = sub_counter_enabled + + def get_next(self): + if self.order_enabled: + self.number += 1 + return self.number + return None + + @contextmanager + def sub_counter(self): + if self.sub_counter_enabled: + counter = self.number + self.number = self.start + yield + self.number = counter + return + yield diff --git a/jinja2schema/visitors/expr.py b/jinja2schema/visitors/expr.py index 724d9ca..5685c37 100644 --- a/jinja2schema/visitors/expr.py +++ b/jinja2schema/visitors/expr.py @@ -163,14 +163,16 @@ def _visit_dict(ast, ctx, macroses, items, config=default_config): :param items: a list of (key, value); key may be either AST node or string """ ctx.meet(Dictionary(), ast) - rtype = Dictionary.from_ast(ast, constant=True) + rtype = Dictionary.from_ast(ast, constant=True, order_nr=config.ORDER_OBJECT.get_next()) struct = Dictionary() for key, value in items: value_rtype, value_struct = visit_expr(value, Context( - predicted_struct=Unknown.from_ast(value)), macroses, config=config) + predicted_struct=Unknown.from_ast(value, order_nr=config.ORDER_OBJECT.get_next())), macroses, config=config) struct = merge(struct, value_struct) if isinstance(key, nodes.Node): - key_rtype, key_struct = visit_expr(key, Context(predicted_struct=Scalar.from_ast(key)), macroses, config=config) + key_rtype, key_struct = visit_expr(key, Context( + predicted_struct=Scalar.from_ast(key, order_nr=config.ORDER_OBJECT.get_next())), macroses, + config=config) struct = merge(struct, key_struct) if isinstance(key, nodes.Const): rtype[key.value] = value_rtype @@ -195,13 +197,14 @@ def visit_unary_expr(ast, ctx, macroses=None, config=default_config): @visits_expr(nodes.Compare) def visit_compare(ast, ctx, macroses=None, config=default_config): ctx.meet(Boolean(), ast) - rtype, struct = visit_expr(ast.expr, Context(predicted_struct=Unknown.from_ast(ast.expr)), - macroses, config=config) + rtype, struct = visit_expr(ast.expr, Context( + predicted_struct=Unknown.from_ast(ast.expr, order_nr=config.ORDER_OBJECT.get_next())), macroses, config=config) for op in ast.ops: - op_rtype, op_struct = visit_expr(op.expr, Context(predicted_struct=Unknown.from_ast(ast.expr)), - macroses, config=config) + op_rtype, op_struct = visit_expr(op.expr, Context( + predicted_struct=Unknown.from_ast(ast.expr, order_nr=config.ORDER_OBJECT.get_next())), macroses, + config=config) struct = merge(struct, op_struct) - return Boolean.from_ast(ast), struct + return Boolean.from_ast(ast, order_nr=config.ORDER_OBJECT.get_next()), struct @visits_expr(nodes.Slice) @@ -215,7 +218,9 @@ def visit_slice(ast, ctx, macroses=None, config=default_config): @visits_expr(nodes.Name) def visit_name(ast, ctx, macroses=None, config=default_config): - kwargs = {} + kwargs = { + 'order_nr': config.ORDER_OBJECT.get_next() + } return ctx.return_struct_cls.from_ast(ast, **kwargs), Dictionary({ ast.name: ctx.get_predicted_struct(label=ast.name) }) @@ -227,7 +232,7 @@ def visit_getattr(ast, ctx, macroses=None, config=default_config): ctx=ctx, predicted_struct=Dictionary.from_ast(ast, { ast.attr: ctx.get_predicted_struct(label=ast.attr), - })) + }, order_nr=config.ORDER_OBJECT.get_next())) return visit_expr(ast.node, context, macroses, config=config) @@ -237,31 +242,35 @@ def visit_getitem(ast, ctx, macroses=None, config=default_config): if isinstance(arg, nodes.Const): if isinstance(arg.value, int): if config.TYPE_OF_VARIABLE_INDEXED_WITH_INTEGER_TYPE == 'list': - predicted_struct = List.from_ast(ast, ctx.get_predicted_struct()) + predicted_struct = List.from_ast(ast, ctx.get_predicted_struct(), + order_nr=config.ORDER_OBJECT.get_next()) elif config.TYPE_OF_VARIABLE_INDEXED_WITH_INTEGER_TYPE == 'dictionary': predicted_struct = Dictionary.from_ast(ast, { arg.value: ctx.get_predicted_struct(), - }) + }, order_nr=config.ORDER_OBJECT.get_next()) elif config.TYPE_OF_VARIABLE_INDEXED_WITH_INTEGER_TYPE == 'tuple': items = [Unknown() for i in range(arg.value + 1)] items[arg.value] = ctx.get_predicted_struct() - predicted_struct = Tuple.from_ast(ast, tuple(items), may_be_extended=True) + predicted_struct = Tuple.from_ast(ast, tuple(items), may_be_extended=True, + order_nr=config.ORDER_OBJECT.get_next()) elif isinstance(arg.value, _compat.string_types): predicted_struct = Dictionary.from_ast(ast, { arg.value: ctx.get_predicted_struct(label=arg.value), - }) + }, order_nr=config.ORDER_OBJECT.get_next()) else: raise InvalidExpression(arg, '{0} is not supported as an index for a list or' ' a key for a dictionary'.format(arg.value)) elif isinstance(arg, nodes.Slice): - predicted_struct = List.from_ast(ast, ctx.get_predicted_struct()) + predicted_struct = List.from_ast(ast, ctx.get_predicted_struct(), order_nr=config.ORDER_OBJECT.get_next()) else: if config.TYPE_OF_VARIABLE_INDEXED_WITH_VARIABLE_TYPE == 'list': - predicted_struct = List.from_ast(ast, ctx.get_predicted_struct()) + predicted_struct = List.from_ast(ast, ctx.get_predicted_struct(), order_nr=config.ORDER_OBJECT.get_next()) elif config.TYPE_OF_VARIABLE_INDEXED_WITH_VARIABLE_TYPE == 'dictionary': - predicted_struct = Dictionary.from_ast(ast) + predicted_struct = Dictionary.from_ast(ast, order_nr=config.ORDER_OBJECT.get_next()) - _, arg_struct = visit_expr(arg, Context(predicted_struct=Scalar.from_ast(arg)), macroses, config=config) + _, arg_struct = visit_expr(arg, + Context(predicted_struct=Scalar.from_ast(arg, order_nr=config.ORDER_OBJECT.get_next())), + macroses, config=config) rtype, struct = visit_expr(ast.node, Context( ctx=ctx, predicted_struct=predicted_struct), macroses, config=config) @@ -273,10 +282,10 @@ def visit_test(ast, ctx, macroses=None, config=default_config): ctx.meet(Boolean(), ast) if ast.name in ('divisibleby', 'escaped', 'even', 'lower', 'odd', 'upper'): # TODO - predicted_struct = Scalar.from_ast(ast.node) + 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'): - predicted_struct = Unknown.from_ast(ast.node) + predicted_struct = Unknown.from_ast(ast.node, order_nr=config.ORDER_OBJECT.get_next()) if ast.name == 'defined': predicted_struct.checked_as_defined = True elif ast.name == 'undefined': @@ -289,7 +298,9 @@ def visit_test(ast, ctx, macroses=None, config=default_config): if not ast.args: raise InvalidExpression(ast, 'divisibleby must have an argument') _, arg_struct = visit_expr(ast.args[0], - Context(predicted_struct=Number.from_ast(ast.args[0])), macroses, config=config) + Context(predicted_struct=Number.from_ast(ast.args[0], + order_nr=config.ORDER_OBJECT.get_next())), + macroses, config=config) struct = merge(arg_struct, struct) return rtype, struct @@ -297,16 +308,18 @@ def visit_test(ast, ctx, macroses=None, config=default_config): @visits_expr(nodes.Concat) def visit_concat(ast, ctx, macroses=None, config=default_config): ctx.meet(Scalar(), ast) - return String.from_ast(ast), visit_many(ast.nodes, macroses, config, predicted_struct_cls=String) + return String.from_ast(ast, order_nr=config.ORDER_OBJECT.get_next()), visit_many(ast.nodes, macroses, config, + predicted_struct_cls=String) @visits_expr(nodes.CondExpr) def visit_cond_expr(ast, ctx, macroses=None, config=default_config): if config.BOOLEAN_CONDITIONS: - test_predicted_struct = Boolean.from_ast(ast.test) + test_predicted_struct = Boolean.from_ast(ast.test, order_nr=config.ORDER_OBJECT.get_next()) else: - test_predicted_struct = Unknown.from_ast(ast.test) - test_rtype, test_struct = visit_expr(ast.test, Context(predicted_struct=test_predicted_struct), macroses, config=config) + test_predicted_struct = Unknown.from_ast(ast.test, order_nr=config.ORDER_OBJECT.get_next()) + test_rtype, test_struct = visit_expr(ast.test, Context(predicted_struct=test_predicted_struct), macroses, + config=config) if_rtype, if_struct = visit_expr(ast.expr1, ctx, macroses, config=config) else_rtype, else_struct = visit_expr(ast.expr2, ctx, macroses, config=config) struct = merge_many(if_struct, test_struct, else_struct) @@ -358,7 +371,8 @@ def visit_call(ast, ctx, macroses=None, config=default_config): struct = Dictionary() for arg in ast.args: arg_rtype, arg_struct = visit_expr(arg, Context( - predicted_struct=Number.from_ast(arg)), macroses, config=config) + predicted_struct=Number.from_ast(arg, order_nr=config.ORDER_OBJECT.get_next())), macroses, + config=config) struct = merge(struct, arg_struct) return List(Number()), struct elif ast.node.name == 'lipsum': @@ -366,10 +380,14 @@ def visit_call(ast, ctx, macroses=None, config=default_config): struct = Dictionary() # probable TODO: set possible types for args and kwargs for arg in ast.args: - arg_rtype, arg_struct = visit_expr(arg, Context(predicted_struct=Scalar.from_ast(arg)), macroses, config=config) + arg_rtype, arg_struct = visit_expr(arg, Context( + predicted_struct=Scalar.from_ast(arg, order_nr=config.ORDER_OBJECT.get_next())), macroses, + config=config) struct = merge(struct, arg_struct) for kwarg in ast.kwargs: - arg_rtype, arg_struct = visit_expr(kwarg.value, Context(predicted_struct=Scalar.from_ast(kwarg)), macroses, config=config) + arg_rtype, arg_struct = visit_expr(kwarg.value, Context( + predicted_struct=Scalar.from_ast(kwarg, order_nr=config.ORDER_OBJECT.get_next())), macroses, + config=config) struct = merge(struct, arg_struct) return String(), struct elif ast.node.name == 'dict': @@ -383,20 +401,28 @@ def visit_call(ast, ctx, macroses=None, config=default_config): if ast.node.attr in ('keys', 'iterkeys', 'values', 'itervalues'): ctx.meet(List(Unknown()), ast) rtype, struct = visit_expr( - ast.node.node, Context(predicted_struct=Dictionary.from_ast(ast.node.node)), macroses, config=config) + ast.node.node, Context( + predicted_struct=Dictionary.from_ast(ast.node.node, order_nr=config.ORDER_OBJECT.get_next())), + macroses, config=config) return List(Unknown()), struct if ast.node.attr in ('startswith', 'endswith'): ctx.meet(Boolean(), ast) rtype, struct = visit_expr( - ast.node.node, Context(predicted_struct=String.from_ast(ast.node.node)), macroses, config=config) + ast.node.node, + Context(predicted_struct=String.from_ast(ast.node.node, order_nr=config.ORDER_OBJECT.get_next())), + macroses, config=config) return Boolean(), struct if ast.node.attr == 'split': ctx.meet(List(String()), ast) rtype, struct = visit_expr( - ast.node.node, Context(predicted_struct=String.from_ast(ast.node.node)), macroses, config=config) + ast.node.node, + Context(predicted_struct=String.from_ast(ast.node.node, order_nr=config.ORDER_OBJECT.get_next())), + macroses, config=config) if ast.args: arg = ast.args[0] - _, arg_struct = visit_expr(arg, Context(predicted_struct=String.from_ast(arg)), macroses, config=config) + _, arg_struct = visit_expr(arg, Context( + predicted_struct=String.from_ast(arg, order_nr=config.ORDER_OBJECT.get_next())), macroses, + config=config) struct = merge(struct, arg_struct) return List(String()), struct raise InvalidExpression(ast, '"{0}" call is not supported'.format(ast.node.attr)) @@ -411,27 +437,27 @@ def visit_filter(ast, ctx, macroses=None, config=default_config): 'urlencode', 'urlize', 'wordcount', 'wordwrap', 'e'): ctx.meet(Scalar(), ast) if ast.name in ('abs', 'round'): - node_struct = Number.from_ast(ast.node) + 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) + 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'): - node_struct = String.from_ast(ast.node) + node_struct = String.from_ast(ast.node, order_nr=config.ORDER_OBJECT.get_next()) return_struct_cls = String elif ast.name == 'filesizeformat': - node_struct = Number.from_ast(ast.node) + node_struct = Number.from_ast(ast.node, order_nr=config.ORDER_OBJECT.get_next()) return_struct_cls = String elif ast.name == 'string': - node_struct = Scalar.from_ast(ast.node) + node_struct = Scalar.from_ast(ast.node, order_nr=config.ORDER_OBJECT.get_next()) return_struct_cls = String elif ast.name == 'wordcount': - node_struct = String.from_ast(ast.node) + node_struct = String.from_ast(ast.node, order_nr=config.ORDER_OBJECT.get_next()) return_struct_cls = Number else: - node_struct = Scalar.from_ast(ast.node) + node_struct = Scalar.from_ast(ast.node, order_nr=config.ORDER_OBJECT.get_next()) elif ast.name in ('batch', 'slice'): ctx.meet(List(List(Unknown())), ast) rtype = List(List(Unknown(), linenos=[ast.node.lineno]), linenos=[ast.node.lineno]) @@ -444,25 +470,29 @@ def visit_filter(ast, ctx, macroses=None, config=default_config): return rtype, struct elif ast.name == 'default': default_value_rtype, default_value_struct = visit_expr( - ast.args[0], Context(predicted_struct=Unknown.from_ast(ast.args[0])), macroses, config=config) + ast.args[0], + Context(predicted_struct=Unknown.from_ast(ast.args[0], order_nr=config.ORDER_OBJECT.get_next())), + macroses, config=config) node_struct = merge( - ctx.get_predicted_struct(), + ctx.get_predicted_struct(), default_value_rtype, ) node_struct.used_with_default = True node_struct.value = default_value_rtype.value elif ast.name == 'dictsort': ctx.meet(List(Tuple([Scalar(), Unknown()])), ast) - node_struct = Dictionary.from_ast(ast.node) + node_struct = Dictionary.from_ast(ast.node, order_nr=config.ORDER_OBJECT.get_next()) elif ast.name == 'join': ctx.meet(Scalar(), ast) - node_struct = List.from_ast(ast.node, String()) + node_struct = List.from_ast(ast.node, String(), order_nr=config.ORDER_OBJECT.get_next()) rtype, struct = visit_expr(ast.node, Context( return_struct_cls=String, predicted_struct=node_struct ), macroses, config=config) arg_rtype, arg_struct = visit_expr(ast.args[0], - Context(predicted_struct=String.from_ast(ast.args[0])), macroses, config=config) + Context(predicted_struct=String.from_ast(ast.args[0], + 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'): @@ -474,7 +504,7 @@ def visit_filter(ast, ctx, macroses=None, config=default_config): else: ctx.meet(Scalar(), ast) el_struct = Scalar() - node_struct = List.from_ast(ast.node, el_struct) + 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'): ctx.meet(List(Unknown()), ast) node_struct = merge( @@ -484,7 +514,7 @@ def visit_filter(ast, ctx, macroses=None, config=default_config): elif ast.name == 'list': ctx.meet(List(Scalar()), ast) node_struct = merge( - List(Scalar.from_ast(ast.node)), + List(Scalar.from_ast(ast.node, order_nr=config.ORDER_OBJECT.get_next())), ctx.get_predicted_struct() ).item elif ast.name == 'pprint': @@ -492,7 +522,7 @@ def visit_filter(ast, ctx, macroses=None, config=default_config): node_struct = ctx.get_predicted_struct() elif ast.name == 'xmlattr': ctx.meet(Scalar(), ast) - node_struct = Dictionary.from_ast(ast.node) + node_struct = Dictionary.from_ast(ast.node, order_nr=config.ORDER_OBJECT.get_next()) elif ast.name == 'attr': raise InvalidExpression(ast, 'attr filter is not supported') else: @@ -516,13 +546,13 @@ def visit_template_data(ast, ctx, macroses=None, config=default_config): def visit_const(ast, ctx, macroses=None, config=default_config): ctx.meet(Scalar(), ast) if isinstance(ast.value, _compat.string_types): - rtype = String.from_ast(ast, constant=True) + rtype = String.from_ast(ast, constant=True, order_nr=config.ORDER_OBJECT.get_next()) elif isinstance(ast.value, bool): - rtype = Boolean.from_ast(ast, constant=True) + rtype = Boolean.from_ast(ast, constant=True, order_nr=config.ORDER_OBJECT.get_next()) elif isinstance(ast.value, (int, float, complex)): - rtype = Number.from_ast(ast, constant=True) + rtype = Number.from_ast(ast, constant=True, order_nr=config.ORDER_OBJECT.get_next()) else: - rtype = Scalar.from_ast(ast, constant=True) + rtype = Scalar.from_ast(ast, constant=True, order_nr=config.ORDER_OBJECT.get_next()) return rtype, Dictionary() @@ -536,7 +566,7 @@ def visit_tuple(ast, ctx, macroses=None, config=default_config): item_rtype, item_struct = visit_expr(item, ctx, macroses, config=config) item_structs.append(item_rtype) struct = merge(struct, item_struct) - rtype = Tuple.from_ast(ast, item_structs, constant=True) + rtype = Tuple.from_ast(ast, item_structs, constant=True, order_nr=config.ORDER_OBJECT.get_next()) return rtype, struct @@ -554,7 +584,7 @@ def visit_list(ast, ctx, macroses=None, config=default_config): el_rtype = item_rtype else: el_rtype = merge_rtypes(el_rtype, item_rtype) - rtype = List.from_ast(ast, el_rtype or Unknown(), constant=True) + rtype = List.from_ast(ast, el_rtype or Unknown(), constant=True, order_nr=config.ORDER_OBJECT.get_next()) return rtype, struct diff --git a/jinja2schema/visitors/stmt.py b/jinja2schema/visitors/stmt.py index 782b8a5..e4dcaa3 100644 --- a/jinja2schema/visitors/stmt.py +++ b/jinja2schema/visitors/stmt.py @@ -56,8 +56,10 @@ def visit_stmt(ast, macroses=None, config=default_config, child_blocks=None): @visits_stmt(nodes.For) def visit_for(ast, macroses=None, config=default_config, child_blocks=None): - body_struct = visit_many(ast.body, macroses, config, predicted_struct_cls=Scalar) - else_struct = visit_many(ast.else_, macroses, config, predicted_struct_cls=Scalar) + with config.ORDER_OBJECT.sub_counter(): + body_struct = visit_many(ast.body, macroses, config, predicted_struct_cls=Scalar) + with config.ORDER_OBJECT.sub_counter(): + else_struct = visit_many(ast.else_, macroses, config, predicted_struct_cls=Scalar) if 'loop' in body_struct: # exclude a special `loop` variable from the body structure @@ -66,16 +68,16 @@ def visit_for(ast, macroses=None, config=default_config, child_blocks=None): if isinstance(ast.target, nodes.Tuple): target_struct = Tuple.from_ast( ast.target, - [body_struct.pop(item.name, Unknown.from_ast(ast.target)) - for item in ast.target.items]) + [body_struct.pop(item.name, Unknown.from_ast(ast.target, order_nr=config.ORDER_OBJECT.get_next())) + for item in ast.target.items], order_nr=config.ORDER_OBJECT.get_next()) else: - target_struct = body_struct.pop(ast.target.name, Unknown.from_ast(ast)) + target_struct = body_struct.pop(ast.target.name, Unknown.from_ast(ast, order_nr=config.ORDER_OBJECT.get_next())) iter_rtype, iter_struct = visit_expr( ast.iter, Context( return_struct_cls=Unknown, - predicted_struct=List.from_ast(ast, target_struct)), + predicted_struct=List.from_ast(ast, target_struct, order_nr=config.ORDER_OBJECT.get_next())), macroses, config) merge(iter_rtype, List(target_struct)) @@ -86,11 +88,11 @@ def visit_for(ast, macroses=None, config=default_config, child_blocks=None): @visits_stmt(nodes.If) def visit_if(ast, macroses=None, config=default_config, child_blocks=None): if config.BOOLEAN_CONDITIONS: - test_predicted_struct = Boolean.from_ast(ast.test) + test_predicted_struct = Boolean.from_ast(ast.test, order_nr=config.ORDER_OBJECT.get_next()) else: - test_predicted_struct = Unknown.from_ast(ast.test) + test_predicted_struct = Unknown.from_ast(ast.test, order_nr=config.ORDER_OBJECT.get_next()) test_rtype, test_struct = visit_expr( - ast.test, Context(predicted_struct=test_predicted_struct), macroses, config) + ast.test, Context(predicted_struct=test_predicted_struct), macroses, config) if_struct = visit_many(ast.body, macroses, config, predicted_struct_cls=Scalar) else_struct = visit_many(ast.else_, macroses, config, predicted_struct_cls=Scalar) if ast.else_ else Dictionary() struct = merge_many(test_struct, if_struct, else_struct) @@ -128,7 +130,8 @@ def visit_assign(ast, macroses=None, config=default_config, child_blocks=None): for name_ast, var_ast in izip(ast.target.items, ast.node.items): variables.append((name_ast.name, var_ast)) for var_name, var_ast in variables: - var_rtype, var_struct = visit_expr(var_ast, Context(predicted_struct=Unknown.from_ast(var_ast)), macroses, config) + var_rtype, var_struct = visit_expr(var_ast, Context( + predicted_struct=Unknown.from_ast(var_ast, order_nr=config.ORDER_OBJECT.get_next())), macroses, config) var_rtype.constant = True var_rtype.label = var_name struct = merge_many(struct, var_struct, Dictionary({ @@ -138,7 +141,7 @@ def visit_assign(ast, macroses=None, config=default_config, child_blocks=None): elif isinstance(ast.target, nodes.Tuple): tuple_items = [] for name_ast in ast.target.items: - var_struct = Unknown.from_ast(name_ast, constant=True) + var_struct = Unknown.from_ast(name_ast, constant=True, order_nr=config.ORDER_OBJECT.get_next()) tuple_items.append(var_struct) struct = merge(struct, Dictionary({name_ast.name: var_struct})) var_rtype, var_struct = visit_expr( diff --git a/jinja2schema/visitors/util.py b/jinja2schema/visitors/util.py index c3f3677..2b999db 100644 --- a/jinja2schema/visitors/util.py +++ b/jinja2schema/visitors/util.py @@ -13,7 +13,8 @@ def visit(node, macroses, config, predicted_struct_cls=Scalar, return_struct_cls if isinstance(node, jinja2.nodes.Stmt): structure = visit_stmt(node, macroses, config) elif isinstance(node, jinja2.nodes.Expr): - ctx = Context(predicted_struct=predicted_struct_cls.from_ast(node), return_struct_cls=return_struct_cls) + ctx = Context(predicted_struct=predicted_struct_cls.from_ast(node, order_nr=config.ORDER_OBJECT.get_next()), + return_struct_cls=return_struct_cls) _, structure = visit_expr(node, ctx, macroses, config) elif isinstance(node, jinja2.nodes.Template): structure = visit_many(node.body, macroses, config) diff --git a/tests/func_tests/test_basics.py b/tests/func_tests/test_basics.py index 06aee99..d35316d 100644 --- a/tests/func_tests/test_basics.py +++ b/tests/func_tests/test_basics.py @@ -428,3 +428,86 @@ def test_block_1(): 'y': Scalar(label='y', linenos=[4]), }) assert struct == expected_struct + + +def test_order_number_setting_1(): + config = Config(ORDER_NUMBER=True) + + template = ''' + {{ x }} + {{ y }} + {{ z }} + {{ x }} + {{ x }} + ''' + struct = infer(template, config) + assert struct['x'].order_nr < struct['y'].order_nr + assert struct['y'].order_nr < struct['z'].order_nr + + +def test_order_number_setting_1_5(): + config = Config(ORDER_NUMBER=True) + + template = ''' + {% if yy %} + {{ intooo }} + {{ zz }} + {% endif %} + ''' + struct = infer(template, config) + assert struct['yy'].order_nr < struct['zz'].order_nr + + +def test_order_number_setting_2(): + config = Config(ORDER_NUMBER=True) + + template = ''' + {% for n in nx %} + {{ y }} + {{ z }} + {% endfor %} + {% if yy %} + {{ zz }} + {{ xx }} + {% endif %} + ''' + struct = infer(template, config) + assert struct['y'].order_nr < struct['z'].order_nr + assert struct['nx'].order_nr < struct['yy'].order_nr + assert struct['zz'].order_nr < struct['xx'].order_nr + + +def test_order_number_setting_3(): + config = Config(ORDER_NUMBER=True) + + template = ''' + {% for a in aa %} + {{ ax }} + {% for b in bb %} + {{ bx }} + {% for c in cc %} + {{ cx }} + {% endfor %} + {% endfor %} + {% endfor %} + ''' + struct = infer(template, config) + assert struct['ax'].order_nr == struct['bx'].order_nr == struct['cx'].order_nr + + +def test_order_number_setting_4(): + config = Config(ORDER_NUMBER=True, ORDER_NUMBER_SUB_COUNTER=False) + + template = ''' + {% for a in aa %} + {{ ax }} + {% for b in bb %} + {{ bx }} + {% for c in cc %} + {{ cx }} + {% endfor %} + {% endfor %} + {% endfor %} + ''' + struct = infer(template, config) + assert struct['ax'].order_nr != struct['bx'].order_nr != struct['cx'].order_nr diff --git a/tests/unit_tests/test_order_number.py b/tests/unit_tests/test_order_number.py new file mode 100644 index 0000000..d32243e --- /dev/null +++ b/tests/unit_tests/test_order_number.py @@ -0,0 +1,29 @@ +# coding: utf-8 +from jinja2schema.order_number import OrderNumber + + +def test_get_next_order_number(): + on = OrderNumber(1, enabled=True) + assert on.get_next() == 2 + on = OrderNumber(1) + assert on.get_next() is None + on = OrderNumber(5, enabled=True) + assert on.get_next() == 6 + + +def test_sub_counter_1(): + on = OrderNumber(1, enabled=True) + on.get_next() + assert on.get_next() == 3 + with on.sub_counter(): + assert on.get_next() == 2 + assert on.get_next() == 4 + + +def test_sub_counter_2(): + on = OrderNumber(12, enabled=True) + on.get_next() + assert on.get_next() == 14 + with on.sub_counter(): + assert on.get_next() == 13 + assert on.get_next() == 15 From 406f29265ea5ce88b1517f0f81dd8e30cbe978ca Mon Sep 17 00:00:00 2001 From: ubaumann Date: Tue, 4 Apr 2017 21:44:36 +0200 Subject: [PATCH 14/16] Fix Python 3.X RuntimeError: dictionary changed size during iteration --- jinja2schema/model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jinja2schema/model.py b/jinja2schema/model.py index bb7076a..3abd5df 100644 --- a/jinja2schema/model.py +++ b/jinja2schema/model.py @@ -85,7 +85,7 @@ def from_ast(cls, ast, **kwargs): :param ast: AST node :type ast: :class:`jinja2.nodes.Node` """ - for k, v in kwargs.items(): + for k, v in list(kwargs.items()): if v is None: del kwargs[k] kwargs = dict(cls._get_kwargs_from_ast(ast), **kwargs) From ca50530e83a928cfa836f257ad232f5c47be1659 Mon Sep 17 00:00:00 2001 From: Anton Romanovich Date: Wed, 5 Apr 2017 23:01:49 +0500 Subject: [PATCH 15/16] Bump version to 0.1.4 --- jinja2schema/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jinja2schema/__init__.py b/jinja2schema/__init__.py index 80904cb..0aaa35d 100644 --- a/jinja2schema/__init__.py +++ b/jinja2schema/__init__.py @@ -17,7 +17,7 @@ __author__ = 'Anton Romanovich' __license__ = 'BSD' __copyright__ = 'Copyright 2017 Anton Romanovich' -__version__ = '0.1.3' +__version__ = '0.1.4' __version_info__ = tuple(int(i) for i in __version__.split('.')) From 071722114064c2a7194225142b195c93d36f21da Mon Sep 17 00:00:00 2001 From: Pachwenko <32424503+Pachwenko@users.noreply.github.com> Date: Thu, 26 Sep 2019 11:06:46 -0500 Subject: [PATCH 16/16] Debugging: short circuit loop cycle --- jinja2schema/visitors/expr.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/jinja2schema/visitors/expr.py b/jinja2schema/visitors/expr.py index 5685c37..4acb804 100644 --- a/jinja2schema/visitors/expr.py +++ b/jinja2schema/visitors/expr.py @@ -137,7 +137,6 @@ def wrapped_func(ast, ctx, macroses=None, config=default_config): return wrapped_func return decorator - def visit_expr(ast, ctx, macroses=None, config=default_config): """Returns a structure of ``ast``. @@ -398,7 +397,9 @@ def visit_call(ast, ctx, macroses=None, config=default_config): else: raise InvalidExpression(ast, '"{0}" call is not supported'.format(ast.node.name)) elif isinstance(ast.node, nodes.Getattr): - if ast.node.attr in ('keys', 'iterkeys', 'values', 'itervalues'): + if ast.node.node.name in ('loop'): + return Unknown(), Unknown() + elif ast.node.attr in ('keys', 'iterkeys', 'values', 'itervalues'): ctx.meet(List(Unknown()), ast) rtype, struct = visit_expr( ast.node.node, Context(