Hello community,

here is the log from the commit of package python-Chameleon for 
openSUSE:Factory checked in at 2020-07-08 19:20:00
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-Chameleon (Old)
 and      /work/SRC/openSUSE:Factory/.python-Chameleon.new.3060 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-Chameleon"

Wed Jul  8 19:20:00 2020 rev:11 rq:819491 version:3.8.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-Chameleon/python-Chameleon.changes        
2020-06-09 00:07:30.509896107 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-Chameleon.new.3060/python-Chameleon.changes  
    2020-07-08 19:20:33.976510513 +0200
@@ -1,0 +2,12 @@
+Wed Jul  8 14:03:55 UTC 2020 - Ondřej Súkup <mimi...@gmail.com>
+
+- Update to 3.8.1
+ * Added code optimization to reduce sequential appends of static text.
+ * The default symbol in dynamic attributes is now symbolic.
+ * The built-in attrs dictionary of static element attributes now correctly
+    works with tal:define, etc.
+ * Fix slice code generation compatibility issue on Python 3.9.
+ * Expose default marker as importable symbol chameleon.tales.DEFAULT_MARKER.
+ * Removed legacy flag literal_false. To get a similar behavior, use 
boolean_attributes.
+
+-------------------------------------------------------------------

Old:
----
  Chameleon-3.7.2.tar.gz

New:
----
  Chameleon-3.8.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-Chameleon.spec ++++++
--- /var/tmp/diff_new_pack.Jeeu4X/_old  2020-07-08 19:20:35.264514788 +0200
+++ /var/tmp/diff_new_pack.Jeeu4X/_new  2020-07-08 19:20:35.264514788 +0200
@@ -18,7 +18,7 @@
 
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 Name:           python-Chameleon
-Version:        3.7.2
+Version:        3.8.1
 Release:        0
 Summary:        Fast HTML/XML Template Compiler
 License:        BSD-3-Clause AND BSD-4-Clause AND Python-2.0 AND ZPL-2.1

++++++ Chameleon-3.7.2.tar.gz -> Chameleon-3.8.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/chameleon-3.7.2/CHANGES.rst 
new/chameleon-3.8.1/CHANGES.rst
--- old/chameleon-3.7.2/CHANGES.rst     2020-05-31 07:51:18.000000000 +0200
+++ new/chameleon-3.8.1/CHANGES.rst     2020-07-06 21:23:38.000000000 +0200
@@ -1,6 +1,43 @@
 Changes
 =======
 
+3.8.1 (2020-07-06)
+------------------
+
+- Added code optimization to reduce sequential appends of static text.
+
+- The `default` symbol in dynamic attributes is now
+  symbolic. Previously, it was assigned the string value of the
+  default attribute text. A similar change has been made for
+  switch/case expressions.
+
+- The built-in `attrs` dictionary of static element attributes now
+  correctly works with `tal:define`, etc.
+
+- Fix slice code generation compatibility issue on Python 3.9.
+
+3.8.0 (2020-06-25)
+------------------
+
+- Expose default marker as importable symbol
+  `chameleon.tales.DEFAULT_MARKER`.
+
+- Removed legacy flag `literal_false`. To get a similar behavior, use
+  `boolean_attributes`.
+
+3.7.4 (2020-06-17)
+------------------
+
+- Fix brown-bag release.
+
+3.7.3 (2020-06-17)
+------------------
+
+- Fix regression introduced in 3.6.2 where the default marker would
+  incorrectly change its value between templates, causing issues in
+  software which depends on the value being treated as a global
+  object.
+
 3.7.2 (2020-05-31)
 ------------------
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/chameleon-3.7.2/setup.py new/chameleon-3.8.1/setup.py
--- old/chameleon-3.7.2/setup.py        2020-05-31 07:51:18.000000000 +0200
+++ new/chameleon-3.8.1/setup.py        2020-07-06 21:23:38.000000000 +0200
@@ -1,4 +1,4 @@
-__version__ = '3.7.2'
+__version__ = '3.8.1'
 
 import os
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/chameleon-3.7.2/src/chameleon/astutil.py 
new/chameleon-3.8.1/src/chameleon/astutil.py
--- old/chameleon-3.7.2/src/chameleon/astutil.py        2020-05-31 
07:51:18.000000000 +0200
+++ new/chameleon-3.8.1/src/chameleon/astutil.py        2020-07-06 
21:23:38.000000000 +0200
@@ -23,6 +23,9 @@
 import weakref
 import collections
 
+
+AST_NONE = ast.Name(id='None', ctx=ast.Load())
+
 node_annotations = weakref.WeakKeyDictionary()
 
 try:
@@ -925,20 +928,31 @@
                 for dim in node.slice.elts[1:]:
                     self._write(', ')
                     self.visit(dim)
+        elif isinstance(node.slice, ast.Slice):
+            self.visit_Slice(node.slice, True)
         else:
             self.visit(node.slice)
         self._write(']')
 
     # Slice(expr? lower, expr? upper, expr? step)
-    def visit_Slice(self, node):
-        if getattr(node, 'lower', None) is not None:
-            self.visit(node.lower)
-        self._write(':')
-        if getattr(node, 'upper', None) is not None:
-            self.visit(node.upper)
-        if getattr(node, 'step', None) is not None:
+    def visit_Slice(self, node, subscription=False):
+        if subscription:
+            if getattr(node, 'lower', None) is not None:
+                self.visit(node.lower)
             self._write(':')
-            self.visit(node.step)
+            if getattr(node, 'upper', None) is not None:
+                self.visit(node.upper)
+            if getattr(node, 'step', None) is not None:
+                self._write(':')
+                self.visit(node.step)
+        else:
+            self._write('slice(')
+            self.visit(getattr(node, "lower", None) or AST_NONE)
+            self._write(', ')
+            self.visit(getattr(node, "upper", None) or AST_NONE)
+            self._write(', ')
+            self.visit(getattr(node, "step", None) or AST_NONE)
+            self._write(')')
 
     # Index(expr value)
     def visit_Index(self, node):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/chameleon-3.7.2/src/chameleon/compiler.py 
