o
    j                     @   sf  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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mZ ddlZdZdZG dd deZG dd deZG dd deZG dd deejeZdd Zdd Z	d3ddZdd Z e dd Z!e dd  Z"d!d" Z#d#d$ Z$d%d& Z%d'd( Z&d)d* Z'd+d, Z(d-d. Z)d/d0 Z*d1d2 Z+dS )4z-Helpers to load commands from the filesystem.    )absolute_import)division)unicode_literalsN)base)command_release_tracks)
exceptions)pkg_resources)yaml
_PARTIALS_	_partialsc                       s    e Zd ZdZ fddZ  ZS )CommandLoadFailurezCAn exception for when a command or group module cannot be imported.c                    s0   || _ || _tt| dj|t|d d S )Nz#Problem loading {command}: {issue}.)commandissue)r   root_exceptionsuperr   __init__formatsix	text_type)selfr   r   	__class__ D/tmp/google-cloud-sdk/lib/googlecloudsdk/calliope/command_loading.pyr   )   s   

zCommandLoadFailure.__init__)__name__
__module____qualname____doc__r   __classcell__r   r   r   r   r   &   s    r   c                   @      e Zd ZdZdS )LayoutExceptionzFAn exception for when a command or group .py file has the wrong types.Nr   r   r   r   r   r   r   r   r    3       r    c                   @   r   )#ReleaseTrackNotImplementedExceptionzJAn exception for when a command or group does not support a release track.Nr!   r   r   r   r   r#   7   r"   r#   c                   @   s   e Zd ZdZejdd ZdS )YamlCommandTranslatorzCAn interface to implement when registering a custom command loader.c                 C   s   dS )a;  Translates a yaml command into a calliope command.

    Args:
      path: [str], A list of group names that got us down to this command group
        with respect to the CLI itself.  This path should be used for things
        like error reporting when a specific element in the tree needs to be
        referenced.
      command_data: dict, The parsed contents of the command spec from the yaml
        file that corresponds to the release track being loaded.

    Returns:
      calliope.base.Command, A command class (not instance) that
      implements the spec.
    Nr   )r   pathcommand_datar   r   r   	Translate>   s   zYamlCommandTranslator.TranslateN)r   r   r   r   abcabstractmethodr'   r   r   r   r   r$   ;   s    r$   c                 C   sP   t | dkrtd|td| d }tj|dgd\}}t||t||fS )aw  Find all the sub groups and commands under this group.

  Args:
    impl_paths: [str], A list of file paths to the command implementation for
      this group.
    path: [str], A list of group names that got us down to this command group
      with respect to the CLI itself.  This path should be used for things like
      error reporting when a specific element in the tree needs to be
      referenced.

  Raises:
    CommandLoadFailure: If the command is invalid and cannot be loaded.
    LayoutException: if there is a command or group with an illegal name.

  Returns:
    ({str: [str]}, {str: [str]), A tuple of groups and commands found where each
    item is a mapping from name to a list of paths that implement that command
    or group. There can be multiple paths because a command or group could be
    implemented in both python and yaml (for different release tracks).
     .,Command groups cannot be implemented in yamlr   .yaml)extra_extensions)lenr   join	Exceptionr   ListPackage_GenerateElementInfo)
impl_pathsr%   	impl_pathgroupscommandsr   r   r   FindSubElementsQ   s   
r8   c                 C   sj   i }|D ].}t d|rtd||dr|dd n|}tj| |}||g }|	| q|S )a&  Generates the data a group needs to load sub elements.

  Args:
    impl_path: The file path to the command implementation for this group.
    names: [str], The names of the sub groups or commands found in the group.

  Raises:
    LayoutException: if there is a command or group with an illegal name.

  Returns:
    {str: [str], A mapping from name to a list of paths that implement that
    command or group. There can be multiple paths because a command or group
    could be implemented in both python and yaml (for different release tracks).
  z[A-Z]z5Commands and groups cannot have capital letters: {0}.r-   N)
