Edgewall Software

Ticket #113: patch

File patch, 14.7 KB (added by darkness-keyword-genshi.e8bcba@…, 16 years ago)

Patch to fix code transformation including item/attr assignment

  • genshi/template/eval.py

    === modified file 'genshi/template/eval.py'
     
    236236        raise UndefinedError(self._name, self._owner)
    237237    __call__ = __getattr__ = __getitem__ = _die
    238238
     239class AccessWrapper (object):
     240    def __init__(self, target):
     241        # Being defensive about name collisions.  Have to use setattr
     242        # and a string to avoid name munging, since I don't want to
     243        # duplicate it for __getattribute__ below.
     244        object.__setattr__(self, "__target", target)
     245
     246    def __transparently__(self, action, exceptions, fallback, on_error=None):
     247        __traceback_hide__ = True
     248        if not callable(on_error):
     249            def on_error(target, exc_info):
     250                raise exc_info[0], exc_info[1], exc_info[2]
     251
     252        target = object.__getattribute__(self, "__target")
     253        try:
     254            return action(target)
     255        except exceptions:
     256            exc_info = sys.exc_info()
     257            try:
     258                try:
     259                    return fallback(target)
     260                except:
     261                    return on_error(target, exc_info)
     262            finally:
     263                del exc_info
     264
     265    def __getattribute__(self, name, on_error=None):
     266        __traceback_hide__ = True
     267        if name == "__transparently__":
     268            return object.__getattribute__(self, "__transparently__")
     269
     270        def action(target):
     271            return object.__getattribute__(target, name)
     272        def fallback(target):
     273            return target[name]
     274        return self.__transparently__(action, AttributeError, fallback,
     275                                      on_error)
     276
     277    def __setattr__(self, name, value):
     278        __traceback_hide__ = True
     279        def action(target):
     280            setattr(target, name, value)
     281        def fallback(target):
     282            target[name] = value
     283        self.__transparently__(action, AttributeError, fallback)
     284
     285    def __delattr__(self, name):
     286        __traceback_hide__ = True
     287        def action(target):
     288            delattr(target, name)
     289        def fallback(target):
     290            del target[name]
     291        self.__transparently__(action, AttributeError, fallback)
     292
     293    def __getitem__(self, key, on_error=None):
     294        __traceback_hide__ = True
     295        def action(target):
     296            return target[key]
     297        def fallback(target):
     298            return getattr(target, key)
     299        return self.__transparently__(action,
     300                                      (KeyError, IndexError, TypeError),
     301                                      fallback,
     302                                      on_error)
     303
     304    def __setitem__(self, key, value):
     305        __traceback_hide__ = True
     306        def action(target):
     307            target[key] = value
     308        def fallback(target):
     309            setattr(target, key, value)
     310        self.__transparently__(action, (IndexError, TypeError), fallback)
     311
     312    def __delitem__(self, key):
     313        __traceback_hide__ = True
     314        def action(target):
     315            del target[key]
     316        def fallback(target):
     317            delattr(target, key)
     318        self.__transparently__(action, (KeyError, IndexError, TypeError),
     319                               fallback)
     320
     321
     322class StrictAccessWrapper (AccessWrapper):
     323    def __getattribute__(self, key):
     324        __traceback_hide__ = True
     325        def on_error(target, exc_info):
     326            __traceback_hide__ = True
     327            raise UndefinedError(key, target)
     328        return AccessWrapper.__getattribute__(self, key, on_error)
     329
     330    def __getitem__(self, key):
     331        __traceback_hide__ = True
     332        def on_error(target, exc_info):
     333            __traceback_hide__ = True
     334            raise UndefinedError(key, target)
     335        return AccessWrapper.__getitem__(self, key, on_error)
     336
     337
     338class LenientAccessWrapper (AccessWrapper):
     339    def __getattribute__(self, key):
     340        __traceback_hide__ = True
     341        on_error = lambda target, exc_info: Undefined(key, target)
     342        return AccessWrapper.__getattribute__(self, key, on_error)
     343
     344    def __getitem__(self, key):
     345        __traceback_hide__ = True
     346        on_error = lambda target, exc_info: Undefined(key, target)
     347        return AccessWrapper.__getitem__(self, key, on_error)
     348
    239349
    240350class LookupBase(object):
    241351    """Abstract base class for variable lookup implementations."""
     
    246356        """
    247357        return {
    248358            '_lookup_name': cls.lookup_name,
    249             '_lookup_attr': cls.lookup_attr,
    250             '_lookup_item': cls.lookup_item
     359            '_wrapper': cls.wrapper_class,
    251360        }
    252361    globals = classmethod(globals)
    253362
     
    261370        return val
    262371    lookup_name = classmethod(lookup_name)
    263372
    264     def lookup_attr(cls, data, obj, key):
    265         __traceback_hide__ = True
    266         if hasattr(obj, key):
    267             return getattr(obj, key)
    268         try:
    269             return obj[key]
    270         except (KeyError, TypeError):
    271             return cls.undefined(key, owner=obj)
    272     lookup_attr = classmethod(lookup_attr)
    273 
    274     def lookup_item(cls, data, obj, key):
    275         __traceback_hide__ = True
    276         if len(key) == 1:
    277             key = key[0]
    278         try:
    279             return obj[key]
    280         except (AttributeError, KeyError, IndexError, TypeError), e:
    281             if isinstance(key, basestring):
    282                 val = getattr(obj, key, UNDEFINED)
    283                 if val is UNDEFINED:
    284                     return cls.undefined(key, owner=obj)
    285                 return val
    286             raise
    287     lookup_item = classmethod(lookup_item)
    288 
    289373    def undefined(cls, key, owner=UNDEFINED):
    290374        """Can be overridden by subclasses to specify behavior when undefined
    291375        variables are accessed.
     
    320404   
    321405    :see: `StrictLookup`
    322406    """
     407    wrapper_class = LenientAccessWrapper
     408
    323409    def undefined(cls, key, owner=UNDEFINED):
    324410        """Return an ``Undefined`` object."""
    325411        __traceback_hide__ = True
     
    348434        ...
    349435    UndefinedError: {} has no member named "nil"
    350436    """
     437    wrapper_class = StrictAccessWrapper
     438
    351439    def undefined(cls, key, owner=UNDEFINED):
    352440        """Raise an ``UndefinedError`` immediately."""
    353441        __traceback_hide__ = True
     
    388476                    code.co_lnotab, (), ())
    389477
    390478BUILTINS = __builtin__.__dict__.copy()
    391 BUILTINS.update({'Markup': Markup, 'Undefined': Undefined})
     479BUILTINS.update({'Markup': Markup, 'Undefined': Undefined,
     480                 'UndefinedError': UndefinedError})
    392481
    393482
    394483class ASTTransformer(object):
     
    475564        node.expr = self.visit(node.expr)
    476565        return node
    477566
     567    def visitAssAttr(self, node):
     568        node.expr = self.visit(node.expr)
     569        return node
     570
     571    def visitAugAssign(self, node):
     572        node.node = self.visit(node.node)
     573        node.expr = self.visit(node.expr)
     574        return node
     575
    478576    def visitDecorators(self, node):
    479577        node.nodes = [self.visit(x) for x in node.nodes]
    480578        return node
    481579
     580    def visitExec(self, node):
     581        node.expr = self.visit(node.expr)
     582        node.locals = self.visit(node.locals)
     583        node.globals = self.visit(node.globals)
     584        return node
     585
    482586    def visitFor(self, node):
    483587        node.assign = self.visit(node.assign)
    484588        node.list = self.visit(node.list)
     
    503607        node.expr3 = self.visit(node.expr3)
    504608        return node
    505609
     610    def visitReturn(self, node):
     611        node.value = self.visit(node.value)
     612        return node
     613
    506614    def visitTryExcept(self, node):
    507615        node.body = self.visit(node.body)
    508616        node.handlers = self.visit(node.handlers)
     
    536644        node.nodes = [self.visit(x) for x in node.nodes]
    537645        return node
    538646    visitAnd = visitOr = visitBitand = visitBitor = visitBitxor = _visitBoolOp
    539     visitAssTuple = _visitBoolOp
     647    visitAssTuple = visitAssList = _visitBoolOp
    540648
    541649    def _visitBinOp(self, node):
    542650        node.left = self.visit(node.left)
     
    646754                return ast.Const(node.value.decode('utf-8'))
    647755        return node
    648756
     757    def visitAugAssign(self, node):
     758        if isinstance(node.node, ast.Name):
     759            name = node.node.name
     760            node.node = ast.Subscript(ast.Name('data'), 'OP_APPLY',
     761                                      [ast.Const(name)])
     762            node.expr = self.visit(node.expr)
     763            # if name in data: do the augmented assignment
     764            # otherwise: raise UndefinedError
     765            #
     766            # I don't think using an Undefined instance here would
     767            # make sense, since the augmented assignment would just
     768            # fail.
     769            return ast.If([
     770                (ast.Compare(ast.Const(name),[('in', ast.Name('data'))]),
     771                 ast.Stmt([node]))
     772                ], ast.Stmt([ast.Raise(ast.CallFunc(ast.Name('UndefinedError'),
     773                                                    [ast.Const(name)]),
     774                                       None, None)]))
     775        else:
     776            return ASTTransformer.visitAugAssign(self, node)
     777
    649778    def visitAssName(self, node):
    650779        if self.locals:
    651780            self.locals[-1].add(node.name)
    652781        return node
    653782
     783    def visitAssAttr(self, node):
     784        node = ASTTransformer.visitAssAttr(self, node)
     785        node.expr = ast.CallFunc(ast.Name("_wrapper"), [node.expr])
     786        return node
     787
    654788    def visitClass(self, node):
    655789        self.locals.append(set())
    656790        node = ASTTransformer.visitClass(self, node)
     
    676810        return node
    677811
    678812    def visitGetattr(self, node):
    679         return ast.CallFunc(ast.Name('_lookup_attr'), [
    680             ast.Name('data'), self.visit(node.expr),
    681             ast.Const(node.attrname)
    682         ])
     813        node = ASTTransformer.visitGetattr(self, node)
     814        node.expr = ast.CallFunc(ast.Name("_wrapper"), [node.expr])
     815        return node
    683816
    684817    def visitLambda(self, node):
    685818        self.locals.append(set(flatten(node.argnames)))
     
    704837        return ast.CallFunc(ast.Name('_lookup_name'), func_args)
    705838
    706839    def visitSubscript(self, node):
    707         return ast.CallFunc(ast.Name('_lookup_item'), [
    708             ast.Name('data'), self.visit(node.expr),
    709             ast.Tuple([self.visit(sub) for sub in node.subs])
    710         ])
     840        node = ASTTransformer.visitSubscript(self, node)
     841        node.expr = ast.CallFunc(ast.Name("_wrapper"), [node.expr])
     842        return node
  • genshi/template/tests/eval.py

    === modified file 'genshi/template/tests/eval.py'
     
    379379            expr.evaluate({'something': Something()})
    380380            self.fail('Expected UndefinedError')
    381381        except UndefinedError, e:
     382            self.assertEqual('<Something> has no member named "nil"', str(e))
    382383            exc_type, exc_value, exc_traceback = sys.exc_info()
     384            search_string = "<Expression 'something.nil'>"
    383385            frame = exc_traceback.tb_next
    384             frames = []
    385386            while frame.tb_next:
    386387                frame = frame.tb_next
    387                 frames.append(frame)
    388             self.assertEqual('<Something> has no member named "nil"', str(e))
    389             self.assertEqual("<Expression 'something.nil'>",
    390                              frames[-3].tb_frame.f_code.co_name)
    391             self.assertEqual('index.html',
    392                              frames[-3].tb_frame.f_code.co_filename)
    393             self.assertEqual(50, frames[-3].tb_lineno)
     388                code = frame.tb_frame.f_code
     389                if code.co_name == search_string:
     390                    break
     391            else:
     392                self.fail("never found the frame I was looking for")
     393            self.assertEqual('index.html', code.co_filename)
     394            self.assertEqual(50, frame.tb_lineno)
    394395
    395396    def test_error_getitem_undefined_string(self):
    396397        class Something(object):
     
    402403            expr.evaluate({'something': Something()})
    403404            self.fail('Expected UndefinedError')
    404405        except UndefinedError, e:
     406            self.assertEqual('<Something> has no member named "nil"', str(e))
    405407            exc_type, exc_value, exc_traceback = sys.exc_info()
     408            search_string = '''<Expression 'something["nil"]'>'''
    406409            frame = exc_traceback.tb_next
    407             frames = []
    408410            while frame.tb_next:
    409411                frame = frame.tb_next
    410                 frames.append(frame)
    411             self.assertEqual('<Something> has no member named "nil"', str(e))
    412             self.assertEqual('''<Expression 'something["nil"]'>''',
    413                              frames[-3].tb_frame.f_code.co_name)
    414             self.assertEqual('index.html',
    415                              frames[-3].tb_frame.f_code.co_filename)
    416             self.assertEqual(50, frames[-3].tb_lineno)
     412                code = frame.tb_frame.f_code
     413                if code.co_name == search_string:
     414                    break
     415            else:
     416                self.fail("never found the frame I was looking for")
     417            self.assertEqual('index.html', code.co_filename)
     418            self.assertEqual(50, frame.tb_lineno)
    417419
    418420
    419421class SuiteTestCase(unittest.TestCase):
     
    504506        suite.execute(data)
    505507        self.assertEqual(4, data['x'])
    506508
     509    def test_augmented_attribute_assignment(self):
     510        suite = Suite("d.k += 42")
     511        d = {"k": 1}
     512        suite.execute({"d": d})
     513        self.assertEqual(43, d["k"])
     514
     515    def test_local_augmented_assign(self):
     516        Suite("x = 1; x += 42; assert x == 43").execute({})
     517
     518    def test_assign_in_list(self):
     519        suite = Suite("[d.k] = 'foo',; assert d['k'] == 'foo'")
     520        d = {"k": "bar"}
     521        suite.execute({"d": d})
     522        self.assertEqual("foo", d["k"])
     523
     524    def test_exec(self):
     525        suite = Suite("x = 1; exec d.k; assert x == 42, x")
     526        suite.execute({"d": {"k": "x = 42"}})
     527
     528    def test_return(self):
     529        suite = Suite("""
     530def f():
     531    return v
     532
     533assert f() == 42
     534""")
     535        suite.execute({"v": 42})
     536
     537    def test_assign_to_dict_item(self):
     538        suite = Suite("d['k'] = 'foo'")
     539        data = {"d": {}}
     540        suite.execute(data)
     541        self.assertEqual("foo", data["d"]["k"])
     542
     543    def test_assign_to_attribute(self):
     544        suite = Suite("d.k = 'foo'")
     545        data = {"d": {}}
     546        suite.execute(data)
     547        self.assertEqual("foo", data["d"]["k"])
     548
     549    def test_delattr(self):
     550        d = {"k": "foo"}
     551        Suite("del d.k").execute({"d": d})
     552        self.failIf("k" in d, `d`)
     553
     554    def test_delitem(self):
     555        class Container (object):
     556            def __init__(self):
     557                self.foo = "bar"
     558        c = Container()
     559        Suite("del c['foo']").execute({"c": c})
     560        self.failIf(hasattr(c, "foo"), dir(c))
     561
    507562
    508563def suite():
    509564    suite = unittest.TestSuite()