new/chameleon-3.8.1/src/chameleon/compiler.py
--- old/chameleon-3.7.2/src/chameleon/compiler.py       2020-05-31 
07:51:18.000000000 +0200
+++ new/chameleon-3.8.1/src/chameleon/compiler.py       2020-07-06 
21:23:38.000000000 +0200
@@ -21,6 +21,7 @@
 from .astutil import Builtin
 from .astutil import Static
 from .astutil import TokenRef
+from .astutil import Node
 
 from .codegen import TemplateCodeGenerator
 from .codegen import template
@@ -35,6 +36,11 @@
 from .nodes import Assignment
 from .nodes import Module
 from .nodes import Context
+from .nodes import Is
+from .nodes import IsNot
+from .nodes import Equals
+from .nodes import Logical
+from .nodes import And
 
 from .tokenize import Token
 from .config import DEBUG_MODE
@@ -54,6 +60,7 @@
 from .utils import safe_native
 from .utils import builtins
 from .utils import decode_htmlentities
+from .utils import join
 
 if version >= (3, 0, 0):
     long = int
@@ -133,10 +140,6 @@
     )
 
 
-emit_node = template(is_func=True, func_args=('node',), source=r"""
-    __append(node)""")
-
-
 emit_node_if_non_trivial = template(is_func=True, func_args=('node',),
                                     source=r"""
     if node is not None:
@@ -270,6 +273,22 @@
         return target""")
 
 
+class EmitText(Node):
+    """Append text to output."""
+
+    _fields = "s",
+
+
+class Scope(Node):
+    """"Set a local output scope."""
+
+    _fields = "body", "append", "stream"
+
+    body = None
+    append = None
+    stream = None
+
+
 class Interpolator(object):
     braces_required_regex = re.compile(
         r'(\$)?\$({(?P<expression>.*)})',
@@ -805,19 +824,21 @@
         return stmts
 
     def visit_Value(self, node, target):
-        engine = self.engine_factory()
+        engine = self.engine_factory(
+            default=node.default,
+            default_marker=node.default_marker
+        )
         compiler = engine.parse(node.value)
         return compiler.assign_value(target)
 
     def visit_Copy(self, node, target):
         return self.translate(node.expression, target)
 
-    def visit_Default(self, node, target):
-        value = annotated(node.marker)
-        return [ast.Assign(targets=[target], value=value)]
-
     def visit_Substitution(self, node, target):
-        engine = self.engine_factory(default=node.default)
+        engine = self.engine_factory(
+            default=node.default,
+            default_marker=node.default_marker
+        )
         compiler = engine.parse(node.value, char_escape=node.char_escape)
         return compiler.assign_text(target)
 
@@ -825,22 +846,23 @@
         return self.translate(node.value, target) + \
                template("TARGET = not TARGET", TARGET=target)
 
-    def visit_Identity(self, node, target):
-        expression = self.translate(node.expression, "__expression")
-        value = self.translate(node.value, "__value")
-
-        return expression + value + \
-               template("TARGET = __expression is __value", TARGET=target)
-
-    def visit_Equality(self, node, target):
-        expression = self.translate(node.expression, "__expression")
-        value = self.translate(node.value, "__value")
-
+    def visit_BinOp(self, node, target):
+        expression = self.translate(node.left, "__expression")
+        value = self.translate(node.right, "__value")
+
+        op = {
+            Is: "is",
+            IsNot: "is not",
+            Equals: "==",
+        }[node.op]
         return expression + value + \
-               template("TARGET = __expression == __value", TARGET=target)
+               template("TARGET = __expression %s __value" % op, TARGET=target)
 
     def visit_Boolean(self, node, target):
-        engine = self.engine_factory()
+        engine = self.engine_factory(
+            default=node.default,
+            default_marker=node.default_marker,
+        )
         compiler = engine.parse(node.value)
         return compiler.assign_bool(target, node.s)
 
@@ -850,9 +872,13 @@
             engine = self.engine_factory(
                 char_escape=expr.char_escape,
                 default=expr.default,
-                )
+                default_marker=expr.default_marker
+            )
         elif isinstance(expr, Value):
-            engine = self.engine_factory()
+            engine = self.engine_factory(
+                default=expr.default,
+                default_marker=expr.default_marker
+            )
         else:
             raise RuntimeError("Bad value: %r." % node.value)
 
@@ -886,6 +912,10 @@
         value = annotated(node)
         return [ast.Assign(targets=[target], value=value)]
 
