o
    |                     @   s   d Z ddlmZ ddlmZ ddlmZ ddlZddlZddlmZ ddlm	Z	 ddlm
Z
 dd	lmZ ddlZdd
lmZ ddlmZ dZed ZG dd deZG dd deZdddZG dd deZdd ZdddZdS )a  Resource expression lexer.

This class is used to parse resource keys, quoted tokens, and operator strings
and characters from resource filter and projection expression strings. Tokens
are defined by isspace() and caller specified per-token terminator characters.
" or ' quotes are supported, with these literal escapes: \\ => \, \' => ',
\" => ", and \<any-other-character> => \<any-other-character>.

Typical resource usage:

  # Initialize a lexer with the expression string.
  lex = resource_lex.Lexer(expression_string)
  # isspace() separated tokens. lex.SkipSpace() returns False at end of input.
  while lex.SkipSpace():
    # Save the expression string position for syntax error annotation.
    here = lex.GetPosition()
    # The next token must be a key.
    key = lex.Key()
    if not key:
      if lex.EndOfInput():
        # End of input is OK here.
        break
      # There were some characters in the input that did not form a valid key.
      raise resource_exceptions.ExpressionSyntaxError(
          'key expected [{0}].'.format(lex.Annotate(here)))
    # Check if the key is a function call.
    if lex.IsCharacter('('):
      # Collect the actual args and convert numeric args to float or int.
      args = lex.Args(convert=True)
    else:
      args = None
    # Skip an isspace() characters. End of input will fail with an
    # 'Operator expected [...]' resource_exceptions.ExpressionSyntaxError.
    lex.SkipSpace(token='Operator')
    # The next token must be one of these operators ...
    operator = lex.IsCharacter('+-*/&|')
    if not operator:
      # ... one of the operator names.
      if lex.IsString('AND'):
        operator = '&'
      elif lex.IsString('OR'):
        operator = '|'
      else:
        raise resource_exceptions.ExpressionSyntaxError(
            'Operator expected [{0}].'.format(lex.Annotate()))
    # The next token must be an operand. Convert to float or int if possible.
    # lex.Token() by default eats leading isspace().
    operand = lex.Token(convert=True)
    if not operand:
      raise resource_exceptions.ExpressionSyntaxErrorSyntaxError(
          'Operand expected [{0}].'.format(lex.Annotate()))
    # Process the key, args, operator and operand.
    Process(key, args, operator, operand)
    )absolute_import)division)unicode_literalsN)resource_exceptions)resource_projection_spec)resource_property)resource_transform)map)rangez:=!<>~()z[].{},+*/%&|^#;?c                   @   s.   e Zd ZdZ		d
ddZdd Zdd	 ZdS )_TransformCalla  A key transform function call with actual args.

  Attributes:
    name: The transform function name.
    func: The transform function.
    active: The parent projection active level. A transform is active if
      transform.active is None or equal to the caller active level.
    map_transform: If r is a list then apply the transform to each list item
      up to map_transform times. map_transform>1 handles nested lists.
    args: List of function call actual arg strings.
    kwargs: List of function call actual keyword arg strings.
  r   Nc                 C   s0   || _ || _|| _|| _|pg | _|pi | _d S N)namefuncactivemap_transformargskwargs)selfr   r   r   r   r   r    r   F/tmp/google-cloud-sdk/lib/googlecloudsdk/core/resource/resource_lex.py__init__n   s   
z_TransformCall.__init__c                 C   sR   dd | j D }| jdkrd| j}n
| jdkrd}nd}d|| jd|S )	Nc                 S   s    g | ]}t |tjrd n|qS )z<projecton>)
isinstancer   ProjectionSpec).0argr   r   r   
<listcomp>x   s    z*_TransformCall.__str__.<locals>.<listcomp>   z	map({0}).zmap(). z{0}{1}({2}),)r   r   formatr   join)r   r   prefixr   r   r   __str__w   s   

z_TransformCall.__str__c                 C   s
   t  | S r   )copy)r   memor   r   r   __deepcopy__      
z_TransformCall.__deepcopy__)r   r   NN)__name__
__module____qualname____doc__r   r"   r%   r   r   r   r   r   `   s    
	r   c                   @   s~   e Zd ZdZdd Zdd Zedd Zedd	 Zed
