901fba6c06703288682201ac467453196062e8a9
[bbaumbach/samba-autobuild/.git] / third_party / waf / waflib / ConfigSet.py
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # Thomas Nagy, 2005-2018 (ita)
4
5 """
6
7 ConfigSet: a special dict
8
9 The values put in :py:class:`ConfigSet` must be serializable (dicts, lists, strings)
10 """
11
12 import copy, re, os
13 from waflib import Logs, Utils
14 re_imp = re.compile(r'^(#)*?([^#=]*?)\ =\ (.*?)$', re.M)
15
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.
21
22         The values can be accessed by attributes or by keys::
23
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
36
37                 if filename:
38                         self.load(filename)
39
40         def __contains__(self, key):
41                 """
42                 Enables the *in* syntax::
43
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
53
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
64
65         def __iter__(self):
66                 return iter(self.keys())
67
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()])
71
72         def __getitem__(self, key):
73                 """
74                 Dictionary interface: get value from key::
75
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 []
88
89         def __setitem__(self, key, value):
90                 """
91                 Dictionary interface: set value from key
92                 """
93                 self.table[key] = value
94
95         def __delitem__(self, key):
96                 """
97                 Dictionary interface: mark the value as missing
98                 """
99                 self[key] = []
100
101         def __getattr__(self, name):
102                 """
103                 Attribute access provided for convenience. The following forms are equivalent::
104
105                         def configure(conf):
106                                 conf.env.value
107                                 conf.env['value']
108                 """
109                 if name in self.__slots__:
110                         return object.__getattribute__(self, name)
111                 else:
112                         return self[name]
113
114         def __setattr__(self, name, value):
115                 """
116                 Attribute access provided for convenience. The following forms are equivalent::
117
118                         def configure(conf):
119                                 conf.env.value = x
120                                 env['value'] = x
121                 """
122                 if name in self.__slots__:
123                         object.__setattr__(self, name, value)
124                 else:
125                         self[name] = value
126
127         def __delattr__(self, name):
128                 """
129                 Attribute access provided for convenience. The following forms are equivalent::
130
131                         def configure(conf):
132                                 del env.value
133                                 del env['value']
134                 """
135                 if name in self.__slots__:
136                         object.__delattr__(self, name)
137                 else:
138                         del self[name]
139
140         def derive(self):
141                 """
142                 Returns a new ConfigSet deriving from self. The copy returned
143                 will be a shallow copy::
144
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
152
153                 Use :py:func:`ConfigSet.detach` to detach the child from the parent.
154                 """
155                 newenv = ConfigSet()
156                 newenv.parent = self
157                 return newenv
158
159         def detach(self):
160                 """
161                 Detaches this instance from its parent (if present)
162
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.
165                 """
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
177
178         def get_flat(self, key):
179                 """
180                 Returns a value as a string. If the input is a list, the value returned is space-separated.
181
182                 :param key: key to use
183                 :type key: string
184                 """
185                 s = self[key]
186                 if isinstance(s, str):
187                         return s
188                 return ' '.join(s)
189
190         def _get_list_value_for_modification(self, key):
191                 """
192                 Returns a list value for further modification.
193
194                 The list may be modified inplace and there is no need to do this afterwards::
195
196                         self.table[var] = value
197                 """
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
216
217         def append_value(self, var, val):
218                 """
219                 Appends a value to the specified config key::
220
221                         def build(bld):
222                                 bld.env.append_value('CFLAGS', ['-O2'])
223
224                 The value must be a list or a tuple
225                 """
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)
230
231         def prepend_value(self, var, val):
232                 """
233                 Prepends a value to the specified item::
234
235                         def configure(conf):
236                                 conf.env.prepend_value('CFLAGS', ['-O2'])
237
238                 The value must be a list or a tuple
239                 """
240                 if isinstance(val, str):
241                         val = [val]
242                 self.table[var] =  val + self._get_list_value_for_modification(var)
243
244         def append_unique(self, var, val):
245                 """
246                 Appends a value to the specified item only if it's not already present::
247
248                         def build(bld):
249                                 bld.env.append_unique('CFLAGS', ['-O2', '-g'])
250
251                 The value must be a list or a tuple
252                 """
253                 if isinstance(val, str):
254                         val = [val]
255                 current_value = self._get_list_value_for_modification(var)
256
257                 for x in val:
258                         if x not in current_value:
259                                 current_value.append(x)
260
261         def get_merged_dict(self):
262                 """
263                 Computes the merged dictionary from the fusion of self and all its parent
264
265                 :rtype: a ConfigSet object
266                 """
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
279
280         def store(self, filename):
281                 """
282                 Serializes the :py:class:`ConfigSet` data to a file. See :py:meth:`ConfigSet.load` for reading such files.
283
284                 :param filename: file to use
285                 :type filename: string
286                 """
287                 try:
288                         os.makedirs(os.path.split(filename)[0])
289                 except OSError:
290                         pass
291
292                 buf = []
293                 merged_table = self.get_merged_dict()
294                 keys = list(merged_table.keys())
295                 keys.sort()
296
297                 try:
298                         fun = ascii
299                 except NameError:
300                         fun = repr
301
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))
306
307         def load(self, filename):
308                 """
309                 Restores contents from a file (current values are not cleared). Files are written using :py:meth:`ConfigSet.store`.
310
311                 :param filename: file to use
312                 :type filename: string
313                 """
314                 tbl = self.table
315                 code = Utils.readf(filename, m='r')
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)
320
321         def update(self, d):
322                 """
323                 Dictionary interface: replace values with the ones from another dict
324
325                 :param d: object to use the value from
326                 :type d: dict-like object
327                 """
328                 self.table.update(d)
329
330         def stash(self):
331                 """
332                 Stores the object state to provide transactionality semantics::
333
334                         env = ConfigSet()
335                         env.stash()
336                         try:
337                                 env.append_value('CFLAGS', '-O3')
338                                 call_some_method(env)
339                         finally:
340                                 env.revert()
341
342                 The history is kept in a stack, and is lost during the serialization by :py:meth:`ConfigSet.store`
343                 """
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]
349
350         def commit(self):
351                 """
352                 Commits transactional changes. See :py:meth:`ConfigSet.stash`
353                 """
354                 self.undo_stack.pop(-1)
355
356         def revert(self):
357                 """
358                 Reverts the object to a previous state. See :py:meth:`ConfigSet.stash`
359                 """
360                 self.table = self.undo_stack.pop(-1)
361