1#-*-python-*-
2#GemRB - Infinity Engine Emulator
3#Copyright (C) 2009 The GemRB Project
4#
5#This program is free software; you can redistribute it and/or
6#modify it under the terms of the GNU General Public License
7#as published by the Free Software Foundation; either version 2
8#of the License, or (at your option) any later version.
9#
10#This program is distributed in the hope that it will be useful,
11#but WITHOUT ANY WARRANTY; without even the implied warranty of
12#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13#GNU General Public License for more details.
14#
15#You should have received a copy of the GNU General Public License
16#along with this program; if not, write to the Free Software
17#Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
19
20# The metaclasses below are used to define objects that call
21# functions in the GemRB module.
22#
23# Example:
24# class GTable:
25#  __metaclass__ = metaIDWrapper
26#  methods = {
27#    'GetValue': GemRB.GetTableValue,
28#  }
29#
30# x = GTable(5)
31#
32# Calling
33# x.GetValue("Row", "Col")
34# will then execute
35# GemRB.GetTableValue(5, "Row", "Col")
36
37from types import MethodType
38
39def add_metaclass(metaclass):
40    """Class decorator for creating a class with a metaclass."""
41    def wrapper(cls):
42        orig_vars = cls.__dict__.copy()
43        slots = orig_vars.get('__slots__')
44        if slots is not None:
45            if isinstance(slots, str):
46                slots = [slots]
47            for slots_var in slots:
48                orig_vars.pop(slots_var, None)
49        orig_vars.pop('__dict__', None)
50        orig_vars.pop('__weakref__', None)
51        if hasattr(cls, '__qualname__'):
52            orig_vars['__qualname__'] = cls.__qualname__
53        return metaclass(cls.__name__, cls.__bases__, orig_vars)
54    return wrapper
55
56def MethodAttributeError(f):
57	def handler(*args, **kwargs):
58		try:
59			return f(*args, **kwargs)
60		except Exception as e:
61			raise type(e)(str(e) + "\nMethod Docs:\n" + str(f.__doc__))
62	return handler
63
64class metaIDWrapper(type):
65	@classmethod
66	def InitMethod(cls, f = None):
67		def __init__(self, *args, **kwargs):
68			for k,v in kwargs.items():
69				setattr(self, k, v)
70
71			#required attributes for bridging to C++
72			assert getattr(self, 'ID', None) is not None
73
74			if f:
75				f(self, *args)
76		return __init__
77
78	def __new__(cls, classname, bases, classdict):
79		classdict['__slots__'] = classdict.get('__slots__', [])
80		classdict['__slots__'].append('ID')
81
82		classdict['__init__'] = classdict.get('__init__', None)
83		classdict['__init__'] = cls.InitMethod(classdict['__init__'])
84
85		methods = classdict.pop('methods', {})
86		c = super(metaIDWrapper, cls).__new__(cls, classname, bases, classdict)
87		# we must bind the methods after the class is created (instead of adding to classdict)
88		# otherwise the methods would be class methods instead of instance methods
89		for key in methods:
90			e = MethodAttributeError(methods[key])
91			try:
92				mtype = MethodType(e, None, c)
93				setattr(c, key, mtype)
94			except TypeError: # Python 3
95				setattr(c, key, e) # FIXME?: I dont actually know if this is correct, may have to circle back here after overcoming other errors
96		return c
97