import types
import re
-key_pattern = re.compile("\A[A-Za-z0-9_-]+\Z")
+key_pattern_str = "\A[A-Za-z0-9_-]+\Z"
+key_pattern = re.compile(key_pattern_str)
-class MoeInvalidStatusFile(Exception):
+class InvalidStatusFile(StandardError):
pass
-class Status:
+class Status(object):
"""
- (One subtree of) Moe status file.
+ (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):
def __setitem__(self, k, v):
self.d[k] = v
+ def __eq__(self, s):
+ return self.d == s.d
+
def keys(self):
return self.d.keys()
for k,v in self.d.items():
if isinstance(v, Status):
l.append(prefix + k + " (")
- l.extend(self.dump(prefix+" "))
+ l.extend(v.dump(prefix+" "))
l.append(prefix + ")")
else:
d = str(v).split('\n')
for l in self.dump():
f.write(l+"\n")
- def read(self, f=None, name=None):
+ def read(self, f=None, name=None, lines=None):
"""
- Parse Status from File ``f`` or from file ``name`` or from ``stdin`` (otherwise)
- Deletes all contents of the Status.
+ 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 not f and name is not None:
+ if f is not None:
+ return self.do_read(f.readlines())
+ if name is not None:
with open(name, 'r') as f:
- self.do_read(f)
- else:
- if not f:
- f = sys.stdin
- self.do_read(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
+ Internal: Safely add a new value to Status
"""
if not key_pattern.match(k):
- raise MoeInvalidStatusFile("Parse error: invalid key %r"%k)
+ raise InvalidStatusFile("Parse error: invalid key %r"%k)
if k in self.d:
- raise MoeInvalidStatusFile("Multiple occurences of key %r"%k)
+ raise InvalidStatusFile("Multiple occurences of key %r"%k)
self.d[k]=v
- def do_read(self, f):
+ def do_read(self, lines):
"""
- Internal: Parse an open file
+ Internal: Parse a status file given as list/iterator of lines
"""
- stk = []
- this = self
- for x in f.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:
+ if sep > 0: # key:value
k = x[:sep].rstrip(" \t")
v = x[sep+1:]
this.read_val(k, v)
- elif x.endswith("("):
+ 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 MoeInvalidStatusFile("Parse error: incorrect nesting")
+ raise InvalidStatusFile("Parse error: incorrect nesting")
else:
this = stk.pop()
else:
- raise MoeInvalidStatusFile("Parse error: malformed line")
+ raise InvalidStatusFile("Parse error: malformed line")
+ if stk:
+ raise InvalidStatusFile("Parse error: not all subtrees closed")