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
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))
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)))
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
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)))
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
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
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
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
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))
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
def RunSubTest(self, key): return resource_lex.GetKeyName(key)
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