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