researchr    r   endswithosr%   r0   
setdefaultappend)r5   nameselementsnamecli_namesub_pathexistingr   r   r   r3   u   s   r3   c                 C   s"   t | ||||}t| d || S )a  Loads a calliope command or group from a file.

  Args:
    impl_paths: [str], A list of file paths to the command implementation for
      this group or command.
    path: [str], A list of group names that got us down to this command group
      with respect to the CLI itself.  This path should be used for things like
      error reporting when a specific element in the tree needs to be
      referenced.
    release_track: ReleaseTrack, The release track that we should load.
    construction_id: str, A unique identifier for the CLILoader that is being
      constructed.
    is_command: bool, True if we are loading a command, False to load a group.
    yaml_command_translator: YamlCommandTranslator, An instance of a translator
      to use to load the yaml data.

  Raises:
    CommandLoadFailure: If the command is invalid and cannot be loaded.

  Returns:
    The base._Common class for the command or group.
  r   )_GetAllImplementations"_ExtractReleaseTrackImplementation)r4   r%   release_trackconstruction_id
is_commandyaml_command_translatorimplementationsr   r   r   LoadCommonType   s   

rM   c                    s   i   fdd}|S )Nc                     s2   z |  W S  t y   |  }| | < | Y S w N)KeyError)argsresultcached_resultsfuncr   r   ReturnCachedOrCallFunc   s   
z%Cache.<locals>.ReturnCachedOrCallFuncr   )rT   rU   r   rR   r   Cache   s   rV   c                 C   s   t t| S rN   )r	   	safe_loadr   GetResourceFromFiler%   r   r   r   _SafeLoadYamlFile   s   rZ   c                 C   s   t | t| S rN   )CreateYamlLoaderloadr   rX   rY   r   r   r   _CustomLoadYamlFile   s   r]   c           	      C   s   g }| D ]B}| dr0|std|tdt||r"t||}nt|}|t||| qt	|||}|t
|jt|j |d q|S )a  Gets all the release track command implementations.

  Can load both python and yaml modules.

  Args:
    impl_paths: [str], A list of file paths to the command implementation for
      this group or command.
    path: [str], A list of group names that got us down to this command group
      with respect to the CLI itself.  This path should be used for things like
      error reporting when a specific element in the tree needs to be
      referenced.
    construction_id: str, A unique identifier for the CLILoader that is being
      constructed.
    is_command: bool, True if we are loading a command, False to load a group.
    yaml_command_translator: YamlCommandTranslator, An instance of a translator
      to use to load the yaml data.

  Raises:
    CommandLoadFailure: If the command is invalid and cannot be loaded.

  Returns:
    [(func->base._Common, [base.ReleaseTrack])], A list of tuples that can be
    passed to _ExtractReleaseTrackImplementation. Each item in this list
    represents a command implementation. The first element is a function that
    returns the implementation, and the second element is a list of release
    tracks it is valid for.
  r-   r+   r,   )rJ   )r<   r   r0   r1   _IsCommandWithPartials_LoadCommandWithPartialsr]   extend_ImplementationsFromYaml_GetModuleFromPath_ImplementationsFromModule__file__list__dict__values)	r4   r%   rI   rJ   rK   rL   	impl_filedatamoduler   r   r   rF      s.   


rF   c                 C   s   d}t | <}|D ]0}| }|r|drq
|t dkr"d}q
|r2td|tdt d W d   |S W d   |S 1 sFw   Y  |S )	a  Checks if the YAML file is a command with partials.

  Args:
    impl_file: file path to the main YAML command implementation.
    path: [str], A list of group names that got us down to this command group
      with respect to the CLI itself.  This path should be used for things like
      error reporting when a specific element in the tree needs to be
      referenced.

  Raises:
    CommandLoadFailure: If the command is invalid and should not be loaded.

  Returns:
    Whether or not it is a valid command with partials to load.
  F#z: trueTr+   zCommand with z$ attribute cannot have extra contentN)r   GetFileTextReaderByLinestrip
startswithPARTIALS_ATTRIBUTEr   r0   r1   )rh   r%   found_partial_tokenfileliner   r   r   r^   	  s.   


