mirror of
https://github.com/facebook/sapling.git
synced 2024-10-07 15:27:13 +03:00
3dfd3d6ee3
This is introduce to allow temporary overwriting of a config value while being able to reinstall the old value once done. The main advantage over using ``config`` and ``setconfig`` is that backup and restore will properly restore the lack of any config. Restoring the fact that there was no value is important to allow config user to keep using meaniful default value. A more naive approach will result in the following scenario:: Before: config(section, item, my_default) --> my_default temporal overwrite old = config(section, item) … setconfig(section, item, old) After config(section, item, my_default) --> None The first user of this feature should be mq to overwriting minimal phase of future commit.
177 lines
6.1 KiB
Python
177 lines
6.1 KiB
Python
# config.py - configuration parsing for Mercurial
|
|
#
|
|
# Copyright 2009 Matt Mackall <mpm@selenic.com> and others
|
|
#
|
|
# This software may be used and distributed according to the terms of the
|
|
# GNU General Public License version 2 or any later version.
|
|
|
|
from i18n import _
|
|
import error, util
|
|
import re, os, errno
|
|
|
|
class sortdict(dict):
|
|
'a simple sorted dictionary'
|
|
def __init__(self, data=None):
|
|
self._list = []
|
|
if data:
|
|
self.update(data)
|
|
def copy(self):
|
|
return sortdict(self)
|
|
def __setitem__(self, key, val):
|
|
if key in self:
|
|
self._list.remove(key)
|
|
self._list.append(key)
|
|
dict.__setitem__(self, key, val)
|
|
def __iter__(self):
|
|
return self._list.__iter__()
|
|
def update(self, src):
|
|
for k in src:
|
|
self[k] = src[k]
|
|
def clear(self):
|
|
dict.clear(self)
|
|
self._list = []
|
|
def items(self):
|
|
return [(k, self[k]) for k in self._list]
|
|
def __delitem__(self, key):
|
|
dict.__delitem__(self, key)
|
|
self._list.remove(key)
|
|
|
|
class config(object):
|
|
def __init__(self, data=None):
|
|
self._data = {}
|
|
self._source = {}
|
|
if data:
|
|
for k in data._data:
|
|
self._data[k] = data[k].copy()
|
|
self._source = data._source.copy()
|
|
def copy(self):
|
|
return config(self)
|
|
def __contains__(self, section):
|
|
return section in self._data
|
|
def __getitem__(self, section):
|
|
return self._data.get(section, {})
|
|
def __iter__(self):
|
|
for d in self.sections():
|
|
yield d
|
|
def update(self, src):
|
|
for s in src:
|
|
if s not in self:
|
|
self._data[s] = sortdict()
|
|
self._data[s].update(src._data[s])
|
|
self._source.update(src._source)
|
|
def get(self, section, item, default=None):
|
|
return self._data.get(section, {}).get(item, default)
|
|
|
|
def backup(self, section, item):
|
|
"""return a tuple allowing restore to reinstall a previous valuesi
|
|
|
|
The main reason we need it is because it handle the "no data" case.
|
|
"""
|
|
try:
|
|
value = self._data[section][item]
|
|
source = self.source(section, item)
|
|
return (section, item, value, source)
|
|
except KeyError:
|
|
return (section, item)
|
|
|
|
def source(self, section, item):
|
|
return self._source.get((section, item), "")
|
|
def sections(self):
|
|
return sorted(self._data.keys())
|
|
def items(self, section):
|
|
return self._data.get(section, {}).items()
|
|
def set(self, section, item, value, source=""):
|
|
if section not in self:
|
|
self._data[section] = sortdict()
|
|
self._data[section][item] = value
|
|
self._source[(section, item)] = source
|
|
|
|
def restore(self, data):
|
|
"""restore data returned by self.backup"""
|
|
if len(data) == 4:
|
|
# restore old data
|
|
section, item, value, source = data
|
|
self._data[section][item] = value
|
|
self._source[(section, item)] = source
|
|
else:
|
|
# no data before, remove everything
|
|
section, item = data
|
|
if section in self._data:
|
|
del self._data[section][item]
|
|
self._source.pop((section, item), None)
|
|
|
|
def parse(self, src, data, sections=None, remap=None, include=None):
|
|
sectionre = re.compile(r'\[([^\[]+)\]')
|
|
itemre = re.compile(r'([^=\s][^=]*?)\s*=\s*(.*\S|)')
|
|
contre = re.compile(r'\s+(\S|\S.*\S)\s*$')
|
|
emptyre = re.compile(r'(;|#|\s*$)')
|
|
commentre = re.compile(r'(;|#)')
|
|
unsetre = re.compile(r'%unset\s+(\S+)')
|
|
includere = re.compile(r'%include\s+(\S|\S.*\S)\s*$')
|
|
section = ""
|
|
item = None
|
|
line = 0
|
|
cont = False
|
|
|
|
for l in data.splitlines(True):
|
|
line += 1
|
|
if cont:
|
|
if commentre.match(l):
|
|
continue
|
|
m = contre.match(l)
|
|
if m:
|
|
if sections and section not in sections:
|
|
continue
|
|
v = self.get(section, item) + "\n" + m.group(1)
|
|
self.set(section, item, v, "%s:%d" % (src, line))
|
|
continue
|
|
item = None
|
|
cont = False
|
|
m = includere.match(l)
|
|
if m:
|
|
inc = util.expandpath(m.group(1))
|
|
base = os.path.dirname(src)
|
|
inc = os.path.normpath(os.path.join(base, inc))
|
|
if include:
|
|
try:
|
|
include(inc, remap=remap, sections=sections)
|
|
except IOError, inst:
|
|
if inst.errno != errno.ENOENT:
|
|
raise error.ParseError(_("cannot include %s (%s)")
|
|
% (inc, inst.strerror),
|
|
"%s:%s" % (src, line))
|
|
continue
|
|
if emptyre.match(l):
|
|
continue
|
|
m = sectionre.match(l)
|
|
if m:
|
|
section = m.group(1)
|
|
if remap:
|
|
section = remap.get(section, section)
|
|
if section not in self:
|
|
self._data[section] = sortdict()
|
|
continue
|
|
m = itemre.match(l)
|
|
if m:
|
|
item = m.group(1)
|
|
cont = True
|
|
if sections and section not in sections:
|
|
continue
|
|
self.set(section, item, m.group(2), "%s:%d" % (src, line))
|
|
continue
|
|
m = unsetre.match(l)
|
|
if m:
|
|
name = m.group(1)
|
|
if sections and section not in sections:
|
|
continue
|
|
if self.get(section, name) is not None:
|
|
del self._data[section][name]
|
|
continue
|
|
|
|
raise error.ParseError(l.rstrip(), ("%s:%s" % (src, line)))
|
|
|
|
def read(self, path, fp=None, sections=None, remap=None):
|
|
if not fp:
|
|
fp = util.posixfile(path)
|
|
self.parse(path, fp.read(), sections, remap, self.read)
|