waf

FORK: waf with some random patches
git clone https://git.neptards.moe/neptards/waf.git
Log | Files | Refs | README

ConfigSet.py (8294B)


      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