o
    wia                     @   s8  d Z ddlZddlZddlZddlZddlZddlZddlZddl	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ejjfddZG dd	 d	ZG d
d dZdd Zd-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d  Z!d!d" Z"d.d#d$Z#d.d%d&Z$d'd( Z%d)d* Z&d.d+d,Z'dS )/z~This library gathers utilities for hyperpyyaml loading

Authors
 * Peter Plantinga 2020
 * Aku Rouhe 2020
 * Jianchen Li 2022
    N)StringIO)RepresenterErrorTc                 C   s   t | ||} |jdtd td}|jd|dd |dt |dt |dt	 |d	t
 d
tjjj_d
tjjjj_tj| |d}dtjjj_dtjjjj_dd | D }|D ]}||= q^|S )a  This function implements the HyperPyYAML syntax

    The purpose for this syntax is a compact, structured hyperparameter and
    function definition. This function implements a few extensions to the yaml
    syntax, listed below.

    **PyYAML complex tag shortcuts**

    Part of our clean structured hyperparameter interface is being able to
    specify python objects easily and cleanly. This is possible with
    native YAML using the following syntax:

    .. code-block:: yaml

        alignment_saver: !!python/object/new:speechbrain.data_io.TensorSaver
            kwargs: {save_dir: results/asr/ali}

    However, due to the extensive use within speechbrain yaml files, we have
    added a shortcut for this that has the following syntax:

    .. code-block:: yaml

        alignment_saver: !new:speechbrain.data_io.TensorSaver
            save_dir: results/asr/ali

    In this example, the alignment_saver will be an instance of the
    ``TensorSaver`` class, with ``'exp/asr/ali'`` passed to the
    ``__init__()`` method as a keyword argument. This is equivalent to:

    .. code-block:: python

        import speechbrain.data_io.data_io
        alignment_saver = speechbrain.data_io.TensorSaver(
            save_dir='exp/asr/ali'
        )

    We have also implemented a few more shortcuts:::

        !!python/name: => !name:
        !!python/module: => !module:
        !!python/object/apply: => !apply:

    **References and copies**

    Allows internal references to any node in the file. Any node with
    tag ``!ref`` will create an object reference to the yaml object at the
    ``<key.subkey>`` location within the yaml itself,
    following reference chains.

    .. code-block:: yaml

        output_folder: results/asr
        alignment_saver: !new:speechbrain.data_io.TensorSaver
            save_dir: !ref <output_folder>

    Strings values are handled specially: references are substituted but
    the rest of the string is left in place, allowing filepaths to be
    easily extended:

    .. code-block:: yaml

        output_folder: results/asr
        alignment_saver: !new:speechbrain.data_io.TensorSaver
            save_dir: !ref <output_folder>/ali  # results/asr/ali

    A more complex example for demonstration purposes:

    .. code-block:: yaml

        key1: {a: !new:object {arg1: 1}}
        key2: !ref <key1[a]>

    Here, ``key2`` will contain a reference to the ``a`` object, so changing
    ``a.arg1`` will also change ``key2.arg1``. If you need a
    deep copy of the object instead of a shallow reference, you
    can use a similar syntax with the tag ``!copy``. For example:

    .. code-block:: yaml

        key1: {a: !new:object {arg1: 1}}
        key2: !copy <key1[a]>

    These will also implement very basic arithmetic, so:

    .. code-block:: yaml

        key1: 1
        key2: !ref <key1> + 3  # this is 4

    **Tuples**

    One last minor enhancement is an implicit tuple resolver. Passing
    a string value of ``(3, 4)`` will be given a tag of ``!tuple`` which is
    then interpreted as a tuple.

    Arguments
    ---------
    yaml_stream : stream
        A file-like object or string from which to read.
    overrides : mapping or str
        A set of overrides for the values read from the stream.
        As yaml implements a nested structure, so can the overrides.
        See `speechbrain.utils.data_utils.recursive_update`
    overrides_must_match : bool
        Whether an error will be thrown when an override does not match
        a corresponding key in the yaml_stream.
    return_dict : bool
        Whether to return a dictionary rather than the default namespace.
    loader : Loader
        Use to parse the yaml_stream, could be `ruamel.yaml.Loader`,
        `yaml.Loader`, etc.

    Returns
    -------
    hparams : dict
        Reflects the structure of ``yaml_stream``.

    Example
    -------
    >>> yaml_string = """
    ... a: 3
    ... thing: !new:collections.Counter
    ...     b: !ref <a>
    ... """
    >>> params = load_hyperpyyaml(yaml_string)
    >>> params["thing"]
    Counter({'b': 3})
    z!tuple)tagconstructorz^\(.*\)$()first!new:!name:!module:!apply:)TLoaderFc                 S   s   g | ]	}| d r|qS )__)
