コード例 #1
0
def GetReferencedKeyNames(filter_string=None,
                          format_string=None,
                          printer=None,
                          defaults=None):
    """Returns the set of key names referenced by filter / format expressions.

  NOTICE: OnePlatform is forgiving on filter and format key reference name
  spelling.  Use resource_property.GetMatchingIndex() when verifying against
  resource dictionaries to handle camel and snake case spellings.

  Args:
    filter_string: The resource filter expression string.
    format_string: The resource format expression string.
    printer: The parsed format_string.
    defaults: The resource format and filter default projection.

  Raises:
    ValueError: If both format_string and printer are specified.

  Returns:
    The set of key names referenced by filter / format expressions.
  """
    keys = set()

    # Add the format key references.
    if printer:
        if format_string:
            raise ValueError('Cannot specify both format_string and printer.')
    elif format_string:
        printer = resource_printer.Printer(format_string, defaults=defaults)
        defaults = printer.column_attributes
    if printer:
        for col in printer.column_attributes.Columns():
            keys.add(resource_lex.GetKeyName(col.key, omit_indices=True))

    # Add the filter key references.
    if filter_string:
        expr = resource_filter.Compile(filter_string,
                                       defaults=defaults,
                                       backend=resource_keys_expr.Backend())
        for key in expr.Evaluate(None):
            keys.add(resource_lex.GetKeyName(key, omit_indices=True))

    return keys
コード例 #2
0
    def Term(self, key, op, operand, transform, args):
        """Returns the rewritten backend term expression.

    Args:
      key: The parsed key.
      op: The operator name.
      operand: The operand.
      transform: The transform object if a transform was specified.
      args: The transform args if a transform was specified.

    Raises:
      UnknownFieldError: If key is not supported on the frontend and backend.

    Returns:
      The rewritten backend term expression.
    """

        # No backend transforms.
        if transform or args:
            return self.Expr(None)

        # Determine the key type if possible.
        key_name = resource_lex.GetKeyName(key)
        if self.message:
            try:
                # key supported in the backend.
                key_type, key = resource_property.GetMessageFieldType(
                    key, self.message)
            except KeyError:
                # key not supported in the backend -- check the frontend if possible.
                if (self.frontend_fields is not None
                        and not resource_property.LookupField(
                            key, self.frontend_fields)):
                    raise resource_exceptions.UnknownFieldError(
                        'Unknown field [{}] in expression.'.format(key_name))
                return self.Expr(None)
            else:
                # GetMessageFieldType camelCase/lower_snake_case normalizes key.
                key_name = resource_lex.GetKeyName(key)
        else:
            # Not enough info to determine the key type.
            key_type = None
        return self.Expr(self.RewriteTerm(key_name, op, operand, key_type))
コード例 #3
0
 def __init__(self, *args, **kwargs):
     super(MultiPrinter, self).__init__(*args, **kwargs)
     self.columns = []
     for col in self.column_attributes.Columns():
         if not col.attribute.subformat:
             raise ProjectionFormatRequiredError(
                 '{key} requires format attribute.'.format(
                     key=resource_lex.GetKeyName(col.key)))
         self.columns.append(
             (col, Printer(col.attribute.subformat, out=self._out)))
コード例 #4
0
  def _LabelsByKey(self):
    """Returns an object that maps keys to projection labels.

    Returns:
      An object of keys to projection labels, None if all labels are empty.
    """
    labels = {}
    for c in self.column_attributes.Columns():
      key_name = resource_lex.GetKeyName(c.key)
      labels[key_name] = c.attribute.label
    return labels if any(labels) else None
コード例 #5
0
 def __init__(self, *args, **kwargs):
   super(MultiPrinter, self).__init__(*args, **kwargs)
   # pylint: disable=line-too-long
   self.columns = []  # type: typing.List[typing.Tuple[resource_projection_spec.ProjectionSpec._Column, resource_printer_base.ResourcePrinter]]
   # pylint: disable=line-too-long
   for col in self.column_attributes.Columns():
     if not col.attribute.subformat:
       raise ProjectionFormatRequiredError(
           '{key} requires format attribute.'.format(
               key=resource_lex.GetKeyName(col.key)))
     self.columns.append(
         (col, Printer(col.attribute.subformat, out=self._out)))
