X-Git-Url: http://mj.ucw.cz/gitweb/?a=blobdiff_plain;f=t%2Fmoe%2Fstatus.py;h=144bf486f66afffaf6e272d79a74ebc7b727e43a;hb=420a49640eb8a89b578c390a939bc0ba818f9a7e;hp=5a5adf22c5b4851b572ff955ca08a0814eacb903;hpb=8faf6b71514231fc3af6f60a9192028e1e64373f;p=moe.git diff --git a/t/moe/status.py b/t/moe/status.py index 5a5adf2..144bf48 100644 --- a/t/moe/status.py +++ b/t/moe/status.py @@ -1,64 +1,82 @@ -#!/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. + 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. + """ + + for k,v2 in stat2.d.items(): + if k not in self.d: + self[k] = v2 + else: + 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: + self[k] = v2 + def dump(self, prefix=""): """ - Dump status in metafile format. - Return a list of lines, `prefix` is indent prefix. + Dump Status in status file format. + Returns a list of lines, ``prefix`` is indentation 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") + for k,v in self.d.items(): + if isinstance(v, Status): + l.append(prefix + k + " (") + l.extend(v.dump(prefix+" ")) + l.append(prefix + ")") + else: + 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 (as a metafile) to `f` or file `name` or `stdout` + 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(): @@ -69,47 +87,75 @@ class MoeStatus: for l in self.dump(): f.write(l+"\n") - def read(self, file=None, name=None): - if file is None: - if name is not None: - file = open(name, "r") - else: - file = sys.stdin - self.stat = {} - self.do_read(file) + 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, lines): + """ + Internal: Parse a status file given as list/iterator of lines + """ - def do_read(self, file): - stk = [] - this = self - for x in file.readlines(): + 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")