startswith).0k r   D/home/ubuntu/.local/lib/python3.10/site-packages/hyperpyyaml/core.py
<listcomp>   s    z$load_hyperpyyaml.<locals>.<listcomp>)resolve_referencesadd_constructor_make_tuplerecompileadd_implicit_resolveradd_multi_constructor_construct_object_construct_name_construct_module_apply_functionyamlr   BaseConstructorconstruct_object__defaults__ruamelloadkeys)yaml_stream	overridesoverrides_must_matchloadertuple_patternhparamsremoval_keyskeyr   r   r   load_hyperpyyaml   s$    
r0   c                   @   s(   e Zd ZdZdZdd Zedd ZdS )RefTagzClass for dumping !ref tags to yaml

    Arguments
    ---------
    ref_str : str
        String including yaml keys in `<key>` notation

    Example
    -------
    See ``dump_hyperpyyaml``
    !refc                 C   s
   || _ d S N)ref_str)selfr4   r   r   r   __init__   s   
zRefTag.__init__c                 C   s   | | j|jS r3   )represent_scalaryaml_tagr4   clsrepresenternoder   r   r   to_yaml   s   zRefTag.to_yamlN)__name__
__module____qualname____doc__r8   r6   classmethodr=   r   r   r   r   r1      s    r1   c                   @   s    e Zd ZdZdZedd ZdS )PlaceholderzfClass for dumping !PLACEHOLDER tags to yaml

    Example
    -------
    See ``dump_hyperpyyaml``
    !PLACEHOLDERc                 C   s   | | jdS )N )r7   r8   r9   r   r   r   r=      s   zPlaceholder.to_yamlN)r>   r?   r@   rA   r8   rB   r=   r   r   r   r   rC      s
    rC   c                 O   sH   t j }|jttj |jttj |j| |g|R i | dS )a  Dump yaml including placeholder and reference tags.

    Arguments
    ---------
    yaml_tree : dict
        An object to dump
    output_stream : stream
        A file stream for putting the yaml
    *args, **kwargs
        Arguments to forward to ruamel.yaml.YAML().dump()

    Example
    -------
    >>> to_yaml = {'a': Placeholder(), 'b': RefTag('<a>')}
    >>> stringio = StringIO()
    >>> dump_hyperpyyaml(to_yaml, stringio)
    >>> stringio.getvalue()
    'a: !PLACEHOLDER\nb: !ref <a>\n'
    N)	r%   r!   YAMLr;   add_representerr1   r=   rC   dump)	yaml_treeoutput_streamargskwargsruamel_yamlr   r   r   dump_hyperpyyaml   s   
rN   Fc              
   C   s   d}t | drtjtj| j}tj }|	| }|durFt
|dkrFt|tr0|	|}z	t|||d W n tyE   td|w td||| t } z|||  | d W | S  tyy } z|jd d f|_||jd}~ww )a  Resolves inter-document references, a component of HyperPyYAML.

    Arguments
    ---------
    yaml_stream : stream
        A file-like object or string with the contents of a yaml file
        written with the HyperPyYAML syntax.
    overrides : mapping or str
        Replacement values, either in a yaml-formatted string or a dict.
    overrides_must_match : bool
        Whether an error will be thrown when an override does not match
        a corresponding key in the yaml_stream. This is the opposite
        default from ``load_hyperpyyaml`` because ``resolve_references``
        doesn't need to be as strict by default.

    Returns
    -------
    stream
        A yaml-formatted stream with all references and overrides resolved.

    Example
    -------
    >>> yaml_string = """
    ... constants:
    ...     a: 3
    ...     b: !ref <constants[a]>
    ... """
    >>> overrides = {'constants': {'a': 4}}
    >>> resolve_references(yaml_string, overrides).getvalue()
    'constants:\n  a: 4\n  b: 4\n'
    Nnamer   )
must_matchzLThe structure of the overrides doesn't match the structure of the document: rootz$. Please use the !apply tag instead.)hasattrospathdirnamerealpathrO   r%   r!   rF   r&   len
isinstancestrrecursive_update	TypeError
ValueError_walk_tree_and_resolver   rH   seekr   rK   with_traceback__traceback__)r(   r)   r*   	file_pathrM   previewer   r   r   r     s6   !




