Bug 1428889: upgrade json-e from 2.3.2 to 2.5.0; r=bstack

MozReview-Commit-ID: FlucI8bQq4h

--HG--
extra : rebase_source : 4cff4a912e33487b9f4133dae200eef051af39fd
This commit is contained in:
Dustin J. Mitchell 2018-01-08 21:14:13 +00:00
parent 82c77e224c
commit a94444c14e
4 changed files with 56 additions and 42 deletions

View File

@ -1,7 +1,7 @@
from __future__ import absolute_import, print_function, unicode_literals
from .prattparser import PrattParser, infix, prefix
from .shared import TemplateError, string
from .shared import TemplateError, InterpreterError, string
import operator
import json
@ -21,8 +21,9 @@ OPERATORS = {
}
def expectationError(operator, expected):
return TemplateError('{} expected {}'.format(operator, expected))
def infixExpectationError(operator, expected):
return InterpreterError('infix: {} expects {} {} {}'.
format(operator, expected, operator, expected))
class ExpressionEvaluator(PrattParser):
@ -44,9 +45,9 @@ class ExpressionEvaluator(PrattParser):
'null', 'number', 'identifier', 'string',
]
precedence = [
['in'],
['||'],
['&&'],
['in'],
['==', '!='],
['>=', '<=', '<', '>'],
['+', '-'],
@ -80,14 +81,14 @@ class ExpressionEvaluator(PrattParser):
def uminus(self, token, pc):
v = pc.parse('unary')
if not isNumber(v):
raise expectationError('unary -', 'number')
raise InterpreterError('{} expects {}'.format('unary -', 'number'))
return -v
@prefix("+")
def uplus(self, token, pc):
v = pc.parse('unary')
if not isNumber(v):
raise expectationError('unary +', 'number')
raise InterpreterError('{} expects {}'.format('unary +', 'number'))
return v
@prefix("identifier")
@ -95,8 +96,8 @@ class ExpressionEvaluator(PrattParser):
try:
return self.context[token.value]
except KeyError:
raise TemplateError(
'no context value named "{}"'.format(token.value))
raise InterpreterError(
'unknown context value {}'.format(token.value))
@prefix("null")
def null(self, token, pc):
@ -131,23 +132,23 @@ class ExpressionEvaluator(PrattParser):
@infix("+")
def plus(self, left, token, pc):
if not isinstance(left, (string, int, float)) or isinstance(left, bool):
raise expectationError('+', 'number or string')
raise infixExpectationError('+', 'number/string')
right = pc.parse(token.kind)
if not isinstance(right, (string, int, float)) or isinstance(right, bool):
raise expectationError('+', 'number or string')
raise infixExpectationError('+', 'number/string')
if type(right) != type(left) and \
(isinstance(left, string) or isinstance(right, string)):
raise expectationError('+', 'matching types')
raise infixExpectationError('+', 'numbers/strings')
return left + right
@infix('-', '*', '/', '**')
def arith(self, left, token, pc):
op = token.kind
if not isNumber(left):
raise expectationError(op, 'number')
raise infixExpectationError(op, 'number')
right = pc.parse({'**': '**-right-associative'}.get(op))
if not isNumber(right):
raise expectationError(op, 'number')
raise infixExpectationError(op, 'number')
return OPERATORS[op](left, right)
@infix("[")
@ -175,7 +176,7 @@ class ExpressionEvaluator(PrattParser):
@infix(".")
def property_dot(self, left, token, pc):
if not isinstance(left, dict):
raise expectationError('.', 'object')
raise infixExpectationError('.', 'object')
k = pc.require('identifier').value
try:
return left[k]
@ -202,7 +203,7 @@ class ExpressionEvaluator(PrattParser):
right = pc.parse(op)
if type(left) != type(right) or \
not (isinstance(left, (int, float, string)) and not isinstance(left, bool)):
raise expectationError(op, 'matching types')
raise infixExpectationError(op, 'numbers/strings')
return OPERATORS[op](left, right)
@infix("in")
@ -210,17 +211,17 @@ class ExpressionEvaluator(PrattParser):
right = pc.parse(token.kind)
if isinstance(right, dict):
if not isinstance(left, string):
raise expectationError('in-object', 'string on left side')
raise infixExpectationError('in-object', 'string on left side')
elif isinstance(right, string):
if not isinstance(left, string):
raise expectationError('in-string', 'string on left side')
raise infixExpectationError('in-string', 'string on left side')
elif not isinstance(right, list):
raise expectationError(
raise infixExpectationError(
'in', 'Array, string, or object on right side')
try:
return left in right
except TypeError:
raise expectationError('in', 'scalar value, collection')
raise infixExpectationError('in', 'scalar value, collection')
def isNumber(v):
@ -268,19 +269,19 @@ def accessProperty(value, a, b, is_interval):
try:
return value[a:b]
except TypeError:
raise expectationError('[..]', 'integer')
raise infixExpectationError('[..]', 'integer')
else:
try:
return value[a]
except IndexError:
raise TemplateError('index out of bounds')
except TypeError:
raise expectationError('[..]', 'integer')
raise infixExpectationError('[..]', 'integer')
if not isinstance(value, dict):
raise expectationError('[..]', 'object, array, or string')
raise infixExpectationError('[..]', 'object, array, or string')
if not isinstance(a, string):
raise expectationError('[..]', 'string index')
raise infixExpectationError('[..]', 'string index')
try:
return value[a]

View File

@ -11,7 +11,7 @@ class SyntaxError(TemplateError):
@classmethod
def unexpected(cls, got, exp):
exp = ', '.join(sorted(exp))
return cls('Found {}, expected {}'.format(got, exp))
return cls('Found {}, expected {}'.format(got.value, exp))
Token = namedtuple('Token', ['kind', 'value', 'start', 'end'])
@ -92,7 +92,7 @@ class PrattParser(with_metaclass(PrattParserMeta, object)):
# if there are any tokens remaining, that's an error..
token = pc.attempt()
if token:
raise SyntaxError.unexpected(token.kind, self.infix_rules)
raise SyntaxError.unexpected(token, self.infix_rules)
return result
def parseUntilTerminator(self, source, terminator):
@ -100,7 +100,7 @@ class PrattParser(with_metaclass(PrattParserMeta, object)):
result = pc.parse()
token = pc.attempt()
if token.kind != terminator:
raise SyntaxError.unexpected(token.kind, [terminator])
raise SyntaxError.unexpected(token, [terminator])
return (result, token.start)
def _generate_tokens(self, source):
@ -169,7 +169,7 @@ class ParseContext(object):
if not token:
raise SyntaxError('Unexpected end of input')
if kinds and token.kind not in kinds:
raise SyntaxError.unexpected(token.kind, kinds)
raise SyntaxError.unexpected(token, kinds)
return token
def parse(self, precedence=None):
@ -178,7 +178,7 @@ class ParseContext(object):
token = self.require()
prefix_rule = parser.prefix_rules.get(token.kind)
if not prefix_rule:
raise SyntaxError.unexpected(token.kind, parser.prefix_rules)
raise SyntaxError.unexpected(token, parser.prefix_rules)
left = prefix_rule(parser, token, self)
while self.next_token:
kind = self.next_token.kind

View File

@ -72,7 +72,10 @@ def checkUndefinedProperties(template, allowed):
@operator('$eval')
def eval(template, context):
return evaluateExpression(renderValue(template['$eval'], context), context)
checkUndefinedProperties(template, ['\$eval'])
if not isinstance(template['$eval'], string):
raise TemplateError("$eval must be given a string expression")
return evaluateExpression(template['$eval'], context)
@operator('$flatten')
@ -140,21 +143,21 @@ def ifConstruct(template, context):
def jsonConstruct(template, context):
checkUndefinedProperties(template, ['\$json'])
value = renderValue(template['$json'], context)
return json.dumps(value, separators=(',', ':'))
return json.dumps(value, separators=(',', ':'), sort_keys=True)
@operator('$let')
def let(template, context):
checkUndefinedProperties(template, ['\$let', 'in'])
variables = renderValue(template['$let'], context)
if not isinstance(variables, dict):
raise TemplateError("$let value must evaluate to an object")
else:
if not all(IDENTIFIER_RE.match(variableNames) for variableNames in variables.keys()):
raise TemplateError('top level keys of $let must follow /[a-zA-Z_][a-zA-Z0-9_]*/')
if not isinstance(template['$let'], dict):
raise TemplateError("$let value must be an object")
subcontext = context.copy()
subcontext.update(variables)
for k, v in template['$let'].items():
if not IDENTIFIER_RE.match(k):
raise TemplateError('top level keys of $let must follow /[a-zA-Z_][a-zA-Z0-9_]*/')
subcontext[k] = renderValue(v, context)
try:
in_expression = template['in']
except KeyError:
@ -243,7 +246,7 @@ def reverse(template, context):
checkUndefinedProperties(template, ['\$reverse'])
value = renderValue(template['$reverse'], context)
if not isinstance(value, list):
raise TemplateError("$reverse value must evaluate to an array")
raise TemplateError("$reverse value must evaluate to an array of objects")
return list(reversed(value))
@ -253,7 +256,7 @@ def sort(template, context):
checkUndefinedProperties(template, ['\$sort', BY_RE])
value = renderValue(template['$sort'], context)
if not isinstance(value, list):
raise TemplateError("$sort value must evaluate to an array")
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(')]
@ -279,9 +282,9 @@ def sort(template, context):
except IndexError:
return []
if eltype in (list, dict, bool, type(None)):
raise TemplateError('$sort values must be sortable')
raise TemplateError('$sorted values to be sorted must have the same type')
if not all(isinstance(e[0], eltype) for e in to_sort):
raise TemplateError('$sorted values must all have the same type')
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))

View File

@ -28,6 +28,10 @@ class TemplateError(JSONTemplateError):
pass
class InterpreterError(JSONTemplateError):
pass
# Regular expression matching: X days Y hours Z minutes
# todo: support hr, wk, yr
FROMNOW_RE = re.compile(''.join([
@ -109,7 +113,13 @@ def to_str(v):
def stringDate(date):
# Convert to isoFormat
string = date.isoformat()
try:
string = date.isoformat(timespec='microseconds')
# py2.7 to py3.5 does not have timespec
except TypeError as e:
string = date.isoformat()
if string.find('.') == -1:
string += '.000'
string = datefmt_re.sub(r'\1Z', string)
return string