d Z	edd Z
edd Zdd Zdd Zdd ZdddZdS )
_TransformzAn object that contains an ordered list of _TransformCall objects.

  Attributes:
    _conditional: The resource_filter expression string for the if() transform.
    _transforms: The list of _TransformCall objects.
  c                 C   s   d | _ g | _d S r   )_conditional_transformsr   r   r   r   r      s   
z_Transform.__init__c                 C   s   d dtt| jS )Nz[{0}].)r   r    r	   strr-   r.   r   r   r   r"      s   z_Transform.__str__c                 C      | j r	| j d jS dS )z4The transform active level or None if always active.r   Nr-   r   r.   r   r   r   r         z_Transform.activec                 C      | j S )z1The if() transform conditional expression string.r,   r.   r   r   r   conditional   s   z_Transform.conditionalc                 C   s4   t | jdks| jd jtjkrdS | jd jd S )a  The global restriction string or None if not a global restriction.

    Terms in a fiter expression are sometimes called "restrictions" because
    they restrict or constrain values.  A regular restriction is of the form
    "attribute<op>operand".  A "global restriction" is a term that has no
    attribute or <op>.  It is a bare string that is matched against every
    attribute value in the resource object being filtered.  The global
    restriction matches if any of those values contains the string using case
    insensitive string match.

    Returns:
      The global restriction string or None if not a global restriction.
    r   r   N)lenr-   r   r   GLOBAL_RESTRICTION_NAMEr   r.   r   r   r   global_restriction   s   
z_Transform.global_restrictionc                 C   r1   )zThe name of the last transform.r   )r-   r   r.   r   r   r   r      r3   z_Transform.namec                 C   s   | j r| j d jd S dS )z"The first global restriction term.r   r   )r-   r   r.   r   r   r   term   s   z_Transform.termc                 C   s   | j o	| jd|fv S )z=Returns True if the Transform active level is None or active.Nr2   )r   r   r   r   r   IsActive   s   z_Transform.IsActivec                 C   s   | j | dS )zAdds a transform to the list.N)r-   append)r   	transformr   r   r   Add   s   z_Transform.Addc                 C   
   || _ dS )z'Sets the conditional expression string.Nr5   )r   exprr   r   r   SetConditional   r&   z_Transform.SetConditionalNc              
   C   s   | j D ]h}|jdkr|dur|}|jrXt|rX|}t|jd D ]}g }z|D ]}|| q(W n
 ty;   Y  nw |}q!g }|D ]}||j	|g|j
R i |j qCq|s]|jsk|j	|g|j
R i |j}q|S )zEApply the list of transforms to obj and return the transformed value.uriNr   )r-   r   r   r   
IsListLiker
   extend	TypeErrorr=   r   r   r   )r   objoriginal_objectr>   items_nesteditemr   r   r   Evaluate   s.   
$
z_Transform.Evaluater   )r'   r(   r)   r*   r   r"   propertyr   r6   r9   r   r;   r<   r?   rB   rM   r   r   r   r   r+      s$    




r+   c                 C   s    t  }|t| |||d |S )a  Returns a transform call object for func(*args, **kwargs).

  Args:
    func_name: The function name.
    func: The function object.
    args: The actual call args.
    kwargs: The actual call kwargs.

  Returns:
    A transform call object for func(obj, *args, **kwargs).
  )r   r   )r+   r?   r   )	func_namer   r   r   callsr   r   r   MakeTransform   s   rQ   c                   @   s   e Zd ZdZdZdZd*ddZd*ddZd	d
 Zdd Z	d*ddZ
d+ddZd,ddZd-ddZ		d.ddZd/ddZdd Zdd  Zd!d" Zd#d$ Zd0d&d'Zd1d(d)ZdS )2Lexera[  Resource expression lexer.

  This lexer handles simple and compound tokens. Compound tokens returned by
  Key() and Args() below are not strictly lexical items (i.e., they are parsed
  against simple grammars), but treating them as tokens here simplifies the
  resource expression parsers that use this class and avoids code replication.

  Attributes:
    _ESCAPE: The quote escape character.
    _QUOTES: The quote characters.
    _defaults: ProjectionSpec object for aliases and symbols defaults.
    _expr: The expression string.
    _position: The index of the next character in _expr to parse.
  \z'"Nc                 C   s"   |pd| _ d| _|pt | _dS )zInitializes a resource lexer.

    Args:
      expression: The expression string.
      defaults: ProjectionSpec object for aliases and symbols defaults.
    r   r   N)_expr	_positionr   r   	_defaults)r   