r   c                 C   s  t |tr&t|D ]\}}| dkr|n|  d| d}t||||||< q	n%t |trK| D ]\}}| dkr9|n|  d| d}t||||||< q/t|dr|jjpUd}|dkrbt	d|  d|d	v ru|d
k}	t
|jg ||	d}|S |dr|tdd }
|durtj||
}
zt|}W n ty   i }Y nw t|
}t||}W d   n1 sw   Y  tj }||}|S |dr|tdd }t||}|S )a  A recursive function for resolving ``!ref``, ``!copy`` and ``!applyref`` tags.

    Loads additional yaml files if ``!include:`` tags are used.
    Also throws an error if ``!PLACEHOLDER`` tags are encountered.

    Arguments
    ---------
    key : str
        The fully-qualified path to current node.
    current_node : node
        A node in the yaml tree loaded with ruamel.yaml.
    tree : node
        The base node in the yaml tree loaded with ruamel.yaml.
    file_path : str
        The location of the directory storing the main yaml file

    Returns
    -------
    yaml.Node
        A yaml tree with all references resolved.
    rQ   []r   rE   rD   'z)' is a !PLACEHOLDER and must be replaced.)r2   !copyrg   )	referencereference_list	full_tree	copy_modez	!include:N
!applyref:)rX   list	enumerater]   dictitemsrR   r   valuer\   recursive_resolver   rW   rS   rT   joinr[   openr   r%   r!   rF   r&   _applyref_function)r/   current_nodetreera   isub_nodesub_keyr   	tag_valuerk   filenamer)   fincluded_yamlrM   functionr   r   r   r]   T  sR   


!





r]   c                 C   s6   |  |}d|dd  d }tj|tjd}t|S )z-Parse scalar node as a list, convert to tuplerd      re   r   )construct_scalarr!   r&   r   tuple)r+   r<   tuple_stringlist_stringparsed_listr   r   r   r     s   
r   c                 C   sh   t |tjst |tjjr| j|dd}g |fS t |tjs%t |tjjr0| j|dd}|i fS g i fS )NT)deep)rX   r!   MappingNoder%   construct_mappingSequenceNodeconstruct_sequence)r+   r<   rL   rK   r   r   r   
_load_node  s   

r   c                 C   sd   t | sg i fS t| tr'd| v r#d| v r#t| dkr#| d | d fS g | fS t| tr0| i fS t)N_args_kwargs   )rY   rX   ro   rW   rm   r\   )r<   r   r   r   	_get_args  s   

r   c              
   C      t |}|d u rtd| t|std| d| zt| |\}}||i |W S  tyF } zd| }|g|jR |_ d }~ww )NzThere is no such class as %sr   z should be a class, but is zInvalid argument to class %s)	pydoclocateImportErrorinspectisclassr\   r   r[   rK   r+   callable_stringr<   	callable_rK   rL   rc   err_msgr   r   r   r         

r   c              
   C   s   t |}|d u rtd| t|s0t|s0t| |\}}|s$|r.td| d| |S zt| |\}}|s<|rItj	|g|R i |W S |W S  t
yd } zd| }|g|jR |_ d }~ww )NzThere is no such entity as %sr	   zK should be class or function, if you specify args or kwargs. Instead it is Invalid argument to callable %s)r   r   r   r   r   	isroutiner   r\   	functoolspartialr[   rK   )r+   r   r<   rO   rK   rL   rc   r   r   r   r   r     s.   
r   c                 C   sf   t |}|d u rtd| t| |\}}|g ks|i kr"tdt|s1td| d| |S )NzThere is no such module as %szCannot pass args to moduler
   z should be module, but is )r   r   r   r   r\   r   ismodule)r+   module_namer<   modulerK   rL   r   r   r   r     s   

r   c              
   C   r   )NThere is no such callable as %sr    should be a callable, but is r   )	r   r   r   r   r   r\   r   r[   rK   r   r   r   r   r      r   r    c              
   C   s   t | }|d u rtd|  t|std|  d| zt|\}}||i |}|W S  tyG } zd|  }|g|jR |_ d }~ww )Nr   rl   r   r   )	r   r   r   r   r   r\   r   r[   rK   )r   r<   r   rK   rL   outrc   r   r   r   r   ru   #  s"   

ru   c                 C   s   d}d| v r| j ddd\} }|}|  dD ]}|d}||vr'td|  || }q|r3t|S |durOtjj }|||g7 }|	tjj
dd	 |S |S )
a	  Find the value referred to by a reference in dot-notation

    Arguments
    ---------
    ref : str
        The location of the requested value, e.g. 'constants.param'
    full_tree : dict
        The dictionary to use for finding values
    copy_mode : bool
        Whether to copy the node before dereferencing.

    Returns
    -------
    node
        The node in the full_tree dictionary referenced by ``ref``.

    Example
    -------
    >>> deref('constants[a][b]', {'constants': {'a': {'b': 'c'}}})
    'c'
    N.r   )maxsplitrd   re   zThe reference "%s" is not validz!apply:getattr)suffix)splitstripr\   copydeepcopyr%   r!   commentsCommentedSeqyaml_set_ctagTag)refrj   rk   attrbranchpartr<   r   r   r   deref8  s&   