コード例 #6
0
  def GetReferencedKeyNames(self):
    """Returns the list of key names referenced by the command."""

    keys = set()

    # Add the format key references.
    self._InitPrinter()
    if self._printer:
      for col in self._printer.column_attributes.Columns():
        keys.add(resource_lex.GetKeyName(col.key))

    # Add the filter key references.
    filter_expression = self._GetFlag('filter')
    if filter_expression:
      expr = resource_filter.Compile(filter_expression,
                                     defaults=self._defaults,
                                     backend=resource_keys_expr.Backend())
      for key in expr.Evaluate(None):
        keys.add(resource_lex.GetKeyName(key))

    return keys
コード例 #7
0
    def GetFormat(self):
        """Determines the display format.

    Returns:
      format: The display format string.
    """
        default_fmt = self._GetDefaultFormat()
        fmt = self._GetExplicitFormat()

        if not fmt:
            # No explicit format.
            if self._GetFlag('uri'):
                return 'value(.)'
            # Use the default format.
            self._default_format_used = True
            fmt = default_fmt
        elif default_fmt:
            # Compose the default and explicit formats.
            #
            # The rightmost format in fmt takes precedence. Appending gives higher
            # precendence to the explicit format over the default format. Appending
            # also makes projection attributes from preceding projections available
            # to subsequent projections. For example, a user specified explicit
            # --format expression can use the column heading names instead of resource
            # key names:
            #
            #   table(foo.bar:label=NICE, state:STATUS) table(NICE, STATUS)
            #
            # or a --filter='s:DONE' expression can use alias named defined by the
            # defaults:
            #
            #   table(foo.bar:alias=b, state:alias=s)
            #
            fmt = default_fmt + ' ' + fmt

        if not fmt:
            return fmt
        sort_keys = self._GetSortKeys()
        if not sort_keys:
            return fmt

        # :(...) adds key only attributes that don't affect the projection.
        orders = []
        for order, (key, reverse) in enumerate(sort_keys, start=1):
            attr = ':reverse' if reverse else ''
            orders.append('{name}:sort={order}{attr}'.format(
                name=resource_lex.GetKeyName(key), order=order, attr=attr))
        fmt += ':({orders})'.format(orders=','.join(orders))
        return fmt
コード例 #8
0
    def __init__(self, backend, key, operand, transform, args):
        """Initializer.

    Args:
      backend: The parser backend object.
      key: Resource object key (list of str, int and/or None values).
      operand: The term ExprOperand operand.
      transform: Optional key value transform function.
      args: Optional list of transform actual args.
    """
        super(_ExprOperator, self).__init__(backend)
        self._supported = (not transform and not args
                           and self.backend.supported_key(key)
                           and self.backend.supported_operand(operand.value))
        if self._supported:
            self._key = resource_lex.GetKeyName(key)
            if '"' in self._key and not self.backend.supported_op('"'):
                self._supported = False
            self._operand = operand