expressiondefaultsr   r   r   r     s   
zLexer.__init__c                 C   s   |du r| j }|t| jkS )zChecks if the current expression string position is at the end of input.

    Args:
      position: Checks position instead of the current expression position.

    Returns:
      True if the expression string position is at the end of input.
    N)rU   r7   rT   r   positionr   r   r   
EndOfInput  s   	zLexer.EndOfInputc                 C   r4   )zbReturns the current expression position.

    Returns:
      The current expression position.
    rU   r.   r   r   r   GetPosition&  s   zLexer.GetPositionc                 C   r@   )zSets the current expression position.

    Args:
      position: Sets the current position to position. Position should be 0 or a
        previous value returned by GetPosition().
    Nr\   rY   r   r   r   SetPosition.  s   
zLexer.SetPositionc                 C   s~   |dur|n| j }d}|dkr| j|d   sd| }|t| jk r.| j|  s.|d7 }d| jd| || j|d S )a*  Returns the expression string annotated for syntax error messages.

    The current position is marked by '*HERE*' for visual effect.

    Args:
      position: Uses position instead of the current expression position.

    Returns:
      The expression string with current position annotated.
    Nz*HERE*r   r    z	{0}{1}{2})rU   rT   isspacer7   r   )r   rZ   herecursorr   r   r   Annotate7  s   "zLexer.Annotater   c                 C   s\   |   s| j| j }| r||v rdS |  jd7  _|   r|r,td||  dS )a  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.
    Tr   z{0} expected [{1}].F)r[   rT   rU   r`   r   ExpressionSyntaxErrorr   rc   )r   tokenterminatorscr   r   r   	SkipSpaceJ  s   zLexer.SkipSpaceFc                 C   sV   |   r|s|r
dS td|  | j| j }||vr dS |s)|  jd7  _|S )a  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.
    NzMore tokens expected [{0}].r   )r[   r   rd   r   rc   rT   rU   )r   
characterspeekeoi_okrg   r   r   r   IsCharacterd  s   zLexer.IsCharacterc                 C   st   |   sdS |  }| j|d |sdS |t|7 }| |s/| j|  s/| j| dkr8|s6| | dS dS )a4  Skips leading space and checks if the next token is name.

    One of space, '(', or end of input terminates the next token.

    Args:
      name: The token name to check.
      peek: Does not consume the string on match if True.

    Returns:
      True if the next space or ( separated token is name.
    FN(T)rh   r]   rT   
startswithr7   r[   r`   r^   )r   r   rj   ir   r   r   IsString  s   &
zLexer.IsStringTc                 C   s4  d}d}d}d}|   }	| |	s| j|	 }
|
| jkrO| |	d sO| j|	d  }
|du r/g }|
| jkrE|
|krE|s?|
| jvrE|| j ||
 |	d7 }	nr|
|krVd}nk|sh|
| jv rh|
}d}|du rgg }nY|ss|
 rs|du rsnN|s|r|
dv r|
dkr|d7 }n|
|v r|sn?|d8 }|du rg }||
 n&|s|s|
|v rn&|s|
 r|dur|r|du rg }||
 n|durn	|	d7 }	| |	r|rtd	|| 
 | |	 |r| j|d	 |durd
|}|r|r|szt|W S  ty   zt|W  Y S  ty   Y Y |S w w |S )a  Parses a possibly quoted token from the current expression position.

    The quote characters are in _QUOTES. The _ESCAPE character can prefix
    an _ESCAPE or _QUOTE character to treat it as a normal character. If
    _ESCAPE is at end of input, or is followed by any other character, then it
    is treated as a normal character.

    Quotes may be adjacent ("foo"" & ""bar" => "foo & bar") and they may appear
    mid token (foo" & "bar => "foo & bar").

    Args:
      terminators: A set of characters that terminate the token. isspace()
        characters always terminate the token. It may be a string, tuple, list
        or set. Terminator characters are not consumed.
      balance_parens: True if (...) must be balanced.
      space: True if space characters should be skipped after the token. Space
        characters are always skipped before the token.
      convert: Converts unquoted numeric string tokens to numbers if True.

    Raises:
      ExpressionSyntaxError: The expression has a syntax error.

    Returns:
      None if there is no token, the token string if convert is False or the
      token is quoted, otherwise the converted float / int / string value of
      the token.
    NFr   r   Tz()rm   zUnterminated [{0}] quote [{1}].)rf   r   )r]   r[   rT   _ESCAPE_QUOTESr=   r`   r   rd   r   rc   r^   rh   r    int
ValueErrorfloat)r   rf   balance_parensspaceconvertquotequotedre   paren_countro   rg   r   r   r   Token  s   






.


zLexer.Tokenr   c           
      C   s   d}g }|d }	 |   }| j|d|d}| d}|r|}	n| j|dd}	|	s7|   }td| ||durG|sA|	 sF|| n|sK|sVtd| ||r[	 |S |	  }q	)	a  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.
    F)T)rv   rx   rk   z*Closing ) expected in argument list [{0}].NzArgument expected [{0}].)	r]   r|   rl   r   rd   r   rc   r`   r=   )