r   c           	      C   s   t d}t| tr|| s| S t|dkr$| |dd v r$td||| r>t| 	d||}|| g7 }t
||||S || }|dd |D 7 }||fdd	}||| }t
||||} t| S )
a  Resolve a reference to a value, following chained references

    Arguments
    ---------
    reference : str
        a string containing '<x[y]>' in it where x[y] refers
        to a scalar node in the file.
    reference_list : list
        list of prior references in the chain, in order
        to catch circular references.
    full_tree : dict
        the dictionary in which to find all references and their values.
    copy_mode : bool
        Whether to perform a deep copy of the referenced node, rather than
        a shallow reference to the same object.

    Returns
    -------
    scalar
        The dereferenced value, with possible string interpolation and
        arithmetic parsing.

    Example
    -------
    >>> tree = {'a': 3, 'b': 'x', 'c': '<a>', 'd': '<c>/<c>', 'e': '<b>/<b>'}
    >>> recursive_resolve('<d>', [], tree)
    1.0
    >>> recursive_resolve('<e>', [], tree)
    'x/x'
    z<[^>]*>r   NzCircular reference detected: <>c                 S   s   g | ]}|d  qS )r   r   )r   matchr   r   r   r     s    z%recursive_resolve.<locals>.<listcomp>c                 S   s   t t| d d||dS )Nr   r   )rj   rk   )rY   r   r   )xrw   rk   r   r   r   
replace_fn  s   z%recursive_resolve.<locals>.replace_fn)r   r   rX   rY   searchrW   r\   	fullmatchr   r   rr   findallsubparse_arithmetic)	rh   ri   rj   rk   reference_finderrq   matchesr   r   r   r   r   rr   l  s   
!



rr   c              
   C   s4   zt tj| ddjW S  tttfy   |  Y S w )a  Parses simple arithmetic operations in references

    Adapted from https://stackoverflow.com/a/9558001/1761970

    Arguments
    ---------
    reference_string : str
        A string with references and possible arithmetic operations.

    Returns
    -------
    str
        Result of parsing and applying the arithmetic.

    Example
    -------
    >>> parse_arithmetic('2 * 6')
    12
    eval)mode)	_ast_evalastparsebodyr[   SyntaxErrorKeyError)reference_stringr   r   r   r     s
   r   c                 C   s   t jtjt jtjt jtjt jtj	t j
tjt jtjt jtji}tjdkr-t| t jr,| jS n	t| t jr6| jS t| t jrL|t| j t| jt| jS t| t jr^|t| j t| jS t| )N)      )r   AddopaddSubr   MultmulDivtruedivFloorDivfloordivPowpowModmodsysversion_inforX   Constantrq   NumnBinOptyper   leftrightUnaryOpoperandr[   )r<   opsr   r   r   r     s&   
 r   c                 C   s   t | tjjstd|  t |tjjstd| | D ]4\}}t |tjjr9|| v r9t| |i | q |rP|| vrPtd| ddd | 	 D  || |< q dS )a  Similar function to `dict.update`, but for a nested `dict`.

    From: https://stackoverflow.com/a/3233356

    If you have to a nested mapping structure, for example:

        {"a": 1, "b": {"c": 2}}

    Say you want to update the above structure with:

        {"b": {"d": 3}}

    This function will produce:

        {"a": 1, "b": {"c": 2, "d": 3}}

    Instead of:

        {"a": 1, "b": {"d": 3}}

    Arguments
    ---------
    d : dict
        mapping to be updated
    u : dict
        mapping to update with
    must_match : bool
        Whether to throw an error if the key in `u` does not exist in `d`.

    Example
    -------
    >>> d = {'a': 1, 'b': {'c': 2}}
    >>> recursive_update(d, {'b': {'d': 3}})
    >>> d
    {'a': 1, 'b': {'c': 2, 'd': 3}}
    z'Expected to update a mapping, but got: z.Expected a mapping to use for update, but got z
Override 'z' not found in: c                 S   s   g | ]}|qS r   r   )r   r/   r   r   r   r     s    z$recursive_update.<locals>.<listcomp>N)
rX   collectionsabcMappingr[   rp   rZ   getr   r'   )durP   r   vr   r   r   rZ     s   '"
rZ   )NFr   )(rA   r   r   r   r!   r   r   os.pathrS   r   r   r   ruamel.yamlr%   operatorr   ior   ruamel.yaml.representerr   r   r0   r1   rC   rN   r   r]   r   r   r   r   r   r   r    ru   r   rr   r   r   rZ   r   r   r   r   <module>   sH    

 4
FR

4?