r^   c           	      C   s   t j| }|dd }t jt j| t}t|d| d}g }t	t j|d| }|D ]}t
| d|rD|t| q2t|| |S )a=  Loads all YAML partials for a command with partials based on conventions.

  Partial files are loaded using _CustomLoadYamlFile as normal YAML commands.

  Conventions:
  - Partials should be placed in subfolder `_partials`.
  - File names of partials should match the main command name and follow this
  format: _[command_name]_[version|release_track].yaml
  - Release tracks should not be duplicatd across all partials.

  Args:
    impl_file: file path to the main YAML command implementation.
    path: [str], A list of group names that got us down to this command group
      with respect to the CLI itself.  This path should be used for things like
      error reporting when a specific element in the tree needs to be
      referenced.

  Returns:
    List with data loaded from partial YAML files for the main command.
  Nr9   _z_*.yamlz_(alpha|beta|ga)\.yaml)r=   r%   basenamer0   dirnamePARTIALS_DIRr   GetFilesFromDirectoryr:   escapematchr`   r]   _ValidateCommandWithPartials)	rh   r%   	file_namecommand_namepartials_dirpartial_filescommand_data_listcommand_pathpartial_filer   r   r   r_   /  s   
r_   c                 C   sP   t  }| D ] }|d D ]}||v rtd|td| d|| qqdS )a  Validates that the command with partials do not have duplicated tracks.

  Args:
    command_data_list: List with data loaded from all YAML partials.
    path: [str], A list of group names that got us down to this command group
      with respect to the CLI itself.  This path should be used for things like
      error reporting when a specific element in the tree needs to be
      referenced.

  Raises:
    CommandLoadFailure: If the command is invalid and should not be loaded.
  release_tracksr+   zMCommand with partials cannot have duplicated release tracks. Found multiple [zs]N)setr   r0   r1   add)r   r%   r   r&   rH   r   r   r   rz   U  s   rz   c                    s   t jt jd}dzt|W n	 ty   Y nw G  fdddtj t } |_|j	
 j j |j	
 j j |S )zCreates a custom yaml loader that handles includes from common data.

  Args:
    impl_path: str, The path to the file we are loading data from.

  Returns:
    yaml.Loader, A yaml loader to use.
  z__init__.yamlNc                       s   e Zd ZdZdZdZdZdZ fddZdd	 Z	 fd
dZ
dd Zdd Zdd ZfddZdd ZfddZ  ZS )z%CreateYamlLoader.<locals>.Constructora,  A custom yaml constructor.

    It adds 2 different import capabilities. Assuming __init__.yaml has the
    contents:

    foo:
      a: b
      c: d

    baz:
      - e: f
      - g: h

    The first uses a custom constructor to insert data into your current file,
    so:

    bar: !COMMON foo.a

    results in:

    bar: b

    The second mechanism overrides construct_mapping and construct_sequence to
    post process the data and replace the merge macro with keys from the other
    file. We can't use the custom constructor for this as well because the
    merge key type in yaml is processed before custom constructors which makes
    importing and merging not possible. So:

    bar:
      _COMMON_: foo
      i: j

    results in:

    bar:
      a: b
      c: d
      i: j

    This can also be used to merge list contexts, so:

    bar:
      - _COMMON_baz
      - i: j

    results in:

    bar:
      - e: f
      - g: h
      - i: j

    You may also use the !REF and _REF_ directives in the same way. Instead of
    pulling from the common file, they can pull from an arbitrary yaml file
    somewhere in the googlecloudsdk tree. The syntax looks like:

    bar: !REF googlecloudsdk.foo.bar:a.b.c

    This will load googlecloudsdk/foo/bar.yaml and from that file return the
    a.b.c nested attribute.
    z!COMMON_COMMON_z!REF_REF_c                    :   t  | j|i |}|  j| j|}|  j| j|S rN   )r   construct_mapping_ConstructMappingHelperMERGE_COMMON_MACRO_GetCommonDataMERGE_REF_MACRO_GetRefDatar   rP   kwargsri   Constructorr   r   r   r        

z7CreateYamlLoader.<locals>.Constructor.construct_mappingc                 S   sD   | |d }|s
|S i }|dD ]	}||| q|| |S N,)popsplitupdate)r   macrosource_funcri   attribute_pathmodified_datar%   r   r   r   r     s   
z=CreateYamlLoader.<locals>.Constructor._ConstructMappingHelperc                    r   rN   )r   construct_sequence_ConstructSequenceHelperr   r   r   r   r   r   r   r   r     r   z8CreateYamlLoader.<locals>.Constructor.construct_sequencec                 S   sb   g }|D ]*}t |tjr)||r)|t|d  }|dD ]	}||| qq|| q|S r   )
isinstancer   string_typesrn   r/   r   r`   r?   )r   r   r   ri   new_listir   r%   r   r   r   r     s   z>CreateYamlLoader.<locals>.Constructor._ConstructSequenceHelperc                 S      |  |}| |S rN   )construct_scalarr   r   noder   r   r   r   IncludeCommon     

