]> mj.ucw.cz Git - moe.git/blob - t/moe/config.py
Configuration values are now cached
[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             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         self.reset_cache()
120
121     def reset_cache(self):
122         self.cache = {}
123
124     def push(self, cfg):
125         self.stk.append(cfg)
126         self.reset_cache()
127
128     def __getitem__(self, k):
129         if self.cache.has_key(k):
130             return self.cache[k]
131         if self.in_progress.has_key(k):
132             raise MoeConfigEvalErr, "Definition of $%s is recursive" % k;
133         self.in_progress[k] = 1;
134         v = self.do_get(k, len(self.stk)-1)
135         del self.in_progress[k]
136         self.cache[k] = v
137         return v
138
139     def do_get(self, k, pos):
140         while pos >= 0:
141             cfg = self.stk[pos]
142             if cfg.vars.has_key(k):
143                 new = cfg.vars[k]
144                 if new[0][0] == 'a':
145                     v = self.do_get(k, pos-1)
146                 else:
147                     v = ''
148                 for op,arg in new:
149                     if op == 's':
150                         v = v + arg
151                     elif op == 'i':
152                         v = v + self[arg]
153                 return v
154             pos -= 1
155         return ''
156
157     def keys(self):
158         seen = {}
159         for cfg in self.stk:
160             for k in cfg.vars.keys():
161                 seen[k] = None
162         return seen.keys()
163
164     def dump(self, file=sys.stdout):
165         for k in self.keys():
166             v = self[k]
167             file.write("%s=%s\n" % (k,v))
168
169     def dump_defs(self, file=sys.stdout):
170         file.write("Configuration stack:\n")
171         level = 0
172         for cfg in self.stk:
173             level += 1
174             file.write("(level %d)\n" % level)
175             cfg.dump(file)
176         file.write("(end)\n")
177
178     def apply_overrides(self, prefix):
179         newstk = []
180         for cfg in self.stk:
181             over = MoeConfig()
182             changed = False
183             for k in cfg.vars.keys():
184                 if k.startswith(prefix):
185                     over.vars[k[len(prefix):]] = cfg.vars[k]
186                     changed = True
187             if changed:
188                 clean = MoeConfig()
189                 for k in cfg.vars.keys():
190                     if not k.startswith(prefix):
191                         clean.vars[k] = cfg.vars[k]
192                 newstk.append(clean)
193                 newstk.append(over)
194             else:
195                 newstk.append(cfg)
196         self.stk = newstk
197         self.reset_cache()