]> mj.ucw.cz Git - moe.git/blob - t/moe/config.py
Better error reporting
[moe.git] / t / moe / config.py
1 #!/usr/bin/env python
2
3 import re
4 import sys
5
6 key_pattern = re.compile("^[A-Za-z0-9_-]+$")
7 ref_pattern = re.compile("^[A-Za-z0-9_-]+")
8
9 class MoeConfigInvalid(Exception):
10     pass
11
12 class MoeConfigEvalErr(Exception):
13     pass
14
15 class MoeConfig:
16     """Moe configuration file. Should be immutable once a part of a stack."""
17
18     def __init__(self, file=None, name=None):
19         self.vars = {}
20         if file is not None:
21             self.load(file)
22         elif name is not None:
23             self.name = name
24             try:
25                 file = open(name, "r")
26             except IOError, err:
27                 raise MoeConfigInvalid, "Cannot open configuration file %s: %s" % (name, err.strerror)
28             else:
29                 self.load(file)
30
31     def parse_line(self, x):
32         x = x.rstrip("\n").lstrip(" \t")
33         if x=="" or x.startswith("#"):
34             pass
35         else:
36             sep = x.find("=")
37             if sep >= 0:
38                 k = x[:sep]
39                 v = x[sep+1:]
40                 if k.endswith("+"):
41                     k = k[:-1]
42                     if not self.vars.has_key(k):
43                         self.vars[k] = [("a","")];
44                 else:
45                     self.vars[k] = []
46                 if not key_pattern.match(k):
47                     raise MoeConfigInvalid, "Malformed name of configuration variable"
48                 if v.startswith("'"):
49                     v=v[1:]
50                     if not v.endswith("'"):
51                         raise MoeConfigInvalid, "Misquoted string"
52                     self.vars[k].append(("s", v[:-1]))
53                 elif v.startswith('"'):
54                     v=v[1:]
55                     if not v.endswith('"'):
56                         raise MoeConfigInvalid, "Misquoted string"
57                     self.parse_interpolated(self.vars[k], v[:-1])
58                 else:
59                     self.parse_interpolated(self.vars[k], v)
60             else:
61                 raise MoeConfigInvalid, "Parse error"
62
63     def load(self, file):
64         lino = 0
65         for x in file.readlines():
66             lino += 1
67             try:
68                 self.parse_line(x)
69             except MoeConfigInvalid, x:
70                 msg = x.message + " at line " + str(lino)
71                 if hasattr(self, "name"):
72                     msg += " of " + self.name
73                 raise MoeConfigInvalid, msg
74
75     def parse_interpolated(self, list, s):
76         while s<>"":
77             if s.startswith("$"):
78                 s = s[1:]
79                 if s.startswith("{"):
80                     p = s.find("}")
81                     if not p:
82                         raise MoeConfigInvalid, "Unbalanced braces"
83                     k, s = s[1:p], s[p+1:]
84                     if not key_pattern.match(k):
85                         raise MoeConfigInvalid, "Invalid variable name"
86                 else:
87                     m = ref_pattern.match(s)
88                     if m:
89                         k, s = s[:m.end()], s[m.end():]
90                     else:
91                         raise MoeConfigInvalid, "Invalid variable reference"
92                 list.append(("i", k))
93             else:
94                 p = s.find("$")
95                 if p < 0:
96                     p = len(s)
97                 list.append(("s", s[:p]))
98                 s = s[p:]
99
100     def dump(self, file=sys.stdout):
101         for k,v in self.vars.items():
102             file.write(k)
103             if len(v) > 0 and v[0][0] == "a":
104                 file.write("+")
105                 v = v[1:]
106             file.write("=")
107             for t,w in v:
108                 if t == "s":
109                     file.write("'" + w + "'")
110                 elif t == "i":
111                     file.write('"$' + w + '"')
112             file.write("\n")
113
114 class MoeConfigStack:
115     """Stack of configuration files."""
116
117     def __init__(self, base=None):
118         ## FIXME: Do we need to duplicate the config files themselves?
119         if base:
120             self.stk = base.stk[:]
121         else:
122             self.stk = []
123         self.in_progress = {}
124         self.reset_cache()
125
126     def reset_cache(self):
127         self.cache = {}
128
129     def push(self, cfg):
130         self.stk.append(cfg)
131         self.reset_cache()
132
133     def __getitem__(self, k):
134         if self.cache.has_key(k):
135             return self.cache[k]
136         if self.in_progress.has_key(k):
137             raise MoeConfigEvalErr, "Definition of $%s is recursive" % k;
138         self.in_progress[k] = 1;
139         v = self.do_get(k, len(self.stk)-1)
140         del self.in_progress[k]
141         self.cache[k] = v
142         return v
143
144     def do_get(self, k, pos):
145         while pos >= 0:
146             cfg = self.stk[pos]
147             if cfg.vars.has_key(k):
148                 new = cfg.vars[k]
149                 if new[0][0] == "a":
150                     v = self.do_get(k, pos-1)
151                 else:
152                     v = ""
153                 for op,arg in new:
154                     if op == "s":
155                         v = v + arg
156                     elif op == "i":
157                         v = v + self[arg]
158                 return v
159             pos -= 1
160         return ""
161
162     def keys(self):
163         seen = {}
164         for cfg in self.stk:
165             for k in cfg.vars.keys():
166                 seen[k] = None
167         return seen.keys()
168
169     def dump(self, file=sys.stdout):
170         for k in self.keys():
171             v = self[k]
172             file.write("%s=%s\n" % (k,v))
173
174     def dump_defs(self, file=sys.stdout):
175         file.write("Configuration stack:\n")
176         level = 0
177         for cfg in self.stk:
178             level += 1
179             file.write("(level %d)\n" % level)
180             cfg.dump(file)
181         file.write("(end)\n")
182
183     def apply_overrides(self, prefix):
184         newstk = []
185         for cfg in self.stk:
186             over = MoeConfig()
187             changed = False
188             for k in cfg.vars.keys():
189                 if k.startswith(prefix):
190                     over.vars[k[len(prefix):]] = cfg.vars[k]
191                     changed = True
192             if changed:
193                 clean = MoeConfig()
194                 for k in cfg.vars.keys():
195                     if not k.startswith(prefix):
196                         clean.vars[k] = cfg.vars[k]
197                 newstk.append(clean)
198                 newstk.append(over)
199             else:
200                 newstk.append(cfg)
201         self.stk = newstk
202         self.reset_cache()