from yaml import safe_load as yaml_load from yaml import dump as yaml_dump from sys import argv def main(): path = "./testdata/hello_world.yaml" if len(argv) > 1: path = argv[1] doc = Doc.from_file(path) print(doc.render()) print(doc.to_dict()) # https://blog.kevinjahns.de/are-crdts-suitable-for-shared-editing/ class Doc(): def __init__(self): self._nodes = [] def from_file(path): doc = Doc() with open(path, "r") as f: d = yaml_load(f) doc._nodes = [Node.from_dict(i) for i in d["nodes"]] return doc def to_dict(self): return yaml_dump(slim_dict({ "nodes": [node.to_dict() for node in self._nodes], })) def render(self): result = [] nodes = [node for node in self._nodes] while nodes: first = self._first_node(nodes) result.append(first) nodes = [node for node in nodes if node != first] return "".join([i.content() for i in result]) def _first_node(self, nodes): for potential_root_node in sorted([node for node in nodes if not node.from_id()], key=lambda x: x.id()): return potential_root_node node_ids = [node.id() for node in nodes] for potential_rootest_node in sorted([node for node in nodes if not node.from_id() in node_ids], key=lambda x: x.id()): return potential_rootest_node raise Exception(nodes) class Node(): def __init__(self, client_name, ts, content, is_delete, from_id, to_id): self._ts = ts self._client_name = client_name self._content = content self._is_delete = is_delete self._from_id = from_id self._to_id = to_id def __str__(self): return str(self.to_dict()) def from_dict(d): return Node( d.get("client_name", ""), d.get("ts", 0), d.get("content", ""), d.get("is_delete", False), d.get("from", ""), d.get("to", ""), ) def to_dict(self): return slim_dict({ "client_name": self._client_name, "ts": self._ts, "content": self._content, "is_delete": self._is_delete, "from": self._from_id, "to": self._to_id, }) def clone(self): return Node( self._ts, self._client_name, self._content, self._is_delete, self._from_id, self._to_id, ) def id(self): return f'{self._ts}/{self._client_name}' def from_id(self): return self._from_id def to_id(self): return self._to_id def content(self): if self._is_delete: self._content = "" return self._content def slim_dict(d): return {k:v for k,v in d.items() if v or (isinstance(v, int) and not isinstance(v, bool))} if __name__ == "__main__": main()