from __future__ import absolute_import, print_function, unicode_literals
import re import json as json from .shared import JSONTemplateError, TemplateError, DeleteMarker, string, to_str from . import shared from .six import viewitems from .parser import Parser, Tokenizer from .interpreter import Interpreter import functools from inspect import isfunction, isbuiltin
def parse(source, context):
parser = Parser(source, tokenizer)
tree = parser.parse() if parser.current_token isnotNone: raise SyntaxError.unexpected(parser.current_token)
interp = Interpreter(context)
result = interp.interpret(tree) return result
def parse_until_terminator(source, context, terminator):
parser = Parser(source, tokenizer)
tree = parser.parse() if parser.current_token.kind != terminator: raise SyntaxError.unexpected(parser.current_token)
interp = Interpreter(context)
result = interp.interpret(tree) return result, parser.current_token.start
_interpolation_start_re = re.compile(r'\$?\${')
def interpolate(string, context):
mo = _interpolation_start_re.search(string) ifnot mo: return string
result = []
whileTrue:
result.append(string[:mo.start()]) if mo.group() != '$${':
string = string[mo.end():]
parsed, offset = parse_until_terminator(string, context, '}') if isinstance(parsed, (list, dict)): raise TemplateError( "interpolation of '{}' produced an array or object".format(string[:offset])) if parsed isNone:
result.append("") else:
result.append(to_str(parsed))
string = string[offset + 1:] else: # found `$${`
result.append('${')
string = string[mo.end():]
mo = _interpolation_start_re.search(string) ifnot mo:
result.append(string) break return''.join(result)
def checkUndefinedProperties(template, allowed):
unknownKeys = []
combined = "|".join(allowed) + "$"
unknownKeys = [key for key in sorted(template) ifnot re.match(combined, key)] if unknownKeys: raise TemplateError(allowed[0].replace('\\', '') + " has undefined properties: " + " ".join(unknownKeys))
@operator('$eval') def eval(template, context):
checkUndefinedProperties(template, [r'\$eval']) ifnot isinstance(template['$eval'], string): raise TemplateError("$eval must be given a string expression") return parse(template['$eval'], context)
@operator('$flatten') def flatten(template, context):
checkUndefinedProperties(template, [r'\$flatten'])
value = renderValue(template['$flatten'], context) ifnot isinstance(value, list): raise TemplateError('$flatten value must evaluate to an array')
def gen(): for e in value: if isinstance(e, list): for e2 in e: yield e2 else: yield e return list(gen())
@operator('$flattenDeep') def flattenDeep(template, context):
checkUndefinedProperties(template, [r'\$flattenDeep'])
value = renderValue(template['$flattenDeep'], context) ifnot isinstance(value, list): raise TemplateError('$flattenDeep value must evaluate to an array')
def gen(value): if isinstance(value, list): for e in value: for sub in gen(e): yield sub else: yield value
@operator('$let') def let(template, context):
checkUndefinedProperties(template, [r'\$let', 'in']) ifnot isinstance(template['$let'], dict): raise TemplateError("$let value must be an object")
subcontext = context.copy()
initial_result = renderValue(template['$let'], context) ifnot isinstance(initial_result, dict): raise TemplateError("$let value must be an object") for k, v in initial_result.items(): ifnot IDENTIFIER_RE.match(k): raise TemplateError("top level keys of $let must follow /[a-zA-Z_][a-zA-Z0-9_]*/") else:
subcontext[k] = v try:
in_expression = template['in'] except KeyError: raise TemplateError("$let operator requires an `in` clause") return renderValue(in_expression, subcontext)
@operator('$map') def map(template, context):
EACH_RE = r'each\([a-zA-Z_][a-zA-Z0-9_]*(,\s*([a-zA-Z_][a-zA-Z0-9_]*))?\)'
checkUndefinedProperties(template, [r'\$map', EACH_RE])
value = renderValue(template['$map'], context) ifnot isinstance(value, list) andnot isinstance(value, dict): raise TemplateError("$map value must evaluate to an array or object")
is_obj = isinstance(value, dict)
each_keys = [k for k in template if k.startswith('each(')] if len(each_keys) != 1: raise TemplateError( "$map requires exactly one other property, each(..)")
each_key = each_keys[0]
each_args = [x.strip() for x in each_key[5:-1].split(',')]
each_var = each_args[0]
each_idx = each_args[1] if len(each_args) > 1 elseNone
each_template = template[each_key]
def gen(val):
subcontext = context.copy() for i, elt in enumerate(val): if each_idx isNone:
subcontext[each_var] = elt else:
subcontext[each_var] = elt['val'] if is_obj else elt
subcontext[each_idx] = elt['key'] if is_obj else i
elt = renderValue(each_template, subcontext) if elt isnot DeleteMarker: yield elt if is_obj:
value = [{'key': v[0], 'val': v[1]} for v in value.items()]
v = dict() for e in gen(value): ifnot isinstance(e, dict): raise TemplateError( "$map on objects expects {0} to evaluate to an object".format(each_key))
v.update(e) return v else: return list(gen(value))
ifnot isinstance(template['$match'], dict): raise TemplateError("$match can evaluate objects only")
result = [] for condition in sorted(template['$match']): if parse(condition, context):
result.append(renderValue(template['$match'][condition], context))
ifnot isinstance(template['$switch'], dict): raise TemplateError("$switch can evaluate objects only")
result = [] for condition in template['$switch']: ifnot condition == '$default'and parse(condition, context):
result.append(renderValue(template['$switch'][condition], context))
if len(result) > 1: raise TemplateError("$switch can only have one truthy condition")
if len(result) == 0: if'$default'in template['$switch']:
result.append(renderValue(template['$switch']['$default'], context))
return result[0] if len(result) > 0 else DeleteMarker
@operator('$merge') def merge(template, context):
checkUndefinedProperties(template, [r'\$merge'])
value = renderValue(template['$merge'], context) ifnot isinstance(value, list) ornot all(isinstance(e, dict) for e in value): raise TemplateError( "$merge value must evaluate to an array of objects")
v = dict() for e in value:
v.update(e) return v
@operator('$mergeDeep') def merge(template, context):
checkUndefinedProperties(template, [r'\$mergeDeep'])
value = renderValue(template['$mergeDeep'], context) ifnot isinstance(value, list) ornot all(isinstance(e, dict) for e in value): raise TemplateError( "$mergeDeep value must evaluate to an array of objects")
def merge(l, r): if isinstance(l, list) and isinstance(r, list): return l + r if isinstance(l, dict) and isinstance(r, dict):
res = l.copy() for k, v in viewitems(r): if k in l:
res[k] = merge(l[k], v) else:
res[k] = v return res return r if len(value) == 0: return {} return functools.reduce(merge, value[1:], value[0])
@operator('$reverse') def reverse(template, context):
checkUndefinedProperties(template, [r'\$reverse'])
value = renderValue(template['$reverse'], context) ifnot isinstance(value, list): raise TemplateError("$reverse value must evaluate to an array of objects") return list(reversed(value))
@operator('$sort') def sort(template, context):
BY_RE = r'by\([a-zA-Z_][a-zA-Z0-9_]*\)'
checkUndefinedProperties(template, [r'\$sort', BY_RE])
value = renderValue(template['$sort'], context) ifnot isinstance(value, list): raise TemplateError('$sorted values to be sorted must have the same type')
# handle by(..) if given, applying the schwartzian transform
by_keys = [k for k in template if k.startswith('by(')] if len(by_keys) == 1:
by_key = by_keys[0]
by_var = by_key[3:-1]
by_expr = template[by_key]
def xform():
subcontext = context.copy() for e in value:
subcontext[by_var] = e yield parse(by_expr, subcontext), e
to_sort = list(xform()) elif len(by_keys) == 0:
to_sort = [(e, e) for e in value] else: raise TemplateError('only one by(..) is allowed')
# check types try:
eltype = type(to_sort[0][0]) except IndexError: return [] if eltype in (list, dict, bool, type(None)): raise TemplateError('$sorted values to be sorted must have the same type') ifnot all(isinstance(e[0], eltype) for e in to_sort): raise TemplateError('$sorted values to be sorted must have the same type')
# unzip the schwartzian transform return list(e[1] for e in sorted(to_sort))
def containsFunctions(rendered): if hasattr(rendered, '__call__'): returnTrue elif isinstance(rendered, list): for e in rendered: if containsFunctions(e): returnTrue returnFalse elif isinstance(rendered, dict): for k, v in viewitems(rendered): if containsFunctions(v): returnTrue returnFalse else: returnFalse
def renderValue(template, context): if isinstance(template, string): return interpolate(template, context)
elif isinstance(template, dict):
matches = [k for k in template if k in operators] if matches: if len(matches) > 1: raise TemplateError("only one operator allowed") return operators[matches[0]](template, context)
def updated(): for k, v in viewitems(template): if k.startswith('$$'):
k = k[1:] elif k.startswith('$') and IDENTIFIER_RE.match(k[1:]): raise TemplateError( '$ is reserved; use $$') else:
k = interpolate(k, context)
try:
v = renderValue(v, context) except JSONTemplateError as e: if IDENTIFIER_RE.match(k):
e.add_location('.{}'.format(k)) else:
e.add_location('[{}]'.format(json.dumps(k))) raise if v isnot DeleteMarker: yield k, v return dict(updated())
elif isinstance(template, list): def updated(): for i, e in enumerate(template): try:
v = renderValue(e, context) if v isnot DeleteMarker: yield v except JSONTemplateError as e:
e.add_location('[{}]'.format(i)) raise
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.