ConfigSet.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3. # Thomas Nagy, 2005-2018 (ita)
  4. """
  5. ConfigSet: a special dict
  6. The values put in :py:class:`ConfigSet` must be serializable (dicts, lists, strings)
  7. """
  8. import copy, re, os
  9. from waflib import Logs, Utils
  10. re_imp = re.compile('^(#)*?([^#=]*?)\ =\ (.*?)$', re.M)
  11. class ConfigSet(object):
  12. """
  13. A copy-on-write dict with human-readable serialized format. The serialization format
  14. is human-readable (python-like) and performed by using eval() and repr().
  15. For high performance prefer pickle. Do not store functions as they are not serializable.
  16. The values can be accessed by attributes or by keys::
  17. from waflib.ConfigSet import ConfigSet
  18. env = ConfigSet()
  19. env.FOO = 'test'
  20. env['FOO'] = 'test'
  21. """
  22. __slots__ = ('table', 'parent')
  23. def __init__(self, filename=None):
  24. self.table = {}
  25. """
  26. Internal dict holding the object values
  27. """
  28. #self.parent = None
  29. if filename:
  30. self.load(filename)
  31. def __contains__(self, key):
  32. """
  33. Enables the *in* syntax::
  34. if 'foo' in env:
  35. print(env['foo'])
  36. """
  37. if key in self.table:
  38. return True
  39. try:
  40. return self.parent.__contains__(key)
  41. except AttributeError:
  42. return False # parent may not exist
  43. def keys(self):
  44. """Dict interface"""
  45. keys = set()
  46. cur = self
  47. while cur:
  48. keys.update(cur.table.keys())
  49. cur = getattr(cur, 'parent', None)
  50. keys = list(keys)
  51. keys.sort()
  52. return keys
  53. def __iter__(self):
  54. return iter(self.keys())
  55. def __str__(self):
  56. """Text representation of the ConfigSet (for debugging purposes)"""
  57. return "\n".join(["%r %r" % (x, self.__getitem__(x)) for x in self.keys()])
  58. def __getitem__(self, key):
  59. """
  60. Dictionary interface: get value from key::
  61. def configure(conf):
  62. conf.env['foo'] = {}
  63. print(env['foo'])
  64. """
  65. try:
  66. while 1:
  67. x = self.table.get(key)
  68. if not x is None:
  69. return x
  70. self = self.parent
  71. except AttributeError:
  72. return []
  73. def __setitem__(self, key, value):
  74. """
  75. Dictionary interface: set value from key
  76. """
  77. self.table[key] = value
  78. def __delitem__(self, key):
  79. """
  80. Dictionary interface: mark the value as missing
  81. """
  82. self[key] = []
  83. def __getattr__(self, name):
  84. """
  85. Attribute access provided for convenience. The following forms are equivalent::
  86. def configure(conf):
  87. conf.env.value
  88. conf.env['value']
  89. """
  90. if name in self.__slots__:
  91. return object.__getattribute__(self, name)
  92. else:
  93. return self[name]
  94. def __setattr__(self, name, value):
  95. """
  96. Attribute access provided for convenience. The following forms are equivalent::
  97. def configure(conf):
  98. conf.env.value = x
  99. env['value'] = x
  100. """
  101. if name in self.__slots__:
  102. object.__setattr__(self, name, value)
  103. else:
  104. self[name] = value
  105. def __delattr__(self, name):
  106. """
  107. Attribute access provided for convenience. The following forms are equivalent::
  108. def configure(conf):
  109. del env.value
  110. del env['value']
  111. """
  112. if name in self.__slots__:
  113. object.__delattr__(self, name)
  114. else:
  115. del self[name]
  116. def derive(self):
  117. """
  118. Returns a new ConfigSet deriving from self. The copy returned
  119. will be a shallow copy::
  120. from waflib.ConfigSet import ConfigSet
  121. env = ConfigSet()
  122. env.append_value('CFLAGS', ['-O2'])
  123. child = env.derive()
  124. child.CFLAGS.append('test') # warning! this will modify 'env'
  125. child.CFLAGS = ['-O3'] # new list, ok
  126. child.append_value('CFLAGS', ['-O3']) # ok
  127. Use :py:func:`ConfigSet.detach` to detach the child from the parent.
  128. """
  129. newenv = ConfigSet()
  130. newenv.parent = self
  131. return newenv
  132. def detach(self):
  133. """
  134. Detaches this instance from its parent (if present)
  135. Modifying the parent :py:class:`ConfigSet` will not change the current object
  136. Modifying this :py:class:`ConfigSet` will not modify the parent one.
  137. """
  138. tbl = self.get_merged_dict()
  139. try:
  140. delattr(self, 'parent')
  141. except AttributeError:
  142. pass
  143. else:
  144. keys = tbl.keys()
  145. for x in keys:
  146. tbl[x] = copy.deepcopy(tbl[x])
  147. self.table = tbl
  148. return self
  149. def get_flat(self, key):
  150. """
  151. Returns a value as a string. If the input is a list, the value returned is space-separated.
  152. :param key: key to use
  153. :type key: string
  154. """
  155. s = self[key]
  156. if isinstance(s, str):
  157. return s
  158. return ' '.join(s)
  159. def _get_list_value_for_modification(self, key):
  160. """
  161. Returns a list value for further modification.
  162. The list may be modified inplace and there is no need to do this afterwards::
  163. self.table[var] = value
  164. """
  165. try:
  166. value = self.table[key]
  167. except KeyError:
  168. try:
  169. value = self.parent[key]
  170. except AttributeError:
  171. value = []
  172. else:
  173. if isinstance(value, list):
  174. # force a copy
  175. value = value[:]
  176. else:
  177. value = [value]
  178. self.table[key] = value
  179. else:
  180. if not isinstance(value, list):
  181. self.table[key] = value = [value]
  182. return value
  183. def append_value(self, var, val):
  184. """
  185. Appends a value to the specified config key::
  186. def build(bld):
  187. bld.env.append_value('CFLAGS', ['-O2'])
  188. The value must be a list or a tuple
  189. """
  190. if isinstance(val, str): # if there were string everywhere we could optimize this
  191. val = [val]
  192. current_value = self._get_list_value_for_modification(var)
  193. current_value.extend(val)
  194. def prepend_value(self, var, val):
  195. """
  196. Prepends a value to the specified item::
  197. def configure(conf):
  198. conf.env.prepend_value('CFLAGS', ['-O2'])
  199. The value must be a list or a tuple
  200. """
  201. if isinstance(val, str):
  202. val = [val]
  203. self.table[var] = val + self._get_list_value_for_modification(var)
  204. def append_unique(self, var, val):
  205. """
  206. Appends a value to the specified item only if it's not already present::
  207. def build(bld):
  208. bld.env.append_unique('CFLAGS', ['-O2', '-g'])
  209. The value must be a list or a tuple
  210. """
  211. if isinstance(val, str):
  212. val = [val]
  213. current_value = self._get_list_value_for_modification(var)
  214. for x in val:
  215. if x not in current_value:
  216. current_value.append(x)
  217. def get_merged_dict(self):
  218. """
  219. Computes the merged dictionary from the fusion of self and all its parent
  220. :rtype: a ConfigSet object
  221. """
  222. table_list = []
  223. env = self
  224. while 1:
  225. table_list.insert(0, env.table)
  226. try:
  227. env = env.parent
  228. except AttributeError:
  229. break
  230. merged_table = {}
  231. for table in table_list:
  232. merged_table.update(table)
  233. return merged_table
  234. def store(self, filename):
  235. """
  236. Serializes the :py:class:`ConfigSet` data to a file. See :py:meth:`ConfigSet.load` for reading such files.
  237. :param filename: file to use
  238. :type filename: string
  239. """
  240. try:
  241. os.makedirs(os.path.split(filename)[0])
  242. except OSError:
  243. pass
  244. buf = []
  245. merged_table = self.get_merged_dict()
  246. keys = list(merged_table.keys())
  247. keys.sort()
  248. try:
  249. fun = ascii
  250. except NameError:
  251. fun = repr
  252. for k in keys:
  253. if k != 'undo_stack':
  254. buf.append('%s = %s\n' % (k, fun(merged_table[k])))
  255. Utils.writef(filename, ''.join(buf))
  256. def load(self, filename):
  257. """
  258. Restores contents from a file (current values are not cleared). Files are written using :py:meth:`ConfigSet.store`.
  259. :param filename: file to use
  260. :type filename: string
  261. """
  262. tbl = self.table
  263. code = Utils.readf(filename, m='rU')
  264. for m in re_imp.finditer(code):
  265. g = m.group
  266. tbl[g(2)] = eval(g(3))
  267. Logs.debug('env: %s', self.table)
  268. def update(self, d):
  269. """
  270. Dictionary interface: replace values with the ones from another dict
  271. :param d: object to use the value from
  272. :type d: dict-like object
  273. """
  274. self.table.update(d)
  275. def stash(self):
  276. """
  277. Stores the object state to provide transactionality semantics::
  278. env = ConfigSet()
  279. env.stash()
  280. try:
  281. env.append_value('CFLAGS', '-O3')
  282. call_some_method(env)
  283. finally:
  284. env.revert()
  285. The history is kept in a stack, and is lost during the serialization by :py:meth:`ConfigSet.store`
  286. """
  287. orig = self.table
  288. tbl = self.table = self.table.copy()
  289. for x in tbl.keys():
  290. tbl[x] = copy.deepcopy(tbl[x])
  291. self.undo_stack = self.undo_stack + [orig]
  292. def commit(self):
  293. """
  294. Commits transactional changes. See :py:meth:`ConfigSet.stash`
  295. """
  296. self.undo_stack.pop(-1)
  297. def revert(self):
  298. """
  299. Reverts the object to a previous state. See :py:meth:`ConfigSet.stash`
  300. """
  301. self.table = self.undo_stack.pop(-1)