예제 #1
0
    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
예제 #2
0
    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)
예제 #4
0
  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
예제 #5
0
    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
예제 #6
0
    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()))
예제 #7
0
    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)
예제 #9
0
  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
예제 #10
0
  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
예제 #11
0
    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()))
예제 #13
0
    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()
예제 #14
0
  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)
예제 #16
0
    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
예제 #18
0
    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
예제 #19
0
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))
예제 #20
0
    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()))
예제 #21
0
    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
예제 #22
0
    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)
예제 #23
0
    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
예제 #24
0
  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
예제 #25
0
    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)
예제 #26
0
    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
예제 #27
0
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
예제 #29
0
    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