r   rx   
separatorsrequiredr   rf   ra   r   endsepr   r   r   Args  s<   


z
Lexer.Argsc                 C   sf   d}|  dr|d7 }|  ds|sdS d| jd| j|  || j| jd | _|  j|8  _dS )z-Checks for N '*' chars shorthand for .map(N).r   *r   Nz{}map({}).{})rl   r   rT   rU   )r   	map_levelr   r   r   _CheckMapShorthand+  s   

zLexer._CheckMapShorthandc                 C   s  g }d}|   s|   |  }| jtdd}|r?| jdddd}|s9|s9|| jjv r9| jj| \}}|| n8|	| n2| jdddsq|sf| d	rf| jd	dddsf|   sa| jtdddrf	 ||fS t
d
| ||   rz	 ||fS | drt
d| || jdddr| jddd}| d |	| | jddds| jd	dds	 ||fS |   rt
d
|  |   r||fS )a  Parses a resource key from the expression.

    A resource key is a '.' separated list of names with optional [] slice or
    [NUMBER] array indices. Names containing _RESERVED_OPERATOR_CHARS must be
    quoted. For example, "k.e.y".value has two name components, 'k.e.y' and
    'value'.

    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:
      ExpressionKeyError: The expression has a key syntax error.

    Returns:
      (key, attribute) The parsed key and attribute. attribute is the alias
        attribute if there was an alias expansion, None otherwise.
    NF)rw   rm   T)rj   rk   [)rj   r/   z"Non-empty key name expected [{0}].]zUnmatched ] in key [{0}].r~   )rx   )r[   r   r]   r|   _RESERVED_OPERATOR_CHARSrl   rV   aliasesrE   r=   r   rd   r   rc   )r   key	attributera   r   is_functionkindexr   r   r   KeyWithAttribute;  sd   


%zLexer.KeyWithAttributec                 C   s   |   \}}|S )zEParses a resource key from the expression and returns the parsed key.)r   )r   r   rJ   r   r   r   Key  s   z	Lexer.Keyc                    s   g  |D ]T}t |}|dstdg }| D ]8}d|v r+|dd\}}d}nd|v r@|dd\}}	t |	 }d}n
t | }d}d}||||f q | q fdd}
|
S )	a  Parses the synthesize() transform args and returns a new transform.

    The args are a list of tuples. Each tuple is a schema that defines the
    synthesis of one resource list item. Each schema item is an attribute
    that defines the synthesis of one synthesized_resource attribute from
    an original_resource attribute.

    There are three kinds of attributes:

      name:literal
        The value for the name attribute in the synthesized resource is the
        literal value.
      name=key
        The value for the name attribute in the synthesized_resource is the
        value of key in the original_resource.
      key:
        All the attributes of the value of key in the original_resource are
        added to the attributes in the synthesized_resource.

    Args:
      args: The original synthesize transform args.

    Returns:
      A synthesize transform function that uses the schema from the parsed
      args.

    Example:
      This returns a list of two resource items:
        synthesize((name:up, upInfo), (name:down, downInfo))
      If upInfo and downInfo serialize to
        {"foo": 1, "bar": "yes"}
      and
        {"foo": 0, "bar": "no"}
      then the synthesized resource list is
        [{"name": "up", "foo": 1, "bar": "yes"},
        {"name": "down", "foo": 0, "bar": "no"}]
      which could be displayed by a nested table using
        synthesize(...):format="table(name, foo, bar)"
    rm   z-(...) args expected in synthesize() transform:r   N=c           	         sl   g } D ]/}i }|D ]#}|\}}}|rt | |dn|}|r#|||< q
