From dbc366feda4b31a8e4fed748c98afd391e799462 Mon Sep 17 00:00:00 2001 From: Taka Date: Sun, 14 Feb 2021 18:52:52 +1100 Subject: [PATCH] Added support for the "in" operator in Jinja templates. --- vasl_templates/webapp/static/jinja/jinja.js | 30 ++++++++++++++--- vasl_templates/webapp/tests/test_snippets.py | 35 ++++++++++++++++++++ 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/vasl_templates/webapp/static/jinja/jinja.js b/vasl_templates/webapp/static/jinja/jinja.js index 6428b03..3a9b5e7 100644 --- a/vasl_templates/webapp/static/jinja/jinja.js +++ b/vasl_templates/webapp/static/jinja/jinja.js @@ -48,9 +48,10 @@ var jinja; var IDENTIFIERS = /[$_a-z][$\w]*/ig; var VARIABLES = /i(\.i|\[[@#i]\])*/g; var ACCESSOR = /(\.i|\[[@#i]\])/g; - var OPERATORS = /(===?|!==?|>=?|<=?|&&|\|\||[+\-\*\/%])/g; + // NOTE: We use ^ for the Python-like "in" operator. + var OPERATORS = /(===?|!==?|>=?|<=?|&&|\|\||[+\-\*\/%|\^])/g; //extended (english) operators - var EOPS = /(^|[^$\w])(and|or|not|is|isnot)([^$\w]|$)/g; + var EOPS = /(^|[^$\w])(and|or|not|is|isnot|in)([^$\w]|$)/g; var LEADING_SPACE = /^\s+/; var TRAILING_SPACE = /\s+$/; @@ -73,7 +74,8 @@ var jinja; or: '||', not: '!', is: '==', - isnot: '!=' + isnot: '!=', + in: '^', }; var constants = { @@ -265,6 +267,26 @@ var jinja; }); parsed3.src = parsed3.src.replace(VARIABLES, this.parseVar.bind(this)); parsed2.src = this.injectEnt(parsed3, 'i'); + + // FUDGE! We want to support the Python "in" operators, but this is problematic since there isn't + // a corresponding Javascript operator, and we have to translate it into a call to indexOf(). + // NOTE: Expressions of the form "'foo' in 'bar'" won't work, because we get a pseudo-expression + // of "@^@", which gets translated to "@.indexOf(@) !== -1", and the call to injectEnt() later + // has no way of knowing that the literals should be swapped around. However, this is not a case + // we really need to worry about :-/ + // NOTE: It also won't handle computed expressions (e.g. "A+B in C+D"), but the user can also calculate + // these values and save them in a temporary variable, and then use those in the "in" expression. + var regex = new RegExp( /(@|get\(.+?\))\^(@|get\(.+?\))/g ) ; + var matches = [] ; + while( (match = regex.exec( parsed2.src )) !== null ) + matches.push( match ) ; + while ( matches.length > 0 ) { + var match = matches.pop() ; + parsed2.src = parsed2.src.substring( 0, match.index ) + + match[2] + ".toLowerCase().indexOf(" + match[1] + ".toLowerCase()) !== -1" + + parsed2.src.substring( match.index + match[0].length ) ; + } + parsed1.src = this.injectEnt(parsed2, '#'); return this.injectEnt(parsed1, '@'); }; @@ -574,4 +596,4 @@ var jinja; } } -}); \ No newline at end of file +}); diff --git a/vasl_templates/webapp/tests/test_snippets.py b/vasl_templates/webapp/tests/test_snippets.py index 9e42f50..88e1039 100644 --- a/vasl_templates/webapp/tests/test_snippets.py +++ b/vasl_templates/webapp/tests/test_snippets.py @@ -400,6 +400,41 @@ def test_snippet_images( webapp, webdriver ): # --------------------------------------------------------------------- +@pytest.mark.skipif( pytest_options.short_tests, reason="--short-tests specified" ) +def test_jinja_in( webapp, webdriver ): + """Test the "in" operator in Jinja templates.""" + + # initialize + init_webapp( webapp, webdriver, edit_template_links=1 ) + + def do_test( search_for, search_in, expected ): + """Test the IN operator.""" + # install a new template + elem = find_child( "a._edit-template-link_[data-id='victory_conditions']" ) + webdriver.execute_script( "$(arguments[0]).click();", elem ) + elem = find_child( "#edit-template textarea" ) + elem.clear() + buf = [ + "{%set HELLO_WORLD = \"Hello, world!\"%}", + "{%set HELLO = \"hello\"%}", + "{%if " + search_for + " in " + search_in + "%} YES {%else%} NO {%endif%}" + ] + template = "\n".join( buf ) + elem.send_keys( template ) + elem.send_keys( Keys.ESCAPE ) + # process the template + elem = find_child( "button.generate[data-id='victory_conditions']" ) + elem.click() + wait_for_clipboard( 2, "YES" if expected else "NO" ) + + # do th tests + do_test( '"foo"', "HELLO_WORLD", False ) + do_test( '"O, W"', "HELLO_WORLD", True ) + do_test( "HELLO", "HELLO_WORLD", True ) + do_test( "HELLO", '"hello, big guy!"', True ) + +# --------------------------------------------------------------------- + def _test_snippet( btn, params, expected, expected2 ): """Do a single test."""