Retrieve contract exception(s) message(s).

    If the custom contract is specified with only one exception the return
    value is the message associated with that exception; if the custom contract
    is specified with several exceptions, the return value is a dictionary
    whose keys are the exception names and whose values are the exception
    messages.

    :raises: RuntimeError (Function object could not be found for
     function *[function_name]*)
    :rtype: string or dictionary

    For example:

    .. =[=cog
    .. import docs.support.incfile
    .. docs.support.incfile.incfile('pcontracts_example_2.py', cog.out)
    .. =]=
    .. code-block:: python

        # pcontracts_example_2.py
        import pexdoc.pcontracts

        @pexdoc.pcontracts.new_contract('Only one exception')
        def custom_contract_a(name):
            msg = pexdoc.pcontracts.get_exdesc()
            if not name:
                raise ValueError(msg)

        @pexdoc.pcontracts.new_contract(ex1='Empty name', ex2='Invalid name')
        def custom_contract_b(name):
            msg = pexdoc.pcontracts.get_exdesc()
            if not name:
                raise ValueError(msg['ex1'])
            elif name.find('[') != -1:
                raise ValueError(msg['ex2'])

    .. =[=end=]=


    In :code:`custom_contract1()` the variable :code:`msg` contains the string
    ``'Only one exception'``, in :code:`custom_contract2()` the variable
    :code:`msg` contains the dictionary
    :code:`{'ex1':'Empty name', 'ex2':'Invalid name'}`.

def get_exdesc():
    """
    Retrieve contract exception(s) message(s).

    If the custom contract is specified with only one exception the return
    value is the message associated with that exception; if the custom contract
    is specified with several exceptions, the return value is a dictionary
    whose keys are the exception names and whose values are the exception
    messages.

    :raises: RuntimeError (Function object could not be found for
     function *[function_name]*)
    :rtype: string or dictionary

    For example:

    .. =[=cog
    .. import docs.support.incfile
    .. docs.support.incfile.incfile('pcontracts_example_2.py', cog.out)
    .. =]=
    .. code-block:: python

        # pcontracts_example_2.py
        import pexdoc.pcontracts

        @pexdoc.pcontracts.new_contract('Only one exception')
        def custom_contract_a(name):
            msg = pexdoc.pcontracts.get_exdesc()
            if not name:
                raise ValueError(msg)

        @pexdoc.pcontracts.new_contract(ex1='Empty name', ex2='Invalid name')
        def custom_contract_b(name):
            msg = pexdoc.pcontracts.get_exdesc()
            if not name:
                raise ValueError(msg['ex1'])
            elif name.find('[') != -1:
                raise ValueError(msg['ex2'])

    .. =[=end=]=


    In :code:`custom_contract1()` the variable :code:`msg` contains the string
    ``'Only one exception'``, in :code:`custom_contract2()` the variable
    :code:`msg` contains the dictionary
    :code:`{'ex1':'Empty name', 'ex2':'Invalid name'}`.

    """
    # First frame is own function (get_exdesc), next frame is the calling
    # function, of which its name is needed
    fname = inspect.getframeinfo(sys._getframe(1))[2]
    # Find function object in stack
    count = 0
    fobj = None
    while not (fobj and hasattr(fobj, "exdesc")):
        count = count + 1
        try:
            sitem = sys._getframe(count)
        except ValueError:
            # Got to top of stack
            raise RuntimeError(
                "Function object could not be found for function `{0}`".format(fname)
            )
        fobj = (
            sitem.f_locals[fname]
            if fname in sitem.f_locals
            else (sitem.f_globals[fname] if fname in sitem.f_globals else None)
        )
    # Return function attribute created by new contract decorator
    exdesc = getattr(fobj, "exdesc")
    return exdesc if len(exdesc) > 1 else exdesc[next(iter(exdesc))]