123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361 |
- #!/usr/bin/env python
- # encoding: utf-8
- # Thomas Nagy, 2005-2018 (ita)
- """
- ConfigSet: a special dict
- The values put in :py:class:`ConfigSet` must be serializable (dicts, lists, strings)
- """
- import copy, re, os
- from waflib import Logs, Utils
- re_imp = re.compile('^(#)*?([^#=]*?)\ =\ (.*?)$', re.M)
- class ConfigSet(object):
- """
- A copy-on-write dict with human-readable serialized format. The serialization format
- is human-readable (python-like) and performed by using eval() and repr().
- For high performance prefer pickle. Do not store functions as they are not serializable.
- The values can be accessed by attributes or by keys::
- from waflib.ConfigSet import ConfigSet
- env = ConfigSet()
- env.FOO = 'test'
- env['FOO'] = 'test'
- """
- __slots__ = ('table', 'parent')
- def __init__(self, filename=None):
- self.table = {}
- """
- Internal dict holding the object values
- """
- #self.parent = None
- if filename:
- self.load(filename)
- def __contains__(self, key):
- """
- Enables the *in* syntax::
- if 'foo' in env:
- print(env['foo'])
- """
- if key in self.table:
- return True
- try:
- return self.parent.__contains__(key)
- except AttributeError:
- return False # parent may not exist
- def keys(self):
- """Dict interface"""
- keys = set()
- cur = self
- while cur:
- keys.update(cur.table.keys())
- cur = getattr(cur, 'parent', None)
- keys = list(keys)
- keys.sort()
- return keys
- def __iter__(self):
- return iter(self.keys())
- def __str__(self):
- """Text representation of the ConfigSet (for debugging purposes)"""
- return "\n".join(["%r %r" % (x, self.__getitem__(x)) for x in self.keys()])
- def __getitem__(self, key):
- """
- Dictionary interface: get value from key::
- def configure(conf):
- conf.env['foo'] = {}
- print(env['foo'])
- """
- try:
- while 1:
- x = self.table.get(key)
- if not x is None:
- return x
- self = self.parent
- except AttributeError:
- return []
- def __setitem__(self, key, value):
- """
- Dictionary interface: set value from key
- """
- self.table[key] = value
- def __delitem__(self, key):
- """
- Dictionary interface: mark the value as missing
- """
- self[key] = []
- def __getattr__(self, name):
- """
- Attribute access provided for convenience. The following forms are equivalent::
- def configure(conf):
- conf.env.value
- conf.env['value']
- """
- if name in self.__slots__:
- return object.__getattribute__(self, name)
- else:
- return self[name]
- def __setattr__(self, name, value):
- """
- Attribute access provided for convenience. The following forms are equivalent::
- def configure(conf):
- conf.env.value = x
- env['value'] = x
- """
- if name in self.__slots__:
- object.__setattr__(self, name, value)
- else:
- self[name] = value
- def __delattr__(self, name):
- """
- Attribute access provided for convenience. The following forms are equivalent::
- def configure(conf):
- del env.value
- del env['value']
- """
- if name in self.__slots__:
- object.__delattr__(self, name)
- else:
- del self[name]
- def derive(self):
- """
- Returns a new ConfigSet deriving from self. The copy returned
- will be a shallow copy::
- from waflib.ConfigSet import ConfigSet
- env = ConfigSet()
- env.append_value('CFLAGS', ['-O2'])
- child = env.derive()
- child.CFLAGS.append('test') # warning! this will modify 'env'
- child.CFLAGS = ['-O3'] # new list, ok
- child.append_value('CFLAGS', ['-O3']) # ok
- Use :py:func:`ConfigSet.detach` to detach the child from the parent.
- """
- newenv = ConfigSet()
- newenv.parent = self
- return newenv
- def detach(self):
- """
- Detaches this instance from its parent (if present)
- Modifying the parent :py:class:`ConfigSet` will not change the current object
- Modifying this :py:class:`ConfigSet` will not modify the parent one.
- """
- tbl = self.get_merged_dict()
- try:
- delattr(self, 'parent')
- except AttributeError:
- pass
- else:
- keys = tbl.keys()
- for x in keys:
- tbl[x] = copy.deepcopy(tbl[x])
- self.table = tbl
- return self
- def get_flat(self, key):
- """
- Returns a value as a string. If the input is a list, the value returned is space-separated.
- :param key: key to use
- :type key: string
- """
- s = self[key]
- if isinstance(s, str):
- return s
- return ' '.join(s)
- def _get_list_value_for_modification(self, key):
- """
- Returns a list value for further modification.
- The list may be modified inplace and there is no need to do this afterwards::
- self.table[var] = value
- """
- try:
- value = self.table[key]
- except KeyError:
- try:
- value = self.parent[key]
- except AttributeError:
- value = []
- else:
- if isinstance(value, list):
- # force a copy
- value = value[:]
- else:
- value = [value]
- self.table[key] = value
- else:
- if not isinstance(value, list):
- self.table[key] = value = [value]
- return value
- def append_value(self, var, val):
- """
- Appends a value to the specified config key::
- def build(bld):
- bld.env.append_value('CFLAGS', ['-O2'])
- The value must be a list or a tuple
- """
- if isinstance(val, str): # if there were string everywhere we could optimize this
- val = [val]
- current_value = self._get_list_value_for_modification(var)
- current_value.extend(val)
- def prepend_value(self, var, val):
- """
- Prepends a value to the specified item::
- def configure(conf):
- conf.env.prepend_value('CFLAGS', ['-O2'])
- The value must be a list or a tuple
- """
- if isinstance(val, str):
- val = [val]
- self.table[var] = val + self._get_list_value_for_modification(var)
- def append_unique(self, var, val):
- """
- Appends a value to the specified item only if it's not already present::
- def build(bld):
- bld.env.append_unique('CFLAGS', ['-O2', '-g'])
- The value must be a list or a tuple
- """
- if isinstance(val, str):
- val = [val]
- current_value = self._get_list_value_for_modification(var)
- for x in val:
- if x not in current_value:
- current_value.append(x)
- def get_merged_dict(self):
- """
- Computes the merged dictionary from the fusion of self and all its parent
- :rtype: a ConfigSet object
- """
- table_list = []
- env = self
- while 1:
- table_list.insert(0, env.table)
- try:
- env = env.parent
- except AttributeError:
- break
- merged_table = {}
- for table in table_list:
- merged_table.update(table)
- return merged_table
- def store(self, filename):
- """
- Serializes the :py:class:`ConfigSet` data to a file. See :py:meth:`ConfigSet.load` for reading such files.
- :param filename: file to use
- :type filename: string
- """
- try:
- os.makedirs(os.path.split(filename)[0])
- except OSError:
- pass
- buf = []
- merged_table = self.get_merged_dict()
- keys = list(merged_table.keys())
- keys.sort()
- try:
- fun = ascii
- except NameError:
- fun = repr
- for k in keys:
- if k != 'undo_stack':
- buf.append('%s = %s\n' % (k, fun(merged_table[k])))
- Utils.writef(filename, ''.join(buf))
- def load(self, filename):
- """
- Restores contents from a file (current values are not cleared). Files are written using :py:meth:`ConfigSet.store`.
- :param filename: file to use
- :type filename: string
- """
- tbl = self.table
- code = Utils.readf(filename, m='rU')
- for m in re_imp.finditer(code):
- g = m.group
- tbl[g(2)] = eval(g(3))
- Logs.debug('env: %s', self.table)
- def update(self, d):
- """
- Dictionary interface: replace values with the ones from another dict
- :param d: object to use the value from
- :type d: dict-like object
- """
- self.table.update(d)
- def stash(self):
- """
- Stores the object state to provide transactionality semantics::
- env = ConfigSet()
- env.stash()
- try:
- env.append_value('CFLAGS', '-O3')
- call_some_method(env)
- finally:
- env.revert()
- The history is kept in a stack, and is lost during the serialization by :py:meth:`ConfigSet.store`
- """
- orig = self.table
- tbl = self.table = self.table.copy()
- for x in tbl.keys():
- tbl[x] = copy.deepcopy(tbl[x])
- self.undo_stack = self.undo_stack + [orig]
- def commit(self):
- """
- Commits transactional changes. See :py:meth:`ConfigSet.stash`
- """
- self.undo_stack.pop(-1)
- def revert(self):
- """
- Reverts the object to a previous state. See :py:meth:`ConfigSet.stash`
- """
- self.table = self.undo_stack.pop(-1)
|