+    def visit_Symbol(self, node, target):
+        value = annotated(node)
+        return template("TARGET = SYMBOL", TARGET=target, SYMBOL=node)
+
 
 class Compiler(object):
     """Generic compiler class.
@@ -949,8 +979,26 @@
             module = ast.Module([])
             module.body += self.visit(node)
             ast.fix_missing_locations(module)
-            prelude = "__filename = %r\n__default = object()" % filename
-            generator = TemplateCodeGenerator(module, source)
+
+            class Generator(TemplateCodeGenerator):
+                scopes = [Scope()]
+
+                def visit_EmitText(self, node):
+                    append = load(self.scopes[-1].append or "__append")
+                    for node in template("append(s)", append=append, 
s=ast.Str(s=node.s)):
+                        self.visit(node)
+
+                def visit_Scope(self, node):
+                    self.scopes.append(node)
+                    body = list(node.body)
+                    swap(body, load(node.append), "__append")
+                    if node.stream:
+                        swap(body, load(node.stream), "__stream")
+                    for node in body:
+                        self.visit(node)
+                    self.scopes.pop()
+
+            generator = Generator(module, source)
             tokens = [
                 Token(source[pos:pos + length], pos, source)
                 for pos, length in generator.tokens
@@ -966,7 +1014,7 @@
                 self.lock.release()
 
         self.code = "\n".join((
-            prelude,
+            "__filename = %r\n" % filename,
             token_map_def,
             generator.code
         ))
@@ -977,7 +1025,15 @@
         kind = type(node).__name__
         visitor = getattr(self, "visit_%s" % kind)
         iterator = visitor(node)
-        return list(iterator)
+        result = []
+        for key, group in itertools.groupby(iterator, lambda node: 
node.__class__):
+            nodes = list(group)
+            if key is EmitText:
+                text = join(node.s for node in nodes)
+                nodes = [EmitText(text)]
+            result.extend(nodes)
+        return result
+
 
     def visit_Sequence(self, node):
         for item in node.items:
@@ -1003,6 +1059,9 @@
         body += template("from itertools import chain as __chain")
         if version < (3, 0, 0):
             body += template("from sys import exc_clear as __exc_clear")
+        else:
+            body += template("from sys import intern")
+        body += template("__default = intern('__default__')")
         body += template("__marker = object()")
         body += template(
             r"g_re_amp = re.compile(r'&(?!([A-Za-z]+|#[0-9]+);)')"
@@ -1125,7 +1184,7 @@
         yield function
 
     def visit_Text(self, node):
-        return emit_node(ast.Str(s=node.value))
+        yield EmitText(node.value)
 
     def visit_Domain(self, node):
         backup = "__previous_i18n_domain_%s" % mangle(id(node))
@@ -1267,22 +1326,44 @@
 
     def visit_Condition(self, node):
         target = "__condition"
-        assignment = self._engine(node.expression, target)
 
-        assert assignment
+        def step(expressions, body, condition):
+            for i, expression in enumerate(reversed(expressions)):
+                stmts = evaluate(expression, body)
+                if i > 0:
+                    stmts.append(
+                        ast.If(
+                            ast.Compare(
+                                left=load(target),
+                                ops=[ast.Is()],
+                                comparators=[load(str(condition))]
+                            ),
+                            body,
+                            None
+                        )
+                    )
+                body = stmts
+            return body
 
-        for stmt in assignment:
-            yield stmt
+        def evaluate(node, body=None):
+            if isinstance(node, Logical):
+                condition = isinstance(node, And)
+                return step(node.expressions, body, condition)
 
-        body = self.visit(node.node) or [ast.Pass()]
+            return self._engine(node, target)
 
+        body = evaluate(node.expression)
         orelse = getattr(node, "orelse", None)
-        if orelse is not None:
-            orelse = self.visit(orelse)
 
-        test = load(target)
+        body.append(
+            ast.If(
+                test=load(target),
+                body=self.visit(node.node) or [ast.Pass()],
+                orelse=self.visit(orelse) if orelse else None,
+            )
+        )
 
-        yield ast.If(test=test, body=body, orelse=orelse)
+        return body
 
     def visit_Translate(self, node):
         """Translation.
@@ -1306,9 +1387,7 @@
 
         # Visit body to generate the message body
         code = self.visit(node.node)
-        swap(code, load(append), "__append")
-        swap(code, load(stream), "__stream")
-        body += code
+        body.append(Scope(code, append, stream))
 
         # Reduce white space and assign as message id
         msgid = identifier("msgid", id(node))
@@ -1369,23 +1448,16 @@
                 node.prefix, node.name, line, column))
 
         if node.attributes:
-            for stmt in emit_node(ast.Str(s=node.prefix + node.name)):
-                yield stmt
-
+            yield EmitText(node.prefix + node.name)
             for stmt in self.visit(node.attributes):
                 yield stmt
 
-            for stmt in emit_node(ast.Str(s=node.suffix)):
-                yield stmt
+            yield EmitText(node.suffix)
         else:
-            for stmt in emit_node(
-                ast.Str(s=node.prefix + node.name + node.suffix)):
-                yield stmt
+            yield EmitText(node.prefix + node.name + node.suffix)
 
     def visit_End(self, node):
-        for stmt in emit_node(ast.Str(
-            s=node.prefix + node.name + node.space + node.suffix)):
-            yield stmt
+        yield EmitText(node.prefix + node.name + node.space + node.suffix)
 
     def visit_Attribute(self, node):
         attr_format = (node.space + node.name + node.eq +
@@ -1414,7 +1486,7 @@
                     "if C: __append(S)", C=filter_condition, S=ast.Str(s=s)
                 )
             else:
-                return template("__append(S)", S=ast.Str(s=s))
+                return [EmitText(s)]
 
         target = identifier("attr", node.name)
         body = self._engine(node.expression, store(target))
@@ -1464,13 +1536,13 @@
         body = []
 
         for expression in node.expressions:
-            name = identifier("cache", id(expression))
-            target = store(name)
-
             # Skip re-evaluation
             if self._expression_cache.get(expression):
                 continue
 
+            name = identifier("cache", id(expression))
+            target = store(name)
+
             body += self._engine(expression, target)
             self._expression_cache[expression] = target
 
@@ -1482,13 +1554,10 @@
         body = []
 
         for expression in node.expressions:
+            assert self._expression_cache.get(expression) is not None
             name = identifier("cache", id(expression))
             target = store(name)
-
-            if not self._expression_cache.get(expression):
-               continue
-
-            body.append(ast.Assign([target], load("None")))
+            body += self._engine(node.value, target)
 
         body += self.visit(node.node)
 
@@ -1545,8 +1614,7 @@
 
         # generate code
         code = self.visit(node.node)
-        swap(code, load(append), "__append")
-        body += code
+        body.append(Scope(code, append))
 
         # output msgid
         text = Text('${%s}' % node.name)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/chameleon-3.7.2/src/chameleon/nodes.py 
new/chameleon-3.8.1/src/chameleon/nodes.py
--- old/chameleon-3.7.2/src/chameleon/nodes.py  2020-05-31 07:51:18.000000000 
+0200
+++ new/chameleon-3.8.1/src/chameleon/nodes.py  2020-07-06 21:23:38.000000000 
+0200
@@ -22,12 +22,6 @@
     _fields = "expression", "char_escape", "translate"
 
 
