Do not assert/crash when bad clients send unordered events
[jack2.git] / waflib / ConfigSet.py
blob2fac2166e27442e67520270a9c45770da8098bf1
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # Thomas Nagy, 2005-2010 (ita)
5 """
7 ConfigSet: a special dict
9 The values put in :py:class:`ConfigSet` must be lists
10 """
12 import copy, re, os
13 from waflib import Logs, Utils
14 re_imp = re.compile('^(#)*?([^#=]*?)\ =\ (.*?)$', re.M)
16 class ConfigSet(object):
17 """
18 A dict that honor serialization and parent relationships. The serialization format
19 is human-readable (python-like) and performed by using eval() and repr().
20 For high performance prefer pickle. Do not store functions as they are not serializable.
22 The values can be accessed by attributes or by keys::
24 from waflib.ConfigSet import ConfigSet
25 env = ConfigSet()
26 env.FOO = 'test'
27 env['FOO'] = 'test'
28 """
29 __slots__ = ('table', 'parent')
30 def __init__(self, filename=None):
31 self.table = {}
32 """
33 Internal dict holding the object values
34 """
35 #self.parent = None
37 if filename:
38 self.load(filename)
40 def __contains__(self, key):
41 """
42 Enable the *in* syntax::
44 if 'foo' in env:
45 print(env['foo'])
46 """
47 if key in self.table: return True
48 try: return self.parent.__contains__(key)
49 except AttributeError: return False # parent may not exist
51 def keys(self):
52 """Dict interface (unknown purpose)"""
53 keys = set()
54 cur = self
55 while cur:
56 keys.update(cur.table.keys())
57 cur = getattr(cur, 'parent', None)
58 keys = list(keys)
59 keys.sort()
60 return keys
62 def __str__(self):
63 """Text representation of the ConfigSet (for debugging purposes)"""
64 return "\n".join(["%r %r" % (x, self.__getitem__(x)) for x in self.keys()])
66 def __getitem__(self, key):
67 """
68 Dictionary interface: get value from key::
70 def configure(conf):
71 conf.env['foo'] = {}
72 print(env['foo'])
73 """
74 try:
75 while 1:
76 x = self.table.get(key, None)
77 if not x is None:
78 return x
79 self = self.parent
80 except AttributeError:
81 return []
83 def __setitem__(self, key, value):
84 """
85 Dictionary interface: get value from key
86 """
87 self.table[key] = value
89 def __delitem__(self, key):
90 """
91 Dictionary interface: get value from key
92 """
93 self[key] = []
95 def __getattr__(self, name):
96 """
97 Attribute access provided for convenience. The following forms are equivalent::
99 def configure(conf):
100 conf.env.value
101 conf.env['value']
103 if name in self.__slots__:
104 return object.__getattr__(self, name)
105 else:
106 return self[name]
108 def __setattr__(self, name, value):
110 Attribute access provided for convenience. The following forms are equivalent::
112 def configure(conf):
113 conf.env.value = x
114 env['value'] = x
116 if name in self.__slots__:
117 object.__setattr__(self, name, value)
118 else:
119 self[name] = value
121 def __delattr__(self, name):
123 Attribute access provided for convenience. The following forms are equivalent::
125 def configure(conf):
126 del env.value
127 del env['value']
129 if name in self.__slots__:
130 object.__delattr__(self, name)
131 else:
132 del self[name]
134 def derive(self):
136 Returns a new ConfigSet deriving from self. The copy returned
137 will be a shallow copy::
139 from waflib.ConfigSet import ConfigSet
140 env = ConfigSet()
141 env.append_value('CFLAGS', ['-O2'])
142 child = env.derive()
143 child.CFLAGS.append('test') # warning! this will modify 'env'
144 child.CFLAGS = ['-O3'] # new list, ok
145 child.append_value('CFLAGS', ['-O3']) # ok
147 Use :py:func:`ConfigSet.detach` to detach the child from the parent.
149 newenv = ConfigSet()
150 newenv.parent = self
151 return newenv
153 def detach(self):
155 Detach self from its parent (if existing)
157 Modifying the parent :py:class:`ConfigSet` will not change the current object
158 Modifying this :py:class:`ConfigSet` will not modify the parent one.
160 tbl = self.get_merged_dict()
161 try:
162 delattr(self, 'parent')
163 except AttributeError:
164 pass
165 else:
166 keys = tbl.keys()
167 for x in keys:
168 tbl[x] = copy.deepcopy(tbl[x])
169 self.table = tbl
170 return self
172 def get_flat(self, key):
174 Return a value as a string. If the input is a list, the value returned is space-separated.
176 :param key: key to use
177 :type key: string
179 s = self[key]
180 if isinstance(s, str): return s
181 return ' '.join(s)
183 def _get_list_value_for_modification(self, key):
185 Return a list value for further modification.
187 The list may be modified inplace and there is no need to do this afterwards::
189 self.table[var] = value
191 try:
192 value = self.table[key]
193 except KeyError:
194 try: value = self.parent[key]
195 except AttributeError: value = []
196 if isinstance(value, list):
197 value = value[:]
198 else:
199 value = [value]
200 else:
201 if not isinstance(value, list):
202 value = [value]
203 self.table[key] = value
204 return value
206 def append_value(self, var, val):
208 Appends a value to the specified config key::
210 def build(bld):
211 bld.env.append_value('CFLAGS', ['-O2'])
213 The value must be a list or a tuple
215 if isinstance(val, str): # if there were string everywhere we could optimize this
216 val = [val]
217 current_value = self._get_list_value_for_modification(var)
218 current_value.extend(val)
220 def prepend_value(self, var, val):
222 Prepends a value to the specified item::
224 def configure(conf):
225 conf.env.prepend_value('CFLAGS', ['-O2'])
227 The value must be a list or a tuple
229 if isinstance(val, str):
230 val = [val]
231 self.table[var] = val + self._get_list_value_for_modification(var)
233 def append_unique(self, var, val):
235 Append a value to the specified item only if it's not already present::
237 def build(bld):
238 bld.env.append_unique('CFLAGS', ['-O2', '-g'])
240 The value must be a list or a tuple
242 if isinstance(val, str):
243 val = [val]
244 current_value = self._get_list_value_for_modification(var)
246 for x in val:
247 if x not in current_value:
248 current_value.append(x)
250 def get_merged_dict(self):
252 Compute the merged dictionary from the fusion of self and all its parent
254 :rtype: a ConfigSet object
256 table_list = []
257 env = self
258 while 1:
259 table_list.insert(0, env.table)
260 try: env = env.parent
261 except AttributeError: break
262 merged_table = {}
263 for table in table_list:
264 merged_table.update(table)
265 return merged_table
267 def store(self, filename):
269 Write the :py:class:`ConfigSet` data into a file. See :py:meth:`ConfigSet.load` for reading such files.
271 :param filename: file to use
272 :type filename: string
274 try:
275 os.makedirs(os.path.split(filename)[0])
276 except OSError:
277 pass
279 buf = []
280 merged_table = self.get_merged_dict()
281 keys = list(merged_table.keys())
282 keys.sort()
284 try:
285 fun = ascii
286 except NameError:
287 fun = repr
289 for k in keys:
290 if k != 'undo_stack':
291 buf.append('%s = %s\n' % (k, fun(merged_table[k])))
292 Utils.writef(filename, ''.join(buf))
294 def load(self, filename):
296 Retrieve the :py:class:`ConfigSet` data from a file. See :py:meth:`ConfigSet.store` for writing such files
298 :param filename: file to use
299 :type filename: string
301 tbl = self.table
302 code = Utils.readf(filename, m='rU')
303 for m in re_imp.finditer(code):
304 g = m.group
305 tbl[g(2)] = eval(g(3))
306 Logs.debug('env: %s' % str(self.table))
308 def update(self, d):
310 Dictionary interface: replace values from another dict
312 :param d: object to use the value from
313 :type d: dict-like object
315 for k, v in d.items():
316 self[k] = v
318 def stash(self):
320 Store the object state, to provide a kind of transaction support::
322 env = ConfigSet()
323 env.stash()
324 try:
325 env.append_value('CFLAGS', '-O3')
326 call_some_method(env)
327 finally:
328 env.revert()
330 The history is kept in a stack, and is lost during the serialization by :py:meth:`ConfigSet.store`
332 orig = self.table
333 tbl = self.table = self.table.copy()
334 for x in tbl.keys():
335 tbl[x] = copy.deepcopy(tbl[x])
336 self.undo_stack = self.undo_stack + [orig]
338 def revert(self):
340 Reverts the object to a previous state. See :py:meth:`ConfigSet.stash`
342 self.table = self.undo_stack.pop(-1)