コード例 #9
0
def _MatchOneWordInText(backend, key, op, warned_attribute, value, pattern):
    """Returns True if value word matches pattern.

  Args:
    backend: The parser backend object.
    key: The parsed expression key.
    op: The expression operator string.
    warned_attribute: Deprecation warning Boolean attribute name.
    value: The value to be matched by pattern.
    pattern: An (operand, standard_regex, deprecated_regex) tuple.

  Raises:
    ValueError: To catch codebase reliance on deprecated usage.

  Returns:
    True if pattern matches value.

  Examples:
    See surface/topic/filters.py for a table of example matches.
  """
    operand, standard_regex, deprecated_regex = pattern
    if isinstance(value, float):
        try:
            if value == float(operand):
                return True
        except ValueError:
            pass
        if value == 0 and operand.lower() == 'false':
            return True
        if value == 1 and operand.lower() == 'true':
            return True
        # Stringize float with trailing .0's stripped.
        text = re.sub(r'\.0*$', '', _Stringize(value))  # pytype: disable=wrong-arg-types
    elif value == operand:
        return True
    elif value is None:
        if operand in ('', None):
            return True
        if operand == '*' and op == ':':
            return False
        text = 'null'
    else:
        text = NormalizeForSearch(value, html=True)

    # TODO(b/64595527): OnePlatform : and = operator deprecation train.
    # Phase 1: return deprecated_matched and warn if different from matched.
    # Phase 2: return matched and warn if different from deprecated_matched.
    # Phase 3: drop deprecated logic.
    matched = bool(standard_regex.search(text))
    if not deprecated_regex:
        return matched

    deprecated_matched = bool(deprecated_regex.search(text))
    if (matched != deprecated_matched and warned_attribute
            and not getattr(backend, warned_attribute, False)):
        setattr(backend, warned_attribute, True)
        old_match = 'matches' if deprecated_matched else 'does not match'
        new_match = 'will match' if matched else 'will not match'
        log.warning(
            '--filter : operator evaluation is changing for '
            'consistency across Google APIs.  {key}{op}{operand} currently '
            '{old_match} but {new_match} in the near future.  Run '
            '`gcloud topic filters` for details.'.format(
                key=resource_lex.GetKeyName(key),
                op=op,
                operand=operand,
                old_match=old_match,
                new_match=new_match))
    return deprecated_matched
コード例 #10
0
 def Term(self, key, op, operand, transform, args):
     if transform or args:
         return self.Expr(None)
     return self.Expr(
         self.RewriteTerm(resource_lex.GetKeyName(key), op, operand))
コード例 #11
0
  def _ParseTerm(self, must=False):
    """Parses a [-]<key> <operator> <operand> term.

    Args:
      must: Raises ExpressionSyntaxError if must is True and there is no
        expression.

    Raises:
      ExpressionSyntaxError: The expression has a syntax error.

    Returns:
      The new backend expression tree.
    """
    here = self._lex.GetPosition()
    if not self._lex.SkipSpace():
      if must:
        raise resource_exceptions.ExpressionSyntaxError(
            'Term expected [{0}].'.format(self._lex.Annotate(here)))
      return None

    # Check for end of (...) term.
    if self._lex.IsCharacter(')', peek=True):
      # The caller will determine if this ends (...) or is a syntax error.
      return None

    # Check for start of (...) term.
    if self._lex.IsCharacter('('):
      self._parenthesize.append(set())
      tree = self._ParseExpr()
      # Either the next char is ')' or we hit an end of expression syntax error.
      self._lex.IsCharacter(')')
      self._parenthesize.pop()
      return tree

    # Check for term inversion.
    invert = self._lex.IsCharacter('-')

    # Parse the key.
    here = self._lex.GetPosition()
    syntax_error = None
    try:
      key, transform = self._ParseKey()
      restriction = None
    except resource_exceptions.ExpressionSyntaxError as syntax_error:
      # An invalid key could be a global restriction.
      self._lex.SetPosition(here)
      restriction = self._lex.Token(resource_lex.OPERATOR_CHARS, space=False)
      transform = None
      key = None

    # Parse the operator.
    here = self._lex.GetPosition()
    operator = self._ParseOperator()
    if not operator:
      if transform and not key:
        # A global restriction function.
        tree = self._backend.ExprGlobal(transform)
      elif transform:
        # key.transform() must be followed by an operator.
        raise resource_exceptions.ExpressionSyntaxError(
            'Operator expected [{0}].'.format(self._lex.Annotate(here)))
      elif restriction in ['AND', 'OR']:
        raise resource_exceptions.ExpressionSyntaxError(
            'Term expected [{0}].'.format(self._lex.Annotate()))
      else:
        # A global restriction on key.
        if not restriction:
          restriction = resource_lex.GetKeyName(key, quote=False)
        pattern = re.compile(re.escape(restriction), re.IGNORECASE)
        name = resource_projection_spec.GLOBAL_RESTRICTION_NAME
        tree = self._backend.ExprGlobal(
            resource_lex.MakeTransform(
                name,
                self._defaults.symbols.get(
                    name,
                    resource_property.EvaluateGlobalRestriction),
                args=[restriction, pattern]))
      if invert:
        tree = self._backend.ExprNOT(tree)
      return tree
    elif syntax_error:
      raise syntax_error  # pylint: disable=raising-bad-type

    # Parse the operand.
    self._lex.SkipSpace(token='Operand')
    here = self._lex.GetPosition()
    if any([self._lex.IsString(x) for x in self._LOGICAL]):
      raise resource_exceptions.ExpressionSyntaxError(
          'Logical operator not expected [{0}].'.format(
              self._lex.Annotate(here)))
    # The '=' and ':' operators accept '('...')' list operands.
    if (operator in (self._backend.ExprEQ, self._backend.ExprHAS) and
        self._lex.IsCharacter('(')):
      # List valued operand.
      operand = [arg for arg in self._lex.Args(separators=' \t\n,')
                 if arg not in self._LOGICAL]
    else:
      operand = self._lex.Token('()')
    if operand is None:
      raise resource_exceptions.ExpressionSyntaxError(
          'Term operand expected [{0}].'.format(self._lex.Annotate(here)))

    # Make an Expr node for the term.
    tree = operator(key=key, operand=self._backend.ExprOperand(operand),
                    transform=transform)
    if invert:
      tree = self._backend.ExprNOT(tree)
    return tree
