201eb75ae4
This commit introduces ref(templateName [, parameterName] [, idOnly]) YAQL function was added to UI definition DSL. This function evaluates template templateName and fixes the result in parameters under parameterName key (or templateName if the second parameter was omitted). Then it generates object ID and places it into ?/id field. On the first use of parameterName or if idOnly is false the function will return the whole object structure. On subsequent calls or if idOnly is true it will return the ID that was generated upon the first call. Thus the function brings ability to reference single object several times. ID generation is done in a safe way with a special temporary type that wraps string ID and replaced with string representation upon serialization. This way it is still impossible for a developer to explicitly provide ID value in the template (it is still going to be overwritten because it is not of an ObjectID type and it is impossible to create instance of ObjectID from yaql expression) Change-Id: Idf114113a73d00ab2132bd085157ca01e94dae3e
163 lines
4.5 KiB
Python
163 lines
4.5 KiB
Python
# Copyright (c) 2013 Mirantis, Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import re
|
|
import string
|
|
import types
|
|
import uuid
|
|
|
|
import six
|
|
|
|
from django.core import validators
|
|
|
|
_LOCALIZABLE_KEYS = set(['label', 'help_text', 'error_messages'])
|
|
|
|
|
|
class ObjectID(object):
|
|
def __init__(self):
|
|
self.object_id = str(uuid.uuid4())
|
|
|
|
|
|
def is_localizable(keys):
|
|
return set(keys).intersection(_LOCALIZABLE_KEYS)
|
|
|
|
|
|
def camelize(name):
|
|
"""Turns snake_case name into SnakeCase."""
|
|
return ''.join([bit.capitalize() for bit in name.split('_')])
|
|
|
|
|
|
def decamelize(name):
|
|
"""Turns CamelCase/camelCase name into camel_case."""
|
|
pat = re.compile(r'([A-Z]*[^A-Z]*)(.*)')
|
|
bits = []
|
|
while True:
|
|
head, tail = re.match(pat, name).groups()
|
|
bits.append(head)
|
|
if tail:
|
|
name = tail
|
|
else:
|
|
break
|
|
return '_'.join([bit.lower() for bit in bits])
|
|
|
|
|
|
def explode(_string):
|
|
"""Explodes a string into a list of one-character strings."""
|
|
if not _string or not isinstance(_string, six.string_types):
|
|
return _string
|
|
else:
|
|
return list(_string)
|
|
|
|
|
|
def prepare_regexp(regexp):
|
|
"""Converts regular expression string pattern into RegexValidator object.
|
|
|
|
Also /regexp/flags syntax is allowed, where flags is a string of
|
|
one-character flags that will be appended to the compiled regexp.
|
|
"""
|
|
if regexp.startswith('/'):
|
|
groups = re.match(r'^/(.*)/([A-Za-z]*)$', regexp).groups()
|
|
regexp, flags_str = groups
|
|
flags = 0
|
|
for flag in explode(flags_str):
|
|
flag = flag.upper()
|
|
if hasattr(re, flag):
|
|
flags |= getattr(re, flag)
|
|
return validators.RegexValidator(re.compile(regexp, flags))
|
|
else:
|
|
return validators.RegexValidator(re.compile(regexp))
|
|
|
|
|
|
def recursive_apply(predicate, transformer, value, *args):
|
|
def rec(val):
|
|
if predicate(val, *args):
|
|
return rec(transformer(val, *args))
|
|
elif isinstance(val, dict):
|
|
return dict((rec(k), rec(v)) for (k, v) in six.iteritems(val))
|
|
elif isinstance(val, list):
|
|
return [rec(v) for v in val]
|
|
elif isinstance(val, tuple):
|
|
return tuple([rec(v) for v in val])
|
|
elif isinstance(val, types.GeneratorType):
|
|
return rec(val)
|
|
else:
|
|
return val
|
|
|
|
return rec(value)
|
|
|
|
|
|
def evaluate(value, context):
|
|
return recursive_apply(
|
|
lambda v, _ctx: hasattr(v, 'evaluate'),
|
|
lambda v, _ctx: v.evaluate(context=_ctx),
|
|
value, context)
|
|
|
|
|
|
def insert_hidden_ids(application):
|
|
def wrap(k, v):
|
|
if k == '?' and isinstance(v, dict) and not isinstance(
|
|
v.get('id'), ObjectID):
|
|
v['id'] = str(uuid.uuid4())
|
|
return k, v
|
|
elif isinstance(v, ObjectID):
|
|
return k, v.object_id
|
|
else:
|
|
return rec(k), rec(v)
|
|
|
|
def rec(val):
|
|
if isinstance(val, dict):
|
|
return dict(wrap(k, v) for k, v in six.iteritems(val))
|
|
elif isinstance(val, list):
|
|
return [rec(v) for v in val]
|
|
else:
|
|
return val
|
|
|
|
return rec(application)
|
|
|
|
|
|
def int2base(x, base):
|
|
"""Converts decimal integers to another number base from base-2 to base-36
|
|
|
|
:param x: decimal integer
|
|
:param base: number base, max value is 36
|
|
:return: integer converted to the specified base
|
|
"""
|
|
digs = string.digits + string.ascii_lowercase
|
|
if x < 0:
|
|
sign = -1
|
|
elif x == 0:
|
|
return '0'
|
|
else:
|
|
sign = 1
|
|
x *= sign
|
|
digits = []
|
|
while x:
|
|
digits.append(digs[x % base])
|
|
x //= base
|
|
if sign < 0:
|
|
digits.append('-')
|
|
digits.reverse()
|
|
return ''.join(digits)
|
|
|
|
|
|
def to_str(text):
|
|
if not isinstance(text, str):
|
|
# unicode in python2
|
|
if isinstance(text, six.text_type):
|
|
text = text.encode('utf-8')
|
|
# bytes in python3
|
|
elif isinstance(text, six.binary_type):
|
|
text = text.decode('utf-8')
|
|
return text
|