def filewrapper(file, mode): if type(file) is str: return open(file, mode) elif hasattr(file, "read"): return file else: raise ValueError("need string or file descriptor object") class FileParsingError(SyntaxError): pass class Record: def __init__(self): self.name = None self.data = {} self.children = {} self.parents = {} self.propagated = False def __getitem__(self, item): return self.data[item] def __setitem__(self, item, value): self.data[item] = value def __repr__(self): return f"[{self.data}, {len(self.children)}, {len(self.parents)}]" class RecordCollection(): def __init__(self, file): self.objects = {} with filewrapper(file, "r") as fd: self._fromFile(fd) self._parent() self._propagate() def _fromFile(self, fd): lines = [i.rstrip() for i in fd.readlines()] current = Record() for i in lines: ind = i[0:2] == ' ' if ind: spl = i[2:].split(': ') try: current[spl[0]] = spl[1] except IndexError: # TODO: maybe add line numbers? raise FileParsingError(f"error parsing '{i}'") else: try: if i[-1] != ":": raise FileParsingError(f"colon must be on last character of '{i}'") name = i.split(' ')[1][0:-1] except IndexError: current = Record() continue current.name = name self.objects[name] = current def _parent(self): for i in self.objects: current = self.objects[i] try: inherit = current['inherit'].split(' ') inherit_order = \ [int(i) for i in current['inherit_order'].split(' ')] # TODO: add more precise error checking if not len(inherit) == len(inherit_order): raise FileParsingError("len(inherit) != len(inherit_order)") for j, k in zip( inherit, inherit_order ): current.parents[j] = self.objects[j] self.objects[j].children[k] = current except KeyError: pass def _resolveNode(self, node): if not node.parents: return elif node.propagated: return else: for i in node.parents: if not node.parents[i].propagated: self._resolveNode(node.parents[i]) for j in node.parents[i].data: if not j in node.data: node.data[j] = node.parents[i].data[j] node.propagated = True def _propagate(self): for i in self.objects: self._resolveNode(self.objects[i]) def findEntrypoints(self): ret = [] for i in self.objects: if not self.objects[i].parents: ret.append(self.objects[i]) return ret def write(self, file): with filewrapper(file, "w") as fd: lines = [] for i in self.objects: if self.objects[i].children: lines.append(f"cat {self.objects[i].name}:") else: lines.append(f"rec {self.objects[i].name}:") for j in self.objects[i].data: lines.append( f" {j}: {self.objects[i].data[j]}" ) lines.append("") lines.pop() for i in range(len(lines)): lines[i] += "\n" fd.writelines(lines)