コード例 #12
0
 def RunSubTest(self, key):
   return resource_lex.GetKeyName(key)
コード例 #13
0
def _MatchOneWordInText(backend, key, op, warned_attribute, value, pattern):
    """Returns True if value word matches pattern.

  Args:
    backend: The parser backend object.
    key: The parsed expression key.
    op: The expression operator string.
    warned_attribute: Deprecation warning Boolean attribute name.
    value: The value to be matched by pattern.
    pattern: An (operand, standard_regex, deprecated_regex) tuple.

  Raises:
    ValueError: To catch codebase reliance on deprecated usage.

  Returns:
    True if pattern matches value.

  Examples:
    See surface/topic/filters.py for a table of example matches.
  """
    operand, standard_regex, deprecated_regex = pattern
    if isinstance(value, float):
        try:
            if value == float(operand):
                return True
        except ValueError:
            pass
        if value == 0 and operand.lower() == 'false':
            return True
        if value == 1 and operand.lower() == 'true':
            return True
        # Stringize float with trailing .0's stripped.
        text = re.sub(r'\.0*$', '', _Stringize(value))
    elif value == operand:
        return True
    elif value is None:
        if operand in ('', None):
            return True
        if operand == '*' and op == ':':
            return False
        text = 'null'
    else:
        text = NormalizeForSearch(value, html=True)

    # Phase 1: return deprecated_matched and warn if different from matched.
    # Phase 2: return matched and warn if different from deprecated_matched.
    # Phase 3: drop deprecated logic.

    matched = bool(standard_regex.search(text))
    if not deprecated_regex:
        return matched

    deprecated_matched = bool(deprecated_regex.search(text))

    # For compute's region and zone keys we also want to exact match segment(-1).
    # We do this because exact match filter fields for zone and region are used to
    # determine which zonal/regional endpoints to scope the request to.

    if len(key) == 1 and key[0] in ['zone', 'region']:
        deprecated_matched |= bool(deprecated_regex.search(
            text.split('/')[-1]))

    if (matched != deprecated_matched and warned_attribute
            and not getattr(backend, warned_attribute, False)):
        setattr(backend, warned_attribute, True)
        old_match = 'matches' if deprecated_matched else 'does not match'
        new_match = 'will match' if matched else 'will not match'
        log.warning(
            '--filter : operator evaluation is changing for '
            'consistency across Google APIs.  {key}{op}{operand} currently '
            '{old_match} but {new_match} in the near future.  Run '
            '`gcloud topic filters` for details.'.format(
                key=resource_lex.GetKeyName(key),
                op=op,
                operand=operand,
                old_match=old_match,
                new_match=new_match))
    return deprecated_matched