-class Default(Node):
-    """Represents a default value."""
-
-    _fields = "marker",
-
-
 class CodeBlock(Node):
     _fields = "source",
 
@@ -35,7 +29,11 @@
 class Value(Node):
     """Expression object value."""
 
-    _fields = "value",
+    _fields = "value", "default", "default_marker"
+
+    default = None
+
+    default_marker = None
 
     def __repr__(self):
         try:
@@ -51,13 +49,13 @@
 class Substitution(Value):
     """Expression value for text substitution."""
 
-    _fields = "value", "char_escape", "default"
+    _fields = "value", "char_escape", "default", "default_marker"
 
     default = None
 
 
 class Boolean(Value):
-    _fields = "value", "s"
+    _fields = "value", "s", "default", "default_marker"
 
 
 class Negate(Node):
@@ -81,7 +79,7 @@
 class Attribute(Node):
     """Element attribute."""
 
-    _fields = "name", "expression", "quote", "eq", "space", "filters"
+    _fields = "name", "expression", "quote", "eq", "space", "default", 
"filters"
 
 
 class Start(Node):
@@ -97,21 +95,45 @@
 
 
 class Condition(Node):
-    """Node visited only if some condition holds."""
+    """Node visited only if one of the condition holds."""
 
     _fields = "expression", "node", "orelse"
 
 
-class Identity(Node):
-    """Condition expression that is true on identity."""
+class Op(Node):
+    """An operator node."""
+
+
+class Is(Op):
+    """Object identity."""
+
+
+class IsNot(Op):
+    """Object identity."""
 
-    _fields = "expression", "value"
 
+class Equals(Op):
+    """Object equality."""
 
-class Equality(Node):
-    """Condition expression that is true on equality."""
 
-    _fields = "expression", "value"
+class Logical(Node):
+    """Logical operator."""
+
+    _fields = "expressions",
+
+
+class And(Logical):
+    """All terms must be met."""
+
+
+class Or(Logical):
+    """At least one term must be met."""
+
+
+class BinOp(Node):
+    """Binary comparison."""
+
+    _fields = "left", "op", "right"
 
 
 class Cache(Node):
@@ -123,7 +145,7 @@
 
 
 class Cancel(Cache):
-    pass
+    _fields = "expressions", "node", "value"
 
 
 class Copy(Node):
@@ -184,7 +206,9 @@
 class Interpolation(Node):
     """String interpolation output."""
 
-    _fields = "value", "braces_required", "translation"
+    _fields = "value", "braces_required", "translation", "default", 
"default_marker"
+
+
 
 
 class Translate(Node):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/chameleon-3.7.2/src/chameleon/tales.py 
new/chameleon-3.8.1/src/chameleon/tales.py
--- old/chameleon-3.7.2/src/chameleon/tales.py  2020-05-31 07:51:18.000000000 
+0200
+++ new/chameleon-3.8.1/src/chameleon/tales.py  2020-07-06 21:23:38.000000000 
+0200
@@ -1,5 +1,7 @@
 import re
 import sys
+import types
+import importlib
 
 from .astutil import parse
 from .astutil import store
@@ -11,13 +13,16 @@
 from .astutil import Builtin
 from .astutil import Symbol
 from .exc import ExpressionError
+from .utils import ast
 from .utils import resolve_dotted
+from .utils import ImportableMarker
 from .utils import Markup
-from .utils import ast
 from .tokenize import Token
 from .parser import substitute
 from .compiler import Interpolator
 
+DEFAULT_MARKER = ImportableMarker(__name__, "DEFAULT")
+
 try:
     from .py26 import lookup_attr
 except SyntaxError:
@@ -390,7 +395,7 @@
     In previous versions, it was possible to escape using a regular
     backslash coding, but this is no longer supported.
 
-    >>> test(StringExpr(r'\${name}'), name='Hello world!')
+    >>> test(StringExpr(r'\\${name}'), name='Hello world!')
     '\\\\Hello world!'
 
     Multiple interpolations in one:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/chameleon-3.7.2/src/chameleon/tests/inputs/005-default.pt 
new/chameleon-3.8.1/src/chameleon/tests/inputs/005-default.pt
--- old/chameleon-3.7.2/src/chameleon/tests/inputs/005-default.pt       
2020-05-31 07:51:18.000000000 +0200
+++ new/chameleon-3.8.1/src/chameleon/tests/inputs/005-default.pt       
2020-07-06 21:23:38.000000000 +0200
@@ -2,7 +2,7 @@
   <body>
     <img class="default" tal:attributes="class default" />
     <img tal:attributes="class default" />
-    <span tal:content="default">Default</span>
+    <span tal:define="foo string:" tal:content="foo or default">Default</span>
     <span tal:content="True">Default</span>
     <span tal:content="False">Default</span>
     <span tal:content="default">
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/chameleon-3.7.2/src/chameleon/tests/inputs/008-builtins.pt 
new/chameleon-3.8.1/src/chameleon/tests/inputs/008-builtins.pt
--- old/chameleon-3.7.2/src/chameleon/tests/inputs/008-builtins.pt      
2020-05-31 07:51:18.000000000 +0200
+++ new/chameleon-3.8.1/src/chameleon/tests/inputs/008-builtins.pt      
2020-07-06 21:23:38.000000000 +0200
@@ -5,6 +5,9 @@
     <div tal:attributes="class string:dynamic" class="static">
       ${attrs['class']}
     </div>
+    <div class="static" tal:content="attrs['class']" />
+    <div class="static" tal:replace="attrs['class']" />
+    <div class="static" tal:define="x attrs">${x['class']}</div>
     <div tal:define="nothing string:nothing">
       ${nothing}
     </div>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/chameleon-3.7.2/src/chameleon/tests/inputs/017-omit-tag.pt 
