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