|
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 or any later version. |
|
7 |
|
8 from i18n import _ |
|
9 import error, util |
|
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.*\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.*\S)\s*$') |
|
80 section = "" |
|
81 item = None |
|
82 line = 0 |
|
83 cont = False |
|
84 |
|
85 for l in data.splitlines(True): |
|
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 cont = False |
|
97 m = includere.match(l) |
|
98 if m: |
|
99 inc = util.expandpath(m.group(1)) |
|
100 base = os.path.dirname(src) |
|
101 inc = os.path.normpath(os.path.join(base, inc)) |
|
102 if include: |
|
103 try: |
|
104 include(inc, remap=remap, sections=sections) |
|
105 except IOError, inst: |
|
106 raise error.ParseError(_("cannot include %s (%s)") |
|
107 % (inc, inst.strerror), |
|
108 "%s:%s" % (src, line)) |
|
109 continue |
|
110 if emptyre.match(l): |
|
111 continue |
|
112 m = sectionre.match(l) |
|
113 if m: |
|
114 section = m.group(1) |
|
115 if remap: |
|
116 section = remap.get(section, section) |
|
117 if section not in self: |
|
118 self._data[section] = sortdict() |
|
119 continue |
|
120 m = itemre.match(l) |
|
121 if m: |
|
122 item = m.group(1) |
|
123 cont = True |
|
124 if sections and section not in sections: |
|
125 continue |
|
126 self.set(section, item, m.group(2), "%s:%d" % (src, line)) |
|
127 continue |
|
128 m = unsetre.match(l) |
|
129 if m: |
|
130 name = m.group(1) |
|
131 if sections and section not in sections: |
|
132 continue |
|
133 if self.get(section, name) != None: |
|
134 del self._data[section][name] |
|
135 continue |
|
136 |
|
137 raise error.ParseError(l.rstrip(), ("%s:%s" % (src, line))) |
|
138 |
|
139 def read(self, path, fp=None, sections=None, remap=None): |
|
140 if not fp: |
|
141 fp = open(path) |
|
142 self.parse(path, fp.read(), sections, remap, self.read) |