new/chameleon-3.8.1/src/chameleon/tests/inputs/017-omit-tag.pt
--- old/chameleon-3.7.2/src/chameleon/tests/inputs/017-omit-tag.pt      
2020-05-31 07:51:18.000000000 +0200
+++ new/chameleon-3.8.1/src/chameleon/tests/inputs/017-omit-tag.pt      
2020-07-06 21:23:38.000000000 +0200
@@ -9,4 +9,4 @@
     <div class="omitted" tal:omit-tag="True">Hello world!</div>
     <div class="${'omitted'}" tal:omit-tag="True">Hello world!</div>
   </body>
-</html>
\ No newline at end of file
+</html>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/chameleon-3.7.2/src/chameleon/tests/inputs/027-attribute-replacement.pt 
new/chameleon-3.8.1/src/chameleon/tests/inputs/027-attribute-replacement.pt
--- old/chameleon-3.7.2/src/chameleon/tests/inputs/027-attribute-replacement.pt 
2020-05-31 07:51:18.000000000 +0200
+++ new/chameleon-3.8.1/src/chameleon/tests/inputs/027-attribute-replacement.pt 
2020-07-06 21:23:38.000000000 +0200
@@ -4,7 +4,7 @@
         class="dummy"
         onClick=""
         tal:define="a 'abc'"
-        tal:attributes="class 'def' + a + default; style 'hij'; onClick 
'alert();;'"
+        tal:attributes="class default; style 'hij'; onClick 'alert();;'"
         tal:content="a + 'ghi'" />
   <span tal:replace="'Hello World!'">Hello <b>Universe</b>!</span>
   <span tal:replace="'Hello World!'"><b>Hello Universe!</b></span>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/chameleon-3.7.2/src/chameleon/tests/inputs/072-repeat-interpolation.pt 
new/chameleon-3.8.1/src/chameleon/tests/inputs/072-repeat-interpolation.pt
--- old/chameleon-3.7.2/src/chameleon/tests/inputs/072-repeat-interpolation.pt  
2020-05-31 07:51:18.000000000 +0200
+++ new/chameleon-3.8.1/src/chameleon/tests/inputs/072-repeat-interpolation.pt  
2020-07-06 21:23:38.000000000 +0200
@@ -6,8 +6,5 @@
     <ul>
       <li tal:repeat="i range(1, 4)" class="${'odd' if i % 2 != 0 else 
None}">${i}</li>
     </ul>
-    <ul>
-      <li tal:repeat="i range(1, 4)" class="${'odd' if i % 2 != 0 else 
False}">${i}</li>
-    </ul>
   </body>
 </html>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/chameleon-3.7.2/src/chameleon/tests/outputs/008.pt 
new/chameleon-3.8.1/src/chameleon/tests/outputs/008.pt
--- old/chameleon-3.7.2/src/chameleon/tests/outputs/008.pt      2020-05-31 
07:51:18.000000000 +0200
+++ new/chameleon-3.8.1/src/chameleon/tests/outputs/008.pt      2020-07-06 
21:23:38.000000000 +0200
@@ -5,6 +5,9 @@
     <div class="dynamic">
       static
     </div>
+    <div class="static">static</div>
+    static
+    <div class="static">static</div>
     <div>
       nothing
     </div>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/chameleon-3.7.2/src/chameleon/tests/outputs/027.pt 
new/chameleon-3.8.1/src/chameleon/tests/outputs/027.pt
--- old/chameleon-3.7.2/src/chameleon/tests/outputs/027.pt      2020-05-31 
07:51:18.000000000 +0200
+++ new/chameleon-3.8.1/src/chameleon/tests/outputs/027.pt      2020-07-06 
21:23:38.000000000 +0200
@@ -1,6 +1,6 @@
 <div xmlns="http://www.w3.org/1999/xhtml";>
   <span id="test"
-        class="defabcdummy"
+        class="dummy"
         onClick="alert();" style="hij">abcghi</span>
   Hello World!
   Hello World!
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/chameleon-3.7.2/src/chameleon/tests/outputs/028.pt 
new/chameleon-3.8.1/src/chameleon/tests/outputs/028.pt
--- old/chameleon-3.7.2/src/chameleon/tests/outputs/028.pt      2020-05-31 
07:51:18.000000000 +0200
+++ new/chameleon-3.8.1/src/chameleon/tests/outputs/028.pt      2020-07-06 
21:23:38.000000000 +0200
@@ -1,5 +1,5 @@
 <div xmlns="http://www.w3.org/1999/xhtml";>
   <option selected="True"></option>
-  <option></option>
+  <option selected="False"></option>
   <option></option>
 </div>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/chameleon-3.7.2/src/chameleon/tests/outputs/071.pt 
new/chameleon-3.8.1/src/chameleon/tests/outputs/071.pt
--- old/chameleon-3.7.2/src/chameleon/tests/outputs/071.pt      2020-05-31 
07:51:18.000000000 +0200
+++ new/chameleon-3.8.1/src/chameleon/tests/outputs/071.pt      2020-07-06 
21:23:38.000000000 +0200
@@ -1,7 +1,7 @@
 <html>
   <body>
     <input type="input" checked="True" />
-    <input type="input" />
+    <input type="input" checked="False" />
     <input type="input" />
     <input type="input" checked="checked" />
     <input type="input" checked />
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/chameleon-3.7.2/src/chameleon/tests/outputs/072.pt 
new/chameleon-3.8.1/src/chameleon/tests/outputs/072.pt
--- old/chameleon-3.7.2/src/chameleon/tests/outputs/072.pt      2020-05-31 
07:51:18.000000000 +0200
+++ new/chameleon-3.8.1/src/chameleon/tests/outputs/072.pt      2020-07-06 
21:23:38.000000000 +0200
@@ -10,10 +10,5 @@
       <li>2</li>
       <li class="odd">3</li>
     </ul>
