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