#!/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