-    <ul>
-      <li class="odd">1</li>
-      <li>2</li>
-      <li class="odd">3</li>
-    </ul>
   </body>
 </html>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/chameleon-3.7.2/src/chameleon/tests/test_astutil.py 
new/chameleon-3.8.1/src/chameleon/tests/test_astutil.py
--- old/chameleon-3.7.2/src/chameleon/tests/test_astutil.py     1970-01-01 
01:00:00.000000000 +0100
+++ new/chameleon-3.8.1/src/chameleon/tests/test_astutil.py     2020-07-06 
21:23:38.000000000 +0200
@@ -0,0 +1,31 @@
+import ast
+import sys
+import unittest
+
+
+class ASTCodeGeneratorTestCase(unittest.TestCase):
+    def _eval(self, tree, env):
+        from chameleon.astutil import ASTCodeGenerator
+        source = ASTCodeGenerator(tree).code
+        code = compile(source, '<string>', 'exec')
+        exec(code, env)
+
+    if sys.version_info >= (3, 7):
+        def test_slice(self):
+            tree = ast.Module(
+                body=[
+                    ast.Assign(
+                        targets=[
+                            ast.Name(id='x', ctx=ast.Store())],
+                        value=ast.Call(
+                            func=ast.Name(id='f', ctx=ast.Load()),
+                            args=[
+                                ast.Slice(
+                                    upper=ast.Constant(value=0))],
+                            keywords=[]))],
+                type_ignores=[]
+            )
+            def f(x): return x
+            d = {"f": f}
+            self._eval(tree, d)
+            assert d['x'] == slice(None, 0, None)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/chameleon-3.7.2/src/chameleon/tests/test_templates.py 
new/chameleon-3.8.1/src/chameleon/tests/test_templates.py
--- old/chameleon-3.7.2/src/chameleon/tests/test_templates.py   2020-05-31 
07:51:18.000000000 +0200
+++ new/chameleon-3.8.1/src/chameleon/tests/test_templates.py   2020-07-06 
21:23:38.000000000 +0200
@@ -23,6 +23,7 @@
 
 from chameleon.utils import byte_string
 from chameleon.exc import RenderError
+from chameleon.tales import DEFAULT_MARKER
 
 
 class Message(object):
@@ -111,6 +112,7 @@
 
 class RenderTestCase(TestCase):
     root = os.path.dirname(__file__)
+    maxDiff = 4096
 
     def find_files(self, ext):
         inputs = os.path.join(self.root, "inputs")
@@ -573,23 +575,13 @@
         self.assertTrue('foo' in result)
         self.assertTrue('foo' in result)
 
-    def test_literal_false(self):
-        template = self.from_string(
-            '<input type="input" tal:attributes="checked False" />'
-            '<input type="input" tal:attributes="checked True" />'
-            '<input type="input" tal:attributes="checked None" />'
-            '<input type="input" tal:attributes="checked default" />',
-            literal_false=True,
-            )
-
+    def test_default_marker(self):
+        template = self.from_string('<span tal:replace="id(default)" />')
         self.assertEqual(
             template(),
-            '<input type="input" checked="False" />'
-            '<input type="input" checked="True" />'
-            '<input type="input" />'
-            '<input type="input" />',
+            str(id(DEFAULT_MARKER)),
             template.source
-            )
+        )
 
     def test_boolean_attributes(self):
         template = self.from_string(
@@ -805,6 +797,9 @@
                 example = Example(input_path, want)
                 diff = checker.output_difference(
                     example, got, 0)
+                source = template.source
+                if sys.version_info < (3, ):
+                    source = source.encode("utf-8")
                 self.fail("(%s) - \n%s\n\nCode:\n%s" % (
                     input_path, diff.rstrip('\n'),
-                    template.source.encode('utf-8')))
+                    source))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/chameleon-3.7.2/src/chameleon/utils.py 
new/chameleon-3.8.1/src/chameleon/utils.py
--- old/chameleon-3.7.2/src/chameleon/utils.py  2020-05-31 07:51:18.000000000 
+0200
+++ new/chameleon-3.8.1/src/chameleon/utils.py  2020-07-06 21:23:38.000000000 
+0200
@@ -474,3 +474,17 @@
 
     def __repr__(self):
         return "s'%s'" % self
+
+
+class ImportableMarker(object):
+    def __init__(self, module, name):
+        self.__module__ = module
+        self.name = name
+
+    @property
+    def __name__(self):
+        return "%s_MARKER" % self.name
+
+    def __repr__(self):
+        return '<%s>' % self.name
+
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/chameleon-3.7.2/src/chameleon/zpt/program.py 
new/chameleon-3.8.1/src/chameleon/zpt/program.py
--- old/chameleon-3.7.2/src/chameleon/zpt/program.py    2020-05-31 
07:51:18.000000000 +0200
+++ new/chameleon-3.8.1/src/chameleon/zpt/program.py    2020-07-06 
21:23:38.000000000 +0200
@@ -23,10 +23,12 @@
 from ..namespaces import META_NS as META
 
 from ..astutil import Static
+from ..astutil import Symbol
 from ..astutil import parse
 from ..astutil import marker
 
 from .. import tal
+from .. import tales
 from .. import metal
 from .. import i18n
 from .. import nodes
@@ -36,6 +38,7 @@
 from ..exc import CompilationError
 
 from ..utils import decode_htmlentities
+from ..utils import ImportableMarker
 
 try:
     str = unicode
@@ -48,6 +51,7 @@
 re_trim = re.compile(r'($\s+|\s+^)', re.MULTILINE)
 
 EMPTY_DICT = Static(ast.Dict(keys=[], values=[]))
+CANCEL_MARKER = ImportableMarker(__name__, "CANCEL")
 
 
 def skip(node):
@@ -102,6 +106,7 @@
     _interpolation_enabled = True
     _whitespace = "\n"
     _last = ""
