]> mj.ucw.cz Git - eval.git/blobdiff - t/moe/config.py
New ConfigError subclasses, move re_VARNAME
[eval.git] / t / moe / config.py
index 3075d8036989a461f47f822499bcec5a4c6a1fc2..a96fdb6207e2dea03870934573b26f39fcff001e 100644 (file)
 #!/usr/bin/env python
 
+import re
+import sys
+import moe
+
+key_pattern = re.compile("^[A-Za-z0-9_-]+$")
+ref_pattern = re.compile("^[A-Za-z0-9_-]+")
+
+class MoeConfigInvalid(moe.MoeError):
+    pass
+
+class MoeConfigEvalError(moe.MoeError):
+    pass
+
 class MoeConfig:
-    """Moe configuration file."""
+    """Moe configuration file. Should be immutable once a part of a stack."""
+
+    def __init__(self, file=None, name=None, type="<unnamed>"):
+        self.vars = {}
+       self.type = type
+       if file is not None:
+           self.load(file)
+       elif name is not None:
+           self.name = name
+           try:
+               file = open(name, "r")
+           except IOError, err:
+               raise MoeConfigInvalid, "Cannot open configuration file %s: %s" % (name, err.strerror)
+           else:
+               self.load(file)
+
+    def set(self, k, v):
+       self.vars[k] = [("s", v)]
+
+    def parse_line(self, x):
+       x = x.rstrip("\n").lstrip(" \t")
+       if x=="" or x.startswith("#"):
+           pass
+       else:
+           sep = x.find("=")
+           if sep >= 0:
+               k = x[:sep]
+               v = x[sep+1:]
+               if k.endswith("+"):
+                   k = k[:-1]
+                   if not self.vars.has_key(k):
+                       self.vars[k] = [("a","")];
+                   else:
+                       self.vars[k] += [("s"," ")]
+               else:
+                   self.vars[k] = []
+               if not key_pattern.match(k):
+                   raise MoeConfigInvalid, "Malformed name of configuration variable"
+               if v.startswith("'"):
+                   v=v[1:]
+                   if not v.endswith("'"):
+                       raise MoeConfigInvalid, "Misquoted string"
+                   self.vars[k].append(("s", v[:-1]))
+               elif v.startswith('"'):
+                   v=v[1:]
+                   if not v.endswith('"'):
+                       raise MoeConfigInvalid, "Misquoted string"
+                   self.parse_interpolated(self.vars[k], v[:-1])
+               else:
+                   self.parse_interpolated(self.vars[k], v)
+           else:
+               raise MoeConfigInvalid, "Parse error"
+
+    def load(self, file):
+        lino = 0
+       for x in file.readlines():
+           lino += 1
+           try:
+               self.parse_line(x)
+           except MoeConfigInvalid, x:
+               msg = x.message + " at line " + str(lino)
+               if hasattr(self, "name"):
+                   msg += " of " + self.name
+               raise MoeConfigInvalid, msg
+
+    def parse_interpolated(self, list, s):
+        while s<>"":
+           if s.startswith("$"):
+               s = s[1:]
+               if s.startswith("{"):
+                   p = s.find("}")
+                   if not p:
+                       raise MoeConfigInvalid, "Unbalanced braces"
+                   k, s = s[1:p], s[p+1:]
+                   if not key_pattern.match(k):
+                       raise MoeConfigInvalid, "Invalid variable name"
+               else:
+                   m = ref_pattern.match(s)
+                   if m:
+                       k, s = s[:m.end()], s[m.end():]
+                   else:
+                       raise MoeConfigInvalid, "Invalid variable reference"
+               list.append(("i", k))
+           else:
+               p = s.find("$")
+               if p < 0:
+                   p = len(s)
+               list.append(("s", s[:p]))
+               s = s[p:]
 
-    def __init__(self):
-        cfg = { 'XYZZY' : 'brum' }
+    def dump(self, file=sys.stdout, prefix=""):
+        for k,v in self.vars.items():
+           file.write(prefix)
+           file.write(k)
+           if len(v) > 0 and v[0][0] == "a":
+               file.write("+")
+               v = v[1:]
+           file.write("=")
+           for t,w in v:
+               if t == "s":
+                   file.write("'" + w + "'")
+               elif t == "i":
+                   file.write('"$' + w + '"')
+           file.write("\n")
 
 class MoeConfigStack:
     """Stack of configuration files."""
 
-    def __init__(self):
-        stk = []
+    def __init__(self, base=None):
+        if base:
+           self.stk = base.stk[:]
+       else:
+           self.stk = []
+       self.in_progress = {}
+
+    def push(self, cfg):
+       self.stk.append(cfg)
+
+    def __getitem__(self, k):
+        if self.in_progress.has_key(k):
+           raise MoeConfigEvalError, "Definition of $%s is recursive" % k;
+       self.in_progress[k] = 1;
+        v = self.do_get(k, len(self.stk)-1)
+       del self.in_progress[k]
+       return v
+
+    def do_get(self, k, pos):
+        while pos >= 0:
+           cfg = self.stk[pos]
+           if cfg.vars.has_key(k):
+               new = cfg.vars[k]
+               if len(new) > 0 and new[0][0] == "a":
+                   v = self.do_get(k, pos-1)
+                   if v != "" and not v.endswith(" "):
+                       v += " "
+               else:
+                   v = ""
+               for op,arg in new:
+                   if op == "s":
+                       v = v + arg
+                   elif op == "i":
+                       v = v + self[arg]
+               return v
+           pos -= 1
+       return ""
+
+    def keys(self):
+        seen = {}
+       for cfg in self.stk:
+           for k in cfg.vars.keys():
+               seen[k] = None
+       return seen.keys()
+
+    def dump(self, file=sys.stdout, prefix=""):
+       for k in sorted(self.keys()):
+           v = self[k]
+           file.write("%s%s=%s\n" % (prefix,k,v))
+
+    def dump_defs(self, file=sys.stdout, prefix=""):
+       level = 0
+       for cfg in self.stk:
+           level += 1
+           file.write("%s(level %d: %s)\n" % (prefix,level,cfg.type))
+           cfg.dump(file, prefix + "\t")
+       file.write("%s(end)\n" % prefix)
+
+    def apply_overrides(self, prefix):
+        newstk = []
+       for cfg in self.stk:
+           over = MoeConfig(type = cfg.type + '-overrides')
+           changed = False
+           for k in cfg.vars.keys():
+               if k.startswith(prefix):
+                   over.vars[k[len(prefix):]] = cfg.vars[k]
+                   changed = True
+           if changed:
+               clean = MoeConfig(type = cfg.type)
+               for k in cfg.vars.keys():
+                   if not k.startswith(prefix):
+                       clean.vars[k] = cfg.vars[k]
+               newstk.append(clean)
+               newstk.append(over)
+           else:
+               newstk.append(cfg)
+       self.stk = newstk
+
+def parse_overrides(argv):
+    cfg = None
+    argv0 = argv.pop(0)
+    while len(argv) > 0 and argv[0].find("=") >= 0:
+       if cfg is None:
+           cfg = MoeConfig(type='cmdline')
+       cfg.parse_line(argv.pop(0))
+    argv.insert(0, argv0)
+    return cfg