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