+    _cancel_marker = Symbol(CANCEL_MARKER)
 
     # Macro name (always trivial for a macro program)
     name = None
@@ -168,7 +173,7 @@
             'enable_data_attributes',
             'enable_comment_interpolation',
             'restricted_namespace',
-            )
+        )
 
         super(MacroProgram, self).__init__(*args, **kwargs)
 
@@ -219,7 +224,7 @@
             switch = None
         else:
             value = nodes.Value(clause)
-            switch = value, nodes.Copy(value)
+            switch = value
 
         self._switches.append(switch)
 
@@ -252,6 +257,7 @@
                 [nodes.Assignment(["macroname"], Static(ast.Str(macro_name)), 
True)],
                 inner,
             )
+            STATIC_ATTRIBUTES = None
         # -or- include tag
         else:
             content = nodes.Sequence(body)
@@ -363,12 +369,6 @@
                     content,
                     )
 
-                # Assign static attributes dictionary to "attrs" value
-                inner = nodes.Define(
-                    [nodes.Alias(["attrs"], STATIC_ATTRIBUTES or EMPTY_DICT)],
-                    inner,
-                    )
-
                 if omit is not False:
                     inner = nodes.Cache([omit], inner)
 
@@ -419,15 +419,15 @@
                 ('local', ("target_language", ), target_language)
             )
 
-        if defines:
-            DEFINE = partial(
-                nodes.Define,
-                [nodes.Assignment(
-                    names, nodes.Value(expr), context == "local")
-                 for (context, names, expr) in defines],
-                )
-        else:
-            DEFINE = skip
+        assignments = [
+            nodes.Assignment(
+                names, nodes.Value(expr), context == "local")
+            for (context, names, expr) in defines
+        ]
+
+        # Assign static attributes dictionary to "attrs" value
+        assignments.insert(0, nodes.Alias(["attrs"], STATIC_ATTRIBUTES or 
EMPTY_DICT))
+        DEFINE = partial(nodes.Define, assignments)
 
         # tal:case
         try:
@@ -445,10 +445,16 @@
                     )
 
             CASE = lambda node: nodes.Define(
-                [nodes.Alias(["default"], switch[1], False)],
+                [nodes.Alias(["default"], self.default_marker)],
                 nodes.Condition(
-                    nodes.Equality(switch[0], value),
-                    nodes.Cancel([switch[0]], node),
+                    nodes.And([
+                        nodes.BinOp(switch, nodes.IsNot, self._cancel_marker),
+                        nodes.Or([
+                            nodes.BinOp(value, nodes.Equals, switch),
+                            nodes.BinOp(value, nodes.Equals, 
self.default_marker)
+                        ])
+                    ]),
+                    nodes.Cancel([switch], node, self._cancel_marker),
                 ))
 
         # tal:repeat
@@ -489,7 +495,7 @@
         if switch is None:
             SWITCH = skip
         else:
-            SWITCH = partial(nodes.Cache, list(switch))
+            SWITCH = partial(nodes.Cache, [switch])
 
         # i18n:domain
         try:
@@ -712,9 +718,9 @@
 
     def _pop_defaults(self, kwargs, *attributes):
         for attribute in attributes:
-            default = getattr(self, attribute)
-            value = kwargs.pop(attribute, default)
-            setattr(self, attribute, value)
+            value = kwargs.pop(attribute, None)
+            if value is not None:
+                setattr(self, attribute, value)
 
     def _check_attributes(self, namespace, ns):
         if namespace in self.DROP_NS and ns.get((TAL, 'attributes')):
@@ -749,7 +755,7 @@
 
         if default is not None:
             content = nodes.Condition(
-                nodes.Identity(value, marker("default")),
+                nodes.BinOp(value, nodes.Is, self.default_marker),
                 default,
                 content,
                 )
@@ -759,7 +765,7 @@
 
             # Define local marker "default"
             content = nodes.Define(
-                [nodes.Alias(["default"], marker("default"))],
+                [nodes.Alias(["default"], self.default_marker)],
                 content
                 )
 
@@ -778,59 +784,52 @@
             )
 
             char_escape = ('&', '<', '>', quote)
-
-            # Use a provided default text as the default marker
-            # (aliased to the name ``default``), otherwise use the
-            # program's default marker value.
-            if text is not None:
-                default_marker = ast.Str(s=text)
-            else:
-                default_marker = self.default_marker
-
             msgid = I18N_ATTRIBUTES.get(name, missing)
 
             # If (by heuristic) ``text`` contains one or more
             # interpolation expressions, apply interpolation
-            # substitution to the text
+            # substitution to the text.
             if expr is None and text is not None and '${' in text:
-                expr = nodes.Substitution(text, char_escape)
+                default = None
+                expr = nodes.Substitution(text, char_escape, default, 
self.default_marker)
                 translation = implicit_i18n and msgid is missing
                 value = nodes.Interpolation(expr, True, translation)
-                default_marker = self.default_marker
+            else:
+                default = ast.Str(s=text) if text is not None else None
 
-            # If the expression is non-trivial, the attribute is
-            # dynamic (computed).
-            elif expr is not None:
-                if name is None:
-                    expression = nodes.Value(decode_htmlentities(expr))
-                    value = nodes.DictAttributes(
-                        expression, ('&', '<', '>', '"'), '"',
-                        set(filter(None, names[i:]))
-                    )
-                    for fs in filtering:
-                        fs.append(expression)
-                    filtering.append([])
-                elif name in self.boolean_attributes:
-                    value = nodes.Boolean(expr, name)
-                else:
-                    if text is not None:
-                        default = default_marker
+                # If the expression is non-trivial, the attribute is
+                # dynamic (computed).
+                if expr is not None:
+                    if name is None:
+                        expression = nodes.Value(
+                            decode_htmlentities(expr),
+                            default,
+                            self.default_marker
+                        )
+                        value = nodes.DictAttributes(
+                            expression, ('&', '<', '>', '"'), '"',
+                            set(filter(None, names[i:]))
+                        )
+                        for fs in filtering:
+                            fs.append(expression)
+                        filtering.append([])
+                    elif name in self.boolean_attributes:
+                        value = nodes.Boolean(expr, name, default, 
self.default_marker)
                     else:
-                        default = None
-
-                    value = nodes.Substitution(
-                        decode_htmlentities(expr),
-                        char_escape,
-                        default
-                    )
+                        value = nodes.Substitution(
+                            decode_htmlentities(expr),
+                            char_escape,
+                            default,
+                            self.default_marker,
+                        )
 
-            # Otherwise, it's a static attribute. We don't include it
-            # here if there's one or more "computed" attributes
-            # (dynamic, from one or more dict values).
-            else:
-                value = ast.Str(s=text)
-                if msgid is missing and implicit_i18n:
-                    msgid = text
+                # Otherwise, it's a static attribute. We don't include it
+                # here if there's one or more "computed" attributes
+                # (dynamic, from one or more dict values).
+                else:
+                    value = ast.Str(s=text)
+                    if msgid is missing and implicit_i18n:
+                        msgid = text
 
             if name is not None:
                 # If translation is required, wrap in a translation
@@ -839,16 +838,24 @@
                     value = nodes.Translate(msgid, value)
 
                 space = self._maybe_trim(space)
-                fs = filtering[-1]
-                attribute = nodes.Attribute(name, value, quote, eq, space, fs)
+
+                attribute = nodes.Attribute(
+                    name,
+                    value,
+                    quote,
+                    eq,
+                    space,
+                    default,
+                    filtering[-1],
+                )
 
                 if not isinstance(value, ast.Str):
                     # Always define a ``default`` alias for non-static
                     # expressions.
                     attribute = nodes.Define(
-                        [nodes.Alias(["default"], default_marker)],
+                        [nodes.Alias(["default"], self.default_marker)],
                         attribute,
-                        )
+                    )
 
                 value = attribute
 
@@ -856,9 +863,12 @@
 
         result = nodes.Sequence(attributes)
 
-        fs = filtering[0]
-        if fs:
-            return nodes.Cache(fs, result)
+        # We're caching all expressions found during attribute processing to
+        # enable the filtering functionality which exists to allow later
+        # definitions to override previous ones.
+        expressions = filtering[0]
+        if expressions:
+            return nodes.Cache(expressions, result)
 
         return result
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/chameleon-3.7.2/src/chameleon/zpt/template.py 
new/chameleon-3.8.1/src/chameleon/zpt/template.py
--- old/chameleon-3.7.2/src/chameleon/zpt/template.py   2020-05-31 
07:51:18.000000000 +0200
+++ new/chameleon-3.8.1/src/chameleon/zpt/template.py   2020-07-06 
21:23:38.000000000 +0200
@@ -16,17 +16,17 @@
 from ..tales import ProxyExpr
 from ..tales import StructureExpr
 from ..tales import ExpressionParser
-
+from ..tales import DEFAULT_MARKER
 from ..tal import RepeatDict
 
 from ..template import BaseTemplate
 from ..template import BaseTemplateFile
 from ..compiler import ExpressionEngine
 from ..loader import TemplateLoader
-from ..astutil import Builtin
 from ..utils import decode_string
 from ..utils import string_type
 from ..utils import unicode_string
+from ..astutil import Symbol
 
 from .program import MacroProgram
 
@@ -64,14 +64,6 @@
         Pass an encoding to allow encoded byte string input
         (e.g. UTF-8).
 
-      ``literal_false``
-
-        Attributes are not dropped for a value of ``False``. Instead,
-        the value is coerced to a string.
-
-        This setting exists to provide compatibility with the
-        reference implementation.
-
       ``boolean_attributes``
 
         Attributes included in this set are treated as booleans: if a
@@ -163,6 +155,15 @@
         exception output. The function must not raise an exception (it
         should be safe to call with any value).
 
+      ``default_marker``
+
+        This default marker is used as the marker object bound to the `default`
+        name available to any expression. The semantics is such that if an
+        expression evaluates to the marker object, the default action is used;
+        for an attribute expression, this is the static attribute text; for an
+        element this is the static element text. If there is no static text
+        then the default action is similar to an expression result of `None`.
+
     Output is unicode on Python 2 and string on Python 3.
 
     """
@@ -184,8 +185,6 @@
 
     boolean_attributes = set()
 
-    literal_false = False
-
     mode = "xml"
 
     implicit_i18n_translate = False
@@ -204,6 +203,8 @@
 
     tokenizer = None
 
+    default_marker = Symbol(DEFAULT_MARKER)
+
     def __init__(self, body, **config):
         self.macros = Macros(self)
         super(PageTemplate, self).__init__(body, **config)
@@ -217,15 +218,10 @@
 
     @property
     def engine(self):
-        if self.literal_false:
-            default_marker = Builtin("__default")
-        else:
-            default_marker = Builtin("False")
-
         return partial(
             ExpressionEngine,
             self.expression_parser,
-            default_marker=default_marker,
+            default_marker=self.default_marker,
             )
 
     @property
@@ -233,15 +229,10 @@
         return ExpressionParser(self.expression_types, self.default_expression)
 
     def parse(self, body):
-        if self.literal_false:
-            default_marker = Builtin("__default")
-        else:
-            default_marker = Builtin("False")
-
         return MacroProgram(
             body, self.mode, self.filename,
             escape=True if self.mode == "xml" else False,
-            default_marker=default_marker,
+            default_marker=self.default_marker,
             boolean_attributes=self.boolean_attributes,
             implicit_i18n_translate=self.implicit_i18n_translate,
             implicit_i18n_attributes=self.implicit_i18n_attributes,
@@ -329,7 +320,6 @@
         for attr in (
             'trim_attribute_space',
             'implicit_i18n_translate',
-            'literal_false',
             'strict'
         ):
             v = getattr(self, attr)


Reply via email to