From 25d633bc4687dff8fcf84905585860ad97839cc5 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 29 Mar 2012 14:16:33 -0700 Subject: [PATCH 01/21] Implemented Python 2.4 support. If json module not available skip json/js/node tests (with warning). NOTE options to implement pep8 and unittests (rather than asserts), could even reuse unittest from https://github.com/defunkt/pystache --- __init__.py | 86 ++++++++++++++++++++++++++++++++++++++++++----------- test.py | 42 ++++++++++++++++---------- 2 files changed, 96 insertions(+), 32 deletions(-) diff --git a/__init__.py b/__init__.py index 55ff995..3d16715 100644 --- a/__init__.py +++ b/__init__.py @@ -100,7 +100,10 @@ def _checkprefix(tag, prefix): - return tag[1:].strip() if tag and tag[0] == prefix else None + if tag and tag[0] == prefix: + return tag[1:].strip() + else: + return None def _lookup(data, datum): @@ -221,23 +224,72 @@ def _tokenize(self, template): while rest and len(rest) > 0: pre_section = rest.split(self.otag, 1) - pre, rest = pre_section if len(pre_section) == 2 else (pre_section[0], None) - taglabel, rest = rest.split(self.ctag, 1) if rest else (None, None) - taglabel = taglabel.strip() if taglabel else '' + + + if len(pre_section) == 2: + pre, rest = pre_section + else: + pre, rest = (pre_section[0], None) + if rest: + taglabel, rest = rest.split(self.ctag, 1) + else: + taglabel, rest = (None, None) + + if taglabel: + taglabel = taglabel.strip() + else: + taglabel = '' open_tag = _checkprefix(taglabel, '#') - invert_tag = _checkprefix(taglabel, '^') if not open_tag else None - close_tag = _checkprefix(taglabel, '/') if not invert_tag else None - comment_tag = _checkprefix(taglabel, '!') if not close_tag else None - partial_tag = _checkprefix(taglabel, '>') if not comment_tag else None - push_tag = _checkprefix(taglabel, '<') if not partial_tag else None - bool_tag = _checkprefix(taglabel, '?') if not push_tag else None - booltern_tag = _checkprefix(taglabel, ':') if not bool_tag else None - unescape_tag = _checkprefix(taglabel, '{') if not booltern_tag else None - rest = rest[1:] if unescape_tag else rest - unescape_tag = (unescape_tag or _checkprefix(taglabel, '&')) if not booltern_tag else None - delim_tag = taglabel[1:-1] if not unescape_tag and len(taglabel) >= 2 and taglabel[0] == '=' and taglabel[-1] == '=' else None - delim_tag = delim_tag.split(' ', 1) if delim_tag else None - delim_tag = delim_tag if delim_tag and len(delim_tag) == 2 else None + if not open_tag: + invert_tag = _checkprefix(taglabel, '^') + else: + invert_tag = None + if not invert_tag: + close_tag = _checkprefix(taglabel, '/') + else: + close_tag = None + comment_tag = None + partial_tag = None + push_tag = None + bool_tag = None + booltern_tag = None + unescape_tag = None + + if not close_tag: + comment_tag = _checkprefix(taglabel, '!') + if not comment_tag: + partial_tag = _checkprefix(taglabel, '>') + if not partial_tag: + push_tag = _checkprefix(taglabel, '<') + if not push_tag: + bool_tag = _checkprefix(taglabel, '?') + if not bool_tag: + booltern_tag = _checkprefix(taglabel, ':') + if not booltern_tag: + unescape_tag = _checkprefix(taglabel, '{') + + if unescape_tag: + rest = rest[1:] + else: + rest = rest # FIXME seems like a NOOP + + if not booltern_tag: + unescape_tag = (unescape_tag or _checkprefix(taglabel, '&')) + else: + unescape_tag = None + if not unescape_tag and len(taglabel) >= 2 and taglabel[0] == '=' and taglabel[-1] == '=': + delim_tag = taglabel[1:-1] + else: + delim_tag = None + if delim_tag: + delim_tag = delim_tag.split(' ', 1) + else: + delim_tag = None + + if delim_tag and len(delim_tag) == 2: + delim_tag = delim_tag + else: + delim_tag = None if push_tag: pre = pre.rstrip() diff --git a/test.py b/test.py index f0770b3..7e944a3 100644 --- a/test.py +++ b/test.py @@ -1,10 +1,15 @@ +import timeit +import subprocess +import warnings + try: - from __init__ import Stache, render, render_js + import json except ImportError: - from . import Stache, render, render_js + # TODO import simplejson, etc. + json = None + +from __init__ import Stache, render, render_js -import timeit -import subprocess def verify(output, template, data): print("%s with %s" % (template, data)) @@ -15,27 +20,37 @@ def verify(output, template, data): assert result == result_iter def verify_js(output, template, data): - import json + if json is None: + warnings.warn('json module missing, skipping this js test') + return script = render_js(template) print("%s with %s" % (template, data)) result = subprocess.check_output(["node", "-e", "console.log({0}({1}))".format(script, json.dumps(data))]).strip() print("Result: %s\n" % result) assert output.lower() == result -def verify_partial(stachio, output, template, data={}): - print("%s with %s" % (template, data)) - result = stachio.render_template(template, data) - print("Result: %s\n" % result) - assert output == result - def verify_js_partial(stachio, output, template, data={}): - import json + if json is None: + warnings.warn('json module missing, skipping this js test') + return print("%s with %s" % (template, data)) script = stachio.render_js_template(template) result = subprocess.check_output(["node", "-e", "console.log({0}({1}))".format(script, json.dumps(data))]).strip() print("Result: %s\n" % result) assert output.lower() == result +def bare_js_partial(stachio, output, template, data={}): + if json is None: + warnings.warn('json module missing, skipping this js test') + return + return stachio.render_js_template(template) + +def verify_partial(stachio, output, template, data={}): + print("%s with %s" % (template, data)) + result = stachio.render_template(template, data) + print("Result: %s\n" % result) + assert output == result + def bench(output, template, data): t = timeit.Timer("render('%s', %s)" % (template, data), "from __main__ import render") print("%.2f\tusec/test > python %s with %s" % (1000000 * t.timeit(number=10000)/10000, template, data)) @@ -61,9 +76,6 @@ def bare_js(output, template, data): def bare_partial(stachio, output, template, data={}): return stachio.render_template(template, data) -def bare_js_partial(stachio, output, template, data={}): - return stachio.render_js_template(template) - def test(method=bare): # test tag lookup yield method, 'a10c', 'a{{b}}c', dict(b=10) From ccd0e754567b7daa2f88ad62b40187be9026d1a7 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 29 Mar 2012 15:42:19 -0700 Subject: [PATCH 02/21] Implemented Jython 2.2 (2.2.1) support, this means the benchmark timing is now optional. The Bool tests are also optional (and are not ran under python 2.2). The reason for this is that the test suite hard fails (unlike a unittest based one) and does not complete if failure are detected. I attempted a Bool replacement but it didn't work and I'm unclear why. It appeared to raise the exception TypeError exception in the wrong place (could be a Jython 2.2 traceback bug). if sys.version_info < (2, 3): # version 2.2 # this kinda works BUT fails with this module's usage of len(Bool) # with Jython 2.2 :-( class MyBool: def __init__(self, is_true=None): self.is_true = is_true def __len__(self): raise TypeError('len() of unsized object') def __repr__(self): if self.is_true: return 'True' else: return 'False' True = MyBool(1) False = MyBool(0) --- __init__.py | 29 ++++++++++++++++----- test.py | 75 +++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 79 insertions(+), 25 deletions(-) diff --git a/__init__.py b/__init__.py index 3d16715..0c68441 100644 --- a/__init__.py +++ b/__init__.py @@ -1,6 +1,23 @@ -import itertools +import sys from cgi import escape +try: + raise ImportError + import itertools + itertools_takewhile = itertools.takewhile +except ImportError: + # fake it + + def takewhile(predicate, iterable): + # takewhile(lambda x: x<5, [1,4,6,4,1]) --> 1 4 + for x in iterable: + if predicate(x): + yield x + else: + break + + itertools_takewhile = takewhile + try: from sys import intern @@ -354,7 +371,7 @@ def _parse(self, tokens, *data): yield escape(str(tagvalue)) elif tag == TOKEN_TAGOPEN or tag == TOKEN_TAGINVERT: tagvalue = _lookup(data, content) - untilclose = itertools.takewhile(lambda x: x != (TOKEN_TAGCLOSE, content, scope), tokens) + untilclose = itertools_takewhile(lambda x: x != (TOKEN_TAGCLOSE, content, scope), tokens) if (tag == TOKEN_TAGOPEN and tagvalue) or (tag == TOKEN_TAGINVERT and not tagvalue): if hasattr(tagvalue, 'items'): #print ' its a dict!', tagvalue, untilclose @@ -382,7 +399,7 @@ def _parse(self, tokens, *data): pass elif tag == TOKEN_BOOL: tagvalue = _lookup(data, content) - untilclose = itertools.takewhile(lambda x: x != (TOKEN_TAGCLOSE, content, scope), tokens) + untilclose = itertools_takewhile(lambda x: x != (TOKEN_TAGCLOSE, content, scope), tokens) if tagvalue: for part in self._parse(untilclose, *data): yield part @@ -394,7 +411,7 @@ def _parse(self, tokens, *data): for part in self._parse(iter(list(self.templates[content])), *data): yield part elif tag == TOKEN_PUSH: - untilclose = itertools.takewhile(lambda x: x != (TOKEN_TAGCLOSE, content, scope), tokens) + untilclose = itertools_takewhile(lambda x: x != (TOKEN_TAGCLOSE, content, scope), tokens) data[-1][content] = ''.join(self._parse(untilclose, *data)) elif tag == TOKEN_TAGDELIM: self.otag, self.ctag = content @@ -413,7 +430,7 @@ def _jsparse(self, tokens): else: yield "htmlEncode(lookup(data, '{0}'))".format(content) elif tag == TOKEN_TAGOPEN or tag == TOKEN_TAGINVERT or tag == TOKEN_BOOL: - untilclose = itertools.takewhile(lambda x: x != (TOKEN_TAGCLOSE, content, scope), tokens) + untilclose = itertools_takewhile(lambda x: x != (TOKEN_TAGCLOSE, content, scope), tokens) inside = self._jsparse(untilclose) if tag == TOKEN_TAGOPEN: pre = "return section(data, lookup(data, tag), function (data) {" @@ -433,7 +450,7 @@ def _jsparse(self, tokens): elif tag == TOKEN_PARTIAL: yield "templates['{0}'](data)".format(content) elif tag == TOKEN_PUSH: - untilclose = itertools.takewhile(lambda x: x != (TOKEN_TAGCLOSE, content, scope), tokens) + untilclose = itertools_takewhile(lambda x: x != (TOKEN_TAGCLOSE, content, scope), tokens) self.hoist_data[content] = _renderjsfunction(self._jsparse(untilclose), params="data") elif tag == TOKEN_TAGDELIM: self.otag, self.ctag = content diff --git a/test.py b/test.py index 7e944a3..076bf76 100644 --- a/test.py +++ b/test.py @@ -1,6 +1,15 @@ -import timeit -import subprocess +from __future__ import generators +import sys import warnings +try: + import timeit +except ImportError: + # TODO fake it? + timeit = None +try: + import subprocess +except ImportError: + subprocess = None try: import json @@ -11,10 +20,16 @@ from __init__ import Stache, render, render_js +skip_bool = False +if sys.version_info < (2, 3): + skip_bool = True + + def verify(output, template, data): print("%s with %s" % (template, data)) result = render(template, data) result_iter = ''.join(Stache().render_iter(template, data)) + print("Output: %s\n" % output) print("Result: %s\n" % result) assert result == output assert result == result_iter @@ -52,18 +67,30 @@ def verify_partial(stachio, output, template, data={}): assert output == result def bench(output, template, data): + if timeit is None: + warnings.warn('timeit module missing, skipping this test') + return t = timeit.Timer("render('%s', %s)" % (template, data), "from __main__ import render") print("%.2f\tusec/test > python %s with %s" % (1000000 * t.timeit(number=10000)/10000, template, data)) def bench_js(output, template, data): + if timeit is None: + warnings.warn('timeit module missing, skipping this test') + return t = timeit.Timer("render_js('%s')" % (template), "from __main__ import render_js") print("%.2f\tusec/test > js %s with %s" % (1000000 * t.timeit(number=10000)/10000, template, data)) def bench_partial(stachio, output, template, data={}): + if timeit is None: + warnings.warn('timeit module missing, skipping this test') + return t = timeit.Timer("s.render_template('%s', %s)" % (template, data), "from __main__ import test_partials, s") print("%.2f\tusec/test > python partial %s with %s" % (1000000 * t.timeit(number=10000)/10000, template, data)) def bench_js_partial(stachio, output, template, data={}): + if timeit is None: + warnings.warn('timeit module missing, skipping this test') + return t = timeit.Timer("s.render_template('%s', %s)" % (template, data), "from __main__ import test_partials, s") print("%.2f\tusec/test > js_partial %s with %s" % (1000000 * t.timeit(number=10000)/10000, template, data)) @@ -83,20 +110,24 @@ def test(method=bare): yield method, 'ac', 'a{{b}}c', dict(c=10) yield method, 'a10c', 'a{{b}}c', dict(b='10') yield method, 'acde', 'a{{!b}}cde', dict(b='10') - yield method, 'aTrue', 'a{{b}}', dict(b=True) + if not skip_bool: + yield method, 'aTrue', 'a{{b}}', dict(b=True) yield method, 'a123', 'a{{b}}{{c}}{{d}}', dict(b=1,c=2,d=3) # test falsy #sections - yield method, 'ab', 'a{{#b}}b{{/b}}', dict(b=True) - yield method, 'a', 'a{{^b}}b{{/b}}', dict(b=True) - yield method, 'a', 'a{{#b}}b{{/}}', dict(b=False) - yield method, 'ab', 'a{{^b}}b{{/b}}', dict(b=False) + if not skip_bool: + yield method, 'ab', 'a{{#b}}b{{/b}}', dict(b=True) + yield method, 'a', 'a{{^b}}b{{/b}}', dict(b=True) + yield method, 'a', 'a{{#b}}b{{/}}', dict(b=False) + yield method, 'ab', 'a{{^b}}b{{/b}}', dict(b=False) #test invert sections yield method, 'ab', 'a{{#b}}ignore me{{/b}}{{^b}}b{{/}}', dict(b=[]) yield method, 'ab', 'a{{#b}}b{{/b}}{{^b}}ignore me{{/}}', dict(b=[1]) - yield method, 'ab', 'a{{#b}}b{{/b}}{{^b}}ignore me{{/}}', dict(b=True) + if not skip_bool: + yield method, 'ab', 'a{{#b}}b{{/b}}{{^b}}ignore me{{/}}', dict(b=True) #test ?sections yield method, 'a- 1 2 3 4', 'a{{?b}}-{{#b}} {{.}}{{/}}{{/}}', dict(b=[1,2,3,4]) - yield method, 'a', 'a{{?b}}ignoreme{{/}}', dict(b=False) + if not skip_bool: + yield method, 'a', 'a{{?b}}ignoreme{{/}}', dict(b=False) yield method, 'a', 'a{{?b}}ignoreme{{/}}', dict(b=[]) yield method, 'a', 'a{{?b}}ignoreme{{/}}', dict() yield method, 'abb', 'a{{?b}}b{{/}}{{?b}}b{{/}}', dict(b=[1,2,3]) @@ -111,7 +142,8 @@ def test(method=bare): yield method, '132', '{{#a}}{{#b}}{{c}}{{/}}{{/}}', dict(a=[{'b':[{'c':1}, {'c':3}]},{'b':{'c':2}}]) yield method, '132456', '{{#a}}{{#b}}{{c}}{{/b}}{{/a}}', dict(a=[{'b':[{'c':1}, {'c':3}]},{'b':{'c':2}},{'b':[{'c':4}, {'c':5}]},{'b':{'c':6}}]) yield method, '1', '{{#a}}{{#a}}{{c}}{{/a}}{{/a}}', dict(a={'a':{'c':1}}) - yield method, '<3><3><3>', '<{{id}}><{{# a? }}{{id}}{{/ a? }}><{{# b? }}{{id}}{{/ b? }}>', {'id':3,'a?':True, 'b?':True} + if not skip_bool: + yield method, '<3><3><3>', '<{{id}}><{{# a? }}{{id}}{{/ a? }}><{{# b? }}{{id}}{{/ b? }}>', {'id':3,'a?':True, 'b?':True} #test delim yield method, 'delim{{a}}', '{{=<% %>=}}<%a%>{{a}}', dict(a='delim') yield method, 'delim{{a}}delim<%a%>', '{{=<% %>=}}<%a%>{{a}}<%={{ }}=%>{{a}}<%a%>', dict(a='delim') @@ -121,7 +153,8 @@ def test(method=bare): yield method, '123abc', '123{{:hi}}{{:hi2}}abc{{/}}{{/}}', dict() yield method, '123cba', '123{{:hi}}{{:hi2}}abc{{/}}{{/}}', dict(hi2='cba') yield method, '123abc', '123{{:hi}}abc{{/}}', dict() - yield method, '123abc', '123{{:hi}}abc{{/}}', dict(hi=False) + if not skip_bool: + yield method, '123abc', '123{{:hi}}abc{{/}}', dict(hi=False) yield method, '123abc', '123{{:hi}}abc{{/}}', dict(hi=[]) yield method, '123test', '{{<3><3>', 'l', {'id':3,'a':True, 'b':True} - yield method, s, '<3><3><3>', 'm', {'id':3,'a?':True, 'b?':True} + if not skip_bool: + yield method, s, '<3><3><3>', 'l', {'id':3,'a':True, 'b':True} + yield method, s, '<3><3><3>', 'm', {'id':3,'a?':True, 'b?':True} yield method, s, 'abb', 'n', dict(b=[1,2,3]) yield method, s, 'ab123d', 'o', dict(b=[1,2,3]) @@ -207,10 +241,13 @@ def test_js_all(): test_js_all() print('starting individual benchmarks') run(bench, bench_partial, bench_js, bench_js_partial) - print('starting combined python benchmark') - t = timeit.Timer("run(method_js=null, method_js_partial=null)", "from __main__ import run, bare, null") - print("%.2f\tusec/all tests" % (1000000 * t.timeit(number=10000)/10000)) - print('starting combined js benchmark') - t = timeit.Timer("run(method=null, method_partial=null)", "from __main__ import run, bare, null") - print("%.2f\tusec/all js tests" % (1000000 * t.timeit(number=10000)/10000)) + if timeit is None: + warnings.warn('timeit module missing, skipping this test') + else: + print('starting combined python benchmark') + t = timeit.Timer("run(method_js=null, method_js_partial=null)", "from __main__ import run, bare, null") + print("%.2f\tusec/all tests" % (1000000 * t.timeit(number=10000)/10000)) + print('starting combined js benchmark') + t = timeit.Timer("run(method=null, method_partial=null)", "from __main__ import run, bare, null") + print("%.2f\tusec/all js tests" % (1000000 * t.timeit(number=10000)/10000)) From d5192e107abf0eca5bd864dde704c084e3d1d5cf Mon Sep 17 00:00:00 2001 From: clach04 Date: Mon, 9 Apr 2012 13:53:34 -0700 Subject: [PATCH 03/21] Jython 2.2 includes _some_ py 2.3 features. These changes add Cpython 2.2 support. --- __init__.py | 1 + test.py | 129 ++++++++++++++++++++++++++++------------------------ 2 files changed, 70 insertions(+), 60 deletions(-) diff --git a/__init__.py b/__init__.py index 0c68441..eaea442 100644 --- a/__init__.py +++ b/__init__.py @@ -1,3 +1,4 @@ +from __future__ import generators import sys from cgi import escape diff --git a/test.py b/test.py index 076bf76..b8abc6d 100644 --- a/test.py +++ b/test.py @@ -17,13 +17,22 @@ # TODO import simplejson, etc. json = None -from __init__ import Stache, render, render_js +from stache import Stache, render, render_js skip_bool = False if sys.version_info < (2, 3): skip_bool = True +def mydict(*args, **kwargs): + """Emulate Python 2.3 dict keyword support + """ + if args: + raise NotImplementedError('args not supported') + if kwargs: + return kwargs + else: + return {} def verify(output, template, data): print("%s with %s" % (template, data)) @@ -105,65 +114,65 @@ def bare_partial(stachio, output, template, data={}): def test(method=bare): # test tag lookup - yield method, 'a10c', 'a{{b}}c', dict(b=10) - yield method, 'a10c', 'a{{b}}c', dict(b=10) - yield method, 'ac', 'a{{b}}c', dict(c=10) - yield method, 'a10c', 'a{{b}}c', dict(b='10') - yield method, 'acde', 'a{{!b}}cde', dict(b='10') + yield method, 'a10c', 'a{{b}}c', mydict(b=10) + yield method, 'a10c', 'a{{b}}c', mydict(b=10) + yield method, 'ac', 'a{{b}}c', mydict(c=10) + yield method, 'a10c', 'a{{b}}c', mydict(b='10') + yield method, 'acde', 'a{{!b}}cde', mydict(b='10') if not skip_bool: - yield method, 'aTrue', 'a{{b}}', dict(b=True) - yield method, 'a123', 'a{{b}}{{c}}{{d}}', dict(b=1,c=2,d=3) + yield method, 'aTrue', 'a{{b}}', mydict(b=True) + yield method, 'a123', 'a{{b}}{{c}}{{d}}', mydict(b=1,c=2,d=3) # test falsy #sections if not skip_bool: - yield method, 'ab', 'a{{#b}}b{{/b}}', dict(b=True) - yield method, 'a', 'a{{^b}}b{{/b}}', dict(b=True) - yield method, 'a', 'a{{#b}}b{{/}}', dict(b=False) - yield method, 'ab', 'a{{^b}}b{{/b}}', dict(b=False) + yield method, 'ab', 'a{{#b}}b{{/b}}', mydict(b=True) + yield method, 'a', 'a{{^b}}b{{/b}}', mydict(b=True) + yield method, 'a', 'a{{#b}}b{{/}}', mydict(b=False) + yield method, 'ab', 'a{{^b}}b{{/b}}', mydict(b=False) #test invert sections - yield method, 'ab', 'a{{#b}}ignore me{{/b}}{{^b}}b{{/}}', dict(b=[]) - yield method, 'ab', 'a{{#b}}b{{/b}}{{^b}}ignore me{{/}}', dict(b=[1]) + yield method, 'ab', 'a{{#b}}ignore me{{/b}}{{^b}}b{{/}}', mydict(b=[]) + yield method, 'ab', 'a{{#b}}b{{/b}}{{^b}}ignore me{{/}}', mydict(b=[1]) if not skip_bool: - yield method, 'ab', 'a{{#b}}b{{/b}}{{^b}}ignore me{{/}}', dict(b=True) + yield method, 'ab', 'a{{#b}}b{{/b}}{{^b}}ignore me{{/}}', mydict(b=True) #test ?sections - yield method, 'a- 1 2 3 4', 'a{{?b}}-{{#b}} {{.}}{{/}}{{/}}', dict(b=[1,2,3,4]) + yield method, 'a- 1 2 3 4', 'a{{?b}}-{{#b}} {{.}}{{/}}{{/}}', mydict(b=[1,2,3,4]) if not skip_bool: - yield method, 'a', 'a{{?b}}ignoreme{{/}}', dict(b=False) - yield method, 'a', 'a{{?b}}ignoreme{{/}}', dict(b=[]) - yield method, 'a', 'a{{?b}}ignoreme{{/}}', dict() - yield method, 'abb', 'a{{?b}}b{{/}}{{?b}}b{{/}}', dict(b=[1,2,3]) - yield method, 'ab123d', 'a{{?b}}b{{#b}}{{.}}{{/}}d{{/}}', dict(b=[1,2,3]) + yield method, 'a', 'a{{?b}}ignoreme{{/}}', mydict(b=False) + yield method, 'a', 'a{{?b}}ignoreme{{/}}', mydict(b=[]) + yield method, 'a', 'a{{?b}}ignoreme{{/}}', mydict() + yield method, 'abb', 'a{{?b}}b{{/}}{{?b}}b{{/}}', mydict(b=[1,2,3]) + yield method, 'ab123d', 'a{{?b}}b{{#b}}{{.}}{{/}}d{{/}}', mydict(b=[1,2,3]) #test #section scope - yield method, 'abbbb', 'a{{#b}}b{{/b}}', dict(b=[1,2,3,4]) - yield method, 'a1234', 'a{{#b}}{{.}}{{/b}}', dict(b=[1,2,3,4]) - yield method, 'a a=1 a=2 a=0 a', 'a {{#b}}a={{a}} {{/b}}a', dict(a=0,b=[{'a':1},{'a':2},{'c':1}]) - yield method, '1', '{{#a}}{{#b}}{{c}}{{/b}}{{/a}}', dict(a={'b':{'c':1}}) - yield method, '12', '{{#a}}{{#b}}{{c}}{{/}}{{/a}}', dict(a={'b':[{'c':1}, {'c':2}]}) - yield method, '12', '{{#a}}{{#b}}{{c}}{{/b}}{{/}}', dict(a=[{'b':{'c':1}},{'b':{'c':2}}]) - yield method, '132', '{{#a}}{{#b}}{{c}}{{/}}{{/}}', dict(a=[{'b':[{'c':1}, {'c':3}]},{'b':{'c':2}}]) - yield method, '132456', '{{#a}}{{#b}}{{c}}{{/b}}{{/a}}', dict(a=[{'b':[{'c':1}, {'c':3}]},{'b':{'c':2}},{'b':[{'c':4}, {'c':5}]},{'b':{'c':6}}]) - yield method, '1', '{{#a}}{{#a}}{{c}}{{/a}}{{/a}}', dict(a={'a':{'c':1}}) + yield method, 'abbbb', 'a{{#b}}b{{/b}}', mydict(b=[1,2,3,4]) + yield method, 'a1234', 'a{{#b}}{{.}}{{/b}}', mydict(b=[1,2,3,4]) + yield method, 'a a=1 a=2 a=0 a', 'a {{#b}}a={{a}} {{/b}}a', mydict(a=0,b=[{'a':1},{'a':2},{'c':1}]) + yield method, '1', '{{#a}}{{#b}}{{c}}{{/b}}{{/a}}', mydict(a={'b':{'c':1}}) + yield method, '12', '{{#a}}{{#b}}{{c}}{{/}}{{/a}}', mydict(a={'b':[{'c':1}, {'c':2}]}) + yield method, '12', '{{#a}}{{#b}}{{c}}{{/b}}{{/}}', mydict(a=[{'b':{'c':1}},{'b':{'c':2}}]) + yield method, '132', '{{#a}}{{#b}}{{c}}{{/}}{{/}}', mydict(a=[{'b':[{'c':1}, {'c':3}]},{'b':{'c':2}}]) + yield method, '132456', '{{#a}}{{#b}}{{c}}{{/b}}{{/a}}', mydict(a=[{'b':[{'c':1}, {'c':3}]},{'b':{'c':2}},{'b':[{'c':4}, {'c':5}]},{'b':{'c':6}}]) + yield method, '1', '{{#a}}{{#a}}{{c}}{{/a}}{{/a}}', mydict(a={'a':{'c':1}}) if not skip_bool: yield method, '<3><3><3>', '<{{id}}><{{# a? }}{{id}}{{/ a? }}><{{# b? }}{{id}}{{/ b? }}>', {'id':3,'a?':True, 'b?':True} #test delim - yield method, 'delim{{a}}', '{{=<% %>=}}<%a%>{{a}}', dict(a='delim') - yield method, 'delim{{a}}delim<%a%>', '{{=<% %>=}}<%a%>{{a}}<%={{ }}=%>{{a}}<%a%>', dict(a='delim') + yield method, 'delim{{a}}', '{{=<% %>=}}<%a%>{{a}}', mydict(a='delim') + yield method, 'delim{{a}}delim<%a%>', '{{=<% %>=}}<%a%>{{a}}<%={{ }}=%>{{a}}<%a%>', mydict(a='delim') #test : - yield method, '123test', '123{{:hi}}abc{{/}}', dict(hi='test') - yield method, '123test', '123{{:hi}}{{:hi}}abc{{/}}{{/}}', dict(hi='test') - yield method, '123abc', '123{{:hi}}{{:hi2}}abc{{/}}{{/}}', dict() - yield method, '123cba', '123{{:hi}}{{:hi2}}abc{{/}}{{/}}', dict(hi2='cba') - yield method, '123abc', '123{{:hi}}abc{{/}}', dict() + yield method, '123test', '123{{:hi}}abc{{/}}', mydict(hi='test') + yield method, '123test', '123{{:hi}}{{:hi}}abc{{/}}{{/}}', mydict(hi='test') + yield method, '123abc', '123{{:hi}}{{:hi2}}abc{{/}}{{/}}', mydict() + yield method, '123cba', '123{{:hi}}{{:hi2}}abc{{/}}{{/}}', mydict(hi2='cba') + yield method, '123abc', '123{{:hi}}abc{{/}}', mydict() if not skip_bool: - yield method, '123abc', '123{{:hi}}abc{{/}}', dict(hi=False) - yield method, '123abc', '123{{:hi}}abc{{/}}', dict(hi=[]) - yield method, '123test', '{{<', '{{&a}}', dict(a='><') - yield method, '><', '{{{a}}}', dict(a='><') + yield method, '><', '{{a}}', mydict(a='><') + yield method, '><', '{{&a}}', mydict(a='><') + yield method, '><', '{{{a}}}', mydict(a='><') s = Stache() s.add_template('a', '1') @@ -183,24 +192,24 @@ def test(method=bare): s.add_template('o', 'a{{?b}}b{{#b}}{{.}}{{/}}d{{/}}') def test_partials(method=bare_partial): - yield method, s, '1', 'a', dict() - yield method, s, '1', 'b', dict() - yield method, s, '11', 'c', dict() - yield method, s, '', 'd', dict() - yield method, s, '555', 'd', dict(a={'b':555}) - yield method, s, '123', 'g', dict(a={}) - yield method, s, '555', 'e', dict(a={'b':555}) - yield method, s, '555', 'f', dict(a={'b':555}) - yield method, s, 'i=123', 'h', dict() - yield method, s, 'i=', 'i', dict() - yield method, s, 'showme', 'j', dict() - yield method, s, 'default', 'k', dict() - yield method, s, 'custom', 'k', dict(e="custom") + yield method, s, '1', 'a', mydict() + yield method, s, '1', 'b', mydict() + yield method, s, '11', 'c', mydict() + yield method, s, '', 'd', mydict() + yield method, s, '555', 'd', mydict(a={'b':555}) + yield method, s, '123', 'g', mydict(a={}) + yield method, s, '555', 'e', mydict(a={'b':555}) + yield method, s, '555', 'f', mydict(a={'b':555}) + yield method, s, 'i=123', 'h', mydict() + yield method, s, 'i=', 'i', mydict() + yield method, s, 'showme', 'j', mydict() + yield method, s, 'default', 'k', mydict() + yield method, s, 'custom', 'k', mydict(e="custom") if not skip_bool: yield method, s, '<3><3><3>', 'l', {'id':3,'a':True, 'b':True} yield method, s, '<3><3><3>', 'm', {'id':3,'a?':True, 'b?':True} - yield method, s, 'abb', 'n', dict(b=[1,2,3]) - yield method, s, 'ab123d', 'o', dict(b=[1,2,3]) + yield method, s, 'abb', 'n', mydict(b=[1,2,3]) + yield method, s, 'ab123d', 'o', mydict(b=[1,2,3]) def test_js(method=verify_js): return From 041a808bb5f8b4ce4eb6dc2124f4dab2d2b1fa38 Mon Sep 17 00:00:00 2001 From: clach04 Date: Sat, 9 Jan 2016 21:08:07 -0800 Subject: [PATCH 04/21] Replace str() calls with string_func() calls --- __init__.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/__init__.py b/__init__.py index eaea442..cc6227e 100644 --- a/__init__.py +++ b/__init__.py @@ -25,6 +25,8 @@ def takewhile(predicate, iterable): except ImportError: pass +string_func = str + TOKEN_RAW = intern('raw') TOKEN_TAGOPEN = intern('tagopen') TOKEN_TAGINVERT = intern('taginvert') @@ -127,7 +129,7 @@ def _checkprefix(tag, prefix): def _lookup(data, datum): for scope in data: if datum == '.': - return str(scope) + return string_func(scope) elif datum in scope: return scope[datum] elif hasattr(scope, datum): @@ -350,10 +352,10 @@ def _tokenize(self, template): def _parse(self, tokens, *data): for token in tokens: - #print ' token:' + str(token) + #print ' token:' + string_func(token) tag, content, scope = token if tag == TOKEN_RAW: - yield str(content) + yield string_func(content) elif tag == TOKEN_TAG: tagvalue = _lookup(data, content) #cant use if tagvalue because we need to render tagvalue if it's 0 @@ -362,14 +364,14 @@ def _parse(self, tokens, *data): try: if len(tagvalue) > 0: if scope: - yield str(tagvalue) + yield string_func(tagvalue) else: - yield escape(str(tagvalue)) + yield escape(string_func(tagvalue)) except TypeError: if scope: - yield str(tagvalue) + yield string_func(tagvalue) else: - yield escape(str(tagvalue)) + yield escape(string_func(tagvalue)) elif tag == TOKEN_TAGOPEN or tag == TOKEN_TAGINVERT: tagvalue = _lookup(data, content) untilclose = itertools_takewhile(lambda x: x != (TOKEN_TAGCLOSE, content, scope), tokens) @@ -423,7 +425,7 @@ def _jsparse(self, tokens): for token in tokens: tag, content, scope = token if tag == TOKEN_RAW: - yield "'{0}'".format(str(content)) + yield "'{0}'".format(string_func(content)) elif tag == TOKEN_TAG: if content != '': if scope: From 24d42ee067b8e590355ba6f9a21570929bed1377 Mon Sep 17 00:00:00 2001 From: clach04 Date: Sat, 9 Jan 2016 21:08:36 -0800 Subject: [PATCH 05/21] Now use unicode() for strings --- __init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__init__.py b/__init__.py index cc6227e..4e32949 100644 --- a/__init__.py +++ b/__init__.py @@ -25,7 +25,7 @@ def takewhile(predicate, iterable): except ImportError: pass -string_func = str +string_func = unicode TOKEN_RAW = intern('raw') TOKEN_TAGOPEN = intern('tagopen') From 096a39bb7521686797ef2aa78d8faf5a104ebc77 Mon Sep 17 00:00:00 2001 From: clach04 Date: Fri, 11 Feb 2022 18:48:54 -0800 Subject: [PATCH 06/21] Python 3 and Python 2.x support Merged Unicode branch and code now works on almost all versions of Python --- README.md | 2 ++ __init__.py | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b5b7fef..4f4e44b 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ Implements everything from [Mustache.5](http://mustache.github.com/mustache.5.ht `{{/}}`, Self referencer `{{.}}`, Existence check `{{?exists}}{{/exists}}` and data pusher `{{< blah}}{{/blah}}`, `{{:default}}` +Works with Cpython 2.2 up to and including Python 3.x. Also works with Jython 2.x. + # Also, the ability to compile to javascript code! ## render_js(template_string) diff --git a/__init__.py b/__init__.py index 4e32949..81e4dd9 100644 --- a/__init__.py +++ b/__init__.py @@ -25,7 +25,13 @@ def takewhile(predicate, iterable): except ImportError: pass -string_func = unicode +py_ver = sys.version[:3] +py_v3 = py_ver >= '3.0' + +if py_v3: + string_func = str +else: + string_func = unicode TOKEN_RAW = intern('raw') TOKEN_TAGOPEN = intern('tagopen') From 33434028ec63a905534d76dd4524275a14efcd5c Mon Sep 17 00:00:00 2001 From: clach04 Date: Sat, 12 Feb 2022 14:08:03 -0800 Subject: [PATCH 07/21] Mention staching --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4f4e44b..65c5dec 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,14 @@ Trimmed mustache logic-less templates +Fork of https://github.com/hyperturtle/Stache with support for Cpython 2.2 up to and including Python 3.x. Also works with Jython 2.x. +Also see https://github.com/SmithSamuelM/staching which includes a fix for https://github.com/hyperturtle/Stache/issues/2 but doesn't support older Python versions, use if older Python support is not required. + Implements everything from [Mustache.5](http://mustache.github.com/mustache.5.html) **except for lambdas** in < 200 lines of code. Plus four new things. Implied closing tags `{{/}}`, Self referencer `{{.}}`, Existence check `{{?exists}}{{/exists}}` and data pusher `{{< blah}}{{/blah}}`, `{{:default}}` -Works with Cpython 2.2 up to and including Python 3.x. Also works with Jython 2.x. # Also, the ability to compile to javascript code! @@ -282,4 +284,4 @@ The main reason I liked Mustache in the first place is because of possibility of template reuse. Some future ideas I have is rendering to javascript templates to be used on browser -frontend, bypassing the need for a client side script to compile it into javascript \ No newline at end of file +frontend, bypassing the need for a client side script to compile it into javascript From 2fdee4bf7e9949431ff38c0a9d0c4691618655bb Mon Sep 17 00:00:00 2001 From: clach04 Date: Sat, 12 Feb 2022 14:24:03 -0800 Subject: [PATCH 08/21] Fix #2 trailing blank newlines Extracted code from https://github.com/SmithSamuelM/staching/commit/f2c591ec69cc922c6ffec67e0d66f8047f2f2bf3 NOTE this no longer passes test suite as test suite is white space sensitive, the test suite has not been updated. The test suite stops on failure, more testing needed. --- __init__.py | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/__init__.py b/__init__.py index 81e4dd9..903da82 100644 --- a/__init__.py +++ b/__init__.py @@ -317,6 +317,61 @@ def _tokenize(self, template): else: delim_tag = None + # fix for https://github.com/hyperturtle/Stache/issues/2 from https://github.com/SmithSamuelM/staching/commit/f2c591ec69cc922c6ffec67e0d66f8047f2f2bf3 + if ( open_tag or invert_tag or comment_tag or + partial_tag or push_tag or bool_tag or + booltern_tag or unescape_tag or delim_tag): # not a variable + inline = False + if rest: # strip trailing whitespace and linefeed if present + front, sep, back = rest.partition("\n") # partition at linefeed + if sep: + if not front.strip(): # only whitespace before linefeed + rest = back # removed whitespace and linefeed + #if _debug: print( "open rest strip front: \n%s" % rest) + else: #inline + inline = True + #if _debug: print( "open inline:") + if not inline and pre: #strip trailing whitespace after linefeed if present + front, sep, back = pre.rpartition("\n") + if sep: + if not back.strip(): # only whitespace after linefeed + pre = ''.join((front, sep)) # restore linefeed + #if _debug: print( "open pre strip back: \n%s" % pre) + else: + pre = back.rstrip() #no linefeed so rstrip + #if _debug: print( "open pre rstrip back: \n%s" % pre) + + elif close_tag: + inline = True # section is inline + follow = False # followed by inline + post = '' + + if rest: # see if inline follows + front, sep, back = rest.partition("\n") + if front.strip(): # not empty before linefeed so inline follows + follow = True # inline follows + #if _debug: print( "close follow:") + + if pre: #strip trailing whitespace after prev linefeed if present + front, sep, back = pre.rpartition("\n") + if sep and not back.strip(): # only whitespace after linefeed + inline = False + #if _debug: print() "close not inline:" ) + if follow: + post = back # save spacing for following inline + pre = ''.join((front, sep)) # restore upto linefeed + #if _debug: print( "close pre strip back: \n%s" % pre) + + if not inline and rest: # strip trailing whitespace and linefeed if present + if follow: # restore saved spacing + rest = post + rest + #print( "close follow rest: \n%s" % rest) + front, sep, back = rest.partition("\n") # partition at linefeed + if sep: + if not front.strip(): # only whitespace before linefeed + rest = back # remove trailing whitespace and linefeed + #if _debug: print( "close rest strip front: \n%s" % rest) + if push_tag: pre = pre.rstrip() rest = rest.lstrip() From 469518b3ec84d652996aa69b03f2f970b58aa290 Mon Sep 17 00:00:00 2001 From: clach04 Date: Sat, 19 Feb 2022 13:38:21 -0800 Subject: [PATCH 09/21] Added testsuite.py, simply an exact copy of test.py --- testsuite.py | 262 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 262 insertions(+) create mode 100644 testsuite.py diff --git a/testsuite.py b/testsuite.py new file mode 100644 index 0000000..b8abc6d --- /dev/null +++ b/testsuite.py @@ -0,0 +1,262 @@ +from __future__ import generators +import sys +import warnings +try: + import timeit +except ImportError: + # TODO fake it? + timeit = None +try: + import subprocess +except ImportError: + subprocess = None + +try: + import json +except ImportError: + # TODO import simplejson, etc. + json = None + +from stache import Stache, render, render_js + + +skip_bool = False +if sys.version_info < (2, 3): + skip_bool = True + +def mydict(*args, **kwargs): + """Emulate Python 2.3 dict keyword support + """ + if args: + raise NotImplementedError('args not supported') + if kwargs: + return kwargs + else: + return {} + +def verify(output, template, data): + print("%s with %s" % (template, data)) + result = render(template, data) + result_iter = ''.join(Stache().render_iter(template, data)) + print("Output: %s\n" % output) + print("Result: %s\n" % result) + assert result == output + assert result == result_iter + +def verify_js(output, template, data): + if json is None: + warnings.warn('json module missing, skipping this js test') + return + script = render_js(template) + print("%s with %s" % (template, data)) + result = subprocess.check_output(["node", "-e", "console.log({0}({1}))".format(script, json.dumps(data))]).strip() + print("Result: %s\n" % result) + assert output.lower() == result + +def verify_js_partial(stachio, output, template, data={}): + if json is None: + warnings.warn('json module missing, skipping this js test') + return + print("%s with %s" % (template, data)) + script = stachio.render_js_template(template) + result = subprocess.check_output(["node", "-e", "console.log({0}({1}))".format(script, json.dumps(data))]).strip() + print("Result: %s\n" % result) + assert output.lower() == result + +def bare_js_partial(stachio, output, template, data={}): + if json is None: + warnings.warn('json module missing, skipping this js test') + return + return stachio.render_js_template(template) + +def verify_partial(stachio, output, template, data={}): + print("%s with %s" % (template, data)) + result = stachio.render_template(template, data) + print("Result: %s\n" % result) + assert output == result + +def bench(output, template, data): + if timeit is None: + warnings.warn('timeit module missing, skipping this test') + return + t = timeit.Timer("render('%s', %s)" % (template, data), "from __main__ import render") + print("%.2f\tusec/test > python %s with %s" % (1000000 * t.timeit(number=10000)/10000, template, data)) + +def bench_js(output, template, data): + if timeit is None: + warnings.warn('timeit module missing, skipping this test') + return + t = timeit.Timer("render_js('%s')" % (template), "from __main__ import render_js") + print("%.2f\tusec/test > js %s with %s" % (1000000 * t.timeit(number=10000)/10000, template, data)) + +def bench_partial(stachio, output, template, data={}): + if timeit is None: + warnings.warn('timeit module missing, skipping this test') + return + t = timeit.Timer("s.render_template('%s', %s)" % (template, data), "from __main__ import test_partials, s") + print("%.2f\tusec/test > python partial %s with %s" % (1000000 * t.timeit(number=10000)/10000, template, data)) + +def bench_js_partial(stachio, output, template, data={}): + if timeit is None: + warnings.warn('timeit module missing, skipping this test') + return + t = timeit.Timer("s.render_template('%s', %s)" % (template, data), "from __main__ import test_partials, s") + print("%.2f\tusec/test > js_partial %s with %s" % (1000000 * t.timeit(number=10000)/10000, template, data)) + +def bare(output, template, data): + return render(template, data) + +def bare_js(output, template, data): + script = render_js(template) + +def bare_partial(stachio, output, template, data={}): + return stachio.render_template(template, data) + +def test(method=bare): + # test tag lookup + yield method, 'a10c', 'a{{b}}c', mydict(b=10) + yield method, 'a10c', 'a{{b}}c', mydict(b=10) + yield method, 'ac', 'a{{b}}c', mydict(c=10) + yield method, 'a10c', 'a{{b}}c', mydict(b='10') + yield method, 'acde', 'a{{!b}}cde', mydict(b='10') + if not skip_bool: + yield method, 'aTrue', 'a{{b}}', mydict(b=True) + yield method, 'a123', 'a{{b}}{{c}}{{d}}', mydict(b=1,c=2,d=3) + # test falsy #sections + if not skip_bool: + yield method, 'ab', 'a{{#b}}b{{/b}}', mydict(b=True) + yield method, 'a', 'a{{^b}}b{{/b}}', mydict(b=True) + yield method, 'a', 'a{{#b}}b{{/}}', mydict(b=False) + yield method, 'ab', 'a{{^b}}b{{/b}}', mydict(b=False) + #test invert sections + yield method, 'ab', 'a{{#b}}ignore me{{/b}}{{^b}}b{{/}}', mydict(b=[]) + yield method, 'ab', 'a{{#b}}b{{/b}}{{^b}}ignore me{{/}}', mydict(b=[1]) + if not skip_bool: + yield method, 'ab', 'a{{#b}}b{{/b}}{{^b}}ignore me{{/}}', mydict(b=True) + #test ?sections + yield method, 'a- 1 2 3 4', 'a{{?b}}-{{#b}} {{.}}{{/}}{{/}}', mydict(b=[1,2,3,4]) + if not skip_bool: + yield method, 'a', 'a{{?b}}ignoreme{{/}}', mydict(b=False) + yield method, 'a', 'a{{?b}}ignoreme{{/}}', mydict(b=[]) + yield method, 'a', 'a{{?b}}ignoreme{{/}}', mydict() + yield method, 'abb', 'a{{?b}}b{{/}}{{?b}}b{{/}}', mydict(b=[1,2,3]) + yield method, 'ab123d', 'a{{?b}}b{{#b}}{{.}}{{/}}d{{/}}', mydict(b=[1,2,3]) + #test #section scope + yield method, 'abbbb', 'a{{#b}}b{{/b}}', mydict(b=[1,2,3,4]) + yield method, 'a1234', 'a{{#b}}{{.}}{{/b}}', mydict(b=[1,2,3,4]) + yield method, 'a a=1 a=2 a=0 a', 'a {{#b}}a={{a}} {{/b}}a', mydict(a=0,b=[{'a':1},{'a':2},{'c':1}]) + yield method, '1', '{{#a}}{{#b}}{{c}}{{/b}}{{/a}}', mydict(a={'b':{'c':1}}) + yield method, '12', '{{#a}}{{#b}}{{c}}{{/}}{{/a}}', mydict(a={'b':[{'c':1}, {'c':2}]}) + yield method, '12', '{{#a}}{{#b}}{{c}}{{/b}}{{/}}', mydict(a=[{'b':{'c':1}},{'b':{'c':2}}]) + yield method, '132', '{{#a}}{{#b}}{{c}}{{/}}{{/}}', mydict(a=[{'b':[{'c':1}, {'c':3}]},{'b':{'c':2}}]) + yield method, '132456', '{{#a}}{{#b}}{{c}}{{/b}}{{/a}}', mydict(a=[{'b':[{'c':1}, {'c':3}]},{'b':{'c':2}},{'b':[{'c':4}, {'c':5}]},{'b':{'c':6}}]) + yield method, '1', '{{#a}}{{#a}}{{c}}{{/a}}{{/a}}', mydict(a={'a':{'c':1}}) + if not skip_bool: + yield method, '<3><3><3>', '<{{id}}><{{# a? }}{{id}}{{/ a? }}><{{# b? }}{{id}}{{/ b? }}>', {'id':3,'a?':True, 'b?':True} + #test delim + yield method, 'delim{{a}}', '{{=<% %>=}}<%a%>{{a}}', mydict(a='delim') + yield method, 'delim{{a}}delim<%a%>', '{{=<% %>=}}<%a%>{{a}}<%={{ }}=%>{{a}}<%a%>', mydict(a='delim') + #test : + yield method, '123test', '123{{:hi}}abc{{/}}', mydict(hi='test') + yield method, '123test', '123{{:hi}}{{:hi}}abc{{/}}{{/}}', mydict(hi='test') + yield method, '123abc', '123{{:hi}}{{:hi2}}abc{{/}}{{/}}', mydict() + yield method, '123cba', '123{{:hi}}{{:hi2}}abc{{/}}{{/}}', mydict(hi2='cba') + yield method, '123abc', '123{{:hi}}abc{{/}}', mydict() + if not skip_bool: + yield method, '123abc', '123{{:hi}}abc{{/}}', mydict(hi=False) + yield method, '123abc', '123{{:hi}}abc{{/}}', mydict(hi=[]) + yield method, '123test', '{{<', '{{&a}}', mydict(a='><') + yield method, '><', '{{{a}}}', mydict(a='><') + +s = Stache() +s.add_template('a', '1') +s.add_template('b', '{{>a}}') +s.add_template('c', '{{>a}}{{>b}}') +s.add_template('d', '{{#a}}{{b}}{{/a}}') +s.add_template('e', '{{>d}}') +s.add_template('f', '{{>e}}') +s.add_template('g', '{{i}}') +s.add_template('i', 'i={{e}}') +s.add_template('j', 'show{{!ignoreme}}me') +s.add_template('k', '{{:e}}default{{/}}') +s.add_template('l', '<{{id}}><{{# a }}{{id}}{{/ a }}><{{# b }}{{id}}{{/ b }}>') +s.add_template('m', '<{{id}}><{{# a? }}{{id}}{{/ a? }}><{{# b? }}{{id}}{{/ b? }}>') +s.add_template('n', 'a{{?b}}b{{/}}{{?b}}b{{/}}') +s.add_template('o', 'a{{?b}}b{{#b}}{{.}}{{/}}d{{/}}') + +def test_partials(method=bare_partial): + yield method, s, '1', 'a', mydict() + yield method, s, '1', 'b', mydict() + yield method, s, '11', 'c', mydict() + yield method, s, '', 'd', mydict() + yield method, s, '555', 'd', mydict(a={'b':555}) + yield method, s, '123', 'g', mydict(a={}) + yield method, s, '555', 'e', mydict(a={'b':555}) + yield method, s, '555', 'f', mydict(a={'b':555}) + yield method, s, 'i=123', 'h', mydict() + yield method, s, 'i=', 'i', mydict() + yield method, s, 'showme', 'j', mydict() + yield method, s, 'default', 'k', mydict() + yield method, s, 'custom', 'k', mydict(e="custom") + if not skip_bool: + yield method, s, '<3><3><3>', 'l', {'id':3,'a':True, 'b':True} + yield method, s, '<3><3><3>', 'm', {'id':3,'a?':True, 'b?':True} + yield method, s, 'abb', 'n', mydict(b=[1,2,3]) + yield method, s, 'ab123d', 'o', mydict(b=[1,2,3]) + +def test_js(method=verify_js): + return + for x in test(method): + yield x + +def null(*args, **kwargs): + return + +def run(method=bare, method_partial=bare_partial, method_js=bare_js, method_js_partial=bare_js_partial): + for x in test(method): + x[0](*x[1:]) + for x in test_partials(method_partial): + x[0](*x[1:]) + for x in test_js(method_js): + x[0](*x[1:]) + for x in test_partials(method_js_partial): + x[0](*x[1:]) + +def test_js_all(): + return + expected = [] + script = 't = (' + script += s.render_all_js() + script += ')();\n' + for x in test_partials(): + script += "console.log(t['{1}']([{2}]));\n".format(x[2], x[3], json.dumps(x[4])) + expected.append(x[2]) + res = subprocess.check_output(["node", "-e", script]).split('\n')[:-1] + assert res == expected + +if __name__ == '__main__': + + print('starting tests') + run(verify, verify_partial, verify_js, verify_js_partial) + print('finished tests') + print('testing js export templates') + test_js_all() + print('starting individual benchmarks') + run(bench, bench_partial, bench_js, bench_js_partial) + if timeit is None: + warnings.warn('timeit module missing, skipping this test') + else: + print('starting combined python benchmark') + t = timeit.Timer("run(method_js=null, method_js_partial=null)", "from __main__ import run, bare, null") + print("%.2f\tusec/all tests" % (1000000 * t.timeit(number=10000)/10000)) + print('starting combined js benchmark') + t = timeit.Timer("run(method=null, method_partial=null)", "from __main__ import run, bare, null") + print("%.2f\tusec/all js tests" % (1000000 * t.timeit(number=10000)/10000)) + From e08faf46e6887ac7665a0903bc0b5edd05a55e62 Mon Sep 17 00:00:00 2001 From: clach04 Date: Sat, 19 Feb 2022 13:39:40 -0800 Subject: [PATCH 10/21] Quick removal of js/javascript/node tests frp, test suite --- testsuite.py | 68 +++------------------------------------------------- 1 file changed, 3 insertions(+), 65 deletions(-) diff --git a/testsuite.py b/testsuite.py index b8abc6d..4b26c26 100644 --- a/testsuite.py +++ b/testsuite.py @@ -43,32 +43,6 @@ def verify(output, template, data): assert result == output assert result == result_iter -def verify_js(output, template, data): - if json is None: - warnings.warn('json module missing, skipping this js test') - return - script = render_js(template) - print("%s with %s" % (template, data)) - result = subprocess.check_output(["node", "-e", "console.log({0}({1}))".format(script, json.dumps(data))]).strip() - print("Result: %s\n" % result) - assert output.lower() == result - -def verify_js_partial(stachio, output, template, data={}): - if json is None: - warnings.warn('json module missing, skipping this js test') - return - print("%s with %s" % (template, data)) - script = stachio.render_js_template(template) - result = subprocess.check_output(["node", "-e", "console.log({0}({1}))".format(script, json.dumps(data))]).strip() - print("Result: %s\n" % result) - assert output.lower() == result - -def bare_js_partial(stachio, output, template, data={}): - if json is None: - warnings.warn('json module missing, skipping this js test') - return - return stachio.render_js_template(template) - def verify_partial(stachio, output, template, data={}): print("%s with %s" % (template, data)) result = stachio.render_template(template, data) @@ -106,9 +80,6 @@ def bench_js_partial(stachio, output, template, data={}): def bare(output, template, data): return render(template, data) -def bare_js(output, template, data): - script = render_js(template) - def bare_partial(stachio, output, template, data={}): return stachio.render_template(template, data) @@ -211,52 +182,19 @@ def test_partials(method=bare_partial): yield method, s, 'abb', 'n', mydict(b=[1,2,3]) yield method, s, 'ab123d', 'o', mydict(b=[1,2,3]) -def test_js(method=verify_js): - return - for x in test(method): - yield x - def null(*args, **kwargs): return -def run(method=bare, method_partial=bare_partial, method_js=bare_js, method_js_partial=bare_js_partial): +def run(method=bare, method_partial=bare_partial): for x in test(method): + print(x[0].__name__, x[0], x[1:]) x[0](*x[1:]) for x in test_partials(method_partial): x[0](*x[1:]) - for x in test_js(method_js): - x[0](*x[1:]) - for x in test_partials(method_js_partial): - x[0](*x[1:]) -def test_js_all(): - return - expected = [] - script = 't = (' - script += s.render_all_js() - script += ')();\n' - for x in test_partials(): - script += "console.log(t['{1}']([{2}]));\n".format(x[2], x[3], json.dumps(x[4])) - expected.append(x[2]) - res = subprocess.check_output(["node", "-e", script]).split('\n')[:-1] - assert res == expected if __name__ == '__main__': print('starting tests') - run(verify, verify_partial, verify_js, verify_js_partial) + run(verify, verify_partial) print('finished tests') - print('testing js export templates') - test_js_all() - print('starting individual benchmarks') - run(bench, bench_partial, bench_js, bench_js_partial) - if timeit is None: - warnings.warn('timeit module missing, skipping this test') - else: - print('starting combined python benchmark') - t = timeit.Timer("run(method_js=null, method_js_partial=null)", "from __main__ import run, bare, null") - print("%.2f\tusec/all tests" % (1000000 * t.timeit(number=10000)/10000)) - print('starting combined js benchmark') - t = timeit.Timer("run(method=null, method_partial=null)", "from __main__ import run, bare, null") - print("%.2f\tusec/all js tests" % (1000000 * t.timeit(number=10000)/10000)) - From 149ce29b7c4bdfeed03f49b8cf064602b760f779 Mon Sep 17 00:00:00 2001 From: clach04 Date: Sat, 19 Feb 2022 13:40:56 -0800 Subject: [PATCH 11/21] Updated testsuite.py to generate a unittest that uses standard python unittest --- testsuite.py | 93 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 88 insertions(+), 5 deletions(-) diff --git a/testsuite.py b/testsuite.py index 4b26c26..96d281a 100644 --- a/testsuite.py +++ b/testsuite.py @@ -186,15 +186,98 @@ def null(*args, **kwargs): return def run(method=bare, method_partial=bare_partial): + print('''class BaseTest(unittest.TestCase): + pass +''') + + + + print('class %s(BaseTest):' % method.__name__) + print(''' + def stache_verify(self, output, template, data): + #print("%s with %s" % (template, data)) + result = render(template, data) + result_iter = ''.join(Stache().render_iter(template, data)) + #print("Output: %s\\n" % output) + #print("Result: %s\\n" % result) + #assert result == output + self.assertEqual(result, output) + #assert result == result_iter + self.assertEqual(result, result_iter) + +''') + test_counter = 0 for x in test(method): - print(x[0].__name__, x[0], x[1:]) - x[0](*x[1:]) + test_counter += 1 + print(''' + def test_%s_%02d(self): + self.stache_verify%r +''' % (x[0].__name__, test_counter, x[1:],)) + #print(x[0].__name__, x[0], x[1:]) + #raise shields + #x[0](*x[1:]) + + + print(''' +s = Stache() +s.add_template('a', '1') +s.add_template('b', '{{>a}}') +s.add_template('c', '{{>a}}{{>b}}') +s.add_template('d', '{{#a}}{{b}}{{/a}}') +s.add_template('e', '{{>d}}') +s.add_template('f', '{{>e}}') +s.add_template('g', '{{i}}') +s.add_template('i', 'i={{e}}') +s.add_template('j', 'show{{!ignoreme}}me') +s.add_template('k', '{{:e}}default{{/}}') +s.add_template('l', '<{{id}}><{{# a }}{{id}}{{/ a }}><{{# b }}{{id}}{{/ b }}>') +s.add_template('m', '<{{id}}><{{# a? }}{{id}}{{/ a? }}><{{# b? }}{{id}}{{/ b? }}>') +s.add_template('n', 'a{{?b}}b{{/}}{{?b}}b{{/}}') +s.add_template('o', 'a{{?b}}b{{#b}}{{.}}{{/}}d{{/}}') + +''') + print('class %s(BaseTest):' % method_partial.__name__) + print(''' + def stache_verify_partial(self, output, template, data={}): + stachio = s + #print("%s with %s" % (template, data)) + result = stachio.render_template(template, data) + #print("Result: %s\\n" % result) + #assert output == result + self.assertEqual(result, output) + +''') + test_counter = 0 for x in test_partials(method_partial): - x[0](*x[1:]) + test_counter += 1 + #print(x) + print(''' + def test_%s_%02d(self): + self.stache_verify_partial%r +''' % (x[0].__name__, test_counter, x[2:],)) + #x[0](*x[1:]) + + print(''' +def main(argv=None): + if argv is None: + argv = sys.argv + + print('Python %s on %s' %(sys.version, sys.platform)) + unittest.main() + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) +''') if __name__ == '__main__': - print('starting tests') + print('# start generated code') + #print('starting tests') run(verify, verify_partial) - print('finished tests') + #print('finished tests') + print('# end generated code') From 70783f9aaf183479f52a9ad8a83f4ec57baf1dc1 Mon Sep 17 00:00:00 2001 From: clach04 Date: Sat, 19 Feb 2022 13:42:57 -0800 Subject: [PATCH 12/21] Replaced testsuite.py with output from testsuite.py --- testsuite.py | 492 +++++++++++++++++++++++++++------------------------ 1 file changed, 260 insertions(+), 232 deletions(-) diff --git a/testsuite.py b/testsuite.py index 96d281a..3bd65bb 100644 --- a/testsuite.py +++ b/testsuite.py @@ -1,224 +1,202 @@ -from __future__ import generators -import sys -import warnings -try: - import timeit -except ImportError: - # TODO fake it? - timeit = None -try: - import subprocess -except ImportError: - subprocess = None - -try: - import json -except ImportError: - # TODO import simplejson, etc. - json = None - -from stache import Stache, render, render_js - - -skip_bool = False -if sys.version_info < (2, 3): - skip_bool = True - -def mydict(*args, **kwargs): - """Emulate Python 2.3 dict keyword support - """ - if args: - raise NotImplementedError('args not supported') - if kwargs: - return kwargs - else: - return {} - -def verify(output, template, data): - print("%s with %s" % (template, data)) - result = render(template, data) - result_iter = ''.join(Stache().render_iter(template, data)) - print("Output: %s\n" % output) - print("Result: %s\n" % result) - assert result == output - assert result == result_iter - -def verify_partial(stachio, output, template, data={}): - print("%s with %s" % (template, data)) - result = stachio.render_template(template, data) - print("Result: %s\n" % result) - assert output == result - -def bench(output, template, data): - if timeit is None: - warnings.warn('timeit module missing, skipping this test') - return - t = timeit.Timer("render('%s', %s)" % (template, data), "from __main__ import render") - print("%.2f\tusec/test > python %s with %s" % (1000000 * t.timeit(number=10000)/10000, template, data)) - -def bench_js(output, template, data): - if timeit is None: - warnings.warn('timeit module missing, skipping this test') - return - t = timeit.Timer("render_js('%s')" % (template), "from __main__ import render_js") - print("%.2f\tusec/test > js %s with %s" % (1000000 * t.timeit(number=10000)/10000, template, data)) - -def bench_partial(stachio, output, template, data={}): - if timeit is None: - warnings.warn('timeit module missing, skipping this test') - return - t = timeit.Timer("s.render_template('%s', %s)" % (template, data), "from __main__ import test_partials, s") - print("%.2f\tusec/test > python partial %s with %s" % (1000000 * t.timeit(number=10000)/10000, template, data)) - -def bench_js_partial(stachio, output, template, data={}): - if timeit is None: - warnings.warn('timeit module missing, skipping this test') - return - t = timeit.Timer("s.render_template('%s', %s)" % (template, data), "from __main__ import test_partials, s") - print("%.2f\tusec/test > js_partial %s with %s" % (1000000 * t.timeit(number=10000)/10000, template, data)) - -def bare(output, template, data): - return render(template, data) - -def bare_partial(stachio, output, template, data={}): - return stachio.render_template(template, data) - -def test(method=bare): - # test tag lookup - yield method, 'a10c', 'a{{b}}c', mydict(b=10) - yield method, 'a10c', 'a{{b}}c', mydict(b=10) - yield method, 'ac', 'a{{b}}c', mydict(c=10) - yield method, 'a10c', 'a{{b}}c', mydict(b='10') - yield method, 'acde', 'a{{!b}}cde', mydict(b='10') - if not skip_bool: - yield method, 'aTrue', 'a{{b}}', mydict(b=True) - yield method, 'a123', 'a{{b}}{{c}}{{d}}', mydict(b=1,c=2,d=3) - # test falsy #sections - if not skip_bool: - yield method, 'ab', 'a{{#b}}b{{/b}}', mydict(b=True) - yield method, 'a', 'a{{^b}}b{{/b}}', mydict(b=True) - yield method, 'a', 'a{{#b}}b{{/}}', mydict(b=False) - yield method, 'ab', 'a{{^b}}b{{/b}}', mydict(b=False) - #test invert sections - yield method, 'ab', 'a{{#b}}ignore me{{/b}}{{^b}}b{{/}}', mydict(b=[]) - yield method, 'ab', 'a{{#b}}b{{/b}}{{^b}}ignore me{{/}}', mydict(b=[1]) - if not skip_bool: - yield method, 'ab', 'a{{#b}}b{{/b}}{{^b}}ignore me{{/}}', mydict(b=True) - #test ?sections - yield method, 'a- 1 2 3 4', 'a{{?b}}-{{#b}} {{.}}{{/}}{{/}}', mydict(b=[1,2,3,4]) - if not skip_bool: - yield method, 'a', 'a{{?b}}ignoreme{{/}}', mydict(b=False) - yield method, 'a', 'a{{?b}}ignoreme{{/}}', mydict(b=[]) - yield method, 'a', 'a{{?b}}ignoreme{{/}}', mydict() - yield method, 'abb', 'a{{?b}}b{{/}}{{?b}}b{{/}}', mydict(b=[1,2,3]) - yield method, 'ab123d', 'a{{?b}}b{{#b}}{{.}}{{/}}d{{/}}', mydict(b=[1,2,3]) - #test #section scope - yield method, 'abbbb', 'a{{#b}}b{{/b}}', mydict(b=[1,2,3,4]) - yield method, 'a1234', 'a{{#b}}{{.}}{{/b}}', mydict(b=[1,2,3,4]) - yield method, 'a a=1 a=2 a=0 a', 'a {{#b}}a={{a}} {{/b}}a', mydict(a=0,b=[{'a':1},{'a':2},{'c':1}]) - yield method, '1', '{{#a}}{{#b}}{{c}}{{/b}}{{/a}}', mydict(a={'b':{'c':1}}) - yield method, '12', '{{#a}}{{#b}}{{c}}{{/}}{{/a}}', mydict(a={'b':[{'c':1}, {'c':2}]}) - yield method, '12', '{{#a}}{{#b}}{{c}}{{/b}}{{/}}', mydict(a=[{'b':{'c':1}},{'b':{'c':2}}]) - yield method, '132', '{{#a}}{{#b}}{{c}}{{/}}{{/}}', mydict(a=[{'b':[{'c':1}, {'c':3}]},{'b':{'c':2}}]) - yield method, '132456', '{{#a}}{{#b}}{{c}}{{/b}}{{/a}}', mydict(a=[{'b':[{'c':1}, {'c':3}]},{'b':{'c':2}},{'b':[{'c':4}, {'c':5}]},{'b':{'c':6}}]) - yield method, '1', '{{#a}}{{#a}}{{c}}{{/a}}{{/a}}', mydict(a={'a':{'c':1}}) - if not skip_bool: - yield method, '<3><3><3>', '<{{id}}><{{# a? }}{{id}}{{/ a? }}><{{# b? }}{{id}}{{/ b? }}>', {'id':3,'a?':True, 'b?':True} - #test delim - yield method, 'delim{{a}}', '{{=<% %>=}}<%a%>{{a}}', mydict(a='delim') - yield method, 'delim{{a}}delim<%a%>', '{{=<% %>=}}<%a%>{{a}}<%={{ }}=%>{{a}}<%a%>', mydict(a='delim') - #test : - yield method, '123test', '123{{:hi}}abc{{/}}', mydict(hi='test') - yield method, '123test', '123{{:hi}}{{:hi}}abc{{/}}{{/}}', mydict(hi='test') - yield method, '123abc', '123{{:hi}}{{:hi2}}abc{{/}}{{/}}', mydict() - yield method, '123cba', '123{{:hi}}{{:hi2}}abc{{/}}{{/}}', mydict(hi2='cba') - yield method, '123abc', '123{{:hi}}abc{{/}}', mydict() - if not skip_bool: - yield method, '123abc', '123{{:hi}}abc{{/}}', mydict(hi=False) - yield method, '123abc', '123{{:hi}}abc{{/}}', mydict(hi=[]) - yield method, '123test', '{{<', '{{&a}}', mydict(a='><') - yield method, '><', '{{{a}}}', mydict(a='><') - -s = Stache() -s.add_template('a', '1') -s.add_template('b', '{{>a}}') -s.add_template('c', '{{>a}}{{>b}}') -s.add_template('d', '{{#a}}{{b}}{{/a}}') -s.add_template('e', '{{>d}}') -s.add_template('f', '{{>e}}') -s.add_template('g', '{{i}}') -s.add_template('i', 'i={{e}}') -s.add_template('j', 'show{{!ignoreme}}me') -s.add_template('k', '{{:e}}default{{/}}') -s.add_template('l', '<{{id}}><{{# a }}{{id}}{{/ a }}><{{# b }}{{id}}{{/ b }}>') -s.add_template('m', '<{{id}}><{{# a? }}{{id}}{{/ a? }}><{{# b? }}{{id}}{{/ b? }}>') -s.add_template('n', 'a{{?b}}b{{/}}{{?b}}b{{/}}') -s.add_template('o', 'a{{?b}}b{{#b}}{{.}}{{/}}d{{/}}') - -def test_partials(method=bare_partial): - yield method, s, '1', 'a', mydict() - yield method, s, '1', 'b', mydict() - yield method, s, '11', 'c', mydict() - yield method, s, '', 'd', mydict() - yield method, s, '555', 'd', mydict(a={'b':555}) - yield method, s, '123', 'g', mydict(a={}) - yield method, s, '555', 'e', mydict(a={'b':555}) - yield method, s, '555', 'f', mydict(a={'b':555}) - yield method, s, 'i=123', 'h', mydict() - yield method, s, 'i=', 'i', mydict() - yield method, s, 'showme', 'j', mydict() - yield method, s, 'default', 'k', mydict() - yield method, s, 'custom', 'k', mydict(e="custom") - if not skip_bool: - yield method, s, '<3><3><3>', 'l', {'id':3,'a':True, 'b':True} - yield method, s, '<3><3><3>', 'm', {'id':3,'a?':True, 'b?':True} - yield method, s, 'abb', 'n', mydict(b=[1,2,3]) - yield method, s, 'ab123d', 'o', mydict(b=[1,2,3]) - -def null(*args, **kwargs): - return - -def run(method=bare, method_partial=bare_partial): - print('''class BaseTest(unittest.TestCase): +# start generated code +class BaseTest(unittest.TestCase): pass -''') - +class verify(BaseTest): - print('class %s(BaseTest):' % method.__name__) - print(''' def stache_verify(self, output, template, data): #print("%s with %s" % (template, data)) result = render(template, data) result_iter = ''.join(Stache().render_iter(template, data)) - #print("Output: %s\\n" % output) - #print("Result: %s\\n" % result) + #print("Output: %s\n" % output) + #print("Result: %s\n" % result) #assert result == output self.assertEqual(result, output) #assert result == result_iter self.assertEqual(result, result_iter) -''') - test_counter = 0 - for x in test(method): - test_counter += 1 - print(''' - def test_%s_%02d(self): - self.stache_verify%r -''' % (x[0].__name__, test_counter, x[1:],)) - #print(x[0].__name__, x[0], x[1:]) - #raise shields - #x[0](*x[1:]) - print(''' + def test_verify_01(self): + self.stache_verify('a10c', 'a{{b}}c', {'b': 10}) + + + def test_verify_02(self): + self.stache_verify('a10c', 'a{{b}}c', {'b': 10}) + + + def test_verify_03(self): + self.stache_verify('ac', 'a{{b}}c', {'c': 10}) + + + def test_verify_04(self): + self.stache_verify('a10c', 'a{{b}}c', {'b': '10'}) + + + def test_verify_05(self): + self.stache_verify('acde', 'a{{!b}}cde', {'b': '10'}) + + + def test_verify_06(self): + self.stache_verify('aTrue', 'a{{b}}', {'b': True}) + + + def test_verify_07(self): + self.stache_verify('a123', 'a{{b}}{{c}}{{d}}', {'c': 2, 'b': 1, 'd': 3}) + + + def test_verify_08(self): + self.stache_verify('ab', 'a{{#b}}b{{/b}}', {'b': True}) + + + def test_verify_09(self): + self.stache_verify('a', 'a{{^b}}b{{/b}}', {'b': True}) + + + def test_verify_10(self): + self.stache_verify('a', 'a{{#b}}b{{/}}', {'b': False}) + + + def test_verify_11(self): + self.stache_verify('ab', 'a{{^b}}b{{/b}}', {'b': False}) + + + def test_verify_12(self): + self.stache_verify('ab', 'a{{#b}}ignore me{{/b}}{{^b}}b{{/}}', {'b': []}) + + + def test_verify_13(self): + self.stache_verify('ab', 'a{{#b}}b{{/b}}{{^b}}ignore me{{/}}', {'b': [1]}) + + + def test_verify_14(self): + self.stache_verify('ab', 'a{{#b}}b{{/b}}{{^b}}ignore me{{/}}', {'b': True}) + + + def test_verify_15(self): + self.stache_verify('a- 1 2 3 4', 'a{{?b}}-{{#b}} {{.}}{{/}}{{/}}', {'b': [1, 2, 3, 4]}) + + + def test_verify_16(self): + self.stache_verify('a', 'a{{?b}}ignoreme{{/}}', {'b': False}) + + + def test_verify_17(self): + self.stache_verify('a', 'a{{?b}}ignoreme{{/}}', {'b': []}) + + + def test_verify_18(self): + self.stache_verify('a', 'a{{?b}}ignoreme{{/}}', {}) + + + def test_verify_19(self): + self.stache_verify('abb', 'a{{?b}}b{{/}}{{?b}}b{{/}}', {'b': [1, 2, 3]}) + + + def test_verify_20(self): + self.stache_verify('ab123d', 'a{{?b}}b{{#b}}{{.}}{{/}}d{{/}}', {'b': [1, 2, 3]}) + + + def test_verify_21(self): + self.stache_verify('abbbb', 'a{{#b}}b{{/b}}', {'b': [1, 2, 3, 4]}) + + + def test_verify_22(self): + self.stache_verify('a1234', 'a{{#b}}{{.}}{{/b}}', {'b': [1, 2, 3, 4]}) + + + def test_verify_23(self): + self.stache_verify('a a=1 a=2 a=0 a', 'a {{#b}}a={{a}} {{/b}}a', {'a': 0, 'b': [{'a': 1}, {'a': 2}, {'c': 1}]}) + + + def test_verify_24(self): + self.stache_verify('1', '{{#a}}{{#b}}{{c}}{{/b}}{{/a}}', {'a': {'b': {'c': 1}}}) + + + def test_verify_25(self): + self.stache_verify('12', '{{#a}}{{#b}}{{c}}{{/}}{{/a}}', {'a': {'b': [{'c': 1}, {'c': 2}]}}) + + + def test_verify_26(self): + self.stache_verify('12', '{{#a}}{{#b}}{{c}}{{/b}}{{/}}', {'a': [{'b': {'c': 1}}, {'b': {'c': 2}}]}) + + + def test_verify_27(self): + self.stache_verify('132', '{{#a}}{{#b}}{{c}}{{/}}{{/}}', {'a': [{'b': [{'c': 1}, {'c': 3}]}, {'b': {'c': 2}}]}) + + + def test_verify_28(self): + self.stache_verify('132456', '{{#a}}{{#b}}{{c}}{{/b}}{{/a}}', {'a': [{'b': [{'c': 1}, {'c': 3}]}, {'b': {'c': 2}}, {'b': [{'c': 4}, {'c': 5}]}, {'b': {'c': 6}}]}) + + + def test_verify_29(self): + self.stache_verify('1', '{{#a}}{{#a}}{{c}}{{/a}}{{/a}}', {'a': {'a': {'c': 1}}}) + + + def test_verify_30(self): + self.stache_verify('<3><3><3>', '<{{id}}><{{# a? }}{{id}}{{/ a? }}><{{# b? }}{{id}}{{/ b? }}>', {'b?': True, 'id': 3, 'a?': True}) + + + def test_verify_31(self): + self.stache_verify('delim{{a}}', '{{=<% %>=}}<%a%>{{a}}', {'a': 'delim'}) + + + def test_verify_32(self): + self.stache_verify('delim{{a}}delim<%a%>', '{{=<% %>=}}<%a%>{{a}}<%={{ }}=%>{{a}}<%a%>', {'a': 'delim'}) + + + def test_verify_33(self): + self.stache_verify('123test', '123{{:hi}}abc{{/}}', {'hi': 'test'}) + + + def test_verify_34(self): + self.stache_verify('123test', '123{{:hi}}{{:hi}}abc{{/}}{{/}}', {'hi': 'test'}) + + + def test_verify_35(self): + self.stache_verify('123abc', '123{{:hi}}{{:hi2}}abc{{/}}{{/}}', {}) + + + def test_verify_36(self): + self.stache_verify('123cba', '123{{:hi}}{{:hi2}}abc{{/}}{{/}}', {'hi2': 'cba'}) + + + def test_verify_37(self): + self.stache_verify('123abc', '123{{:hi}}abc{{/}}', {}) + + + def test_verify_38(self): + self.stache_verify('123abc', '123{{:hi}}abc{{/}}', {'hi': False}) + + + def test_verify_39(self): + self.stache_verify('123abc', '123{{:hi}}abc{{/}}', {'hi': []}) + + + def test_verify_40(self): + self.stache_verify('123test', '{{<'}) + + + def test_verify_44(self): + self.stache_verify('><', '{{&a}}', {'a': '><'}) + + + def test_verify_45(self): + self.stache_verify('><', '{{{a}}}', {'a': '><'}) + + s = Stache() s.add_template('a', '1') s.add_template('b', '{{>a}}') @@ -236,29 +214,87 @@ def test_%s_%02d(self): s.add_template('n', 'a{{?b}}b{{/}}{{?b}}b{{/}}') s.add_template('o', 'a{{?b}}b{{#b}}{{.}}{{/}}d{{/}}') -''') - print('class %s(BaseTest):' % method_partial.__name__) - print(''' + +class verify_partial(BaseTest): + def stache_verify_partial(self, output, template, data={}): stachio = s #print("%s with %s" % (template, data)) result = stachio.render_template(template, data) - #print("Result: %s\\n" % result) + #print("Result: %s\n" % result) #assert output == result self.assertEqual(result, output) -''') - test_counter = 0 - for x in test_partials(method_partial): - test_counter += 1 - #print(x) - print(''' - def test_%s_%02d(self): - self.stache_verify_partial%r -''' % (x[0].__name__, test_counter, x[2:],)) - #x[0](*x[1:]) - - print(''' + + + def test_verify_partial_01(self): + self.stache_verify_partial('1', 'a', {}) + + + def test_verify_partial_02(self): + self.stache_verify_partial('1', 'b', {}) + + + def test_verify_partial_03(self): + self.stache_verify_partial('11', 'c', {}) + + + def test_verify_partial_04(self): + self.stache_verify_partial('', 'd', {}) + + + def test_verify_partial_05(self): + self.stache_verify_partial('555', 'd', {'a': {'b': 555}}) + + + def test_verify_partial_06(self): + self.stache_verify_partial('123', 'g', {'a': {}}) + + + def test_verify_partial_07(self): + self.stache_verify_partial('555', 'e', {'a': {'b': 555}}) + + + def test_verify_partial_08(self): + self.stache_verify_partial('555', 'f', {'a': {'b': 555}}) + + + def test_verify_partial_09(self): + self.stache_verify_partial('i=123', 'h', {}) + + + def test_verify_partial_10(self): + self.stache_verify_partial('i=', 'i', {}) + + + def test_verify_partial_11(self): + self.stache_verify_partial('showme', 'j', {}) + + + def test_verify_partial_12(self): + self.stache_verify_partial('default', 'k', {}) + + + def test_verify_partial_13(self): + self.stache_verify_partial('custom', 'k', {'e': 'custom'}) + + + def test_verify_partial_14(self): + self.stache_verify_partial('<3><3><3>', 'l', {'a': True, 'b': True, 'id': 3}) + + + def test_verify_partial_15(self): + self.stache_verify_partial('<3><3><3>', 'm', {'b?': True, 'id': 3, 'a?': True}) + + + def test_verify_partial_16(self): + self.stache_verify_partial('abb', 'n', {'b': [1, 2, 3]}) + + + def test_verify_partial_17(self): + self.stache_verify_partial('ab123d', 'o', {'b': [1, 2, 3]}) + + def main(argv=None): if argv is None: argv = sys.argv @@ -271,13 +307,5 @@ def main(argv=None): if __name__ == "__main__": sys.exit(main()) -''') - -if __name__ == '__main__': - - print('# start generated code') - #print('starting tests') - run(verify, verify_partial) - #print('finished tests') - print('# end generated code') +# end generated code From c3646961987a740c2e53e946b32da715fc65745b Mon Sep 17 00:00:00 2001 From: clach04 Date: Sat, 19 Feb 2022 13:43:41 -0800 Subject: [PATCH 13/21] Added imports end code needed to run testsuite.py --- testsuite.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/testsuite.py b/testsuite.py index 3bd65bb..d538666 100644 --- a/testsuite.py +++ b/testsuite.py @@ -1,3 +1,16 @@ +import os +import string +import sys +try: + import unittest2 as unittest +except ImportError: + import unittest + + +import stache +from stache import render, Stache + + # start generated code class BaseTest(unittest.TestCase): pass From 2721e8dd8f02127d0822f34fcccc0b498251bdb9 Mon Sep 17 00:00:00 2001 From: clach04 Date: Sat, 19 Feb 2022 13:47:18 -0800 Subject: [PATCH 14/21] Add comments for tests, from test.py --- testsuite.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/testsuite.py b/testsuite.py index d538666..480f231 100644 --- a/testsuite.py +++ b/testsuite.py @@ -28,8 +28,7 @@ def stache_verify(self, output, template, data): #assert result == result_iter self.assertEqual(result, result_iter) - - + # test tag lookup def test_verify_01(self): self.stache_verify('a10c', 'a{{b}}c', {'b': 10}) @@ -58,6 +57,7 @@ def test_verify_07(self): self.stache_verify('a123', 'a{{b}}{{c}}{{d}}', {'c': 2, 'b': 1, 'd': 3}) + # test falsy #sections def test_verify_08(self): self.stache_verify('ab', 'a{{#b}}b{{/b}}', {'b': True}) @@ -74,6 +74,7 @@ def test_verify_11(self): self.stache_verify('ab', 'a{{^b}}b{{/b}}', {'b': False}) + #test invert sections def test_verify_12(self): self.stache_verify('ab', 'a{{#b}}ignore me{{/b}}{{^b}}b{{/}}', {'b': []}) @@ -86,6 +87,7 @@ def test_verify_14(self): self.stache_verify('ab', 'a{{#b}}b{{/b}}{{^b}}ignore me{{/}}', {'b': True}) + #test ?sections def test_verify_15(self): self.stache_verify('a- 1 2 3 4', 'a{{?b}}-{{#b}} {{.}}{{/}}{{/}}', {'b': [1, 2, 3, 4]}) @@ -110,6 +112,7 @@ def test_verify_20(self): self.stache_verify('ab123d', 'a{{?b}}b{{#b}}{{.}}{{/}}d{{/}}', {'b': [1, 2, 3]}) + #test #section scope def test_verify_21(self): self.stache_verify('abbbb', 'a{{#b}}b{{/b}}', {'b': [1, 2, 3, 4]}) @@ -150,6 +153,7 @@ def test_verify_30(self): self.stache_verify('<3><3><3>', '<{{id}}><{{# a? }}{{id}}{{/ a? }}><{{# b? }}{{id}}{{/ b? }}>', {'b?': True, 'id': 3, 'a?': True}) + #test delim def test_verify_31(self): self.stache_verify('delim{{a}}', '{{=<% %>=}}<%a%>{{a}}', {'a': 'delim'}) @@ -158,6 +162,7 @@ def test_verify_32(self): self.stache_verify('delim{{a}}delim<%a%>', '{{=<% %>=}}<%a%>{{a}}<%={{ }}=%>{{a}}<%a%>', {'a': 'delim'}) + #test : def test_verify_33(self): self.stache_verify('123test', '123{{:hi}}abc{{/}}', {'hi': 'test'}) @@ -190,6 +195,7 @@ def test_verify_40(self): self.stache_verify('123test', '{{<'}) From cf6c14da86384c8ccf801813aeb2e53a6a250868 Mon Sep 17 00:00:00 2001 From: clach04 Date: Sat, 19 Feb 2022 13:48:12 -0800 Subject: [PATCH 15/21] Comment formatting --- testsuite.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/testsuite.py b/testsuite.py index 480f231..b766d9e 100644 --- a/testsuite.py +++ b/testsuite.py @@ -74,7 +74,7 @@ def test_verify_11(self): self.stache_verify('ab', 'a{{^b}}b{{/b}}', {'b': False}) - #test invert sections + # test invert sections def test_verify_12(self): self.stache_verify('ab', 'a{{#b}}ignore me{{/b}}{{^b}}b{{/}}', {'b': []}) @@ -87,7 +87,7 @@ def test_verify_14(self): self.stache_verify('ab', 'a{{#b}}b{{/b}}{{^b}}ignore me{{/}}', {'b': True}) - #test ?sections + # test ?sections def test_verify_15(self): self.stache_verify('a- 1 2 3 4', 'a{{?b}}-{{#b}} {{.}}{{/}}{{/}}', {'b': [1, 2, 3, 4]}) @@ -112,7 +112,7 @@ def test_verify_20(self): self.stache_verify('ab123d', 'a{{?b}}b{{#b}}{{.}}{{/}}d{{/}}', {'b': [1, 2, 3]}) - #test #section scope + # test #section scope def test_verify_21(self): self.stache_verify('abbbb', 'a{{#b}}b{{/b}}', {'b': [1, 2, 3, 4]}) @@ -153,7 +153,7 @@ def test_verify_30(self): self.stache_verify('<3><3><3>', '<{{id}}><{{# a? }}{{id}}{{/ a? }}><{{# b? }}{{id}}{{/ b? }}>', {'b?': True, 'id': 3, 'a?': True}) - #test delim + # test delim def test_verify_31(self): self.stache_verify('delim{{a}}', '{{=<% %>=}}<%a%>{{a}}', {'a': 'delim'}) @@ -162,7 +162,7 @@ def test_verify_32(self): self.stache_verify('delim{{a}}delim<%a%>', '{{=<% %>=}}<%a%>{{a}}<%={{ }}=%>{{a}}<%a%>', {'a': 'delim'}) - #test : + # test : def test_verify_33(self): self.stache_verify('123test', '123{{:hi}}abc{{/}}', {'hi': 'test'}) @@ -195,7 +195,7 @@ def test_verify_40(self): self.stache_verify('123test', '{{<'}) From 44ab16aef9e0741a61099b42c7a0a23f0a67d3d8 Mon Sep 17 00:00:00 2001 From: clach04 Date: Sat, 19 Feb 2022 13:52:43 -0800 Subject: [PATCH 16/21] Doc testsuite --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 65c5dec..98ddf71 100644 --- a/README.md +++ b/README.md @@ -224,9 +224,17 @@ Custom Footer pip install stache +Optionally install Nose for running nosetests, there is a regular unittest that does not require nose). + + pip install nose + # Test -You can run `python test.py` or if you have nosetests: +Pure python tests can be ran with: + + python testsuite.py + +The Python and javascript tests can be ran with `python test.py` or if you have nosetests: cd stache nosetests From 0bb75b9dff31159cfc93771a8e3c2990b6401bde Mon Sep 17 00:00:00 2001 From: clach04 Date: Sat, 19 Feb 2022 14:14:31 -0800 Subject: [PATCH 17/21] Finalize fix #2 trailing blank newlines Working test suite to match previous test, adds a new test for with and without white space. --- README.md | 8 +++++++- testsuite.py | 5 ++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 98ddf71..5079572 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,13 @@ Trimmed mustache logic-less templates -Fork of https://github.com/hyperturtle/Stache with support for Cpython 2.2 up to and including Python 3.x. Also works with Jython 2.x. +Fork of https://github.com/hyperturtle/Stache with: + + * support for Cpython 2.2 up to and including Python 3.x + * Also works with Jython 2.x. + * Fix for trailing blank newlines (https://github.com/hyperturtle/Stache/issues/2) + * regular Python unittest based test suite + Also see https://github.com/SmithSamuelM/staching which includes a fix for https://github.com/hyperturtle/Stache/issues/2 but doesn't support older Python versions, use if older Python support is not required. Implements everything from [Mustache.5](http://mustache.github.com/mustache.5.html) diff --git a/testsuite.py b/testsuite.py index b766d9e..b973a4a 100644 --- a/testsuite.py +++ b/testsuite.py @@ -122,7 +122,10 @@ def test_verify_22(self): def test_verify_23(self): - self.stache_verify('a a=1 a=2 a=0 a', 'a {{#b}}a={{a}} {{/b}}a', {'a': 0, 'b': [{'a': 1}, {'a': 2}, {'c': 1}]}) + self.stache_verify('aa=1 a=2 a=0 a', 'a {{#b}}a={{a}} {{/b}}a', {'a': 0, 'b': [{'a': 1}, {'a': 2}, {'c': 1}]}) + + def test_verify_23_a(self): + self.stache_verify('a a=1 a=2 a=0 a', 'a {{#b}} a={{a}}{{/b}} a', {'a': 0, 'b': [{'a': 1}, {'a': 2}, {'c': 1}]}) def test_verify_24(self): From 0638f9d2b20a3c43aad4fb2288cafd369985645c Mon Sep 17 00:00:00 2001 From: clach04 Date: Wed, 9 Mar 2022 14:33:32 -0800 Subject: [PATCH 18/21] Python 3.8 support - Python 3.8 removed cgi.escape for html --- __init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/__init__.py b/__init__.py index 903da82..febbf39 100644 --- a/__init__.py +++ b/__init__.py @@ -1,6 +1,11 @@ from __future__ import generators import sys -from cgi import escape +try: + # py2 + from cgi import escape +except ImportError: + # py 3.8+ + from html import escape try: raise ImportError From 254b88fb3fe27227cfc6e7b6b7bcb6fab799c28b Mon Sep 17 00:00:00 2001 From: clach04 Date: Fri, 25 Mar 2022 12:07:15 -0700 Subject: [PATCH 19/21] Switch cgi escape import attempt order around py3 first, this avoids DeprecationWarning in older 3.x version (pre 3.8) --- __init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/__init__.py b/__init__.py index febbf39..2db116a 100644 --- a/__init__.py +++ b/__init__.py @@ -1,11 +1,11 @@ from __future__ import generators import sys try: - # py2 - from cgi import escape -except ImportError: # py 3.8+ from html import escape +except ImportError: + # py2 + from cgi import escape try: raise ImportError From 9f0427beaf64a9fa9c7ecc46342bfbe055955dbe Mon Sep 17 00:00:00 2001 From: clach04 Date: Thu, 23 Feb 2023 18:40:53 -0800 Subject: [PATCH 20/21] Add test for unclosed blocks --- __init__.py | 2 +- testsuite.py | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/__init__.py b/__init__.py index 2db116a..e2326b7 100644 --- a/__init__.py +++ b/__init__.py @@ -396,7 +396,7 @@ def _tokenize(self, template): elif close_tag is not None: current_scope = scope.pop() if close_tag: - assert (current_scope == close_tag), 'Mismatch open/close blocks' + assert (current_scope == close_tag), 'Mismatch open/close blocks, %r != %r' % (current_scope, close_tag) # TODO replace with a check, assertions can be optimized out yield TOKEN_TAGCLOSE, current_scope, len(scope)+1 elif booltern_tag: scope.append(booltern_tag) diff --git a/testsuite.py b/testsuite.py index b973a4a..763204c 100644 --- a/testsuite.py +++ b/testsuite.py @@ -13,10 +13,6 @@ # start generated code class BaseTest(unittest.TestCase): - pass - -class verify(BaseTest): - def stache_verify(self, output, template, data): #print("%s with %s" % (template, data)) result = render(template, data) @@ -28,6 +24,9 @@ def stache_verify(self, output, template, data): #assert result == result_iter self.assertEqual(result, result_iter) + +class verify(BaseTest): + # test tag lookup def test_verify_01(self): self.stache_verify('a10c', 'a{{b}}c', {'b': 10}) @@ -318,6 +317,16 @@ def test_verify_partial_17(self): self.stache_verify_partial('ab123d', 'o', {'b': [1, 2, 3]}) +class RaisesErrors(BaseTest): + + def test_good_tags(self): + self.stache_verify('1', '{{#a}}{{b}}{{/a}}', {'a': {'b': '1'}}) + + def test_mismatched_tags(self): # compare with test_good_tags() + #self.stache_verify('1', '{{#a}}{{b}}{{/b}}', {'a': {'b': '1'}}) # 'a' not closed, attempts to close 'b' instead + self.assertRaises(AssertionError, self.stache_verify, '1', '{{#a}}{{b}}{{/b}}', {'a': {'b': '1'}}) # 'a' not closed, attempts to close 'b' instead + # TODO check error/exception documents correct tags in error + def main(argv=None): if argv is None: argv = sys.argv From 9d7ceeea1d156df4fa32524ed513cc8d530a56f7 Mon Sep 17 00:00:00 2001 From: clach04 Date: Sat, 6 Jan 2024 10:35:03 -0800 Subject: [PATCH 21/21] mention https://github.com/clach04/stache and --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 5079572..40e49fe 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ # Stache +NOTE see hard-fork https://github.com/clach04/stache (and issue tracker there). I may not continue updating this repo. + Trimmed mustache logic-less templates Fork of https://github.com/hyperturtle/Stache with: