def Transform(self, func_name, active=0): """Parses one or more transform calls and returns a _Transform call object. The initial '(' has already been consumed by the caller. Args: func_name: The name of the first transform function. active: The transform active level, None for always active. Returns: The _Transform object containing the ordered list of transform calls. """ here = self.GetPosition() calls = _Transform() map_transform = 0 while True: transform = self._ParseTransform(func_name, active=active, map_transform=map_transform) if transform.func == resource_transform.TransformAlways: active = None # Always active. func_name = None elif transform.func == resource_transform.TransformMap: map_transform = int(transform.args[0]) if transform.args else 1 func_name = None elif transform.func == resource_transform.TransformIf: if len(transform.args) != 1: raise resource_exceptions.ExpressionSyntaxError( 'Conditional filter expression expected [{0}].'.format( self.Annotate(here))) calls.SetConditional(transform.args[0]) elif transform.func == resource_transform.TransformSynthesize: transform.func = self._ParseSynthesize(transform.args) transform.args = [] transform.kwargs = {} calls.Add(transform) else: # always() applies to all transforms for key. # map() applies to the next transform. map_transform = 0 calls.Add(transform) if not self.IsCharacter('.', eoi_ok=True): break call = self.Key() here = self.GetPosition() if not self.IsCharacter('('): raise resource_exceptions.ExpressionSyntaxError( 'Transform function expected [{0}].'.format( self.Annotate(here))) if len(call) != 1: raise resource_exceptions.ExpressionSyntaxError( 'Unknown transform function {0} [{1}].'.format( '.'.join(call), self.Annotate(here))) func_name = call.pop() return calls
def Transform(self, func_name, active=0, restriction=False): """Parses one or more transform calls and returns a _Transform call object. The cursor is positioned at the '(' just after the transform name. Args: func_name: The name of the first transform function. active: The transform active level, None for always active. restriction: Transform is a global restriction that does not have an obj arg. Returns: The _Transform object containing the ordered list of transform calls. """ here = self.GetPosition() calls = _Transform() map_transform = False while True: transform = self._ParseTransform(func_name, active=active, map_transform=map_transform, restriction=restriction) restriction = False if transform.func == resource_transform.TransformAlways: active = None # Always active. func_name = None elif transform.func == resource_transform.TransformMap: map_transform = True func_name = None elif transform.func == resource_transform.TransformIf: if len(transform.args) != 1: raise resource_exceptions.ExpressionSyntaxError( 'Conditional filter expression expected [{0}].'.format( self.Annotate(here))) calls.SetConditional(transform.args[0]) else: # always() applies to all transforms for key. # map() applies to the next transform. map_transform = False calls.Add(transform) if not self.IsCharacter('.', eoi_ok=True): break call = self.Key() here = self.GetPosition() if not self.IsCharacter('('): raise resource_exceptions.ExpressionSyntaxError( 'Transform function expected [{0}].'.format( self.Annotate(here))) if len(call) != 1: raise resource_exceptions.ExpressionSyntaxError( 'Unknown transform function {0} [{1}].'.format( '.'.join(call), self.Annotate(here))) func_name = call.pop() return calls
def _ParseKey(self): """Parses a key and optional attributes from the expression. Transform functions and key attributes are also handled here. Raises: ExpressionSyntaxError: The expression has a syntax error. Returns: The parsed key. """ key = self._lex.Key() here = self._lex.GetPosition() attribute = self._Attribute(self._projection.PROJECT) if self._lex.IsCharacter('(', eoi_ok=True): func_name = key.pop() active = self._projection.active map_transform = False while True: transform = self._ParseTransform(func_name, active, map_transform) if transform.func == self._builtin_transforms.TransformAlways: active = None # Always active. func_name = None elif transform.func == self._builtin_transforms.TransformMap: map_transform = True func_name = None else: # always() applies to all transforms for key. # map() applies to the next transform. map_transform = False attribute.transform.append(transform) if not self._lex.IsCharacter('.', eoi_ok=True): break call = self._lex.Key() here = self._lex.GetPosition() if not self._lex.IsCharacter('('): raise resource_exceptions.ExpressionSyntaxError( 'Transform function expected [{0}].'.format( self._lex.Annotate(here))) if len(call) != 1: raise resource_exceptions.ExpressionSyntaxError( 'Unknown transform function {0} [{1}].'.format( '.'.join(call), self._lex.Annotate(here))) func_name = call.pop() else: func_name = None self._lex.SkipSpace() if self._lex.IsCharacter(':'): self._ParseKeyAttributes(key, attribute) if func_name and attribute.label is None and not key: attribute.label = self._AngrySnakeCase([func_name]) self._AddKey(key, attribute)
def Key(self): """Parses a resource key from the expression. A resource key is a '.' separated list of names with optional [] slice or [NUMBER] array indices. A parsed key is encoded as an ordered list of tokens, where each token may be: KEY VALUE PARSED VALUE DESCRIPTION --------- ------------ ----------- name string A dotted name list element. [NUMBER] NUMBER An array index. [] None An array slice. For example, the key 'abc.def[123].ghi[].jkl' parses to this encoded list: ['abc', 'def', 123, 'ghi', None, 'jkl'] Raises: ExpressionSyntaxError: The expression has a syntax error. Returns: The parsed key which is a list of string, int and/or None elements. """ key = [] while not self.EndOfInput(): here = self.GetPosition() name = self.Token(_RESERVED_OPERATOR_CHARS, space=False) if name: is_not_function = not self.IsCharacter('(', peek=True, eoi_ok=True) if not key and is_not_function and name in self._aliases: key.extend(self._aliases[name]) else: key.append(name) elif not self.IsCharacter('[', peek=True): raise resource_exceptions.ExpressionSyntaxError( 'Non-empty key name expected [{0}].'.format(self.Annotate(here))) if self.EndOfInput(): break if self.IsCharacter(']'): raise resource_exceptions.ExpressionSyntaxError( 'Unmatched ] in key [{0}].'.format(self.Annotate(here))) if self.IsCharacter('['): # [] slice or [NUMBER] array index. index = self.Token(']', convert=True) self.IsCharacter(']') key.append(index) if not self.IsCharacter('.'): break if self.EndOfInput(): # Dangling '.' is not allowed. raise resource_exceptions.ExpressionSyntaxError( 'Non-empty key name expected [{0}].'.format(self.Annotate())) return key
def Key(self): """Parses a resource key from the expression. A resource key is a '.' separated list of names with optional [] slice or [NUMBER] array indices. Raises: ExpressionSyntaxError: The expression has a syntax error. Returns: The key which is a list of string, int and/or None elements. """ key = [] while not self.EndOfInput(): here = self.GetPosition() # Key names may not contain any operator-ish characters. This prevents # keys from clashing with expressions that may contain keys. The excluded # characters are defined here for consistency. name = self.Token('[].(){},:=!<>+*/%&|^~@#;?', space=False) if name: # The first key name could be an alias except functions are not aliased. if (not key and not self.IsCharacter('(', peek=True, eoi_ok=True) and name in self._aliases): key.extend(self._aliases[name]) else: key.append(name) elif not self.IsCharacter('[', peek=True): raise resource_exceptions.ExpressionSyntaxError( 'Non-empty key name expected [{0}].'.format( self.Annotate(here))) if self.EndOfInput(): break if self.IsCharacter(']'): raise resource_exceptions.ExpressionSyntaxError( 'Unmatched ] in key [{0}].'.format(self.Annotate(here))) if self.IsCharacter('['): # [] slice or [NUMBER] array index. index = self.Token(']', convert=True) self.IsCharacter(']') key.append(index) if not self.IsCharacter('.'): break if self.EndOfInput(): raise resource_exceptions.ExpressionSyntaxError( 'Non-empty key name expected [{0}].'.format( self.Annotate())) return key
def _CheckParenthesization(self, op): """Checks that AND and OR do not appear in the same parenthesis group. This method is called each time an AND or OR operator is seen in an expression. self._parenthesize[] keeps track of AND and OR operators seen in the nested parenthesis groups. ExpressionSyntaxError is raised if both AND and OR appear in the same parenthesis group. The top expression with no parentheses is considered a parenthesis group. The One-Platform list filter spec on which this parser is based has an unconventional OR higher than AND logical operator precedence. Allowing that in the Cloud SDK would lead to user confusion and many bug reports. To avoid that and still be true to the spec this method forces expressions containing AND and OR combinations to be fully parenthesized so that the desired precedence is explicit and unambiguous. Args: op: self._OP_AND or self._OP_OR. Raises: ExpressionSyntaxError: AND and OR appear in the same parenthesis group. """ self._parenthesize[-1].add(op) if len(self._parenthesize[-1]) > 1: raise resource_exceptions.ExpressionSyntaxError( 'Parenthesis grouping is required when AND and OR are ' 'are combined [{0}].'.format(self._lex.Annotate()))
def _ParseOperator(self): """Parses an operator token. All operators match the RE [_operator_char_1][_operator_char_2]. Invalid operators are 2 character sequences that are not valid operators and match the RE [_operator_char_1][_operator_char_1+_operator_char_2]. Raises: ExpressionSyntaxError: The operator spelling is malformed. Returns: The operator backend expression, None if the next token is not an operator. """ if not self._lex.SkipSpace(): return None here = self._lex.GetPosition() op = self._lex.IsCharacter(self._operator_char_1) if not op: return None if not self._lex.EndOfInput(): o2 = self._lex.IsCharacter(self._operator_char_1 + self._operator_char_2) if o2: op += o2 if op not in self._operator: raise resource_exceptions.ExpressionSyntaxError( 'Malformed operator [{0}].'.format(self._lex.Annotate(here))) self._lex.SkipSpace(token='Term operand') return self._operator[op]
def _ParseTransform(self, func_name, active, map_transform): """Parses a transform function call. Args: func_name: The transform function name. active: The transform active level or None if always active. map_transform: Apply the transform to each resource list item. Returns: A _Transform call item. The caller appends these to a list that is used to apply the transform functions. Raises: ExpressionSyntaxError: The expression has a syntax error. """ here = self._lex.GetPosition() if (not self._projection.symbols or func_name not in self._projection.symbols): raise resource_exceptions.ExpressionSyntaxError( 'Unknown transform function {0} [{1}].'.format( func_name, self._lex.Annotate(here))) kwargs = {} func = self._projection.symbols[func_name] if func == self._builtin_transforms.TransformFormat: args = [self._projection] + self._lex.Args() else: args = [] for arg in self._lex.Args(): name, sep, val = arg.partition('=') if sep: kwargs[name] = val else: args.append(arg) return self._Transform(func_name, func, active, map_transform, args, kwargs)
def SkipSpace(self, token=None, terminators=''): """Skips spaces in the expression string. Args: token: The expected next token description string, None if end of input is OK. This string is used in the exception message. It is not used to validate the type of the next token. terminators: Space characters in this string will not be skipped. Raises: ExpressionSyntaxError: End of input reached after skipping and a token is expected. Returns: True if the expression is not at end of input. """ while not self.EndOfInput(): c = self._expr[self._position] if not c.isspace() or c in terminators: return True self._position += 1 if token: raise resource_exceptions.ExpressionSyntaxError( '{0} expected [{1}].'.format(token, self.Annotate())) return False
def IsCharacter(self, characters, peek=False, eoi_ok=False): """Checks if the next character is in characters and consumes it if it is. Args: characters: A set of characters to check for. It may be a string, tuple, list or set. peek: Does not consume a matching character if True. eoi_ok: True if end of input is OK. Returns None if at end of input. Raises: ExpressionSyntaxError: End of input reached and peek and eoi_ok are False. Returns: The matching character or None if no match. """ if self.EndOfInput(): if peek or eoi_ok: return None raise resource_exceptions.ExpressionSyntaxError( 'More tokens expected [{0}].'.format(self.Annotate())) c = self._expr[self._position] if c not in characters: return None if not peek: self._position += 1 return c
def Args(self, convert=False): """Parses a ,-separated, )-terminated arg list. The initial '(' has already been consumed by the caller. The arg list may be empty. Otherwise the first ',' must be preceded by a non-empty argument, and every ',' must be followed by a non-empty argument. Args: convert: Converts unquoted numeric string args to numbers if True. Raises: ExpressionSyntaxError: The expression has a syntax error. Returns: [...]: The arg list. """ required = False # True if there must be another argument token. args = [] while True: here = self.GetPosition() arg = self.Token(',)', balance_parens=True, convert=convert) end = self.IsCharacter(')') if arg is not None: args.append(arg) elif required or not end: raise resource_exceptions.ExpressionSyntaxError( 'Argument expected [{0}].'.format(self.Annotate(here))) if end: break self.IsCharacter(',') required = True return args
def _ParseAttributes(self): """Parses a comma separated [no-]name[=value] projection attribute list. The initial '[' has already been consumed by the caller. Raises: ExpressionSyntaxError: The expression has a syntax error. """ while True: name = self._lex.Token('=,])', space=False) # type: str if name: if self._lex.IsCharacter('='): value = self._lex.Token(',])', space=False, convert=True) else: value = 1 self._projection.AddAttribute(name, value) if name.startswith('no-'): self._projection.DelAttribute(name[3:]) else: self._projection.DelAttribute('no-' + name) if self._lex.IsCharacter(']'): break if not self._lex.IsCharacter(','): raise resource_exceptions.ExpressionSyntaxError( 'Expected ] in attribute list [{0}].'.format( self._lex.Annotate()))
def Parse(self, expression, aliases=None): """Parses a resource list filter expression. This is a hand-rolled recursive descent parser based directly on the left-factorized BNF grammar in the file docstring. The parser is not thread safe. Each thread should use distinct _Parser objects. Args: expression: A resource list filter expression string. aliases: Resource key alias dictionary. Raises: ExpressionSyntaxError: The expression has a syntax error. Returns: tree: The backend expression tree. """ self._lex = resource_lex.Lexer(expression, aliases=aliases) tree = self._ParseExpr() if not self._lex.EndOfInput(): raise resource_exceptions.ExpressionSyntaxError( 'Unexpected tokens [{0}] in expression.'.format( self._lex.Annotate())) self._lex = None return tree or self._backend.ExprTRUE()
def Args(self, convert=False, separators=','): """Parses a separators-separated, )-terminated arg list. The initial '(' has already been consumed by the caller. The arg list may be empty. Otherwise the first ',' must be preceded by a non-empty argument, and every ',' must be followed by a non-empty argument. Args: convert: Converts unquoted numeric string args to numbers if True. separators: A string of argument separator characters. Raises: ExpressionSyntaxError: The expression has a syntax error. Returns: [...]: The arg list. """ required = False # True if there must be another argument token. args = [] terminators = separators + ')' # The closing ')' also terminates an arg. while True: here = self.GetPosition() arg = self.Token(terminators, balance_parens=True, convert=convert) end = self.IsCharacter(')') if end: sep = end else: sep = self.IsCharacter(separators, eoi_ok=True) if not sep: # This branch "cannot happen". End of input, separators and # terminators have already been handled. Retained to guard against # future ingenuity. here = self.GetPosition() raise resource_exceptions.ExpressionSyntaxError( 'Closing ) expected in argument list [{0}].'.format( self.Annotate(here))) if arg is not None: # No empty args with space separators. if arg or not sep.isspace(): args.append(arg) elif required or not end: raise resource_exceptions.ExpressionSyntaxError( 'Argument expected [{0}].'.format(self.Annotate(here))) if end: break required = not sep.isspace() return args
def _ParseTransform(self, func_name, active=0, map_transform=None, restriction=False): """Parses a transform function call. The cursor is positioned at the '(' after func_name. Args: func_name: The transform function name. active: The transform active level or None if always active. map_transform: Apply the transform to each resource list item this many times. restriction: Transform is a global restriction that does not have an obj arg. Returns: A _TransformCall object. The caller appends these to a list that is used to apply the transform functions. Raises: ExpressionSyntaxError: The expression has a syntax error. """ here = self.GetPosition() if func_name not in self._defaults.symbols: raise resource_exceptions.ExpressionSyntaxError( 'Unknown transform function {0} [{1}].'.format( func_name, self.Annotate(here))) func = self._defaults.symbols[func_name] args = [] kwargs = {} doc = getattr(func, 'func_doc', None) if doc and resource_projection_spec.PROJECTION_ARG_DOC in doc: # The second transform arg is the caller projection. args.append(self._defaults) if getattr(func, 'func_defaults', None): # Separate the args from the kwargs. for arg in self.Args(): name, sep, val = arg.partition('=') if sep: kwargs[name] = val else: args.append(arg) else: # No kwargs. args += self.Args() return _TransformCall(func_name, func, active=active, map_transform=map_transform, args=args, kwargs=kwargs, restriction=restriction)
def _ParseKey(self): """Parses a key with optional trailing transform. Raises: ExpressionSyntaxError: Missing term, unknown transform function. Returns: (key, transform, args): key: The key expression, None means transform is a global restriction. transform: A transform function if not None. If key is None then the transform is a global restriction. args: The transform actual args, None if transform is None or of there are no args. """ here = self._lex.GetPosition() key = self._lex.Key() if key and key[0] in self._LOGICAL: raise resource_exceptions.ExpressionSyntaxError( 'Term expected [{0}].'.format(self._lex.Annotate(here))) if not self._lex.IsCharacter('(', eoi_ok=True): return key, None, None # A global restriction function or key transform. args = self._lex.Args(convert=True) name = key.pop() if name not in self._symbols: # Symbol table lookup could be delayed until evaluation time, but # catching errors early on is good practice in the Cloud SDK. Otherwise: # - a filter expression applied client-side could fetch part or all of # a server resource before failing # - a filter expression applied server-side would add another # client-server failure case to handle # Doing the symbol table lookup here makes the return value of Compile() # a hermetic unit. This will make it easier to: # - apply optimizations based on function semantics # - apply client-side vs server-side expression splitting raise resource_exceptions.ExpressionSyntaxError( 'Unknown transform function [{0}].'.format( self._lex.Annotate(here))) return key, self._symbols[name], args
def Parse(self, expression=None): """Parse a projection expression. An empty projection is OK. Args: expression: The resource projection expression string. Raises: ExpressionSyntaxError: The expression has a syntax error. Returns: A ProjectionSpec for the expression. """ self._root = self._projection.GetRoot() if not self._root: self._root = self._Tree(self._Attribute(self._projection.DEFAULT)) self._projection.SetRoot(self._root) self._projection.SetEmpty( self._Tree(self._Attribute(self._projection.PROJECT))) if expression: self._lex = resource_lex.Lexer(expression, self._projection) defaults = False self.__key_attributes_only = False while self._lex.SkipSpace(): if self._lex.IsCharacter('('): if not self.__key_attributes_only: defaults = False self._projection.Defaults() self._ParseKeys() if self.__key_attributes_only: self.__key_attributes_only = False self._Reorder() elif self._lex.IsCharacter('['): self._ParseAttributes() elif self._lex.IsCharacter(':'): self.__key_attributes_only = True self.__key_order_offset = 0 else: here = self._lex.GetPosition() name = self._lex.Token(':([') # type: str if not name.isalpha(): raise resource_exceptions.ExpressionSyntaxError( 'Name expected [{0}].'.format( self._lex.Annotate(here))) self._projection.SetName(name) defaults = True self._lex = None if defaults: self._projection.Defaults() return self._projection
def _ParseKeyAttributes(self, key, attribute): """Parses one or more key attributes and adds them to attribute. The initial ':' has been consumed by the caller. Args: key: The parsed key name of the attributes. attribute: Add the parsed transform to this resource_projector._Attribute. Raises: ExpressionSyntaxError: The expression has a syntax error. """ while True: here = self._lex.GetPosition() name = self._lex.Token('=', space=False) if not self._lex.IsCharacter('=', eoi_ok=True): raise resource_exceptions.ExpressionSyntaxError( 'name=value expected [{0}].'.format( self._lex.Annotate(here))) value = self._lex.Token(':,)', space=False, convert=True) if name == 'alias': self._projection.AddAlias(value, key) elif name == 'align': if value not in resource_projection_spec.ALIGNMENTS: raise resource_exceptions.ExpressionSyntaxError( 'Unnown alignment [{0}].'.format( self._lex.Annotate(here))) attribute.align = value elif name == 'label': attribute.label = value elif name == 'sort': attribute.order = value else: raise resource_exceptions.ExpressionSyntaxError( 'Unnown key attribute [{0}].'.format( self._lex.Annotate(here))) if not self._lex.IsCharacter(':'): break
def _ReCompile(pattern, flags=0): """Returns a compiled RE pattern. Args: pattern: The RE pattern string. flags: Optional RE flags. Raises: ExpressionSyntaxError: RE pattern error. Returns: The compiled RE. """ try: return re.compile(pattern, flags) except re.error as e: raise resource_exceptions.ExpressionSyntaxError( 'Filter expression RE pattern [{}]: {}'.format(pattern, e))
def _ParseKeys(self): """Parses a comma separated list of keys. The initial '(' has already been consumed by the caller. Raises: ExpressionSyntaxError: The expression has a syntax error. """ if self._lex.IsCharacter(')'): # An empty projection is OK. return while True: self._ParseKey() if self._lex.IsCharacter(')'): break if not self._lex.IsCharacter(','): raise resource_exceptions.ExpressionSyntaxError( 'Expected ) in projection expression [{0}].'.format( self._lex.Annotate()))
def _ParseExpr(self, must=False): """Parses an expr term. Args: must: ExpressionSyntaxError if must is True and there is no expression. Raises: ExpressionSyntaxError: The expression has a syntax error. Returns: The new backend expression tree. """ tree = self._ParseAdjTerm() if tree: tree = self._ParseAdjTail(tree) elif must: raise resource_exceptions.ExpressionSyntaxError( 'Term expected [{0}].'.format(self._lex.Annotate())) return tree
def _AddPattern(self, pattern): """Adds a HAS match pattern to self._patterns. The pattern is a list of strings of length 1 or 2: [string]: The subject string must be equal to string ignoring case. [prefix, suffix]: The subject string must start with prefix and and with suffix ignoring case. Args: pattern: A string containing at most one * glob character. Raises: resource_exceptions.ExpressionSyntaxError if the pattern contains more than one * glob character. """ parts = unicode(pattern).lower().split('*') if len(parts) > 2: raise resource_exceptions.ExpressionSyntaxError( 'Zero or one * expected in : patterns.') self._patterns.append(parts)
def _ParseExpr(self, must=False): """Parses an expr term. Args: must: ExpressionSyntaxError if must is True and there is no expression. Raises: ExpressionSyntaxError: The expression has a syntax error. Returns: The new backend expression tree. """ tree = self._ParseAdjTerm() if tree: tree = self._ParseAdjTail(tree) # isinstance(self._backend, resource_expr_rewrite.Backend) => import loop elif must and not hasattr(self._backend, 'Rewrite'): raise resource_exceptions.ExpressionSyntaxError( 'Term expected [{0}].'.format(self._lex.Annotate())) return tree
def SkipSpace(self, token=None): """Skips spaces in the expression string. Args: token: The expected token description, None if end of input is OK. Raises: ExpressionSyntaxError: End of input reached after skipping. Returns: True if the expression is not at end of input. """ while not self.EndOfInput(): if not self._expr[self._position].isspace(): return True self._position += 1 if token: raise resource_exceptions.ExpressionSyntaxError( '{0} expected [{1}].'.format(token, self.Annotate())) return False
def _ParseTransform(self, func_name, active, map_transform): """Parses a transform function call. Args: func_name: The transform function name. active: The transform active level or None if always active. map_transform: Apply the transform to each resource list item. Returns: A _Transform call item. The caller appends these to a list that is used to apply the transform functions. Raises: ExpressionSyntaxError: The expression has a syntax error. """ here = self._lex.GetPosition() if (not self._projection.symbols or func_name not in self._projection.symbols): raise resource_exceptions.ExpressionSyntaxError( 'Unknown transform function {0} [{1}].'.format( func_name, self._lex.Annotate(here))) func = self._projection.symbols[func_name] args = [] kwargs = {} doc = func.func_doc if doc and resource_projection_spec.PROJECTION_ARG_DOC in doc: # The second transform arg is the parent ProjectionSpec. args.append(self._projection) if func.func_defaults: # Separate the args from the kwargs. for arg in self._lex.Args(): name, sep, val = arg.partition('=') if sep: kwargs[name] = val else: args.append(arg) else: # No kwargs. args += self._lex.Args() return self._Transform(func_name, func, active, map_transform, args, kwargs)
def _ParseKey(self): """Parses a key with optional trailing transforms. Raises: ExpressionSyntaxError: Missing term, unknown transform function. Returns: (key, transform): key: The key expression, None means transform is a global restriction. transform: A transform call object if not None. If key is None then the transform is a global restriction. """ here = self._lex.GetPosition() key = self._lex.Key() if key and key[0] in self._LOGICAL: raise resource_exceptions.ExpressionSyntaxError( 'Term expected [{0}].'.format(self._lex.Annotate(here))) if self._lex.IsCharacter('(', eoi_ok=True): func_name = key.pop() return key, self._lex.Transform(func_name, 0, restriction=not key) return key, None
def ParseKey(name): """Returns a parsed key for the dotted resource name string. This is an encapsulation of Lexer.Key(). That docstring has the input/output details for this function. Args: name: A resource name string that may contain dotted components and multi-value indices. Raises: ExpressionSyntaxError: If there are unexpected tokens after the key name. Returns: A parsed key for he dotted resource name string. """ lex = Lexer(name) key = lex.Key() if not lex.EndOfInput(): raise resource_exceptions.ExpressionSyntaxError( 'Unexpected tokens [{0}] in key.'.format(lex.Annotate())) return key
def _ParseOrTerm(self, must=False): """Parses an orterm term. Args: must: Raises ExpressionSyntaxError if must is True and there is no expression. Raises: ExpressionSyntaxError: Term expected in expression. Returns: The new backend expression tree. """ tree = self._ParseAndTerm() # In case the backend is a rewriter, the tree from AndTerm can be None if # only frontend-only fields are present in this part of the term. We still # need to parse the rest of the expression if it exists. if tree or self._backend.IsRewriter(): tree = self._ParseAndTail(tree) elif must: raise resource_exceptions.ExpressionSyntaxError( 'Term expected [{0}].'.format(self._lex.Annotate())) return tree
def _ParseKey(self): """Parses a key and optional attributes from the expression. Transform functions and key attributes are also handled here. Raises: ExpressionSyntaxError: The expression has a syntax error. Returns: The parsed key. """ key = self._lex.Key() here = self._lex.GetPosition() attribute = self._Attribute(self._projection.PROJECT) if self._lex.IsCharacter('(', eoi_ok=True): args = [] kwargs = {} for arg in self._lex.Args(): name, sep, val = arg.partition('=') if sep: kwargs[name] = val else: args.append(arg) fun = key.pop() if not self._projection.symbols or fun not in self._projection.symbols: raise resource_exceptions.ExpressionSyntaxError( 'Unknown filter function [{0}].'.format( self._lex.Annotate(here))) attribute.transform = self._Transform( fun, self._projection.symbols[fun], args, kwargs) else: fun = None if self._lex.IsCharacter(':'): self._ParseKeyAttributes(key, attribute) if fun and attribute.label is None and not key: attribute.label = self._AngrySnakeCase([fun]) self._AddKey(key, attribute)
def _ParseKeyAttributes(self, key, attribute): """Parses one or more key attributes and adds them to attribute. The initial ':' has been consumed by the caller. Args: key: The parsed key name of the attributes. attribute: Add the parsed transform to this resource_projector._Attribute. Raises: ExpressionSyntaxError: The expression has a syntax error. """ while True: name = self._lex.Token('=:,)', space=False) # type: str here = self._lex.GetPosition() if self._lex.IsCharacter('=', eoi_ok=True): boolean_value = False value = self._lex.Token(':,)', space=False, convert=True) else: boolean_value = True if name.startswith('no-'): name = name[3:] value = False else: value = True if name in self._BOOLEAN_ATTRIBUTES: if not boolean_value: # A Boolean attribute with a non-Boolean value. raise resource_exceptions.ExpressionSyntaxError( 'value not expected [{0}].'.format( self._lex.Annotate(here))) elif boolean_value and name not in self._OPTIONAL_BOOLEAN_ATTRIBUTES: # A non-Boolean attribute without a value or a no- prefix. raise resource_exceptions.ExpressionSyntaxError( 'value expected [{0}].'.format(self._lex.Annotate(here))) if name == 'alias': if not value: raise resource_exceptions.ExpressionSyntaxError( 'Cannot unset alias [{0}].'.format( self._lex.Annotate(here))) self._projection.AddAlias(value, key, attribute) elif name == 'align': if value not in resource_projection_spec.ALIGNMENTS: raise resource_exceptions.ExpressionSyntaxError( 'Unknown alignment [{0}].'.format( self._lex.Annotate(here))) attribute.align = value elif name == 'format': attribute.subformat = value or '' elif name == 'label': attribute.label = value or '' elif name == 'optional': attribute.optional = value elif name == 'reverse': attribute.reverse = value elif name == 'sort': attribute.order = value elif name == 'wrap': attribute.wrap = value else: raise resource_exceptions.ExpressionSyntaxError( 'Unknown key attribute [{0}].'.format( self._lex.Annotate(here))) if not self._lex.IsCharacter(':'): break