z3CreateYamlLoader.<locals>.Constructor.IncludeCommonc                 S   r   rN   )r   r   r   r   r   r   
IncludeRef  r   z0CreateYamlLoader.<locals>.Constructor.IncludeRefc                    s     s	t d|  |dS )NzDCommand [{}] references [common command] data but it does not exist.zcommon command)r    r   _GetAttribute)r   r   )common_datar5   r   r   r     s   z4CreateYamlLoader.<locals>.Constructor._GetCommonDatac              
   S   s   | d}t|dkrtd||d  d}z"t|d }tjjtj	|j
g|dd R  d }t|}W n ttfyS } z
td	|d |d}~ww | ||d |S )
a  Loads the YAML data from the given reference.

      A YAML reference must refer to a YAML file and an attribute within that
      file to extract.

      Args:
        path: str, The path of the YAML file to import. It must be in the form
          of package.module:attribute.attribute, where the module path is
          separated from the sub attributes within the YAML by a ':'.

      Raises:
        LayoutException: If the given module or attribute cannot be loaded.

      Returns:
        The referenced YAML data.
      :   zcInvalid Yaml reference: [{}]. References must be in the format: path(.path)+:attribute(.attribute)*r   r+   r*   Nr-   z+Failed to load Yaml reference file [{}]: {})r   r/   r    r   	importlibimport_moduler=   r%   r0   ru   rd   rZ   ImportErrorIOErrorr   )r   r%   partspath_segmentsroot_module	yaml_pathri   er   r   r   r     s2   

z1CreateYamlLoader.<locals>.Constructor._GetRefDatac                    s<   |}| dD ]}||d }|std |||q|S )Nr+   zTCommand [{}] references [{}] data attribute [{}] in path [{}] but it does not exist.)r   getr    r   )r   ri   r   locationvalue	attribute)r5   r   r   r   &  s   z3CreateYamlLoader.<locals>.Constructor._GetAttribute)r   r   r   r   INCLUDE_COMMON_MACROr   INCLUDE_REF_MACROr   r   r   r   r   r   r   r   r   r   r   r   r   r   r5   r   r   r     s    >		(r   )r=   r%   r0   ru   rZ   r   r	   r   YAMLconstructoradd_constructorr   r   r   r   )r5   common_file_pathloaderr   r   r   r[   q  s$   	 3r[   c              
   C   sh   dj |d|ddd}zt|| W S  ty3 } zttd|| W Y d}~dS d}~ww )a  Import the module and dig into it to return the namespace we are after.

  Import the module relative to the top level directory.  Then return the
  actual module corresponding to the last bit of the path.

  Args:
    impl_file: str, The path to the file this was loaded from (for error
      reporting).
    path: [str], A list of group names that got us down to this command group
      with respect to the CLI itself.  This path should be used for things like
      error reporting when a specific element in the tree needs to be
      referenced.
    construction_id: str, A unique identifier for the CLILoader that is being
      constructed.

  Returns:
    The imported module.
  z.__calliope__command__.{construction_id}.{name}r+   -rs   )rI   rB   N)	r   r0   replacer   GetModuleFromPathr1   r   reraiser   )rh   r%   rI   name_to_giver   r   r   r   rb   >  s   $rb   c                 C   s   g }g }|D ]}t |ddr|| qt |ddr|| q|r@|r4tdddd |D | |s=td| |}n|rRtd	dd
d |D | |s[td| |}dd |D S )a  Gets all the release track command implementations from the module.

  Args:
    mod_file: str, The __file__ attribute of the module resulting from importing
      the file containing a command.
    module_attributes: The __dict__.values() of the module.
    is_command: bool, True if we are loading a command, False to load a group.

  Raises:
    LayoutException: If there is not exactly one type inheriting CommonBase.

  Returns:
    [(func->base._Common, [base.ReleaseTrack])], A list of tuples that can be
    passed to _ExtractReleaseTrackImplementation. Each item in this list
    represents a command implementation. The first element is a function that
    returns the implementation, and the second element is a list of release
    tracks it is valid for.
  
