{
    "file_diffs": [
        {
            "old_file_content": "# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 noet:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"Utils to help with docstrings etc.\n\nLargerly borrowed from PyMVPA (as of upstream/2.4.1-23-g170496e).  Copyright of\nthe same developers as DataLad\n\"\"\"\n\nimport logging\nimport re\nimport textwrap\n\nlgr = logging.getLogger(\"datalad.docutils\")\n\n__add_init2doc = False\n__in_ipython = False  # TODO: determine exists('running ipython env')\n\n# if ran within IPython -- might need to add doc to init\nif __in_ipython:\n    __rst_mode = False                       # either to do ReST links at all\n    # if versions['ipython'] <= '0.8.1':\n    #     __add_init2doc = True\nelse:\n    __rst_mode = True\n\n#\n# Predefine some sugarings depending on syntax convention to be used\n#\n# XXX Might need to be removed or become proper cfg parameter\n__rst_conventions = 'numpy'\nif __rst_conventions == 'epydoc':\n    _rst_sep = \"`\"\n    _rst_indentstr = \"  \"\n    def _rst_section(section_name):\n        \"\"\"Provide section heading\"\"\"\n        return \":%s:\" % section_name\nelif __rst_conventions == 'numpy':\n    _rst_sep = \"\"\n    _rst_indentstr = \"\"\n    def _rst_section(section_name):\n        \"\"\"Provide section heading\"\"\"\n        return \"%s\\n%s\" % (section_name, '-'*len(section_name))\nelse:\n    raise ValueError(\"Unknown convention %s for RST\" % __rst_conventions)\n\n\ndef _rst(s, snotrst=''):\n    \"\"\"Produce s only in __rst mode\"\"\"\n    if __rst_mode:\n        return s\n    else:\n        return snotrst\n\ndef _rst_underline(text, markup):\n    \"\"\"Add and underline RsT string matching the length of the given string.\n    \"\"\"\n    return text + '\\n' + markup * len(text)\n\n\ndef single_or_plural(single, plural, n):\n    \"\"\"Little helper to spit out single or plural version of a word.\n    \"\"\"\n    ni = int(n)\n    if ni > 1 or ni == 0:\n        # 1 forest, 2 forests, 0 forests\n        return plural\n    else:\n        return single\n\ndef handle_docstring(text, polite=True):\n    \"\"\"Take care of empty and non existing doc strings.\"\"\"\n    if text is None or not len(text):\n        if polite:\n            return '' #No documentation found. Sorry!'\n        else:\n            return ''\n    else:\n        # Problem is that first line might often have no offset, so might\n        # need to be ignored from dedent call\n        if not text.startswith(' '):\n            lines = text.split('\\n')\n            text2 = '\\n'.join(lines[1:])\n            return lines[0] + \"\\n\" + textwrap.dedent(text2)\n        else:\n            return textwrap.dedent(text)\n\n\ndef _indent(text, istr=_rst_indentstr):\n    \"\"\"Simple indenter\n    \"\"\"\n    return '\\n'.join(istr + s for s in text.split('\\n'))\n\n\n__parameters_str_re = re.compile(\"[\\n^]\\s*:?Parameters?:?\\s*\\n(:?\\s*-+\\s*\\n)?\")\n\"\"\"regexp to match :Parameter: and :Parameters: stand alone in a line\nor\nParameters\n----------\nin multiple lines\"\"\"\n\n\ndef _split_out_parameters(initdoc):\n    \"\"\"Split documentation into (header, parameters, suffix)\n\n    Parameters\n    ----------\n    initdoc : string\n      The documentation string\n    \"\"\"\n\n    # TODO: bind it to the only word in the line\n    p_res = __parameters_str_re.search(initdoc)\n    if p_res is None:\n        return initdoc, \"\", \"\"\n    else:\n        # Could have been accomplished also via re.match\n\n        # where new line is after :Parameters:\n        # parameters header index\n        ph_i = p_res.start()\n\n        # parameters body index\n        pb_i = p_res.end()\n\n        # end of parameters\n        try:\n            pe_i = initdoc.index('\\n\\n', pb_i)\n        except ValueError:\n            pe_i = len(initdoc)\n\n        result = initdoc[:ph_i].rstrip('\\n '), \\\n                 initdoc[pb_i:pe_i], initdoc[pe_i:]\n\n    # XXX a bit of duplication of effort since handle_docstring might\n    # do splitting internally\n    return handle_docstring(result[0], polite=False).strip('\\n'), \\\n           textwrap.dedent(result[1]).strip('\\n'), \\\n           textwrap.dedent(result[2]).strip('\\n')\n\n\n__re_params = re.compile('(?:\\n\\S.*?)+$')\n__re_spliter1 = re.compile('(?:\\n|\\A)(?=\\S)')\n__re_spliter2 = re.compile('[\\n:]')\ndef _parse_parameters(paramdoc):\n    \"\"\"Parse parameters and return list of (name, full_doc_string)\n\n    It is needed to remove multiple entries for the same parameter\n    like it could be with adding parameters from the parent class\n\n    It assumes that previously parameters were unwrapped, so their\n    documentation starts at the begining of the string, like what\n    should it be after _split_out_parameters\n    \"\"\"\n    entries = __re_spliter1.split(paramdoc)\n    result = [(__re_spliter2.split(e)[0].strip(), e)\n              for e in entries if e != '']\n    lgr.debug('parseParameters: Given \"%s\", we split into %s' %\n              (paramdoc, result))\n    return result\n\ndef get_docstring_split(f):\n    \"\"\"Given a function, break it up into portions\n\n    Parameters\n    ----------\n    f : function\n\n    Returns\n    -------\n\n    (initial doc string, params (as list of tuples), suffix string)\n    \"\"\"\n\n    if not hasattr(f, '__doc__') or f.__doc__ in (None, \"\"):\n        return None, None, None\n    initdoc, params, suffix = _split_out_parameters(\n        f.__doc__)\n    params_list = _parse_parameters(params)\n    return initdoc, params_list, suffix\n\ndef borrowdoc(cls, methodname=None):\n    \"\"\"Return a decorator to borrow docstring from another `cls`.`methodname`\n\n    It should not be used for __init__ methods of classes derived from\n    ClassWithCollections since __doc__'s of those are handled by the\n    AttributeCollector anyways.\n\n    Common use is to borrow a docstring from the class's method for an\n    adapter function (e.g. sphere_searchlight borrows from Searchlight)\n\n    Examples\n    --------\n    To borrow `__repr__` docstring from parent class `Mapper`, do::\n\n       @borrowdoc(Mapper)\n       def __repr__(self):\n           ...\n\n    Parameters\n    ----------\n    cls\n      Usually a parent class\n    methodname : None or str\n      Name of the method from which to borrow.  If None, would use\n      the same name as of the decorated method\n    \"\"\"\n\n    def _borrowdoc(method):\n        \"\"\"Decorator which assigns to the `method` docstring from another\n        \"\"\"\n        if methodname is None:\n            other_method = getattr(cls, method.__name__)\n        else:\n            other_method = getattr(cls, methodname)\n        if hasattr(other_method, '__doc__'):\n            method.__doc__ = other_method.__doc__\n        return method\n    return _borrowdoc\n\n\ndef borrowkwargs(cls, methodname=None, exclude=None):\n    \"\"\"Return  a decorator which would borrow docstring for ``**kwargs``\n\n    Notes\n    -----\n    TODO: take care about ``*args`` in  a clever way if those are also present\n\n    Examples\n    --------\n    In the simplest scenario -- just grab all arguments from parent class::\n\n           @borrowkwargs(A)\n           def met1(self, bu, **kwargs):\n               pass\n\n    Parameters\n    ----------\n    methodname : None or str\n      Name of the method from which to borrow.  If None, would use\n      the same name as of the decorated method\n    exclude : None or list of arguments to exclude\n      If function does not pass all ``**kwargs``, you would need to list\n      those here to be excluded from borrowed docstring\n    \"\"\"\n\n    def _borrowkwargs(method):\n        \"\"\"Decorator which borrows docstrings for ``**kwargs`` for the `method`\n        \"\"\"\n        if methodname is None:\n            other_method = getattr(cls, method.__name__)\n        else:\n            other_method = getattr(cls, methodname)\n        # TODO:\n        # method.__doc__ = enhanced_from(other_method.__doc__)\n\n        mdoc, odoc = method.__doc__, other_method.__doc__\n        if mdoc is None:\n            mdoc = ''\n\n        mpreamble, mparams, msuffix = _split_out_parameters(mdoc)\n        opreamble, oparams, osuffix = _split_out_parameters(odoc)\n        mplist = _parse_parameters(mparams)\n        oplist = _parse_parameters(oparams)\n        known_params = set([i[0] for i in mplist])\n\n        # !!! has to not rebind exclude variable\n        skip_params = exclude or []         # handle None\n        skip_params = set(['kwargs', '**kwargs'] + skip_params)\n\n        # combine two and filter out items to skip\n        aplist = [i for i in mplist if not i[0] in skip_params]\n        aplist += [i for i in oplist\n                   if not i[0] in skip_params.union(known_params)]\n\n        docstring = mpreamble\n        if len(aplist):\n            params_ = '\\n'.join([i[1].rstrip() for i in aplist])\n            docstring += \"\\n\\n%s\\n\" \\\n                         % _rst_section('Parameters') + _indent(params_)\n\n        if msuffix != \"\":\n            docstring += \"\\n\\n\" + msuffix\n\n        docstring = handle_docstring(docstring)\n\n        # Finally assign generated doc to the method\n        method.__doc__ = docstring\n        return method\n    return _borrowkwargs\n\nimport os\nimport sys\nimport traceback\n# TODO: make limit respect config/environment parameter\ndef exc_str(exc=None, limit=None):\n    \"\"\"Enhanced str for exceptions.  Should include original location\n\n    Parameters\n    ----------\n    Exception to\n    \"\"\"\n    out = str(exc)\n    if limit is None:\n        # TODO: config logging.exceptions.traceback_levels = 1\n        limit = int(os.environ.get('DATALAD_EXC_STR_TBLIMIT', '1'))\n    try:\n        exctype, value, tb = sys.exc_info()\n        if not exc:\n            exc = value\n            out = str(exc)\n        # verify that it seems to be the exception we were passed\n        #assert(isinstance(exc, exctype))\n        if exc:\n            assert(exc is value)\n        entries = traceback.extract_tb(tb)\n        if entries:\n            out += \" [%s]\" % (','.join(['%s:%s:%d' % (os.path.basename(x[0]), x[2], x[1]) for x in entries[-limit:]]))\n    except:\n        return out  # To the best of our abilities\n    finally:\n        # As the bible teaches us:\n        # https://docs.python.org/2/library/sys.html#sys.exc_info\n        del tb\n    return out\n",
            "new_file_content": "# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 noet:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"Utils to help with docstrings etc.\n\nLargerly borrowed from PyMVPA (as of upstream/2.4.1-23-g170496e).  Copyright of\nthe same developers as DataLad\n\"\"\"\n\nimport logging\nimport re\nimport textwrap\n\nlgr = logging.getLogger(\"datalad.docutils\")\n\n__add_init2doc = False\n__in_ipython = False  # TODO: determine exists('running ipython env')\n\n# if ran within IPython -- might need to add doc to init\nif __in_ipython:\n    __rst_mode = False                       # either to do ReST links at all\n    # if versions['ipython'] <= '0.8.1':\n    #     __add_init2doc = True\nelse:\n    __rst_mode = True\n\n#\n# Predefine some sugarings depending on syntax convention to be used\n#\n# XXX Might need to be removed or become proper cfg parameter\n__rst_conventions = 'numpy'\nif __rst_conventions == 'epydoc':\n    _rst_sep = \"`\"\n    _rst_indentstr = \"  \"\n    def _rst_section(section_name):\n        \"\"\"Provide section heading\"\"\"\n        return \":%s:\" % section_name\nelif __rst_conventions == 'numpy':\n    _rst_sep = \"\"\n    _rst_indentstr = \"\"\n    def _rst_section(section_name):\n        \"\"\"Provide section heading\"\"\"\n        return \"%s\\n%s\" % (section_name, '-'*len(section_name))\nelse:\n    raise ValueError(\"Unknown convention %s for RST\" % __rst_conventions)\n\n\ndef _rst(s, snotrst=''):\n    \"\"\"Produce s only in __rst mode\"\"\"\n    if __rst_mode:\n        return s\n    else:\n        return snotrst\n\ndef _rst_underline(text, markup):\n    \"\"\"Add and underline RsT string matching the length of the given string.\n    \"\"\"\n    return text + '\\n' + markup * len(text)\n\n\ndef single_or_plural(single, plural, n):\n    \"\"\"Little helper to spit out single or plural version of a word.\n    \"\"\"\n    ni = int(n)\n    if ni > 1 or ni == 0:\n        # 1 forest, 2 forests, 0 forests\n        return plural\n    else:\n        return single\n\ndef handle_docstring(text, polite=True):\n    \"\"\"Take care of empty and non existing doc strings.\"\"\"\n    if text is None or not len(text):\n        if polite:\n            return '' #No documentation found. Sorry!'\n        else:\n            return ''\n    else:\n        # Problem is that first line might often have no offset, so might\n        # need to be ignored from dedent call\n        if not text.startswith(' '):\n            lines = text.split('\\n')\n            text2 = '\\n'.join(lines[1:])\n            return lines[0] + \"\\n\" + textwrap.dedent(text2)\n        else:\n            return textwrap.dedent(text)\n\n\ndef _indent(text, istr=_rst_indentstr):\n    \"\"\"Simple indenter\n    \"\"\"\n    return '\\n'.join(istr + s for s in text.split('\\n'))\n\n\n__parameters_str_re = re.compile(\"[\\n^]\\s*:?Parameters?:?\\s*\\n(:?\\s*-+\\s*\\n)?\")\n\"\"\"regexp to match :Parameter: and :Parameters: stand alone in a line\nor\nParameters\n----------\nin multiple lines\"\"\"\n\n\ndef _split_out_parameters(initdoc):\n    \"\"\"Split documentation into (header, parameters, suffix)\n\n    Parameters\n    ----------\n    initdoc : string\n      The documentation string\n    \"\"\"\n\n    # TODO: bind it to the only word in the line\n    p_res = __parameters_str_re.search(initdoc)\n    if p_res is None:\n        return initdoc, \"\", \"\"\n    else:\n        # Could have been accomplished also via re.match\n\n        # where new line is after :Parameters:\n        # parameters header index\n        ph_i = p_res.start()\n\n        # parameters body index\n        pb_i = p_res.end()\n\n        # end of parameters\n        try:\n            pe_i = initdoc.index('\\n\\n', pb_i)\n        except ValueError:\n            pe_i = len(initdoc)\n\n        result = initdoc[:ph_i].rstrip('\\n '), \\\n                 initdoc[pb_i:pe_i], initdoc[pe_i:]\n\n    # XXX a bit of duplication of effort since handle_docstring might\n    # do splitting internally\n    return handle_docstring(result[0], polite=False).strip('\\n'), \\\n           textwrap.dedent(result[1]).strip('\\n'), \\\n           textwrap.dedent(result[2]).strip('\\n')\n\n\n__re_params = re.compile('(?:\\n\\S.*?)+$')\n__re_spliter1 = re.compile('(?:\\n|\\A)(?=\\S)')\n__re_spliter2 = re.compile('[\\n:]')\ndef _parse_parameters(paramdoc):\n    \"\"\"Parse parameters and return list of (name, full_doc_string)\n\n    It is needed to remove multiple entries for the same parameter\n    like it could be with adding parameters from the parent class\n\n    It assumes that previously parameters were unwrapped, so their\n    documentation starts at the begining of the string, like what\n    should it be after _split_out_parameters\n    \"\"\"\n    entries = __re_spliter1.split(paramdoc)\n    result = [(__re_spliter2.split(e)[0].strip(), e)\n              for e in entries if e != '']\n    lgr.debug('parseParameters: Given \"%s\", we split into %s' %\n              (paramdoc, result))\n    return result\n\ndef get_docstring_split(f):\n    \"\"\"Given a function, break it up into portions\n\n    Parameters\n    ----------\n    f : function\n\n    Returns\n    -------\n\n    (initial doc string, params (as list of tuples), suffix string)\n    \"\"\"\n\n    if not hasattr(f, '__doc__') or f.__doc__ in (None, \"\"):\n        return None, None, None\n    initdoc, params, suffix = _split_out_parameters(\n        f.__doc__)\n    params_list = _parse_parameters(params)\n    return initdoc, params_list, suffix\n\ndef borrowdoc(cls, methodname=None):\n    \"\"\"Return a decorator to borrow docstring from another `cls`.`methodname`\n\n    It should not be used for __init__ methods of classes derived from\n    ClassWithCollections since __doc__'s of those are handled by the\n    AttributeCollector anyways.\n\n    Common use is to borrow a docstring from the class's method for an\n    adapter function (e.g. sphere_searchlight borrows from Searchlight)\n\n    Examples\n    --------\n    To borrow `__repr__` docstring from parent class `Mapper`, do::\n\n       @borrowdoc(Mapper)\n       def __repr__(self):\n           ...\n\n    Parameters\n    ----------\n    cls\n      Usually a parent class\n    methodname : None or str\n      Name of the method from which to borrow.  If None, would use\n      the same name as of the decorated method\n    \"\"\"\n\n    def _borrowdoc(method):\n        \"\"\"Decorator which assigns to the `method` docstring from another\n        \"\"\"\n        if methodname is None:\n            other_method = getattr(cls, method.__name__)\n        else:\n            other_method = getattr(cls, methodname)\n        if hasattr(other_method, '__doc__'):\n            method.__doc__ = other_method.__doc__\n        return method\n    return _borrowdoc\n\n\ndef borrowkwargs(cls=None, methodname=None, exclude=None):\n    \"\"\"Return  a decorator which would borrow docstring for ``**kwargs``\n\n    Notes\n    -----\n    TODO: take care about ``*args`` in  a clever way if those are also present\n\n    Examples\n    --------\n    In the simplest scenario -- just grab all arguments from parent class::\n\n           @borrowkwargs(A)\n           def met1(self, bu, **kwargs):\n               pass\n\n    Parameters\n    ----------\n    methodname : None or str\n      Name of the method from which to borrow.  If None, would use\n      the same name as of the decorated method\n    exclude : None or list of arguments to exclude\n      If function does not pass all ``**kwargs``, you would need to list\n      those here to be excluded from borrowed docstring\n    \"\"\"\n\n    def _borrowkwargs(method):\n        \"\"\"Decorator which borrows docstrings for ``**kwargs`` for the `method`\n        \"\"\"\n        if cls:\n            if methodname is None:\n                other_method = getattr(cls, method.__name__)\n            else:\n                other_method = getattr(cls, methodname)\n        elif methodname:\n            other_method = methodname\n\n        # TODO:\n        # method.__doc__ = enhanced_from(other_method.__doc__)\n\n        mdoc, odoc = method.__doc__, other_method.__doc__\n        if mdoc is None:\n            mdoc = ''\n\n        mpreamble, mparams, msuffix = _split_out_parameters(mdoc)\n        opreamble, oparams, osuffix = _split_out_parameters(odoc)\n        mplist = _parse_parameters(mparams)\n        oplist = _parse_parameters(oparams)\n        known_params = set([i[0] for i in mplist])\n\n        # !!! has to not rebind exclude variable\n        skip_params = exclude or []         # handle None\n        skip_params = set(['kwargs', '**kwargs'] + skip_params)\n\n        # combine two and filter out items to skip\n        aplist = [i for i in mplist if not i[0] in skip_params]\n        aplist += [i for i in oplist\n                   if not i[0] in skip_params.union(known_params)]\n\n        docstring = mpreamble\n        if len(aplist):\n            params_ = '\\n'.join([i[1].rstrip() for i in aplist])\n            docstring += \"\\n\\n%s\\n\" \\\n                         % _rst_section('Parameters') + _indent(params_)\n\n        if msuffix != \"\":\n            docstring += \"\\n\\n\" + msuffix\n\n        docstring = handle_docstring(docstring)\n\n        # Finally assign generated doc to the method\n        method.__doc__ = docstring\n        return method\n    return _borrowkwargs\n\nimport os\nimport sys\nimport traceback\n# TODO: make limit respect config/environment parameter\ndef exc_str(exc=None, limit=None):\n    \"\"\"Enhanced str for exceptions.  Should include original location\n\n    Parameters\n    ----------\n    Exception to\n    \"\"\"\n    out = str(exc)\n    if limit is None:\n        # TODO: config logging.exceptions.traceback_levels = 1\n        limit = int(os.environ.get('DATALAD_EXC_STR_TBLIMIT', '1'))\n    try:\n        exctype, value, tb = sys.exc_info()\n        if not exc:\n            exc = value\n            out = str(exc)\n        # verify that it seems to be the exception we were passed\n        #assert(isinstance(exc, exctype))\n        if exc:\n            assert(exc is value)\n        entries = traceback.extract_tb(tb)\n        if entries:\n            out += \" [%s]\" % (','.join(['%s:%s:%d' % (os.path.basename(x[0]), x[2], x[1]) for x in entries[-limit:]]))\n    except:\n        return out  # To the best of our abilities\n    finally:\n        # As the bible teaches us:\n        # https://docs.python.org/2/library/sys.html#sys.exc_info\n        del tb\n    return out\n",
            "header": {
                "file": {
                    "path": "datalad/dochelpers.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "d25e018c4",
                "new_commit_hash": "40f205ded",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/datalad/dochelpers.py"
            },
            "plus_file": {
                "path": "b/datalad/dochelpers.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 224,
                            "length": 7
                        },
                        "new_range": {
                            "start": 224,
                            "length": 7
                        },
                        "section": "def borrowdoc(cls, methodname=None):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    return _borrowdoc",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "def borrowkwargs(cls, methodname=None, exclude=None):",
                                "type": "deleted"
                            },
                            {
                                "content": "def borrowkwargs(cls=None, methodname=None, exclude=None):",
                                "type": "added"
                            },
                            {
                                "content": "    \"\"\"Return  a decorator which would borrow docstring for ``**kwargs``",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    Notes",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/dochelpers.py",
                            "type": "function",
                            "name": "borrowkwargs",
                            "content": "def borrowkwargs(cls, methodname=None, exclude=None):\n    \"\"\"Return  a decorator which would borrow docstring for ``**kwargs``\n\n    Notes\n    -----\n    TODO: take care about ``*args`` in  a clever way if those are also present\n\n    Examples\n    --------\n    In the simplest scenario -- just grab all arguments from parent class::\n\n           @borrowkwargs(A)\n           def met1(self, bu, **kwargs):\n               pass\n\n    Parameters\n    ----------\n    methodname : None or str\n      Name of the method from which to borrow.  If None, would use\n      the same name as of the decorated method\n    exclude : None or list of arguments to exclude\n      If function does not pass all ``**kwargs``, you would need to list\n      those here to be excluded from borrowed docstring\n    \"\"\"\n\n    def _borrowkwargs(method):\n        \"\"\"Decorator which borrows docstrings for ``**kwargs`` for the `method`\n        \"\"\"\n        if methodname is None:\n            other_method = getattr(cls, method.__name__)\n        else:\n            other_method = getattr(cls, methodname)\n        # TODO:\n        # method.__doc__ = enhanced_from(other_method.__doc__)\n\n        mdoc, odoc = method.__doc__, other_method.__doc__\n        if mdoc is None:\n            mdoc = ''\n\n        mpreamble, mparams, msuffix = _split_out_parameters(mdoc)\n        opreamble, oparams, osuffix = _split_out_parameters(odoc)\n        mplist = _parse_parameters(mparams)\n        oplist = _parse_parameters(oparams)\n        known_params = set([i[0] for i in mplist])\n\n        # !!! has to not rebind exclude variable\n        skip_params = exclude or []         # handle None\n        skip_params = set(['kwargs', '**kwargs'] + skip_params)\n\n        # combine two and filter out items to skip\n        aplist = [i for i in mplist if not i[0] in skip_params]\n        aplist += [i for i in oplist\n                   if not i[0] in skip_params.union(known_params)]\n\n        docstring = mpreamble\n        if len(aplist):\n            params_ = '\\n'.join([i[1].rstrip() for i in aplist])\n            docstring += \"\\n\\n%s\\n\" \\\n                         % _rst_section('Parameters') + _indent(params_)\n\n        if msuffix != \"\":\n            docstring += \"\\n\\n\" + msuffix\n\n        docstring = handle_docstring(docstring)\n\n        # Finally assign generated doc to the method\n        method.__doc__ = docstring\n        return method\n    return _borrowkwargs",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 227,
                            "end_lineno": 295,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 252,
                            "length": 10
                        },
                        "new_range": {
                            "start": 252,
                            "length": 14
                        },
                        "section": "def borrowkwargs(cls, methodname=None, exclude=None):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    def _borrowkwargs(method):",
                                "type": "context"
                            },
                            {
                                "content": "        \"\"\"Decorator which borrows docstrings for ``**kwargs`` for the `method`",
                                "type": "context"
                            },
                            {
                                "content": "        \"\"\"",
                                "type": "context"
                            },
                            {
                                "content": "        if methodname is None:",
                                "type": "deleted"
                            },
                            {
                                "content": "            other_method = getattr(cls, method.__name__)",
                                "type": "deleted"
                            },
                            {
                                "content": "        else:",
                                "type": "deleted"
                            },
                            {
                                "content": "            other_method = getattr(cls, methodname)",
                                "type": "deleted"
                            },
                            {
                                "content": "        if cls:",
                                "type": "added"
                            },
                            {
                                "content": "            if methodname is None:",
                                "type": "added"
                            },
                            {
                                "content": "                other_method = getattr(cls, method.__name__)",
                                "type": "added"
                            },
                            {
                                "content": "            else:",
                                "type": "added"
                            },
                            {
                                "content": "                other_method = getattr(cls, methodname)",
                                "type": "added"
                            },
                            {
                                "content": "        elif methodname:",
                                "type": "added"
                            },
                            {
                                "content": "            other_method = methodname",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "        # TODO:",
                                "type": "context"
                            },
                            {
                                "content": "        # method.__doc__ = enhanced_from(other_method.__doc__)",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/dochelpers.py",
                            "type": "function",
                            "name": "borrowkwargs",
                            "content": "def borrowkwargs(cls, methodname=None, exclude=None):\n    \"\"\"Return  a decorator which would borrow docstring for ``**kwargs``\n\n    Notes\n    -----\n    TODO: take care about ``*args`` in  a clever way if those are also present\n\n    Examples\n    --------\n    In the simplest scenario -- just grab all arguments from parent class::\n\n           @borrowkwargs(A)\n           def met1(self, bu, **kwargs):\n               pass\n\n    Parameters\n    ----------\n    methodname : None or str\n      Name of the method from which to borrow.  If None, would use\n      the same name as of the decorated method\n    exclude : None or list of arguments to exclude\n      If function does not pass all ``**kwargs``, you would need to list\n      those here to be excluded from borrowed docstring\n    \"\"\"\n\n    def _borrowkwargs(method):\n        \"\"\"Decorator which borrows docstrings for ``**kwargs`` for the `method`\n        \"\"\"\n        if methodname is None:\n            other_method = getattr(cls, method.__name__)\n        else:\n            other_method = getattr(cls, methodname)\n        # TODO:\n        # method.__doc__ = enhanced_from(other_method.__doc__)\n\n        mdoc, odoc = method.__doc__, other_method.__doc__\n        if mdoc is None:\n            mdoc = ''\n\n        mpreamble, mparams, msuffix = _split_out_parameters(mdoc)\n        opreamble, oparams, osuffix = _split_out_parameters(odoc)\n        mplist = _parse_parameters(mparams)\n        oplist = _parse_parameters(oparams)\n        known_params = set([i[0] for i in mplist])\n\n        # !!! has to not rebind exclude variable\n        skip_params = exclude or []         # handle None\n        skip_params = set(['kwargs', '**kwargs'] + skip_params)\n\n        # combine two and filter out items to skip\n        aplist = [i for i in mplist if not i[0] in skip_params]\n        aplist += [i for i in oplist\n                   if not i[0] in skip_params.union(known_params)]\n\n        docstring = mpreamble\n        if len(aplist):\n            params_ = '\\n'.join([i[1].rstrip() for i in aplist])\n            docstring += \"\\n\\n%s\\n\" \\\n                         % _rst_section('Parameters') + _indent(params_)\n\n        if msuffix != \"\":\n            docstring += \"\\n\\n\" + msuffix\n\n        docstring = handle_docstring(docstring)\n\n        # Finally assign generated doc to the method\n        method.__doc__ = docstring\n        return method\n    return _borrowkwargs",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 227,
                            "end_lineno": 295,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 noet:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"Tests for dochelpers (largely copied from PyMVPA, the same copyright)\n\"\"\"\n\nimport os\nfrom mock import patch\n\nfrom ..dochelpers import single_or_plural, borrowdoc, borrowkwargs\nfrom ..dochelpers import exc_str\n\nfrom .utils import assert_equal, assert_true\nfrom .utils import assert_re_in\n\ndef test_basic():\n    assert_equal(single_or_plural('a', 'b', 1), 'a')\n    assert_equal(single_or_plural('a', 'b', 0), 'b')\n    assert_equal(single_or_plural('a', 'b', 123), 'b')\n\n\ndef test_borrow_doc():\n\n    class A(object):\n        def met1(self):\n            \"\"\"met1doc\"\"\"\n            pass\n        def met2(self):\n            \"\"\"met2doc\"\"\"\n            pass\n\n    class B(object):\n        @borrowdoc(A)\n        def met1(self):\n            pass\n        @borrowdoc(A, 'met1')\n        def met2(self):\n            pass\n\n    assert_equal(B.met1.__doc__, A.met1.__doc__)\n    assert_equal(B.met2.__doc__, A.met1.__doc__)\n\n\ndef test_borrow_kwargs():\n\n    class A(object):\n        def met1(self, kp1=None, kp2=1):\n            \"\"\"met1 doc\n\n            Parameters\n            ----------\n            kp1 : None or int\n              keyword parameter 1\n            kp2 : int, optional\n              something\n            \"\"\"\n            pass\n\n        def met2(self):\n            \"\"\"met2doc\"\"\"\n            pass\n\n    class B(object):\n\n        @borrowkwargs(A)\n        def met1(self, bu, **kwargs):\n            \"\"\"B.met1 doc\n\n            Parameters\n            ----------\n            bu\n              description\n            **kwargs\n              Same as in A.met1\n\n            Some postamble\n            \"\"\"\n            pass\n\n        @borrowkwargs(A, 'met1')\n        def met_nodoc(self, **kwargs):\n            pass\n\n        @borrowkwargs(A, 'met1')\n        def met_nodockwargs(self, bogus=None, **kwargs):\n            \"\"\"B.met_nodockwargs\n\n            Parameters\n            ----------\n            bogus\n              something\n            \"\"\"\n            pass\n\n        if True:\n            # Just so we get different indentation level\n            @borrowkwargs(A, 'met1', ['kp1'])\n            def met_excludes(self, boguse=None, **kwargs):\n                \"\"\"B.met_excludes\n\n                Parameters\n                ----------\n                boguse\n                  something\n                \"\"\"\n                pass\n\n    assert_true('B.met1 doc' in B.met1.__doc__)\n    for m in (B.met1,\n              B.met_nodoc,\n              B.met_nodockwargs,\n              B.met_excludes):\n        docstring = m.__doc__\n        assert_true('Parameters' in docstring)\n        assert_true(not '*kwargs' in docstring,\n            msg=\"We shouldn't carry kwargs in docstring now,\"\n                \"Got %r for %s\" % (docstring, m))\n        assert_true('kp2 ' in docstring)\n        assert_true((('kp1 ' in docstring)\n                             ^ (m == B.met_excludes)))\n        # indentation should have been squashed properly\n        assert_true(not '   ' in docstring)\n\n    # some additional checks to see if we are not loosing anything\n    assert_true('Some postamble' in B.met1.__doc__)\n    assert_true('B.met_nodockwargs' in B.met_nodockwargs.__doc__)\n    assert_true('boguse' in B.met_excludes.__doc__)\n\ndef test_exc_str():\n    try:\n        raise Exception(\"my bad\")\n    except Exception as e:\n        estr = exc_str(e)\n    assert_re_in(\"my bad \\[test_dochelpers.py:test_exc_str:...\\]\", estr)\n\n    def f():\n        def f2():\n            raise Exception(\"my bad again\")\n        f2()\n    try:\n        f()\n    except Exception as e:\n        # default one:\n        estr2 = exc_str(e, 2)\n        estr1 = exc_str(e, 1)\n        # and we can control it via environ by default\n        with patch.dict('os.environ', {'DATALAD_EXC_STR_TBLIMIT': '3'}):\n            estr3 = exc_str(e)\n        with patch.dict('os.environ', {}, clear=True):\n            estr_ = exc_str()\n\n    assert_re_in(\"my bad again \\[test_dochelpers.py:test_exc_str:...,test_dochelpers.py:f:...,test_dochelpers.py:f2:...\\]\", estr3)\n    assert_re_in(\"my bad again \\[test_dochelpers.py:f:...,test_dochelpers.py:f2:...\\]\", estr2)\n    assert_re_in(\"my bad again \\[test_dochelpers.py:f2:...\\]\", estr1)\n    assert_equal(estr_, estr1)",
            "new_file_content": "# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 noet:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"Tests for dochelpers (largely copied from PyMVPA, the same copyright)\n\"\"\"\n\nimport os\nfrom mock import patch\n\nfrom ..dochelpers import single_or_plural, borrowdoc, borrowkwargs\nfrom ..dochelpers import exc_str\n\nfrom .utils import assert_equal, assert_true, assert_raises\nfrom .utils import assert_re_in\n\ndef test_basic():\n    assert_equal(single_or_plural('a', 'b', 1), 'a')\n    assert_equal(single_or_plural('a', 'b', 0), 'b')\n    assert_equal(single_or_plural('a', 'b', 123), 'b')\n\n\ndef test_borrow_doc():\n\n    class A(object):\n        def met1(self):\n            \"\"\"met1doc\"\"\"\n            pass\n        def met2(self):\n            \"\"\"met2doc\"\"\"\n            pass\n\n    class B(object):\n        @borrowdoc(A)\n        def met1(self):\n            pass\n        @borrowdoc(A, 'met1')\n        def met2(self):\n            pass\n\n    assert_equal(B.met1.__doc__, A.met1.__doc__)\n    assert_equal(B.met2.__doc__, A.met1.__doc__)\n\n\ndef test_borrow_kwargs():\n\n    class A(object):\n        def met1(self, kp1=None, kp2=1):\n            \"\"\"met1 doc\n\n            Parameters\n            ----------\n            kp1 : None or int\n              keyword parameter 1\n            kp2 : int, optional\n              something\n            \"\"\"\n            pass\n\n        def met2(self):\n            \"\"\"met2doc\"\"\"\n            pass\n\n    class B(object):\n\n        @borrowkwargs(A)\n        def met1(self, bu, **kwargs):\n            \"\"\"B.met1 doc\n\n            Parameters\n            ----------\n            bu\n              description\n            **kwargs\n              Same as in A.met1\n\n            Some postamble\n            \"\"\"\n            pass\n\n        @borrowkwargs(A, 'met1')\n        def met_nodoc(self, **kwargs):\n            pass\n\n        @borrowkwargs(methodname=A.met1)\n        def met_anothermet(self, **kwargs):\n            pass\n\n        @borrowkwargs(A, 'met1')\n        def met_nodockwargs(self, bogus=None, **kwargs):\n            \"\"\"B.met_nodockwargs\n\n            Parameters\n            ----------\n            bogus\n              something\n            \"\"\"\n            pass\n\n        if True:\n            # Just so we get different indentation level\n            @borrowkwargs(A, 'met1', ['kp1'])\n            def met_excludes(self, boguse=None, **kwargs):\n                \"\"\"B.met_excludes\n\n                Parameters\n                ----------\n                boguse\n                  something\n                \"\"\"\n                pass\n\n    assert_true('B.met1 doc' in B.met1.__doc__)\n    for m in (B.met1,\n              B.met_nodoc,\n              B.met_anothermet,\n              B.met_nodockwargs,\n              B.met_excludes):\n        docstring = m.__doc__\n        assert_true('Parameters' in docstring)\n        assert_true(not '*kwargs' in docstring,\n            msg=\"We shouldn't carry kwargs in docstring now,\"\n                \"Got %r for %s\" % (docstring, m))\n        assert_true('kp2 ' in docstring)\n        assert_true((('kp1 ' in docstring)\n                             ^ (m == B.met_excludes)))\n        # indentation should have been squashed properly\n        assert_true(not '   ' in docstring)\n\n    # some additional checks to see if we are not loosing anything\n    assert_true('Some postamble' in B.met1.__doc__)\n    assert_true('B.met_nodockwargs' in B.met_nodockwargs.__doc__)\n    assert_true('boguse' in B.met_excludes.__doc__)\n\ndef test_exc_str():\n    try:\n        raise Exception(\"my bad\")\n    except Exception as e:\n        estr = exc_str(e)\n    assert_re_in(\"my bad \\[test_dochelpers.py:test_exc_str:...\\]\", estr)\n\n    def f():\n        def f2():\n            raise Exception(\"my bad again\")\n        f2()\n    try:\n        f()\n    except Exception as e:\n        # default one:\n        estr2 = exc_str(e, 2)\n        estr1 = exc_str(e, 1)\n        # and we can control it via environ by default\n        with patch.dict('os.environ', {'DATALAD_EXC_STR_TBLIMIT': '3'}):\n            estr3 = exc_str(e)\n        with patch.dict('os.environ', {}, clear=True):\n            estr_ = exc_str()\n\n    assert_re_in(\"my bad again \\[test_dochelpers.py:test_exc_str:...,test_dochelpers.py:f:...,test_dochelpers.py:f2:...\\]\", estr3)\n    assert_re_in(\"my bad again \\[test_dochelpers.py:f:...,test_dochelpers.py:f2:...\\]\", estr2)\n    assert_re_in(\"my bad again \\[test_dochelpers.py:f2:...\\]\", estr1)\n    assert_equal(estr_, estr1)\n",
            "header": {
                "file": {
                    "path": "datalad/tests/test_dochelpers.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "445205a80",
                "new_commit_hash": "d7677e413",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/datalad/tests/test_dochelpers.py"
            },
            "plus_file": {
                "path": "b/datalad/tests/test_dochelpers.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 15,
                            "length": 7
                        },
                        "new_range": {
                            "start": 15,
                            "length": 7
                        },
                        "section": "from mock import patch"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "from ..dochelpers import single_or_plural, borrowdoc, borrowkwargs",
                                "type": "context"
                            },
                            {
                                "content": "from ..dochelpers import exc_str",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "from .utils import assert_equal, assert_true",
                                "type": "deleted"
                            },
                            {
                                "content": "from .utils import assert_equal, assert_true, assert_raises",
                                "type": "added"
                            },
                            {
                                "content": "from .utils import assert_re_in",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "def test_basic():",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [
                        {
                            "file_name": "datalad/tests/test_dochelpers.py",
                            "type": "statement",
                            "name": "",
                            "content": "from .utils import assert_equal, assert_true, assert_raises",
                            "ast_type_str": "ImportFrom",
                            "start_lineno": 18,
                            "end_lineno": 18,
                            "parent": null
                        }
                    ],
                    "deleted_entities": [
                        {
                            "file_name": "datalad/tests/test_dochelpers.py",
                            "type": "statement",
                            "name": "",
                            "content": "from .utils import assert_equal, assert_true",
                            "ast_type_str": "ImportFrom",
                            "start_lineno": 18,
                            "end_lineno": 18,
                            "parent": null
                        }
                    ]
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 86,
                            "length": 6
                        },
                        "new_range": {
                            "start": 86,
                            "length": 10
                        },
                        "section": "def test_borrow_kwargs():"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        def met_nodoc(self, **kwargs):",
                                "type": "context"
                            },
                            {
                                "content": "            pass",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        @borrowkwargs(methodname=A.met1)",
                                "type": "added"
                            },
                            {
                                "content": "        def met_anothermet(self, **kwargs):",
                                "type": "added"
                            },
                            {
                                "content": "            pass",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "        @borrowkwargs(A, 'met1')",
                                "type": "context"
                            },
                            {
                                "content": "        def met_nodockwargs(self, bogus=None, **kwargs):",
                                "type": "context"
                            },
                            {
                                "content": "            \"\"\"B.met_nodockwargs",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/tests/test_dochelpers.py",
                            "type": "function",
                            "name": "test_borrow_kwargs",
                            "content": "def test_borrow_kwargs():\n\n    class A(object):\n        def met1(self, kp1=None, kp2=1):\n            \"\"\"met1 doc\n\n            Parameters\n            ----------\n            kp1 : None or int\n              keyword parameter 1\n            kp2 : int, optional\n              something\n            \"\"\"\n            pass\n\n        def met2(self):\n            \"\"\"met2doc\"\"\"\n            pass\n\n    class B(object):\n\n        @borrowkwargs(A)\n        def met1(self, bu, **kwargs):\n            \"\"\"B.met1 doc\n\n            Parameters\n            ----------\n            bu\n              description\n            **kwargs\n              Same as in A.met1\n\n            Some postamble\n            \"\"\"\n            pass\n\n        @borrowkwargs(A, 'met1')\n        def met_nodoc(self, **kwargs):\n            pass\n\n        @borrowkwargs(methodname=A.met1)\n        def met_anothermet(self, **kwargs):\n            pass\n\n        @borrowkwargs(A, 'met1')\n        def met_nodockwargs(self, bogus=None, **kwargs):\n            \"\"\"B.met_nodockwargs\n\n            Parameters\n            ----------\n            bogus\n              something\n            \"\"\"\n            pass\n\n        if True:\n            # Just so we get different indentation level\n            @borrowkwargs(A, 'met1', ['kp1'])\n            def met_excludes(self, boguse=None, **kwargs):\n                \"\"\"B.met_excludes\n\n                Parameters\n                ----------\n                boguse\n                  something\n                \"\"\"\n                pass\n\n    assert_true('B.met1 doc' in B.met1.__doc__)\n    for m in (B.met1,\n              B.met_nodoc,\n              B.met_anothermet,\n              B.met_nodockwargs,\n              B.met_excludes):\n        docstring = m.__doc__\n        assert_true('Parameters' in docstring)\n        assert_true(not '*kwargs' in docstring,\n            msg=\"We shouldn't carry kwargs in docstring now,\"\n                \"Got %r for %s\" % (docstring, m))\n        assert_true('kp2 ' in docstring)\n        assert_true((('kp1 ' in docstring)\n                             ^ (m == B.met_excludes)))\n        # indentation should have been squashed properly\n        assert_true(not '   ' in docstring)\n\n    # some additional checks to see if we are not loosing anything\n    assert_true('Some postamble' in B.met1.__doc__)\n    assert_true('B.met_nodockwargs' in B.met_nodockwargs.__doc__)\n    assert_true('boguse' in B.met_excludes.__doc__)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 49,
                            "end_lineno": 137,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 113,
                            "length": 6
                        },
                        "new_range": {
                            "start": 117,
                            "length": 7
                        },
                        "section": "def test_borrow_kwargs():"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    assert_true('B.met1 doc' in B.met1.__doc__)",
                                "type": "context"
                            },
                            {
                                "content": "    for m in (B.met1,",
                                "type": "context"
                            },
                            {
                                "content": "              B.met_nodoc,",
                                "type": "context"
                            },
                            {
                                "content": "              B.met_anothermet,",
                                "type": "added"
                            },
                            {
                                "content": "              B.met_nodockwargs,",
                                "type": "context"
                            },
                            {
                                "content": "              B.met_excludes):",
                                "type": "context"
                            },
                            {
                                "content": "        docstring = m.__doc__",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/tests/test_dochelpers.py",
                            "type": "function",
                            "name": "test_borrow_kwargs",
                            "content": "def test_borrow_kwargs():\n\n    class A(object):\n        def met1(self, kp1=None, kp2=1):\n            \"\"\"met1 doc\n\n            Parameters\n            ----------\n            kp1 : None or int\n              keyword parameter 1\n            kp2 : int, optional\n              something\n            \"\"\"\n            pass\n\n        def met2(self):\n            \"\"\"met2doc\"\"\"\n            pass\n\n    class B(object):\n\n        @borrowkwargs(A)\n        def met1(self, bu, **kwargs):\n            \"\"\"B.met1 doc\n\n            Parameters\n            ----------\n            bu\n              description\n            **kwargs\n              Same as in A.met1\n\n            Some postamble\n            \"\"\"\n            pass\n\n        @borrowkwargs(A, 'met1')\n        def met_nodoc(self, **kwargs):\n            pass\n\n        @borrowkwargs(methodname=A.met1)\n        def met_anothermet(self, **kwargs):\n            pass\n\n        @borrowkwargs(A, 'met1')\n        def met_nodockwargs(self, bogus=None, **kwargs):\n            \"\"\"B.met_nodockwargs\n\n            Parameters\n            ----------\n            bogus\n              something\n            \"\"\"\n            pass\n\n        if True:\n            # Just so we get different indentation level\n            @borrowkwargs(A, 'met1', ['kp1'])\n            def met_excludes(self, boguse=None, **kwargs):\n                \"\"\"B.met_excludes\n\n                Parameters\n                ----------\n                boguse\n                  something\n                \"\"\"\n                pass\n\n    assert_true('B.met1 doc' in B.met1.__doc__)\n    for m in (B.met1,\n              B.met_nodoc,\n              B.met_anothermet,\n              B.met_nodockwargs,\n              B.met_excludes):\n        docstring = m.__doc__\n        assert_true('Parameters' in docstring)\n        assert_true(not '*kwargs' in docstring,\n            msg=\"We shouldn't carry kwargs in docstring now,\"\n                \"Got %r for %s\" % (docstring, m))\n        assert_true('kp2 ' in docstring)\n        assert_true((('kp1 ' in docstring)\n                             ^ (m == B.met_excludes)))\n        # indentation should have been squashed properly\n        assert_true(not '   ' in docstring)\n\n    # some additional checks to see if we are not loosing anything\n    assert_true('Some postamble' in B.met1.__doc__)\n    assert_true('B.met_nodockwargs' in B.met_nodockwargs.__doc__)\n    assert_true('boguse' in B.met_excludes.__doc__)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 49,
                            "end_lineno": 137,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 157,
                            "length": 4
                        },
                        "new_range": {
                            "start": 162,
                            "length": 4
                        },
                        "section": "def test_exc_str():"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    assert_re_in(\"my bad again \\[test_dochelpers.py:test_exc_str:...,test_dochelpers.py:f:...,test_dochelpers.py:f2:...\\]\", estr3)",
                                "type": "context"
                            },
                            {
                                "content": "    assert_re_in(\"my bad again \\[test_dochelpers.py:f:...,test_dochelpers.py:f2:...\\]\", estr2)",
                                "type": "context"
                            },
                            {
                                "content": "    assert_re_in(\"my bad again \\[test_dochelpers.py:f2:...\\]\", estr1)",
                                "type": "context"
                            },
                            {
                                "content": "    assert_equal(estr_, estr1)",
                                "type": "deleted"
                            },
                            {
                                "content": "No newline at end of file",
                                "type": "note"
                            },
                            {
                                "content": "    assert_equal(estr_, estr1)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/tests/test_dochelpers.py",
                            "type": "function",
                            "name": "test_exc_str",
                            "content": "def test_exc_str():\n    try:\n        raise Exception(\"my bad\")\n    except Exception as e:\n        estr = exc_str(e)\n    assert_re_in(\"my bad \\[test_dochelpers.py:test_exc_str:...\\]\", estr)\n\n    def f():\n        def f2():\n            raise Exception(\"my bad again\")\n        f2()\n    try:\n        f()\n    except Exception as e:\n        # default one:\n        estr2 = exc_str(e, 2)\n        estr1 = exc_str(e, 1)\n        # and we can control it via environ by default\n        with patch.dict('os.environ', {'DATALAD_EXC_STR_TBLIMIT': '3'}):\n            estr3 = exc_str(e)\n        with patch.dict('os.environ', {}, clear=True):\n            estr_ = exc_str()\n\n    assert_re_in(\"my bad again \\[test_dochelpers.py:test_exc_str:...,test_dochelpers.py:f:...,test_dochelpers.py:f2:...\\]\", estr3)\n    assert_re_in(\"my bad again \\[test_dochelpers.py:f:...,test_dochelpers.py:f2:...\\]\", estr2)\n    assert_re_in(\"my bad again \\[test_dochelpers.py:f2:...\\]\", estr1)\n    assert_equal(estr_, estr1)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 134,
                            "end_lineno": 160,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "f2029223d9f6d87b56e194794b9ffa0486546625^",
    "new_commit_hash": "f2029223d9f6d87b56e194794b9ffa0486546625",
    "commit_message": "ENH: Allow borrowkwargs to work on only method too",
    "commit_date": "2016-07-15T12:13:01-04:00",
    "metadata": {}
}
{
    "repo_name": "datalad",
    "new_commit_hash": "f2029223d9f6d87b56e194794b9ffa0486546625",
    "test_file_codes": [
        "# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 noet:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"Tests for dochelpers (largely copied from PyMVPA, the same copyright)\n\"\"\"\n\nimport os\nfrom mock import patch\n\nfrom datalad.dochelpers import single_or_plural, borrowdoc, borrowkwargs\nfrom datalad.dochelpers import exc_str\n\nfrom datalad.tests.utils import assert_equal, assert_true, assert_raises\nfrom datalad.tests.utils import assert_re_in\n\ndef test_basic():\n    assert_equal(single_or_plural('a', 'b', 1), 'a')\n    assert_equal(single_or_plural('a', 'b', 0), 'b')\n    assert_equal(single_or_plural('a', 'b', 123), 'b')\n\n\ndef test_borrow_doc():\n\n    class A(object):\n        def met1(self):\n            \"\"\"met1doc\"\"\"\n            pass\n        def met2(self):\n            \"\"\"met2doc\"\"\"\n            pass\n\n    class B(object):\n        @borrowdoc(A)\n        def met1(self):\n            pass\n        @borrowdoc(A, 'met1')\n        def met2(self):\n            pass\n\n    assert_equal(B.met1.__doc__, A.met1.__doc__)\n    assert_equal(B.met2.__doc__, A.met1.__doc__)\n\n\ndef test_borrow_kwargs():\n\n    class A(object):\n        def met1(self, kp1=None, kp2=1):\n            \"\"\"met1 doc\n\n            Parameters\n            ----------\n            kp1 : None or int\n              keyword parameter 1\n            kp2 : int, optional\n              something\n            \"\"\"\n            pass\n\n        def met2(self):\n            \"\"\"met2doc\"\"\"\n            pass\n\n    class B(object):\n\n        @borrowkwargs(A)\n        def met1(self, bu, **kwargs):\n            \"\"\"B.met1 doc\n\n            Parameters\n            ----------\n            bu\n              description\n            **kwargs\n              Same as in A.met1\n\n            Some postamble\n            \"\"\"\n            pass\n\n        @borrowkwargs(A, 'met1')\n        def met_nodoc(self, **kwargs):\n            pass\n\n        @borrowkwargs(methodname=A.met1)\n        def met_anothermet(self, **kwargs):\n            pass\n\n        @borrowkwargs(A, 'met1')\n        def met_nodockwargs(self, bogus=None, **kwargs):\n            \"\"\"B.met_nodockwargs\n\n            Parameters\n            ----------\n            bogus\n              something\n            \"\"\"\n            pass\n\n        if True:\n            # Just so we get different indentation level\n            @borrowkwargs(A, 'met1', ['kp1'])\n            def met_excludes(self, boguse=None, **kwargs):\n                \"\"\"B.met_excludes\n\n                Parameters\n                ----------\n                boguse\n                  something\n                \"\"\"\n                pass\n\n    assert_true('B.met1 doc' in B.met1.__doc__)\n    for m in (B.met1,\n              B.met_nodoc,\n              B.met_anothermet,\n              B.met_nodockwargs,\n              B.met_excludes):\n        docstring = m.__doc__\n        assert_true('Parameters' in docstring)\n        assert_true(not '*kwargs' in docstring,\n            msg=\"We shouldn't carry kwargs in docstring now,\"\n                \"Got %r for %s\" % (docstring, m))\n        assert_true('kp2 ' in docstring)\n        assert_true((('kp1 ' in docstring)\n                             ^ (m == B.met_excludes)))\n        # indentation should have been squashed properly\n        assert_true(not '   ' in docstring)\n\n    # some additional checks to see if we are not loosing anything\n    assert_true('Some postamble' in B.met1.__doc__)\n    assert_true('B.met_nodockwargs' in B.met_nodockwargs.__doc__)\n    assert_true('boguse' in B.met_excludes.__doc__)\n\ndef test_exc_str():\n    try:\n        raise Exception(\"my bad\")\n    except Exception as e:\n        estr = exc_str(e)\n    assert_re_in(\"my bad \\[test_dochelpers.py:test_exc_str:...\\]\", estr)\n\n    def f():\n        def f2():\n            raise Exception(\"my bad again\")\n        f2()\n    try:\n        f()\n    except Exception as e:\n        # default one:\n        estr2 = exc_str(e, 2)\n        estr1 = exc_str(e, 1)\n        # and we can control it via environ by default\n        with patch.dict('os.environ', {'DATALAD_EXC_STR_TBLIMIT': '3'}):\n            estr3 = exc_str(e)\n        with patch.dict('os.environ', {}, clear=True):\n            estr_ = exc_str()\n\n    assert_re_in(\"my bad again \\[test_dochelpers.py:test_exc_str:...,test_dochelpers.py:f:...,test_dochelpers.py:f2:...\\]\", estr3)\n    assert_re_in(\"my bad again \\[test_dochelpers.py:f:...,test_dochelpers.py:f2:...\\]\", estr2)\n    assert_re_in(\"my bad again \\[test_dochelpers.py:f2:...\\]\", estr1)\n    assert_equal(estr_, estr1)\n",
        "import pytest\nimport tempfile\nimport os\n\n\n@pytest.fixture\ndef path():\n    with tempfile.TemporaryDirectory() as tmpdir:\n        yield tmpdir\n\n\n@pytest.fixture(autouse=True)\ndef setup_git_config():\n    with tempfile.TemporaryDirectory() as home:\n        old_home = os.environ.get(\"HOME\")\n        os.environ[\"HOME\"] = home\n        os.system('git config --global user.name \"DataLad Tester\"')\n        os.system('git config --global user.email \"test@example.com\"')\n        yield\n        if old_home:\n            os.environ[\"HOME\"] = old_home\n"
    ],
    "test_file_names": [
        "test_1.py",
        "conftest.py"
    ],
    "setup_res_code": 0,
    "setup_res_stdout": "Starting Datalad installation attempts...\nVerifying installation...\n❌ Verification failed\nPython 3.9 installation failed, trying Python 3.7...\nVerifying installation...\nDatalad version: 0.2.3\n✅ Installation successful!\nSuccessfully installed Datalad using Python 3.7\n",
    "setup_res_stderr": "Using CPython 3.9.21\nCreating virtual environment at: .venv\nActivate with: source .venv/bin/activate\nResolved 25 packages in 109ms\nInstalled 25 packages in 5.12s\n + coverage==7.6.10\n + docopt==0.6.2\n + exceptiongroup==1.2.2\n + grabbit==0.2.6\n + importlib-resources==6.4.5\n + iniconfig==2.0.0\n + nibabel==5.3.2\n + num2words==0.5.14\n + numpy==2.0.2\n + packaging==24.2\n + pandas==2.2.3\n + patsy==1.0.1\n + pluggy==1.5.0\n + pybids==0.6.5\n + pytest==8.3.4\n + pytest-cov==6.0.0\n + python-dateutil==2.9.0.post0\n + pytz==2024.2\n + scipy==1.13.1\n + setuptools==75.6.0\n + six==1.17.0\n + tomli==2.2.1\n + typing-extensions==4.12.2\n + tzdata==2024.2\n + zipp==3.21.0\nerror: The build backend returned an error\n  Caused by: Call to `setuptools.build_meta:__legacy__.build_editable` failed (exit status: 1)\n\n[stderr]\nTraceback (most recent call last):\n  File \"<string>\", line 14, in <module>\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmpIOgFWe/lib/python3.9/site-packages/setuptools/build_meta.py\", line 483, in get_requires_for_build_editable\n    return self.get_requires_for_build_wheel(config_settings)\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmpIOgFWe/lib/python3.9/site-packages/setuptools/build_meta.py\", line 334, in get_requires_for_build_wheel\n    return self._get_build_requires(config_settings, requirements=[])\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmpIOgFWe/lib/python3.9/site-packages/setuptools/build_meta.py\", line 304, in _get_build_requires\n    self.run_setup()\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmpIOgFWe/lib/python3.9/site-packages/setuptools/build_meta.py\", line 522, in run_setup\n    super().run_setup(setup_script=setup_script)\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmpIOgFWe/lib/python3.9/site-packages/setuptools/build_meta.py\", line 320, in run_setup\n    exec(code, locals())\n  File \"<string>\", line 39, in <module>\nAttributeError: module 'platform' has no attribute 'dist'\n\nhint: This usually indicates a problem with the package or the build environment.\nerror: The build backend returned an error\n  Caused by: Call to `setuptools.build_meta:__legacy__.build_editable` failed (exit status: 1)\n\n[stderr]\nTraceback (most recent call last):\n  File \"<string>\", line 14, in <module>\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmpkGpi8g/lib/python3.9/site-packages/setuptools/build_meta.py\", line 483, in get_requires_for_build_editable\n    return self.get_requires_for_build_wheel(config_settings)\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmpkGpi8g/lib/python3.9/site-packages/setuptools/build_meta.py\", line 334, in get_requires_for_build_wheel\n    return self._get_build_requires(config_settings, requirements=[])\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmpkGpi8g/lib/python3.9/site-packages/setuptools/build_meta.py\", line 304, in _get_build_requires\n    self.run_setup()\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmpkGpi8g/lib/python3.9/site-packages/setuptools/build_meta.py\", line 522, in run_setup\n    super().run_setup(setup_script=setup_script)\n  File \"/home/gcpuser/.cache/uv/builds-v0/.tmpkGpi8g/lib/python3.9/site-packages/setuptools/build_meta.py\", line 320, in run_setup\n    exec(code, locals())\n  File \"<string>\", line 39, in <module>\nAttributeError: module 'platform' has no attribute 'dist'\n\nhint: This usually indicates a problem with the package or the build environment.\nTraceback (most recent call last):\n  File \"<string>\", line 1, in <module>\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_f2029223d9f6d87b56e194794b9ffa0486546625/datalad/__init__.py\", line 20, in <module>\n    from .config import ConfigManager\n  File \"/home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_f2029223d9f6d87b56e194794b9ffa0486546625/datalad/config.py\", line 17, in <module>\n    from appdirs import AppDirs\nModuleNotFoundError: No module named 'appdirs'\nUsing CPython 3.7.9\nCreating virtual environment at: .venv\nActivate with: source .venv/bin/activate\nResolved 26 packages in 205ms\nInstalled 26 packages in 6.13s\n + bids==0.0\n + coverage==7.2.7\n + docopt==0.6.2\n + exceptiongroup==1.2.2\n + fasteners==0.19\n + grabbit==0.2.6\n + importlib-metadata==6.7.0\n + iniconfig==2.0.0\n + nibabel==4.0.2\n + num2words==0.5.14\n + numpy==1.21.6\n + packaging==24.0\n + pandas==1.3.5\n + patsy==1.0.1\n + pluggy==1.2.0\n + pybids==0.6.5\n + pytest==7.4.4\n + pytest-cov==4.1.0\n + python-dateutil==2.9.0.post0\n + pytz==2024.2\n + scipy==1.7.3\n + setuptools==68.0.0\n + six==1.17.0\n + tomli==2.0.1\n + typing-extensions==4.7.1\n + zipp==3.15.0\nResolved 68 packages in 2.67s\nPrepared 1 package in 1.07s\nInstalled 62 packages in 77ms\n + appdirs==1.4.4\n + attrs==24.2.0\n + automat==22.10.0\n + beautifulsoup4==4.12.3\n + boto==2.49.0\n + certifi==2024.12.14\n + cffi==1.15.1\n + charset-normalizer==3.4.1\n + constantly==15.1.0\n + cryptography==44.0.0\n + cssselect==1.2.0\n + datalad==0.2.3 (from file:///home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_f2029223d9f6d87b56e194794b9ffa0486546625)\n + filelock==3.12.2\n + gitdb==4.0.12\n + gitpython==3.1.44\n + httpretty==1.1.4\n + humanize==4.6.0\n + hyperlink==21.0.0\n + idna==3.10\n + importlib-resources==5.12.0\n + incremental==22.10.0\n + iso8601==2.1.0\n + itemadapter==0.8.0\n + itemloaders==1.1.0\n + jaraco-classes==3.2.3\n + jeepney==0.8.0\n + jmespath==1.0.1\n + keyring==24.1.1\n + keyrings-alt==4.2.0\n + lxml==5.3.0\n + mock==5.1.0\n + more-itertools==9.1.0\n + msgpack-python==0.5.6\n + multidict==6.0.5\n + nose==1.3.7\n + parsel==1.8.1\n + patool==1.12\n + protego==0.3.0\n + pyasn1==0.5.1\n + pyasn1-modules==0.3.0\n + pycparser==2.21\n + pydispatcher==2.0.7\n + pyopenssl==24.3.0\n + pyyaml==6.0.1\n + queuelib==1.6.2\n + requests==2.31.0\n + requests-file==2.1.0\n + scrapy==2.9.0\n + secretstorage==3.3.3\n + service-identity==21.1.0\n + smmap==5.0.2\n + soupsieve==2.4.1\n + testtools==2.7.1\n + tldextract==4.0.0\n + tqdm==4.67.1\n + twisted==23.8.0\n + urllib3==2.0.7\n + vcrpy==4.3.0\n + w3lib==2.1.2\n + wrapt==1.16.0\n + yarl==1.9.4\n + zope-interface==6.4.post2\nAudited 1 package in 13ms\n/home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_f2029223d9f6d87b56e194794b9ffa0486546625/.venv/lib/python3.7/site-packages/urllib3/__init__.py:38: NotOpenSSLWarning: urllib3 v2.0 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'LibreSSL 3.1.4'. See: https://github.com/urllib3/urllib3/issues/3020\n  exceptions.NotOpenSSLWarning,\n",
    "new_commit_res_code": 1,
    "new_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.7.9, pytest-7.4.4, pluggy-1.2.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_f2029223d9f6d87b56e194794b9ffa0486546625\nplugins: cov-4.1.0\ncollected 4 items\n\nr2e_tests/test_1.py ...F                                                 [100%]\n\n=================================== FAILURES ===================================\n_________________________________ test_exc_str _________________________________\n\n    def test_exc_str():\n        try:\n            raise Exception(\"my bad\")\n        except Exception as e:\n            estr = exc_str(e)\n>       assert_re_in(\"my bad \\[test_dochelpers.py:test_exc_str:...\\]\", estr)\n\nr2e_tests/test_1.py:144: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nregex = 'my bad \\\\[test_dochelpers.py:test_exc_str:...\\\\]'\nc = ['my bad [test_1.py:test_exc_str:141]'], flags = 0\n\n    def assert_re_in(regex, c, flags=0):\n        \"\"\"Assert that container (list, str, etc) contains entry matching the regex\n        \"\"\"\n        if not isinstance(c, (list, tuple)):\n            c = [c]\n        for e in c:\n            if re.match(regex, e, flags=flags):\n                return\n>       raise AssertionError(\"Not a single entry matched %r in %r\" % (regex, c))\nE       AssertionError: Not a single entry matched 'my bad \\\\[test_dochelpers.py:test_exc_str:...\\\\]' in ['my bad [test_1.py:test_exc_str:141]']\n\ndatalad/tests/utils.py:820: AssertionError\n==================================== PASSES ====================================\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::test_basic\nPASSED r2e_tests/test_1.py::test_borrow_doc\nPASSED r2e_tests/test_1.py::test_borrow_kwargs\nFAILED r2e_tests/test_1.py::test_exc_str - AssertionError: Not a single entry...\n========================= 1 failed, 3 passed in 1.08s ==========================\n",
    "new_commit_res_stderr": "",
    "old_commit_res_code": 1,
    "old_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.7.9, pytest-7.4.4, pluggy-1.2.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_f2029223d9f6d87b56e194794b9ffa0486546625\nplugins: cov-4.1.0\ncollected 4 items\n\nr2e_tests/test_1.py ..FF                                                 [100%]\n\n=================================== FAILURES ===================================\n______________________________ test_borrow_kwargs ______________________________\n\n    def test_borrow_kwargs():\n    \n        class A(object):\n            def met1(self, kp1=None, kp2=1):\n                \"\"\"met1 doc\n    \n                Parameters\n                ----------\n                kp1 : None or int\n                  keyword parameter 1\n                kp2 : int, optional\n                  something\n                \"\"\"\n                pass\n    \n            def met2(self):\n                \"\"\"met2doc\"\"\"\n                pass\n    \n>       class B(object):\n\nr2e_tests/test_1.py:68: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\n    class B(object):\n    \n        @borrowkwargs(A)\n        def met1(self, bu, **kwargs):\n            \"\"\"B.met1 doc\n    \n            Parameters\n            ----------\n            bu\n              description\n            **kwargs\n              Same as in A.met1\n    \n            Some postamble\n            \"\"\"\n            pass\n    \n        @borrowkwargs(A, 'met1')\n        def met_nodoc(self, **kwargs):\n            pass\n    \n>       @borrowkwargs(methodname=A.met1)\n        def met_anothermet(self, **kwargs):\nE       TypeError: borrowkwargs() missing 1 required positional argument: 'cls'\n\nr2e_tests/test_1.py:89: TypeError\n_________________________________ test_exc_str _________________________________\n\n    def test_exc_str():\n        try:\n            raise Exception(\"my bad\")\n        except Exception as e:\n            estr = exc_str(e)\n>       assert_re_in(\"my bad \\[test_dochelpers.py:test_exc_str:...\\]\", estr)\n\nr2e_tests/test_1.py:144: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nregex = 'my bad \\\\[test_dochelpers.py:test_exc_str:...\\\\]'\nc = ['my bad [test_1.py:test_exc_str:141]'], flags = 0\n\n    def assert_re_in(regex, c, flags=0):\n        \"\"\"Assert that container (list, str, etc) contains entry matching the regex\n        \"\"\"\n        if not isinstance(c, (list, tuple)):\n            c = [c]\n        for e in c:\n            if re.match(regex, e, flags=flags):\n                return\n>       raise AssertionError(\"Not a single entry matched %r in %r\" % (regex, c))\nE       AssertionError: Not a single entry matched 'my bad \\\\[test_dochelpers.py:test_exc_str:...\\\\]' in ['my bad [test_1.py:test_exc_str:141]']\n\ndatalad/tests/utils.py:820: AssertionError\n==================================== PASSES ====================================\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::test_basic\nPASSED r2e_tests/test_1.py::test_borrow_doc\nFAILED r2e_tests/test_1.py::test_borrow_kwargs - TypeError: borrowkwargs() mi...\nFAILED r2e_tests/test_1.py::test_exc_str - AssertionError: Not a single entry...\n========================= 2 failed, 2 passed in 0.50s ==========================\n",
    "old_commit_res_stderr": ""
}
datalad/dochelpers.py datalad/tests/test_dochelpers.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
**Analysis**

- **Key Parts of Commit Details and Test Results:**
  - **Commit Message:** "ENH: Allow borrowkwargs to work on only method too."
  - **Change in `borrowkwargs` Definition:** Made the `cls` parameter optional by setting `cls=None`.
  - **Test Results:** Previously, `test_borrow_kwargs` failed with a `TypeError` stating that the `borrowkwargs` function was missing the `cls` argument. After the commit, this test passes, indicating that the change fixed the issue where `cls` was required.

- **Main Problem Highlighted by Test Results:**
  - The `borrowkwargs` decorator initially required the `cls` argument, causing tests to fail when it wasn't provided. This made the decorator less flexible and harder to use in certain scenarios.

- **Expected Behavior:**
  - The `borrowkwargs` decorator should work both with and without the `cls` argument, allowing developers to use it flexibly depending on their needs.

- **Actual Behavior:**
  - When the `cls` argument was not provided, using the `borrowkwargs` decorator resulted in a `TypeError` stating that the `cls` argument was missing.

- **Issue Description:**
  - When using the `borrowkwargs` decorator without specifying the `cls` argument, it raises a `TypeError`. This restricts the decorator's flexibility and usability in scenarios where the class reference isn't necessary or available.

---

**Drafted GitHub Issue**

[ISSUE]

**Title:** `borrowkwargs` Decorator Requires `cls` Argument, Causing TypeError When Unspecified

**Description:**
When using the `borrowkwargs` decorator without providing the `cls` argument, a `TypeError` is raised indicating that the `cls` parameter is missing. This limitation restricts the decorator's flexibility, making it cumbersome to use in cases where the class reference isn't needed.

**Example Code:**
```python
class A:
    def met1(self, kp1=None, kp2=1):
        pass

class B:
    @borrowkwargs(methodname=A.met1)
    def met_anothermet(self, **kwargs):
        pass
```

**Expected Behavior:**
The `borrowkwargs` decorator should function correctly without requiring the `cls` argument, allowing it to borrow keyword arguments from the specified method seamlessly.

**Actual Behavior:**
Using the decorator without the `cls` argument results in the following error:
```
TypeError: borrowkwargs() missing 1 required positional argument: 'cls'
```

[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### datalad/dochelpers.py
# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-
# ex: set sts=4 ts=4 sw=4 noet:
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
#
#   See COPYING file distributed along with the datalad package for the
#   copyright and license terms.
#
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
"""Utils to help with docstrings etc.

Largerly borrowed from PyMVPA (as of upstream/2.4.1-23-g170496e).  Copyright of
the same developers as DataLad
"""

import logging
import re
import textwrap

lgr = logging.getLogger("datalad.docutils")

__add_init2doc = False
__in_ipython = False  # TODO: determine exists('running ipython env')

# if ran within IPython -- might need to add doc to init
if __in_ipython:
    __rst_mode = False                       # either to do ReST links at all
    # if versions['ipython'] <= '0.8.1':
    #     __add_init2doc = True
else:
    __rst_mode = True

#
# Predefine some sugarings depending on syntax convention to be used
#
# XXX Might need to be removed or become proper cfg parameter
__rst_conventions = 'numpy'
if __rst_conventions == 'epydoc':
    _rst_sep = "`"
    _rst_indentstr = "  "
    def _rst_section(section_name):
        """Provide section heading"""
        return ":%s:" % section_name
elif __rst_conventions == 'numpy':
    _rst_sep = ""
    _rst_indentstr = ""
    def _rst_section(section_name):
        """Provide section heading"""
        return "%s\n%s" % (section_name, '-'*len(section_name))
else:
    raise ValueError("Unknown convention %s for RST" % __rst_conventions)


def _rst(s, snotrst=''):
    """Produce s only in __rst mode"""
    if __rst_mode:
        return s
    else:
        return snotrst

def _rst_underline(text, markup):
    """Add and underline RsT string matching the length of the given string.
    """
    return text + '\n' + markup * len(text)


def single_or_plural(single, plural, n):
    """Little helper to spit out single or plural version of a word.
    """
    ni = int(n)
    if ni > 1 or ni == 0:
        # 1 forest, 2 forests, 0 forests
        return plural
    else:
        return single

def handle_docstring(text, polite=True):
    """Take care of empty and non existing doc strings."""
    if text is None or not len(text):
        if polite:
            return '' #No documentation found. Sorry!'
        else:
            return ''
    else:
        # Problem is that first line might often have no offset, so might
        # need to be ignored from dedent call
        if not text.startswith(' '):
            lines = text.split('\n')
            text2 = '\n'.join(lines[1:])
            return lines[0] + "\n" + textwrap.dedent(text2)
        else:
            return textwrap.dedent(text)


def _indent(text, istr=_rst_indentstr):
    """Simple indenter
    """
    return '\n'.join(istr + s for s in text.split('\n'))


__parameters_str_re = re.compile("[\n^]\s*:?Parameters?:?\s*\n(:?\s*-+\s*\n)?")
"""regexp to match :Parameter: and :Parameters: stand alone in a line
or
Parameters
----------
in multiple lines"""


def _split_out_parameters(initdoc):
    """Split documentation into (header, parameters, suffix)

    Parameters
    ----------
    initdoc : string
      The documentation string
    """

    # TODO: bind it to the only word in the line
    p_res = __parameters_str_re.search(initdoc)
    if p_res is None:
        return initdoc, "", ""
    else:
        # Could have been accomplished also via re.match

        # where new line is after :Parameters:
        # parameters header index
        ph_i = p_res.start()

        # parameters body index
        pb_i = p_res.end()

        # end of parameters
        try:
            pe_i = initdoc.index('\n\n', pb_i)
        except ValueError:
            pe_i = len(initdoc)

        result = initdoc[:ph_i].rstrip('\n '), \
                 initdoc[pb_i:pe_i], initdoc[pe_i:]

    # XXX a bit of duplication of effort since handle_docstring might
    # do splitting internally
    return handle_docstring(result[0], polite=False).strip('\n'), \
           textwrap.dedent(result[1]).strip('\n'), \
           textwrap.dedent(result[2]).strip('\n')


__re_params = re.compile('(?:\n\S.*?)+$')
__re_spliter1 = re.compile('(?:\n|\A)(?=\S)')
__re_spliter2 = re.compile('[\n:]')
def _parse_parameters(paramdoc):
    """Parse parameters and return list of (name, full_doc_string)

    It is needed to remove multiple entries for the same parameter
    like it could be with adding parameters from the parent class

    It assumes that previously parameters were unwrapped, so their
    documentation starts at the begining of the string, like what
    should it be after _split_out_parameters
    """
    entries = __re_spliter1.split(paramdoc)
    result = [(__re_spliter2.split(e)[0].strip(), e)
              for e in entries if e != '']
    lgr.debug('parseParameters: Given "%s", we split into %s' %
              (paramdoc, result))
    return result

def get_docstring_split(f):
    """Given a function, break it up into portions

    Parameters
    ----------
    f : function

    Returns
    -------

    (initial doc string, params (as list of tuples), suffix string)
    """

    if not hasattr(f, '__doc__') or f.__doc__ in (None, ""):
        return None, None, None
    initdoc, params, suffix = _split_out_parameters(
        f.__doc__)
    params_list = _parse_parameters(params)
    return initdoc, params_list, suffix

def borrowdoc(cls, methodname=None):
    """Return a decorator to borrow docstring from another `cls`.`methodname`

    It should not be used for __init__ methods of classes derived from
    ClassWithCollections since __doc__'s of those are handled by the
    AttributeCollector anyways.

    Common use is to borrow a docstring from the class's method for an
    adapter function (e.g. sphere_searchlight borrows from Searchlight)

    Examples
    --------
    To borrow `__repr__` docstring from parent class `Mapper`, do::

       @borrowdoc(Mapper)
       def __repr__(self):
           ...

    Parameters
    ----------
    cls
      Usually a parent class
    methodname : None or str
      Name of the method from which to borrow.  If None, would use
      the same name as of the decorated method
    """

    def _borrowdoc(method):
        """Decorator which assigns to the `method` docstring from another
        """
        if methodname is None:
            other_method = getattr(cls, method.__name__)
        else:
            other_method = getattr(cls, methodname)
        if hasattr(other_method, '__doc__'):
            method.__doc__ = other_method.__doc__
        return method
    return _borrowdoc


def borrowkwargs(cls, methodname=None, exclude=None):
    """Return  a decorator which would borrow docstring for ``**kwargs``

    Notes
    -----
    TODO: take care about ``*args`` in  a clever way if those are also present

    Examples
    --------
    In the simplest scenario -- just grab all arguments from parent class::

           @borrowkwargs(A)
           def met1(self, bu, **kwargs):
               pass

    Parameters
    ----------
    methodname : None or str
      Name of the method from which to borrow.  If None, would use
      the same name as of the decorated method
    exclude : None or list of arguments to exclude
      If function does not pass all ``**kwargs``, you would need to list
      those here to be excluded from borrowed docstring
    """

    def _borrowkwargs(method):
        """Decorator which borrows docstrings for ``**kwargs`` for the `method`
        """
        if methodname is None:
            other_method = getattr(cls, method.__name__)
        else:
            other_method = getattr(cls, methodname)
        # TODO:
        # method.__doc__ = enhanced_from(other_method.__doc__)

        mdoc, odoc = method.__doc__, other_method.__doc__
        if mdoc is None:
            mdoc = ''

        mpreamble, mparams, msuffix = _split_out_parameters(mdoc)
        opreamble, oparams, osuffix = _split_out_parameters(odoc)
        mplist = _parse_parameters(mparams)
        oplist = _parse_parameters(oparams)
        known_params = set([i[0] for i in mplist])

        # !!! has to not rebind exclude variable
        skip_params = exclude or []         # handle None
        skip_params = set(['kwargs', '**kwargs'] + skip_params)

        # combine two and filter out items to skip
        aplist = [i for i in mplist if not i[0] in skip_params]
        aplist += [i for i in oplist
                   if not i[0] in skip_params.union(known_params)]

        docstring = mpreamble
        if len(aplist):
            params_ = '\n'.join([i[1].rstrip() for i in aplist])
            docstring += "\n\n%s\n" \
                         % _rst_section('Parameters') + _indent(params_)

        if msuffix != "":
            docstring += "\n\n" + msuffix

        docstring = handle_docstring(docstring)

        # Finally assign generated doc to the method
        method.__doc__ = docstring
        return method
    return _borrowkwargs

import os
import sys
import traceback
# TODO: make limit respect config/environment parameter
def exc_str(exc=None, limit=None):
    """Enhanced str for exceptions.  Should include original location

    Parameters
    ----------
    Exception to
    """
    out = str(exc)
    if limit is None:
        # TODO: config logging.exceptions.traceback_levels = 1
        limit = int(os.environ.get('DATALAD_EXC_STR_TBLIMIT', '1'))
    try:
        exctype, value, tb = sys.exc_info()
        if not exc:
            exc = value
            out = str(exc)
        # verify that it seems to be the exception we were passed
        #assert(isinstance(exc, exctype))
        if exc:
            assert(exc is value)
        entries = traceback.extract_tb(tb)
        if entries:
            out += " [%s]" % (','.join(['%s:%s:%d' % (os.path.basename(x[0]), x[2], x[1]) for x in entries[-limit:]]))
    except:
        return out  # To the best of our abilities
    finally:
        # As the bible teaches us:
        # https://docs.python.org/2/library/sys.html#sys.exc_info
        del tb
    return out

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
**Analysis**

- **Key Parts of Commit Details and Test Results:**
  - **Commit Message:** "ENH: Allow borrowkwargs to work on only method too."
  - **Change in `borrowkwargs` Definition:** Made the `cls` parameter optional by setting `cls=None`.
  - **Test Results:** Previously, `test_borrow_kwargs` failed with a `TypeError` stating that the `borrowkwargs` function was missing the `cls` argument. After the commit, this test passes, indicating that the change fixed the issue where `cls` was required.

- **Main Problem Highlighted by Test Results:**
  - The `borrowkwargs` decorator initially required the `cls` argument, causing tests to fail when it wasn't provided. This made the decorator less flexible and harder to use in certain scenarios.

- **Expected Behavior:**
  - The `borrowkwargs` decorator should work both with and without the `cls` argument, allowing developers to use it flexibly depending on their needs.

- **Actual Behavior:**
  - When the `cls` argument was not provided, using the `borrowkwargs` decorator resulted in a `TypeError` stating that the `cls` argument was missing.

- **Issue Description:**
  - When using the `borrowkwargs` decorator without specifying the `cls` argument, it raises a `TypeError`. This restricts the decorator's flexibility and usability in scenarios where the class reference isn't necessary or available.

---

**Drafted GitHub Issue**

[ISSUE]

**Title:** `borrowkwargs` Decorator Requires `cls` Argument, Causing TypeError When Unspecified

**Description:**
When using the `borrowkwargs` decorator without providing the `cls` argument, a `TypeError` is raised indicating that the `cls` parameter is missing. This limitation restricts the decorator's flexibility, making it cumbersome to use in cases where the class reference isn't needed.

**Example Code:**
```python
class A:
    def met1(self, kp1=None, kp2=1):
        pass

class B:
    @borrowkwargs(methodname=A.met1)
    def met_anothermet(self, **kwargs):
        pass
```

**Expected Behavior:**
The `borrowkwargs` decorator should function correctly without requiring the `cls` argument, allowing it to borrow keyword arguments from the specified method seamlessly.

**Actual Behavior:**
Using the decorator without the `cls` argument results in the following error:
```
TypeError: borrowkwargs() missing 1 required positional argument: 'cls'
```

[/ISSUE]
{
    "test_basic": "PASSED",
    "test_borrow_doc": "PASSED",
    "test_borrow_kwargs": "PASSED",
    "test_exc_str": "FAILED"
}
diff --git a/datalad/dochelpers.py b/datalad/dochelpers.py
index d25e018c4..40f205ded 100644
--- a/datalad/dochelpers.py
+++ b/datalad/dochelpers.py
@@ -224,7 +224,7 @@ def borrowdoc(cls, methodname=None):
     return _borrowdoc
 
 
-def borrowkwargs(cls, methodname=None, exclude=None):
+def borrowkwargs(cls=None, methodname=None, exclude=None):
     """Return  a decorator which would borrow docstring for ``**kwargs``
 
     Notes
@@ -252,10 +252,14 @@ def borrowkwargs(cls, methodname=None, exclude=None):
     def _borrowkwargs(method):
         """Decorator which borrows docstrings for ``**kwargs`` for the `method`
         """
-        if methodname is None:
-            other_method = getattr(cls, method.__name__)
-        else:
-            other_method = getattr(cls, methodname)
+        if cls:
+            if methodname is None:
+                other_method = getattr(cls, method.__name__)
+            else:
+                other_method = getattr(cls, methodname)
+        elif methodname:
+            other_method = methodname
+
         # TODO:
         # method.__doc__ = enhanced_from(other_method.__doc__)
 

{"datalad/dochelpers.py": "# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 noet:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"Utils to help with docstrings etc.\n\nLargerly borrowed from PyMVPA (as of upstream/2.4.1-23-g170496e).  Copyright of\nthe same developers as DataLad\n\"\"\"\n\nimport logging\nimport re\nimport textwrap\n\nlgr = logging.getLogger(\"datalad.docutils\")\n\n__add_init2doc = False\n__in_ipython = False  # TODO: determine exists('running ipython env')\n\n# if ran within IPython -- might need to add doc to init\nif __in_ipython:\n    __rst_mode = False                       # either to do ReST links at all\n    # if versions['ipython'] <= '0.8.1':\n    #     __add_init2doc = True\nelse:\n    __rst_mode = True\n\n#\n# Predefine some sugarings depending on syntax convention to be used\n#\n# XXX Might need to be removed or become proper cfg parameter\n__rst_conventions = 'numpy'\nif __rst_conventions == 'epydoc':\n    _rst_sep = \"`\"\n    _rst_indentstr = \"  \"\n    def _rst_section(section_name):\n        \"\"\"Provide section heading\"\"\"\n        return \":%s:\" % section_name\nelif __rst_conventions == 'numpy':\n    _rst_sep = \"\"\n    _rst_indentstr = \"\"\n    def _rst_section(section_name):\n        \"\"\"Provide section heading\"\"\"\n        return \"%s\\n%s\" % (section_name, '-'*len(section_name))\nelse:\n    raise ValueError(\"Unknown convention %s for RST\" % __rst_conventions)\n\n\ndef _rst(s, snotrst=''):\n    \"\"\"Produce s only in __rst mode\"\"\"\n    if __rst_mode:\n        return s\n    else:\n        return snotrst\n\ndef _rst_underline(text, markup):\n    \"\"\"Add and underline RsT string matching the length of the given string.\n    \"\"\"\n    return text + '\\n' + markup * len(text)\n\n\ndef single_or_plural(single, plural, n):\n    \"\"\"Little helper to spit out single or plural version of a word.\n    \"\"\"\n    ni = int(n)\n    if ni > 1 or ni == 0:\n        # 1 forest, 2 forests, 0 forests\n        return plural\n    else:\n        return single\n\ndef handle_docstring(text, polite=True):\n    \"\"\"Take care of empty and non existing doc strings.\"\"\"\n    if text is None or not len(text):\n        if polite:\n            return '' #No documentation found. Sorry!'\n        else:\n            return ''\n    else:\n        # Problem is that first line might often have no offset, so might\n        # need to be ignored from dedent call\n        if not text.startswith(' '):\n            lines = text.split('\\n')\n            text2 = '\\n'.join(lines[1:])\n            return lines[0] + \"\\n\" + textwrap.dedent(text2)\n        else:\n            return textwrap.dedent(text)\n\n\ndef _indent(text, istr=_rst_indentstr):\n    \"\"\"Simple indenter\n    \"\"\"\n    return '\\n'.join(istr + s for s in text.split('\\n'))\n\n\n__parameters_str_re = re.compile(\"[\\n^]\\s*:?Parameters?:?\\s*\\n(:?\\s*-+\\s*\\n)?\")\n\"\"\"regexp to match :Parameter: and :Parameters: stand alone in a line\nor\nParameters\n----------\nin multiple lines\"\"\"\n\n\ndef _split_out_parameters(initdoc):\n    \"\"\"Split documentation into (header, parameters, suffix)\n\n    Parameters\n    ----------\n    initdoc : string\n      The documentation string\n    \"\"\"\n\n    # TODO: bind it to the only word in the line\n    p_res = __parameters_str_re.search(initdoc)\n    if p_res is None:\n        return initdoc, \"\", \"\"\n    else:\n        # Could have been accomplished also via re.match\n\n        # where new line is after :Parameters:\n        # parameters header index\n        ph_i = p_res.start()\n\n        # parameters body index\n        pb_i = p_res.end()\n\n        # end of parameters\n        try:\n            pe_i = initdoc.index('\\n\\n', pb_i)\n        except ValueError:\n            pe_i = len(initdoc)\n\n        result = initdoc[:ph_i].rstrip('\\n '), \\\n                 initdoc[pb_i:pe_i], initdoc[pe_i:]\n\n    # XXX a bit of duplication of effort since handle_docstring might\n    # do splitting internally\n    return handle_docstring(result[0], polite=False).strip('\\n'), \\\n           textwrap.dedent(result[1]).strip('\\n'), \\\n           textwrap.dedent(result[2]).strip('\\n')\n\n\n__re_params = re.compile('(?:\\n\\S.*?)+$')\n__re_spliter1 = re.compile('(?:\\n|\\A)(?=\\S)')\n__re_spliter2 = re.compile('[\\n:]')\ndef _parse_parameters(paramdoc):\n    \"\"\"Parse parameters and return list of (name, full_doc_string)\n\n    It is needed to remove multiple entries for the same parameter\n    like it could be with adding parameters from the parent class\n\n    It assumes that previously parameters were unwrapped, so their\n    documentation starts at the begining of the string, like what\n    should it be after _split_out_parameters\n    \"\"\"\n    entries = __re_spliter1.split(paramdoc)\n    result = [(__re_spliter2.split(e)[0].strip(), e)\n              for e in entries if e != '']\n    lgr.debug('parseParameters: Given \"%s\", we split into %s' %\n              (paramdoc, result))\n    return result\n\ndef get_docstring_split(f):\n    \"\"\"Given a function, break it up into portions\n\n    Parameters\n    ----------\n    f : function\n\n    Returns\n    -------\n\n    (initial doc string, params (as list of tuples), suffix string)\n    \"\"\"\n\n    if not hasattr(f, '__doc__') or f.__doc__ in (None, \"\"):\n        return None, None, None\n    initdoc, params, suffix = _split_out_parameters(\n        f.__doc__)\n    params_list = _parse_parameters(params)\n    return initdoc, params_list, suffix\n\ndef borrowdoc(cls, methodname=None):\n    \"\"\"Return a decorator to borrow docstring from another `cls`.`methodname`\n\n    It should not be used for __init__ methods of classes derived from\n    ClassWithCollections since __doc__'s of those are handled by the\n    AttributeCollector anyways.\n\n    Common use is to borrow a docstring from the class's method for an\n    adapter function (e.g. sphere_searchlight borrows from Searchlight)\n\n    Examples\n    --------\n    To borrow `__repr__` docstring from parent class `Mapper`, do::\n\n       @borrowdoc(Mapper)\n       def __repr__(self):\n           ...\n\n    Parameters\n    ----------\n    cls\n      Usually a parent class\n    methodname : None or str\n      Name of the method from which to borrow.  If None, would use\n      the same name as of the decorated method\n    \"\"\"\n\n    def _borrowdoc(method):\n        \"\"\"Decorator which assigns to the `method` docstring from another\n        \"\"\"\n        if methodname is None:\n            other_method = getattr(cls, method.__name__)\n        else:\n            other_method = getattr(cls, methodname)\n        if hasattr(other_method, '__doc__'):\n            method.__doc__ = other_method.__doc__\n        return method\n    return _borrowdoc\n\n\ndef borrowkwargs(cls, methodname=None, exclude=None):\n    \"\"\"Return  a decorator which would borrow docstring for ``**kwargs``\n\n    Notes\n    -----\n    TODO: take care about ``*args`` in  a clever way if those are also present\n\n    Examples\n    --------\n    In the simplest scenario -- just grab all arguments from parent class::\n\n           @borrowkwargs(A)\n           def met1(self, bu, **kwargs):\n               pass\n\n    Parameters\n    ----------\n    methodname : None or str\n      Name of the method from which to borrow.  If None, would use\n      the same name as of the decorated method\n    exclude : None or list of arguments to exclude\n      If function does not pass all ``**kwargs``, you would need to list\n      those here to be excluded from borrowed docstring\n    \"\"\"\n\n    def _borrowkwargs(method):\n        \"\"\"Decorator which borrows docstrings for ``**kwargs`` for the `method`\n        \"\"\"\n        if methodname is None:\n            other_method = getattr(cls, method.__name__)\n        else:\n            other_method = getattr(cls, methodname)\n        # TODO:\n        # method.__doc__ = enhanced_from(other_method.__doc__)\n\n        mdoc, odoc = method.__doc__, other_method.__doc__\n        if mdoc is None:\n            mdoc = ''\n\n        mpreamble, mparams, msuffix = _split_out_parameters(mdoc)\n        opreamble, oparams, osuffix = _split_out_parameters(odoc)\n        mplist = _parse_parameters(mparams)\n        oplist = _parse_parameters(oparams)\n        known_params = set([i[0] for i in mplist])\n\n        # !!! has to not rebind exclude variable\n        skip_params = exclude or []         # handle None\n        skip_params = set(['kwargs', '**kwargs'] + skip_params)\n\n        # combine two and filter out items to skip\n        aplist = [i for i in mplist if not i[0] in skip_params]\n        aplist += [i for i in oplist\n                   if not i[0] in skip_params.union(known_params)]\n\n        docstring = mpreamble\n        if len(aplist):\n            params_ = '\\n'.join([i[1].rstrip() for i in aplist])\n            docstring += \"\\n\\n%s\\n\" \\\n                         % _rst_section('Parameters') + _indent(params_)\n\n        if msuffix != \"\":\n            docstring += \"\\n\\n\" + msuffix\n\n        docstring = handle_docstring(docstring)\n\n        # Finally assign generated doc to the method\n        method.__doc__ = docstring\n        return method\n    return _borrowkwargs\n\nimport os\nimport sys\nimport traceback\n# TODO: make limit respect config/environment parameter\ndef exc_str(exc=None, limit=None):\n    \"\"\"Enhanced str for exceptions.  Should include original location\n\n    Parameters\n    ----------\n    Exception to\n    \"\"\"\n    out = str(exc)\n    if limit is None:\n        # TODO: config logging.exceptions.traceback_levels = 1\n        limit = int(os.environ.get('DATALAD_EXC_STR_TBLIMIT', '1'))\n    try:\n        exctype, value, tb = sys.exc_info()\n        if not exc:\n            exc = value\n            out = str(exc)\n        # verify that it seems to be the exception we were passed\n        #assert(isinstance(exc, exctype))\n        if exc:\n            assert(exc is value)\n        entries = traceback.extract_tb(tb)\n        if entries:\n            out += \" [%s]\" % (','.join(['%s:%s:%d' % (os.path.basename(x[0]), x[2], x[1]) for x in entries[-limit:]]))\n    except:\n        return out  # To the best of our abilities\n    finally:\n        # As the bible teaches us:\n        # https://docs.python.org/2/library/sys.html#sys.exc_info\n        del tb\n    return out\n"}
A user will ask you to solve a task. You should first draft your thinking process (inner monologue). Then, generate the solution.

Your response format must follow the template below:
<think>
Your thoughts or/and draft, like working through an exercise on scratch paper. Be as casual and as long as you want until you are confident to generate a correct solution.
</think>
<solution>
Final solution presented to the user.
</solution>
system
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
**Analysis**

- **Key Parts of Commit Details and Test Results:**
  - **Commit Message:** "ENH: Allow borrowkwargs to work on only method too."
  - **Change in `borrowkwargs` Definition:** Made the `cls` parameter optional by setting `cls=None`.
  - **Test Results:** Previously, `test_borrow_kwargs` failed with a `TypeError` stating that the `borrowkwargs` function was missing the `cls` argument. After the commit, this test passes, indicating that the change fixed the issue where `cls` was required.

- **Main Problem Highlighted by Test Results:**
  - The `borrowkwargs` decorator initially required the `cls` argument, causing tests to fail when it wasn't provided. This made the decorator less flexible and harder to use in certain scenarios.

- **Expected Behavior:**
  - The `borrowkwargs` decorator should work both with and without the `cls` argument, allowing developers to use it flexibly depending on their needs.

- **Actual Behavior:**
  - When the `cls` argument was not provided, using the `borrowkwargs` decorator resulted in a `TypeError` stating that the `cls` argument was missing.

- **Issue Description:**
  - When using the `borrowkwargs` decorator without specifying the `cls` argument, it raises a `TypeError`. This restricts the decorator's flexibility and usability in scenarios where the class reference isn't necessary or available.

---

**Drafted GitHub Issue**

[ISSUE]

**Title:** `borrowkwargs` Decorator Requires `cls` Argument, Causing TypeError When Unspecified

**Description:**
When using the `borrowkwargs` decorator without providing the `cls` argument, a `TypeError` is raised indicating that the `cls` parameter is missing. This limitation restricts the decorator's flexibility, making it cumbersome to use in cases where the class reference isn't needed.

**Example Code:**
```python
class A:
    def met1(self, kp1=None, kp2=1):
        pass

class B:
    @borrowkwargs(methodname=A.met1)
    def met_anothermet(self, **kwargs):
        pass
```

**Expected Behavior:**
The `borrowkwargs` decorator should function correctly without requiring the `cls` argument, allowing it to borrow keyword arguments from the specified method seamlessly.

**Actual Behavior:**
Using the decorator without the `cls` argument results in the following error:
```
TypeError: borrowkwargs() missing 1 required positional argument: 'cls'
```

[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### datalad/dochelpers.py
# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-
# ex: set sts=4 ts=4 sw=4 noet:
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
#
#   See COPYING file distributed along with the datalad package for the
#   copyright and license terms.
#
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
"""Utils to help with docstrings etc.

Largerly borrowed from PyMVPA (as of upstream/2.4.1-23-g170496e).  Copyright of
the same developers as DataLad
"""

import logging
import re
import textwrap

lgr = logging.getLogger("datalad.docutils")

__add_init2doc = False
__in_ipython = False  # TODO: determine exists('running ipython env')

# if ran within IPython -- might need to add doc to init
if __in_ipython:
    __rst_mode = False                       # either to do ReST links at all
    # if versions['ipython'] <= '0.8.1':
    #     __add_init2doc = True
else:
    __rst_mode = True

#
# Predefine some sugarings depending on syntax convention to be used
#
# XXX Might need to be removed or become proper cfg parameter
__rst_conventions = 'numpy'
if __rst_conventions == 'epydoc':
    _rst_sep = "`"
    _rst_indentstr = "  "
    def _rst_section(section_name):
        """Provide section heading"""
        return ":%s:" % section_name
elif __rst_conventions == 'numpy':
    _rst_sep = ""
    _rst_indentstr = ""
    def _rst_section(section_name):
        """Provide section heading"""
        return "%s\n%s" % (section_name, '-'*len(section_name))
else:
    raise ValueError("Unknown convention %s for RST" % __rst_conventions)


def _rst(s, snotrst=''):
    """Produce s only in __rst mode"""
    if __rst_mode:
        return s
    else:
        return snotrst

def _rst_underline(text, markup):
    """Add and underline RsT string matching the length of the given string.
    """
    return text + '\n' + markup * len(text)


def single_or_plural(single, plural, n):
    """Little helper to spit out single or plural version of a word.
    """
    ni = int(n)
    if ni > 1 or ni == 0:
        # 1 forest, 2 forests, 0 forests
        return plural
    else:
        return single

def handle_docstring(text, polite=True):
    """Take care of empty and non existing doc strings."""
    if text is None or not len(text):
        if polite:
            return '' #No documentation found. Sorry!'
        else:
            return ''
    else:
        # Problem is that first line might often have no offset, so might
        # need to be ignored from dedent call
        if not text.startswith(' '):
            lines = text.split('\n')
            text2 = '\n'.join(lines[1:])
            return lines[0] + "\n" + textwrap.dedent(text2)
        else:
            return textwrap.dedent(text)


def _indent(text, istr=_rst_indentstr):
    """Simple indenter
    """
    return '\n'.join(istr + s for s in text.split('\n'))


__parameters_str_re = re.compile("[\n^]\s*:?Parameters?:?\s*\n(:?\s*-+\s*\n)?")
"""regexp to match :Parameter: and :Parameters: stand alone in a line
or
Parameters
----------
in multiple lines"""


def _split_out_parameters(initdoc):
    """Split documentation into (header, parameters, suffix)

    Parameters
    ----------
    initdoc : string
      The documentation string
    """

    # TODO: bind it to the only word in the line
    p_res = __parameters_str_re.search(initdoc)
    if p_res is None:
        return initdoc, "", ""
    else:
        # Could have been accomplished also via re.match

        # where new line is after :Parameters:
        # parameters header index
        ph_i = p_res.start()

        # parameters body index
        pb_i = p_res.end()

        # end of parameters
        try:
            pe_i = initdoc.index('\n\n', pb_i)
        except ValueError:
            pe_i = len(initdoc)

        result = initdoc[:ph_i].rstrip('\n '), \
                 initdoc[pb_i:pe_i], initdoc[pe_i:]

    # XXX a bit of duplication of effort since handle_docstring might
    # do splitting internally
    return handle_docstring(result[0], polite=False).strip('\n'), \
           textwrap.dedent(result[1]).strip('\n'), \
           textwrap.dedent(result[2]).strip('\n')


__re_params = re.compile('(?:\n\S.*?)+$')
__re_spliter1 = re.compile('(?:\n|\A)(?=\S)')
__re_spliter2 = re.compile('[\n:]')
def _parse_parameters(paramdoc):
    """Parse parameters and return list of (name, full_doc_string)

    It is needed to remove multiple entries for the same parameter
    like it could be with adding parameters from the parent class

    It assumes that previously parameters were unwrapped, so their
    documentation starts at the begining of the string, like what
    should it be after _split_out_parameters
    """
    entries = __re_spliter1.split(paramdoc)
    result = [(__re_spliter2.split(e)[0].strip(), e)
              for e in entries if e != '']
    lgr.debug('parseParameters: Given "%s", we split into %s' %
              (paramdoc, result))
    return result

def get_docstring_split(f):
    """Given a function, break it up into portions

    Parameters
    ----------
    f : function

    Returns
    -------

    (initial doc string, params (as list of tuples), suffix string)
    """

    if not hasattr(f, '__doc__') or f.__doc__ in (None, ""):
        return None, None, None
    initdoc, params, suffix = _split_out_parameters(
        f.__doc__)
    params_list = _parse_parameters(params)
    return initdoc, params_list, suffix

def borrowdoc(cls, methodname=None):
    """Return a decorator to borrow docstring from another `cls`.`methodname`

    It should not be used for __init__ methods of classes derived from
    ClassWithCollections since __doc__'s of those are handled by the
    AttributeCollector anyways.

    Common use is to borrow a docstring from the class's method for an
    adapter function (e.g. sphere_searchlight borrows from Searchlight)

    Examples
    --------
    To borrow `__repr__` docstring from parent class `Mapper`, do::

       @borrowdoc(Mapper)
       def __repr__(self):
           ...

    Parameters
    ----------
    cls
      Usually a parent class
    methodname : None or str
      Name of the method from which to borrow.  If None, would use
      the same name as of the decorated method
    """

    def _borrowdoc(method):
        """Decorator which assigns to the `method` docstring from another
        """
        if methodname is None:
            other_method = getattr(cls, method.__name__)
        else:
            other_method = getattr(cls, methodname)
        if hasattr(other_method, '__doc__'):
            method.__doc__ = other_method.__doc__
        return method
    return _borrowdoc


def borrowkwargs(cls, methodname=None, exclude=None):
    """Return  a decorator which would borrow docstring for ``**kwargs``

    Notes
    -----
    TODO: take care about ``*args`` in  a clever way if those are also present

    Examples
    --------
    In the simplest scenario -- just grab all arguments from parent class::

           @borrowkwargs(A)
           def met1(self, bu, **kwargs):
               pass

    Parameters
    ----------
    methodname : None or str
      Name of the method from which to borrow.  If None, would use
      the same name as of the decorated method
    exclude : None or list of arguments to exclude
      If function does not pass all ``**kwargs``, you would need to list
      those here to be excluded from borrowed docstring
    """

    def _borrowkwargs(method):
        """Decorator which borrows docstrings for ``**kwargs`` for the `method`
        """
        if methodname is None:
            other_method = getattr(cls, method.__name__)
        else:
            other_method = getattr(cls, methodname)
        # TODO:
        # method.__doc__ = enhanced_from(other_method.__doc__)

        mdoc, odoc = method.__doc__, other_method.__doc__
        if mdoc is None:
            mdoc = ''

        mpreamble, mparams, msuffix = _split_out_parameters(mdoc)
        opreamble, oparams, osuffix = _split_out_parameters(odoc)
        mplist = _parse_parameters(mparams)
        oplist = _parse_parameters(oparams)
        known_params = set([i[0] for i in mplist])

        # !!! has to not rebind exclude variable
        skip_params = exclude or []         # handle None
        skip_params = set(['kwargs', '**kwargs'] + skip_params)

        # combine two and filter out items to skip
        aplist = [i for i in mplist if not i[0] in skip_params]
        aplist += [i for i in oplist
                   if not i[0] in skip_params.union(known_params)]

        docstring = mpreamble
        if len(aplist):
            params_ = '\n'.join([i[1].rstrip() for i in aplist])
            docstring += "\n\n%s\n" \
                         % _rst_section('Parameters') + _indent(params_)

        if msuffix != "":
            docstring += "\n\n" + msuffix

        docstring = handle_docstring(docstring)

        # Finally assign generated doc to the method
        method.__doc__ = docstring
        return method
    return _borrowkwargs

import os
import sys
import traceback
# TODO: make limit respect config/environment parameter
def exc_str(exc=None, limit=None):
    """Enhanced str for exceptions.  Should include original location

    Parameters
    ----------
    Exception to
    """
    out = str(exc)
    if limit is None:
        # TODO: config logging.exceptions.traceback_levels = 1
        limit = int(os.environ.get('DATALAD_EXC_STR_TBLIMIT', '1'))
    try:
        exctype, value, tb = sys.exc_info()
        if not exc:
            exc = value
            out = str(exc)
        # verify that it seems to be the exception we were passed
        #assert(isinstance(exc, exctype))
        if exc:
            assert(exc is value)
        entries = traceback.extract_tb(tb)
        if entries:
            out += " [%s]" % (','.join(['%s:%s:%d' % (os.path.basename(x[0]), x[2], x[1]) for x in entries[-limit:]]))
    except:
        return out  # To the best of our abilities
    finally:
        # As the bible teaches us:
        # https://docs.python.org/2/library/sys.html#sys.exc_info
        del tb
    return out

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
user