[CORD-1440] Moving the generative toolchain in a library
Change-Id: Ifa8e8f930ac34e1f8952099b7e34842a52f4664d
diff --git a/lib/xos-genx/xosgenx/jinja2_extensions.py b/lib/xos-genx/xosgenx/jinja2_extensions.py
new file mode 100644
index 0000000..8377d5a
--- /dev/null
+++ b/lib/xos-genx/xosgenx/jinja2_extensions.py
@@ -0,0 +1,436 @@
+import pdb
+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)
+
+def unquote(s):
+ if (s.startswith('"') and s.endswith('"')):
+ return s[1:-1]
+
+def xproto_singularize(field):
+ try:
+ # The user has set a singular, as an exception that cannot be handled automatically
+ singular = field['options']['singular']
+ singular = unquote(singular)
+ except KeyError:
+ singular = en.singularize(field['name'])
+
+ return singular
+
+def xproto_singularize_pluralize(field):
+ try:
+ # The user has set a plural, as an exception that cannot be handled automatically
+ plural = field['options']['plural']
+ plural = unquote(plural)
+ except KeyError:
+ plural = en.pluralize(en.singularize(field['name']))
+
+ return plural
+
+def xproto_pluralize(field):
+ try:
+ # The user has set a plural, as an exception that cannot be handled automatically
+ plural = field['options']['plural']
+ plural = unquote(plural)
+ except KeyError:
+ plural = en.pluralize(field['name'])
+
+ return plural
+
+def xproto_unquote(s):
+ if (s.startswith('"') and s.endswith('"')):
+ s = s[1:-1]
+ return s
+
+def xproto_links_to_modeldef_relations(llst):
+ outlist = []
+ seen = []
+ for l in llst:
+ try:
+ t = l['link_type']
+ except KeyError, e:
+ raise e
+
+ if l['peer']['fqn'] not in seen and t!='manytomany':
+ outlist.append('- {model: %s, type: %s}\n'%(l['peer']['name'], l['link_type']))
+ seen.append(l['peer'])
+
+ return outlist
+
+def django_content_type_string(xptags):
+ # Check possibility of KeyError in caller
+ content_type = xptags['content_type']
+
+ try:
+ content_type = eval(content_type)
+ except:
+ pass
+
+ if (content_type=='url'):
+ return 'URLField'
+ if (content_type=='date'):
+ return 'DateTimeField'
+ elif (content_type=='ip'):
+ return 'GenericIPAddressField'
+ elif (content_type=='stripped' or content_type=='"stripped"'):
+ return 'StrippedCharField'
+ else:
+ raise Exception('Unknown Type: %s'%content_type)
+
+def django_string_type(xptags):
+ try:
+ max_length = eval(xptags['max_length'])
+ except:
+ max_length = 1024 * 1024
+
+ if ('content_type' in xptags):
+ return django_content_type_string(xptags)
+ elif (max_length<1024*1024):
+ return 'CharField'
+ else:
+ return 'TextField'
+
+def xproto_base_def(model_name, base):
+ if (model_name=='XOSBase'):
+ return '(models.Model, PlModelMixIn)'
+ elif (not base):
+ return ''
+ else:
+ base = map(lambda s:s['name'], base)
+ return '(' + ','.join(base) + ')'
+
+def xproto_first_non_empty(lst):
+ for l in lst:
+ if l: return l
+
+def xproto_api_type(field):
+ try:
+ if (unquote(field['options']['content_type'])=='date'):
+ return 'float'
+ except KeyError:
+ pass
+
+ return field['type']
+
+def xproto_django_type(xptype, xptags):
+ if (xptype=='string'):
+ return django_string_type(xptags)
+ elif (xptype=='float'):
+ return 'FloatField'
+ elif (xptype=='bool'):
+ return 'BooleanField'
+ elif (xptype=='uint32'):
+ return 'IntegerField'
+ elif (xptype=='int32'):
+ return 'IntegerField'
+ elif (xptype=='int64'):
+ return 'BigIntegerField'
+ else:
+ raise Exception('Unknown Type: %s'%xptype)
+
+
+
+def xproto_django_link_type(f):
+ if (f['link_type']=='manytoone'):
+ return 'ForeignKey'
+ elif (f['link_type']=='manytomany'):
+ if (f['dst_port']):
+ return 'ManyToManyField'
+ else:
+ return 'GenericRelation'
+
+def format_options_string(d):
+ if (not d):
+ return ''
+ else:
+
+ lst = []
+ for k,v in d.items():
+ if (type(v)==str and k=='default' and v.endswith('()"')):
+ lst.append('%s = %s'%(k,v[1:-3]))
+ elif (type(v)==str and v.startswith('"')):
+ try:
+ tup = eval(v[1:-1])
+ if (type(tup)==tuple):
+ lst.append('%s = %r'%(k,tup))
+ else:
+ lst.append('%s = %s'%(k,v))
+ except:
+ lst.append('%s = %s'%(k,v))
+ elif (type(v)==bool):
+ lst.append('%s = %r'%(k,bool(v)))
+ else:
+ try:
+ lst.append('%s = %r'%(k,int(v)))
+ except ValueError:
+ lst.append('%s = %s'%(k,v))
+
+ return ', '.join(lst)
+
+def map_xproto_to_django(f):
+ allowed_keys=['help_text','default','max_length','modifier','blank','choices','db_index','null','editable','on_delete','verbose_name', 'auto_now_add']
+
+ m = {'modifier':{'optional':True, 'required':False, '_target':'null'}}
+ out = {}
+
+ for k,v in f['options'].items():
+ if (k in allowed_keys):
+ try:
+ kv2 = m[k]
+ out[kv2['_target']] = kv2[v]
+ except:
+ out[k] = v
+ return out
+
+
+def xproto_django_link_options_str(field, dport=None):
+ output_dict = map_xproto_to_django(field)
+
+ if (dport and (dport=='+' or '+' not in dport)):
+ output_dict['related_name'] = '%r'%dport
+
+ try:
+ if field['through']:
+ d = {}
+ if isinstance(field['through'], str):
+ split = field['through'].rsplit('.',1)
+ d['name'] = split[-1]
+ if len(split)==2:
+ d['package'] = split[0]
+ d['fqn'] = 'package' + '.' + d['name']
+ else:
+ d['fqn'] = d['name']
+ d['package'] = ''
+ else:
+ d = field['through']
+
+ if not d['name'].endswith('_'+field['name']):
+ output_dict['through'] = '%r'%d['fqn']
+ except KeyError:
+ pass
+
+ return format_options_string(output_dict)
+
+def xproto_django_options_str(field, dport=None):
+ output_dict = map_xproto_to_django(field)
+
+ if (dport=='_'):
+ dport = '+'
+
+ if (dport and (dport=='+' or '+' not in dport)):
+ output_dict['related_name'] = '%r'%dport
+
+ return format_options_string(output_dict)
+
+def xproto_base_name(n):
+ # Hack - Refactor NetworkParameter* to make this go away
+ if (n.startswith('NetworkParameter')):
+ return '_'
+
+ expr = r'^[A-Z]+[a-z]*'
+
+ try:
+ match = re.findall(expr, n)[0]
+ except:
+ return '_'
+
+ return match
+
+def xproto_base_fields(m, table):
+ fields = []
+
+ for b in m['bases']:
+ option1 = b['fqn']
+ try:
+ option2 = m['package'] + '.' + b['name']
+ except TypeError:
+ option2 = option1
+
+ accessor = None
+ if option1 in table: accessor = option1
+ elif option2 in table: accessor = option2
+
+ if accessor:
+ base_fields = xproto_base_fields(table[accessor], table)
+
+ model_fields = table[accessor]['fields']
+ fields.extend(base_fields)
+ fields.extend(model_fields)
+
+ return fields
+
+def xproto_base_rlinks(m, table):
+ links = []
+
+ for base in m['bases']:
+ b = base['name']
+ if b in table:
+ base_rlinks = xproto_base_rlinks(table[b], table)
+
+ model_rlinks = table[b]['rlinks']
+ links.extend(base_rlinks)
+ links.extend(model_rlinks)
+
+ return links
+
+def xproto_base_links(m, table):
+ links = []
+
+ for base in m['bases']:
+ b = base['name']
+ if b in table:
+ base_links = xproto_base_links(table[b], table)
+
+ model_links = table[b]['links']
+ links.extend(base_links)
+ links.extend(model_links)
+ return links
+
+
+def xproto_validators(f):
+ # To be cleaned up when we formalize validation in xproto
+ validators = []
+
+ # bound-based validators
+ bound_validators = [('max_length','maxlength'), ('min', 'min'), ('max', 'max')]
+
+ for v0, v1 in bound_validators:
+ try:
+ validators.append({'name':v1, 'int_value':int(f['options'][v0])})
+ except KeyError:
+ pass
+
+ # validators based on content_type
+ content_type_validators = ['ip', 'url', 'email']
+
+ for v in content_type_validators:
+ #if f['name']=='ip': pdb.set_trace()
+ try:
+ val = unquote(f['options']['content_type'])==v
+ if not val:
+ raise KeyError
+
+ validators.append({'name':v, 'bool_value': True})
+ except KeyError:
+ pass
+
+ # required validator
+ try:
+ required = f['options']['blank']=='False' and f['options']['null']=='False'
+ if required:
+ validators.append({'name':'required', 'bool_value':required})
+ except KeyError:
+ pass
+
+ return validators
+
+def xproto_string_type(xptags):
+ try:
+ max_length = eval(xptags['max_length'])
+ except:
+ max_length = 1024
+
+ if ('varchar' not in xptags):
+ return 'string'
+ else:
+ return 'text'
+
+def xproto_type_to_ui_type(f):
+ try:
+ content_type = f['options']['content_type']
+ content_type = eval(content_type)
+ except:
+ content_type = None
+ pass
+
+ if content_type == 'date':
+ return 'date'
+ elif f['type'] == 'bool':
+ return 'boolean'
+ elif f['type'] == 'string':
+ return xproto_string_type(f['options'])
+ elif f['type'] in ['int','uint32','int32'] or 'link' in f:
+ 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
+
+def xproto_api_opts(field):
+ options = []
+ if 'max_length' in field['options'] and field['type']=='string':
+ options.append('(val).maxLength = %s'%field['options']['max_length'])
+
+ try:
+ if field['options']['null'] == 'False':
+ options.append('(val).nonNull = true')
+ except KeyError:
+ pass
+
+ if 'link' in field and 'model' in field['options']:
+ options.append('(foreignKey).modelName = "%s"'%field['options']['model'])
+
+ if options:
+ options_str = '[' + ', '.join(options) + ']'
+ else:
+ options_str = ''
+
+ return options_str
+
+def xproto_tosca_required(blank):
+ if blank == "False":
+ return "true"
+ return "false"
+