IS_COMMANDFIS_COMMAND_GROUPz7You cannot define groups [{0}] in a command file: [{1}], c                 S      g | ]}|j qS r   r   ).0gr   r   r   
<listcomp>      z._ImplementationsFromModule.<locals>.<listcomp>z"No commands defined in file: [{0}]z?You cannot define commands [{0}] in a command group file: [{1}]c                 S   r   r   r   r   cr   r   r   r     r   z(No command groups defined in file: [{0}]c                 S   s    g | ]}|fd d|  fqS )c                 S   s   | S rN   r   )r   r   r   r   <lambda>  s    z7_ImplementationsFromModule.<locals>.<listcomp>.<lambda>)ValidReleaseTracksr   r   r   r   r     s     )getattrr?   r    r   r0   )mod_filemodule_attributesrJ   r7   r6   command_or_groupcommands_or_groupsr   r   r   rc   _  s>   
rc   c                    s6   st d td fddt|D }|S )a  Gets all the release track command implementations from the yaml file.

  Args:
    path: [str], A list of group names that got us down to this command group
      with respect to the CLI itself.  This path should be used for things like
      error reporting when a specific element in the tree needs to be
      referenced.
    data: dict, The loaded yaml data.
    yaml_command_translator: YamlCommandTranslator, An instance of a translator
      to use to load the yaml data.

  Raises:
    CommandLoadFailure: If the command is invalid and cannot be loaded.

  Returns:
    [(func->base._Common, [base.ReleaseTrack])], A list of tuples that can be
    passed to _ExtractReleaseTrackImplementation. Each item in this list
    represents a command implementation. The first element is a function that
    returns the implementation, and the second element is a list of release
    tracks it is valid for.
  r+   z.No yaml command translator has been registeredc                    s4   g | ]}|f fd d	dd | dg D fqS )c                    s     | S rN   )r'   )r   r%   rK   r   r   r     s    z5_ImplementationsFromYaml.<locals>.<listcomp>.<lambda>c                 S   s   h | ]}t j|qS r   )r   ReleaseTrackFromId)r   tr   r   r   	<setcomp>  s    z6_ImplementationsFromYaml.<locals>.<listcomp>.<setcomp>r   )r   )r   r   r   r   r   r     s    z,_ImplementationsFromYaml.<locals>.<listcomp>)r   r0   r1   r    SeparateDeclarativeCommandTracks)r%   ri   rK   rL   r   r   r   ra     s   	ra   c                    s   t |dkr|d \}}|r |v r|S td j| t }|D ]'\}}|s/td| ||@ }|rEtdddd |D | ||O }q" fd	d|D }t |dkrbtd j| |d S )
ae  Validates and extracts the correct implementation of the command or group.

  Args:
    impl_file: str, The path to the file this was loaded from (for error
      reporting).
    expected_track: base.ReleaseTrack, The release track we are trying to load.
    implementations: [(func->base._Common, [base.ReleaseTrack])], A list of
      tuples where each item in this list represents a command implementation.
      The first element is a function that returns the implementation, and the
      second element is a list of release tracks it is valid for.

  Raises:
    LayoutException: If there is not exactly one type inheriting
        CommonBase.
    ReleaseTrackNotImplementedException: If there is no command or group
      implementation for the request release track.

  Returns:
    object, The single implementation that matches the expected release track.
  r*   r   z<No implementation for release track [{0}] for element: [{1}]zgMultiple implementations defined for element: [{0}]. Each must explicitly declare valid release tracks.z@Multiple definitions for release tracks [{0}] for element: [{1}]r   c                 S   s   g | ]}t |qS r   )r   r   )r   dr   r   r   r     s    z6_ExtractReleaseTrackImplementation.<locals>.<listcomp>c                    s   g | ]
\}} |v r|qS r   r   )r   implvalid_tracksexpected_trackr   r   r      s
    )r/   r#   r   idr   r    r0   )rh   r   rL   r   r   implemented_release_tracks
duplicatesvalid_commands_or_groupsr   r   r   rG     sB   

rG   rN   ),r   
__future__r   r   r   r(   r   r=   r:   googlecloudsdk.callioper   r   googlecloudsdk.corer   googlecloudsdk.core.utilr   ruamelr	   r   ro   rv   r1   r   r    r#   with_metaclassABCMetaobjectr$   r8   r3   rM   rV   rZ   r]   rF   r^   r_   rz   r[   rb   rc   ra   rG   r   r   r   r   <module>   sN   $#
&

9&& N!@*