CORD-1382: Support operations on field sets and generate unique_together

Change-Id: I5d7e67492734b9de116ca954ef815ac03ba2108f
diff --git a/xos/genx/tool/lib.py b/xos/genx/tool/lib.py
index e9acbe5..ed55134 100644
--- a/xos/genx/tool/lib.py
+++ b/xos/genx/tool/lib.py
@@ -2,6 +2,10 @@
 import re
 from pattern import en
 
+class FieldNotFound(Exception):
+    def __init__(self, message):
+        super(FieldNotFound, self).__init__(message)
+
 def xproto_unquote(s):
     return unquote(s)
 
@@ -331,3 +335,56 @@
         return 'number'
     elif f['type'] in ['double','float']:
         return 'string'
+
+def xproto_tuplify(nested_list_or_set):
+    if not isinstance(nested_list_or_set, list) and not isinstance(nested_list_or_set, set):
+        return nested_list_or_set
+    else:
+        return tuple([xproto_tuplify(i) for i in nested_list_or_set])
+
+def xproto_field_graph_components(fields, tag='unique_with'):
+    def find_components(graph):
+        pending = set(graph.keys())
+        components = []
+
+        while pending:
+            front = { pending.pop() }
+            component = set()
+
+            while front:
+                node = front.pop()
+                neighbours = graph[node]
+                neighbours-=component # These we have already visited
+                front |= neighbours
+
+                pending-=neighbours
+                component |= neighbours
+            
+            components.append(component)
+
+        return components
+
+    field_graph = {}
+    field_names = {f['name'] for f in fields}
+
+    for f in fields:
+        try:
+            tagged_str = unquote(f['options'][tag])
+            tagged_fields = tagged_str.split(',')
+
+            for uf in tagged_fields:
+                if uf not in field_names:
+                    raise FieldNotFound('Field %s not found'%uf)
+
+                field_graph.setdefault(f['name'],set()).add(uf)
+                field_graph.setdefault(uf,set()).add(f['name'])
+        except KeyError:
+            pass
+
+    components = find_components(field_graph)
+    return components
+
+
+
+
+