28
|
1 # config.py - configuration parsing for Mercurial
|
|
2 #
|
|
3 # Copyright 2009 Matt Mackall <mpm@selenic.com> and others
|
|
4 #
|
|
5 # This software may be used and distributed according to the terms of the
|
|
6 # GNU General Public License version 2, incorporated herein by reference.
|
|
7
|
|
8 from i18n import _
|
|
9 import error
|
|
10 import re, os
|
|
11
|
|
12 class sortdict(dict):
|
|
13 'a simple sorted dictionary'
|
|
14 def __init__(self, data=None):
|
|
15 self._list = []
|
|
16 if data:
|
|
17 self.update(data)
|
|
18 def copy(self):
|
|
19 return sortdict(self)
|
|
20 def __setitem__(self, key, val):
|
|
21 if key in self:
|
|
22 self._list.remove(key)
|
|
23 self._list.append(key)
|
|
24 dict.__setitem__(self, key, val)
|
|
25 def __iter__(self):
|
|
26 return self._list.__iter__()
|
|
27 def update(self, src):
|
|
28 for k in src:
|
|
29 self[k] = src[k]
|
|
30 def items(self):
|
|
31 return [(k, self[k]) for k in self._list]
|
|
32 def __delitem__(self, key):
|
|
33 dict.__delitem__(self, key)
|
|
34 self._list.remove(key)
|
|
35
|
|
36 class config(object):
|
|
37 def __init__(self, data=None):
|
|
38 self._data = {}
|
|
39 self._source = {}
|
|
40 if data:
|
|
41 for k in data._data:
|
|
42 self._data[k] = data[k].copy()
|
|
43 self._source = data._source.copy()
|
|
44 def copy(self):
|
|
45 return config(self)
|
|
46 def __contains__(self, section):
|
|
47 return section in self._data
|
|
48 def __getitem__(self, section):
|
|
49 return self._data.get(section, {})
|
|
50 def __iter__(self):
|
|
51 for d in self.sections():
|
|
52 yield d
|
|
53 def update(self, src):
|
|
54 for s in src:
|
|
55 if s not in self:
|
|
56 self._data[s] = sortdict()
|
|
57 self._data[s].update(src._data[s])
|
|
58 self._source.update(src._source)
|
|
59 def get(self, section, item, default=None):
|
|
60 return self._data.get(section, {}).get(item, default)
|
|
61 def source(self, section, item):
|
|
62 return self._source.get((section, item), "")
|
|
63 def sections(self):
|
|
64 return sorted(self._data.keys())
|
|
65 def items(self, section):
|
|
66 return self._data.get(section, {}).items()
|
|
67 def set(self, section, item, value, source=""):
|
|
68 if section not in self:
|
|
69 self._data[section] = sortdict()
|
|
70 self._data[section][item] = value
|
|
71 self._source[(section, item)] = source
|
|
72
|
|
73 def parse(self, src, data, sections=None, remap=None, include=None):
|
|
74 sectionre = re.compile(r'\[([^\[]+)\]')
|
|
75 itemre = re.compile(r'([^=\s][^=]*?)\s*=\s*(.*\S|)')
|
|
76 contre = re.compile(r'\s+(\S.*\S)')
|
|
77 emptyre = re.compile(r'(;|#|\s*$)')
|
|
78 unsetre = re.compile(r'%unset\s+(\S+)')
|
|
79 includere = re.compile(r'%include\s+(\S.*\S)')
|
|
80 section = ""
|
|
81 item = None
|
|
82 line = 0
|
|
83 cont = 0
|
|
84
|
|
85 for l in data.splitlines(1):
|
|
86 line += 1
|
|
87 if cont:
|
|
88 m = contre.match(l)
|
|
89 if m:
|
|
90 if sections and section not in sections:
|
|
91 continue
|
|
92 v = self.get(section, item) + "\n" + m.group(1)
|
|
93 self.set(section, item, v, "%s:%d" % (src, line))
|
|
94 continue
|
|
95 item = None
|
|
96 m = includere.match(l)
|
|
97 if m:
|
|
98 inc = m.group(1)
|
|
99 base = os.path.dirname(src)
|
|
100 inc = os.path.normpath(os.path.join(base, inc))
|
|
101 if include:
|
|
102 include(inc, remap=remap, sections=sections)
|
|
103 continue
|
|
104 if emptyre.match(l):
|
|
105 continue
|
|
106 m = sectionre.match(l)
|
|
107 if m:
|
|
108 section = m.group(1)
|
|
109 if remap:
|
|
110 section = remap.get(section, section)
|
|
111 if section not in self:
|
|
112 self._data[section] = sortdict()
|
|
113 continue
|
|
114 m = itemre.match(l)
|
|
115 if m:
|
|
116 item = m.group(1)
|
|
117 cont = 1
|
|
118 if sections and section not in sections:
|
|
119 continue
|
|
120 self.set(section, item, m.group(2), "%s:%d" % (src, line))
|
|
121 continue
|
|
122 m = unsetre.match(l)
|
|
123 if m:
|
|
124 name = m.group(1)
|
|
125 if sections and section not in sections:
|
|
126 continue
|
|
127 if self.get(section, name) != None:
|
|
128 del self._data[section][name]
|
|
129 continue
|
|
130
|
|
131 raise error.ConfigError(_('config error at %s:%d: \'%s\'')
|
|
132 % (src, line, l.rstrip()))
|
|
133
|
|
134 def read(self, path, fp=None, sections=None, remap=None):
|
|
135 if not fp:
|
|
136 fp = open(path)
|
|
137 self.parse(path, fp.read(), sections, remap, self.read)
|