t|tr-|| q
|| q|S )zSynthesize a new resource list from the original resource r.

      Args:
        r: The original resource.

      Returns:
        The synthesized resource list.
      N)r   Getr   dictupdater=   )	rsynthesized_resource_listschemasynthesized_resourceattrr   r   literalvalueschemasr   r   _Synthesize  s   	



z+Lexer._ParseSynthesize.<locals>._Synthesize)rR   rl   r   rd   r   splitr   r=   )r   r   r   lexr   r   r   r   r   r   r   r   r   r   _ParseSynthesize  s.   (
zLexer._ParseSynthesizer   c                 C   s   |   }| jj|}|std|| |g }i }t|dd}|r0t	j
|v r0|| j t|ddrR|  D ]}	|	d\}
}}|rK|||
< q:||	 q:n||  7 }t||||||dS )a  Parses a transform function call.

    The initial '(' has already been consumed by the caller.

    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.

    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.
    %Unknown transform function {0} [{1}].r*   N__defaults__r   )r   r   r   r   )r]   rV   symbolsgetr   UnknownTransformErrorr   rc   getattrr   PROJECTION_ARG_DOCr=   r   	partitionr   )r   rO   r   r   ra   r   r   r   docr   r   r   valr   r   r   _ParseTransform  s0   

zLexer._ParseTransformc                 C   s^  |   }t }d}	 | j|||d}|jtjkrd}d}nV|jtjkr2|jr-t|jd nd}d}nA|jtj	krSt
|jdkrJtd| |||jd  n |jtjkrl| |j|_g |_i |_|| nd}|| | jddds}	 |S |  }|   }| d	std
| |t
|dkrtdd|| || }q
)am  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.
    r   T)r   r   Nr   z-Conditional filter expression expected [{0}].r/   r~   rm   z"Transform function expected [{0}].r   )r]   r+   r   r   r   TransformAlwaysTransformMapr   rs   TransformIfr7   r   rd   r   rc   rB   TransformSynthesizer   r   r?   rl   r   r   r    pop)r   rO   r   ra   rP   r   r>   callr   r   r   	Transform  s^   

zLexer.Transformr   )Nr   )FF)F)r   FTF)Fr   )r   N)r   )r'   r(   r)   r*   rq   rr   r   r[   r]   r^   rc   rh   rl   rp   r|   r   r   r   r   r   r   r   r   r   r   r   rR      s,    


	




d0D
V,rR   c                 C   s0   t | }| }| std| |S )a  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.
  zUnexpected tokens [{0}] in key.)rR   r   r[   r   rd   r   rc   )r   r   r   r   r   r   ParseKey>  s   r   TFc                 C   s   g }| D ]Q}|du r|rqd}|r|d  |7  < qn5t |tjr6|r$qdj|d}|r5|d  |7  < qn|rPtd|rP|dd}|d	d
}dj|d}|| q|r]d|S dS )a  Returns the string representation for a parsed key.

  This is the inverse of Lexer.Key(). That docstring has the input/output
  details for this function.

  Args:
    key: A parsed key, which is an ordered list of key names/indices. Each
      element in the list may be one of:
        str - A resource property name. This could be a class attribute name or
          a dict index.
        int - A list index. Selects one member is the list. Negative indices
          count from the end of the list, starting with -1 for the last element
          in the list. An out of bounds index is not an error; it produces the
          value None.
        None - A list slice. Selects all members of a list or dict like object.
          A slice of an empty dict or list is an empty dict or list.
    quote: "..." the key name if it contains non-alphanum characters.
    omit_indices: Omit [...] indices if True.

  Returns:
    The string representation of the parsed key.
  Nz[]r:   z[{part}])partz[^-@\w]rS   z\\"z\"z"{part}"r/   )	r   sixinteger_typesr   researchreplacer=   r    )r   ry   omit_indicespartsr   r   r   r   
GetKeyNameV  s0   r   )NN)TF)r*   
__future__r   r   r   r#   r   googlecloudsdk.core.resourcer   r   r   r   r   	six.movesr	   r
   OPERATOR_CHARSr   objectr   r+   rQ   rR   r   r   r   r   r   r   <module>   s2   7(
c    F