X-Git-Url: http://mj.ucw.cz/gitweb/?a=blobdiff_plain;ds=sidebyside;f=t%2Fmoe%2Fstatus.py;h=144bf486f66afffaf6e272d79a74ebc7b727e43a;hb=3b2c0c783bf7284037f4c5c5ffd6582ee7b1f89c;hp=320d7c9308d566d5082c0217462f05fd9a6fdbd8;hpb=293027e28828b13ec2c2428d661e7036acb6d34a;p=moe.git diff --git a/t/moe/status.py b/t/moe/status.py index 320d7c9..144bf48 100644 --- a/t/moe/status.py +++ b/t/moe/status.py @@ -1,118 +1,161 @@ -#!/usr/bin/env python - import sys import types import re -key_pattern = re.compile("^[A-Za-z0-9_-]+$") +key_pattern_str = "\A[A-Za-z0-9_-]+\Z" +key_pattern = re.compile(key_pattern_str) -class MoeStatusInvalid(Exception): +class InvalidStatusFile(StandardError): pass -class MoeStatus: - """Moe status file.""" +class Status(object): + """ + (One subtree of) a status file. + + Each Status is a dictionary with string keys matching the specs. and + values either strings or nested Status subtrees. + + The class defines `__getitem__`, `__setitem__`, `__eq__` and `keys`. + """ def __init__(self): - self.stat = {} + self.d = {} def __getitem__(self, k): - if not self.stat.has_key(k): return None - v = self.stat[k] - if type(v) == types.ListType: - if len(v) > 0: return v[0] - else: return None - else: return v + return self.d[k] def __setitem__(self, k, v): - self.stat[k] = v + self.d[k] = v + + def __eq__(self, s): + return self.d == s.d def keys(self): - return self.stat.keys() + return self.d.keys() - def get_list(self, k): - m = self.stat - if not m.has_key(k): - m[k] = [] - elif type(m[k]) != types.ListType: - m[k] = [m[k]] - return m[k] + def update(self, stat2): + """ + Updates values of `self` with values of `stat2`, recursively. - def dump(self, prefix=""): - "Return a list of lines, `prefix` is indent prefix." - l = [] - for k,v in self.stat.items(): - if type(v) == types.ListType: vals = v - else: vals = [v] - for w in vals: - if isinstance(w, MoeStatus): - l.append(prefix + k + "(\n") - l.extend(self.str_lines(prefix+" ")) - l.append(prefix + ")\n") - else: - l.append(prefix + k + ":" + str(w) + "\n") - return l + Directly references objects (values and subtrees) of `stat2`, so making a deep copy of `stat2` + may be necessary if you intend to modify `stat2` afterwards. + """ - def write(self, file=None, name=None): - if file is None: - if name is not None: - file = open(name, "w") + for k,v2 in stat2.d.items(): + if k not in self.d: + self[k] = v2 else: - file = sys.stdout - self.write_nested(file, 0) - - def write_nested(self, file, indent): - for k,v in self.stat.items(): - if type(v) == types.ListType: vals = v - else: vals = [v] - for w in vals: - if isinstance(w, MoeStatus): - file.write("\t" * indent + k + "(\n") - w.write_nested(file, indent+1); - file.write("\t" * indent + ")\n") + v = self[k] + if isinstance(v, Status) != isinstance(v2, Status): + raise TypeError("Mixing Status and value while updating key %r"%k) + if isinstance(v, Status): + v.update(v2) else: - file.write("\t" * indent + k + ":" + str(w) + "\n") + self[k] = v2 + + def dump(self, prefix=""): + """ + Dump Status in status file format. + Returns a list of lines, ``prefix`` is indentation prefix. + """ - def read(self, file=None, name=None): - if file is None: - if name is not None: - file = open(name, "r") + l = [] + for k,v in self.d.items(): + if isinstance(v, Status): + l.append(prefix + k + " (") + l.extend(v.dump(prefix+" ")) + l.append(prefix + ")") else: - file = sys.stdin - self.stat = {} - self.do_read(file) + d = str(v).split('\n') + l.append(prefix + k + ":" + d[0]) + for i in d[1:]: + l.append(prefix + ' '*len(k) + ':' + i) + return l + + def write(self, f=None, name=None): + """ + Write Status to File ``f`` or overwrite file ``name`` or write to ``stdout`` (otherwise). + """ + + if not f and name is not None: + with open(name, "w") as f: + for l in self.dump(): + f.write(l+"\n") + else: + if not f: + f = sys.stdout + for l in self.dump(): + f.write(l+"\n") + + def read(self, f=None, name=None, lines=None): + """ + Parse Status file + * from File ``f`` + * or from file ``name`` opened for reading 8-bit ASCII + * or from ``lines`` (a list/iterator of lines) + + Deletes all previous contents of the Status. + """ + + self.d = {} + if f is not None: + return self.do_read(f.readlines()) + if name is not None: + with open(name, 'r') as f: + return self.do_read(f.readlines()) + if lines is not None: + return self.do_read(lines) + raise ValueError('Provide at least one parameter to Status.read()') def read_val(self, k, v): + """ + Internal: Safely add a new value to Status + """ + if not key_pattern.match(k): - raise MoeStatusInvalid, "Parse error: invalid key syntax" - m = self.stat - if not m.has_key(k): - m[k] = v - else: - self.get_list(k).append(v) + raise InvalidStatusFile("Parse error: invalid key %r"%k) + if k in self.d: + raise InvalidStatusFile("Multiple occurences of key %r"%k) + self.d[k]=v - def do_read(self, file): - stk = [] - this = self - for x in file.readlines(): + def do_read(self, lines): + """ + Internal: Parse a status file given as list/iterator of lines + """ + + stk = [] # stack + this = self # currently read nested Status + lastk = None # for multiline appending + for x in lines: x = x.rstrip("\n").lstrip(" \t") if x=="" or x.startswith("#"): - pass + lastk = None else: sep = x.find(":") - if sep >= 0: - k = x[:sep] + if sep > 0: # key:value + k = x[:sep].rstrip(" \t") v = x[sep+1:] this.read_val(k, v) - elif x.endswith("("): - k = x[:-1] - new = MoeStatus() + lastk = k + elif sep == 0: # continuation of multiline :value + if not lastk: + raise InvalidStatusFile("Parse error: key expected before ':'") + v = x[sep+1:] + this[lastk] += '\n' + v + elif x.endswith("("): # close subtree + k = x[:-1].rstrip(" \t") + new = Status() this.read_val(k, new) stk.append(this) this = new + lastk = None elif x == ")": + lastk = None if len(stk) == 0: - raise MoeStatusInvalid, "Parse error: incorrect nesting" + raise InvalidStatusFile("Parse error: incorrect nesting") else: this = stk.pop() else: - raise MoeStatusInvalid, "Parse error: malformed line" + raise InvalidStatusFile("Parse error: malformed line") + if stk: + raise InvalidStatusFile("Parse error: not all subtrees closed")