Revert "metadata: Make JACK_METADATA_* constant"
[jack2.git] / waflib / ConfigSet.py
blobb300bb56b7cfee1c2375346304ff3f0bbef36fa9
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # Thomas Nagy, 2005-2018 (ita)
5 """
7 ConfigSet: a special dict
9 The values put in :py:class:`ConfigSet` must be serializable (dicts, lists, strings)
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 copy-on-write dict with human-readable serialized format. 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 Enables the *in* syntax::
44 if 'foo' in env:
45 print(env['foo'])
46 """
47 if key in self.table:
48 return True
49 try:
50 return self.parent.__contains__(key)
51 except AttributeError:
52 return False # parent may not exist
54 def keys(self):
55 """Dict interface"""
56 keys = set()
57 cur = self
58 while cur:
59 keys.update(cur.table.keys())
60 cur = getattr(cur, 'parent', None)
61 keys = list(keys)
62 keys.sort()
63 return keys
65 def __iter__(self):
66 return iter(self.keys())
68 def __str__(self):
69 """Text representation of the ConfigSet (for debugging purposes)"""
70 return "\n".join(["%r %r" % (x, self.__getitem__(x)) for x in self.keys()])
72 def __getitem__(self, key):
73 """
74 Dictionary interface: get value from key::
76 def configure(conf):
77 conf.env['foo'] = {}
78 print(env['foo'])
79 """
80 try:
81 while 1:
82 x = self.table.get(key)
83 if not x is None:
84 return x
85 self = self.parent
86 except AttributeError:
87 return []
89 def __setitem__(self, key, value):
90 """
91 Dictionary interface: set value from key
92 """
93 self.table[key] = value
95 def __delitem__(self, key):
96 """
97 Dictionary interface: mark the value as missing
98 """
99 self[key] = []
101 def __getattr__(self, name):
103 Attribute access provided for convenience. The following forms are equivalent::
105 def configure(conf):
106 conf.env.value
107 conf.env['value']
109 if name in self.__slots__:
110 return object.__getattribute__(self, name)
111 else:
112 return self[name]
114 def __setattr__(self, name, value):
116 Attribute access provided for convenience. The following forms are equivalent::
118 def configure(conf):
119 conf.env.value = x
120 env['value'] = x
122 if name in self.__slots__:
123 object.__setattr__(self, name, value)
124 else:
125 self[name] = value
127 def __delattr__(self, name):
129 Attribute access provided for convenience. The following forms are equivalent::
131 def configure(conf):
132 del env.value
133 del env['value']
135 if name in self.__slots__:
136 object.__delattr__(self, name)
137 else:
138 del self[name]
140 def derive(self):
142 Returns a new ConfigSet deriving from self. The copy returned
143 will be a shallow copy::
145 from waflib.ConfigSet import ConfigSet
146 env = ConfigSet()
147 env.append_value('CFLAGS', ['-O2'])
148 child = env.derive()
149 child.CFLAGS.append('test') # warning! this will modify 'env'
150 child.CFLAGS = ['-O3'] # new list, ok
151 child.append_value('CFLAGS', ['-O3']) # ok
153 Use :py:func:`ConfigSet.detach` to detach the child from the parent.
155 newenv = ConfigSet()
156 newenv.parent = self
157 return newenv
159 def detach(self):
161 Detaches this instance from its parent (if present)
163 Modifying the parent :py:class:`ConfigSet` will not change the current object
164 Modifying this :py:class:`ConfigSet` will not modify the parent one.
166 tbl = self.get_merged_dict()
167 try:
168 delattr(self, 'parent')
169 except AttributeError:
170 pass
171 else:
172 keys = tbl.keys()
173 for x in keys:
174 tbl[x] = copy.deepcopy(tbl[x])
175 self.table = tbl
176 return self
178 def get_flat(self, key):
180 Returns a value as a string. If the input is a list, the value returned is space-separated.
182 :param key: key to use
183 :type key: string
185 s = self[key]
186 if isinstance(s, str):
187 return s
188 return ' '.join(s)
190 def _get_list_value_for_modification(self, key):
192 Returns a list value for further modification.
194 The list may be modified inplace and there is no need to do this afterwards::
196 self.table[var] = value
198 try:
199 value = self.table[key]
200 except KeyError:
201 try:
202 value = self.parent[key]
203 except AttributeError:
204 value = []
205 else:
206 if isinstance(value, list):
207 # force a copy
208 value = value[:]
209 else:
210 value = [value]
211 self.table[key] = value
212 else:
213 if not isinstance(value, list):
214 self.table[key] = value = [value]
215 return value
217 def append_value(self, var, val):
219 Appends a value to the specified config key::
221 def build(bld):
222 bld.env.append_value('CFLAGS', ['-O2'])
224 The value must be a list or a tuple
226 if isinstance(val, str): # if there were string everywhere we could optimize this
227 val = [val]
228 current_value = self._get_list_value_for_modification(var)
229 current_value.extend(val)
231 def prepend_value(self, var, val):
233 Prepends a value to the specified item::
235 def configure(conf):
236 conf.env.prepend_value('CFLAGS', ['-O2'])
238 The value must be a list or a tuple
240 if isinstance(val, str):
241 val = [val]
242 self.table[var] = val + self._get_list_value_for_modification(var)
244 def append_unique(self, var, val):
246 Appends a value to the specified item only if it's not already present::
248 def build(bld):
249 bld.env.append_unique('CFLAGS', ['-O2', '-g'])
251 The value must be a list or a tuple
253 if isinstance(val, str):
254 val = [val]
255 current_value = self._get_list_value_for_modification(var)
257 for x in val:
258 if x not in current_value:
259 current_value.append(x)
261 def get_merged_dict(self):
263 Computes the merged dictionary from the fusion of self and all its parent
265 :rtype: a ConfigSet object
267 table_list = []
268 env = self
269 while 1:
270 table_list.insert(0, env.table)
271 try:
272 env = env.parent
273 except AttributeError:
274 break
275 merged_table = {}
276 for table in table_list:
277 merged_table.update(table)
278 return merged_table
280 def store(self, filename):
282 Serializes the :py:class:`ConfigSet` data to a file. See :py:meth:`ConfigSet.load` for reading such files.
284 :param filename: file to use
285 :type filename: string
287 try:
288 os.makedirs(os.path.split(filename)[0])
289 except OSError:
290 pass
292 buf = []
293 merged_table = self.get_merged_dict()
294 keys = list(merged_table.keys())
295 keys.sort()
297 try:
298 fun = ascii
299 except NameError:
300 fun = repr
302 for k in keys:
303 if k != 'undo_stack':
304 buf.append('%s = %s\n' % (k, fun(merged_table[k])))
305 Utils.writef(filename, ''.join(buf))
307 def load(self, filename):
309 Restores contents from a file (current values are not cleared). Files are written using :py:meth:`ConfigSet.store`.
311 :param filename: file to use
312 :type filename: string
314 tbl = self.table
315 code = Utils.readf(filename, m='rU')
316 for m in re_imp.finditer(code):
317 g = m.group
318 tbl[g(2)] = eval(g(3))
319 Logs.debug('env: %s', self.table)
321 def update(self, d):
323 Dictionary interface: replace values with the ones from another dict
325 :param d: object to use the value from
326 :type d: dict-like object
328 self.table.update(d)
330 def stash(self):
332 Stores the object state to provide transactionality semantics::
334 env = ConfigSet()
335 env.stash()
336 try:
337 env.append_value('CFLAGS', '-O3')
338 call_some_method(env)
339 finally:
340 env.revert()
342 The history is kept in a stack, and is lost during the serialization by :py:meth:`ConfigSet.store`
344 orig = self.table
345 tbl = self.table = self.table.copy()
346 for x in tbl.keys():
347 tbl[x] = copy.deepcopy(tbl[x])
348 self.undo_stack = self.undo_stack + [orig]
350 def commit(self):
352 Commits transactional changes. See :py:meth:`ConfigSet.stash`
354 self.undo_stack.pop(-1)
356 def revert(self):
358 Reverts the object to a previous state. See :py:meth:`ConfigSet.stash`
360 self.table = self.undo_stack.pop(-1)