From 5ca74feda2a53d203523940cc62c04f176310838 Mon Sep 17 00:00:00 2001 From: Paul van Mulbregt Date: Thu, 12 Apr 2018 16:48:57 -0400 Subject: [PATCH 1/8] ENH: Added support for multiple functions+description in a See Also block. --- numpydoc/docscrape.py | 122 +++++++++++++++++++++---------- numpydoc/tests/test_docscrape.py | 63 ++++++++++------ 2 files changed, 121 insertions(+), 64 deletions(-) diff --git a/numpydoc/docscrape.py b/numpydoc/docscrape.py index f3453c65..43160810 100644 --- a/numpydoc/docscrape.py +++ b/numpydoc/docscrape.py @@ -236,6 +236,24 @@ def _parse_param_list(self, content): return params + _role = r":(?P\w+):" + _funcbacktick = r"`(?P(?:~\w+\.)?[a-zA-Z0-9_.-]+)`" + _funcplain = r"(?P[a-zA-Z0-9_.-]+)" + _funcname = r"(" + _role + _funcbacktick + r"|" + _funcplain + r")" + _funcnamenext = _funcname.replace('role', 'rolenext').replace('name', 'namenext') + _description = r"(?P\s*:(\s+(?P\S+.*))?)?\s*$" + _func_rgx = re.compile(r"^\s*" + _funcname + r"\s*", re.X) + # _funcs_rgx = re.compile(r"^\s*" + _funcname + r"(?P([,\s]\s*" + _funcnamenext + r")*)" + r"\s*", re.X) + _line_rgx = re.compile(r"^\s*" + + r"(?P" # group for all function names + + _funcname + + r"(?P([,]\s+" + + _funcnamenext + r")*)" + + r")" # end of "allfuncs" + + r"(\s*,)?" # Some function lists have a trailing comma + + _description, + re.X) + _name_rgx = re.compile(r"^\s*(:(?P\w+):" r"`(?P(?:~\w+\.)?[a-zA-Z0-9_.-]+)`|" r" (?P[a-zA-Z0-9_.-]+))\s*", re.X) @@ -252,48 +270,62 @@ def _parse_see_also(self, content): def parse_item_name(text): """Match ':role:`name`' or 'name'""" - m = self._name_rgx.match(text) - if m: - g = m.groups() - if g[1] is None: - return g[3], None - else: - return g[2], g[1] - raise ParseError("%s is not a item name" % text) + m = self._func_rgx.match(text) + if not m: + raise ParseError("%s is not a item name" % text) + role = m.groupdict().get('role') + if role: + name = m.group('name') + else: + name = m.group('name2') + return name, role, m def push_item(name, rest): if not name: return - name, role = parse_item_name(name) + name, role, m2 = parse_item_name(name) items.append((name, list(rest), role)) del rest[:] - current_func = None rest = [] for line in content: if not line.strip(): continue - m = self._name_rgx.match(line) - if m and line[m.end():].strip().startswith(':'): - push_item(current_func, rest) - current_func, line = line[:m.end()], line[m.end():] - rest = [line.split(':', 1)[1].strip()] - if not rest[0]: - rest = [] - elif not line.startswith(' '): - push_item(current_func, rest) - current_func = None - if ',' in line: - for func in line.split(','): - if func.strip(): - push_item(func, []) - elif line.strip(): - current_func = line - elif current_func is not None: + ml = self._line_rgx.match(line) + description = None + if ml: + if 'description' in ml.groupdict(): + description = ml.groupdict().get('desc') + if not description and line.startswith(' '): rest.append(line.strip()) - push_item(current_func, rest) + elif ml: + funcs = [] + text = ml.group('allfuncs') + while True: + if not text.strip(): + break + name, role, m2 = parse_item_name(text) + # m2 = self._func_rgx.match(text) + # if not m2: + # raise ParseError("%s is not a item name" % line) + # role = m2.groupdict().get('role') + # if role: + # name = m2.group('name') + # else: + # name = m2.group('name2') + funcs.append((name, role)) + text = text[m2.end():].strip() + if text and text[0] == ',': + text = text[1:].strip() + if description: + rest = [description] + else: + rest = [] + items.append((funcs, rest)) + else: + raise ParseError("%s is not a item name" % line) return items def _parse_index(self, section, content): @@ -440,25 +472,35 @@ def _str_see_also(self, func_role): return [] out = [] out += self._str_header("See Also") + out += [''] last_had_desc = True - for func, desc, role in self['See Also']: - if role: - link = ':%s:`%s`' % (role, func) - elif func_role: - link = ':%s:`%s`' % (func_role, func) - else: - link = "`%s`_" % func - if desc or last_had_desc: - out += [''] - out += [link] - else: - out[-1] += ", %s" % link + for funcs, desc in self['See Also']: + assert isinstance(funcs, (list, tuple)) + links = [] + for func, role in funcs: + if role: + link = ':%s:`%s`' % (role, func) + elif func_role: + link = ':%s:`%s`' % (func_role, func) + else: + link = "`%s`_" % func + links.append(link) + link = ', '.join(links) + out += [link] if desc: out += self._str_indent([' '.join(desc)]) last_had_desc = True else: last_had_desc = False + out += [''] + if last_had_desc: + out += [''] out += [''] + # if 1: + # print() + # for l in out: + # print(repr(l)) + # # print(out) return out def _str_index(self): diff --git a/numpydoc/tests/test_docscrape.py b/numpydoc/tests/test_docscrape.py index 20859488..b0170c07 100644 --- a/numpydoc/tests/test_docscrape.py +++ b/numpydoc/tests/test_docscrape.py @@ -709,36 +709,51 @@ def test_see_also(): multiple lines func_f, func_g, :meth:`func_h`, func_j, func_k + func_f1, func_g1, :meth:`func_h1`, func_j1 + func_f2, func_g2, :meth:`func_h2`, func_j2 : description of multiple :obj:`baz.obj_q` :obj:`~baz.obj_r` :class:`class_j`: fubar foobar """) - assert len(doc6['See Also']) == 13 - for func, desc, role in doc6['See Also']: - if func in ('func_a', 'func_b', 'func_c', 'func_f', - 'func_g', 'func_h', 'func_j', 'func_k', 'baz.obj_q', - '~baz.obj_r'): - assert(not desc) - else: - assert(desc) - - if func == 'func_h': - assert role == 'meth' - elif func == 'baz.obj_q' or func == '~baz.obj_r': - assert role == 'obj' - elif func == 'class_j': - assert role == 'class' - else: - assert role is None - - if func == 'func_d': - assert desc == ['some equivalent func'] - elif func == 'foo.func_e': - assert desc == ['some other func over', 'multiple lines'] - elif func == 'class_j': - assert desc == ['fubar', 'foobar'] + assert len(doc6['See Also']) == 10, str([len(doc6['See Also'])]) + for funcs, desc in doc6['See Also']: + print(funcs, desc) + for func, role in funcs: + if func in ('func_a', 'func_b', 'func_c', 'func_f', + 'func_g', 'func_h', 'func_j', 'func_k', 'baz.obj_q', + 'func_f1', 'func_g1', 'func_h1', 'func_j1', + '~baz.obj_r'): + assert (not desc), str([func, desc]) + elif func in ('func_f2', 'func_g2', 'func_h2', 'func_j2'): + assert (desc), str([func, desc]) + else: + assert(desc), str([func, desc]) + + if func == 'func_h': + assert role == 'meth' + elif func == 'baz.obj_q' or func == '~baz.obj_r': + assert role == 'obj' + elif func == 'class_j': + assert role == 'class' + elif func in ['func_h1', 'func_h2']: + assert role == 'meth' + else: + assert role is None, str([func, role]) + + if func == 'func_d': + assert desc == ['some equivalent func'] + elif func == 'foo.func_e': + assert desc == ['some other func over', 'multiple lines'] + elif func == 'class_j': + assert desc == ['fubar', 'foobar'] + elif func == 'func_j2': + assert desc == ['description of multiple'], str([desc, ['description of multiple']]) + + # s = str(doc6) + # print(repr(s)) + # assert 1 == 0 def test_see_also_parse_error(): From bb118215db77813ccbe9cd6c834bb9e3007b3ff7 Mon Sep 17 00:00:00 2001 From: Paul van Mulbregt Date: Thu, 12 Apr 2018 18:08:52 -0400 Subject: [PATCH 2/8] ENH: Use a zero-width space for empty definitions in See Also blocks. Also remove the zero-width space \u200B when comparing test output. --- numpydoc/docscrape.py | 7 +------ numpydoc/tests/test_docscrape.py | 11 +++-------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/numpydoc/docscrape.py b/numpydoc/docscrape.py index 43160810..e518571c 100644 --- a/numpydoc/docscrape.py +++ b/numpydoc/docscrape.py @@ -492,15 +492,10 @@ def _str_see_also(self, func_role): last_had_desc = True else: last_had_desc = False - out += [''] + out += self._str_indent(['\u200B']) if last_had_desc: out += [''] out += [''] - # if 1: - # print() - # for l in out: - # print(repr(l)) - # # print(out) return out def _str_index(self): diff --git a/numpydoc/tests/test_docscrape.py b/numpydoc/tests/test_docscrape.py index b0170c07..14cffec6 100644 --- a/numpydoc/tests/test_docscrape.py +++ b/numpydoc/tests/test_docscrape.py @@ -331,7 +331,7 @@ def _strip_blank_lines(s): def line_by_line_compare(a, b): - a = textwrap.dedent(a) + a = textwrap.dedent(a).replace(u'\u200B', '') b = textwrap.dedent(b) a = [l.rstrip() for l in _strip_blank_lines(a).split('\n')] b = [l.rstrip() for l in _strip_blank_lines(b).split('\n')] @@ -717,9 +717,8 @@ def test_see_also(): foobar """) - assert len(doc6['See Also']) == 10, str([len(doc6['See Also'])]) + assert len(doc6['See Also']) == 10 for funcs, desc in doc6['See Also']: - print(funcs, desc) for func, role in funcs: if func in ('func_a', 'func_b', 'func_c', 'func_f', 'func_g', 'func_h', 'func_j', 'func_k', 'baz.obj_q', @@ -748,13 +747,9 @@ def test_see_also(): assert desc == ['some other func over', 'multiple lines'] elif func == 'class_j': assert desc == ['fubar', 'foobar'] - elif func == 'func_j2': + elif func in ['func_f2', 'func_g2', 'func_h2', 'func_j2']: assert desc == ['description of multiple'], str([desc, ['description of multiple']]) - # s = str(doc6) - # print(repr(s)) - # assert 1 == 0 - def test_see_also_parse_error(): text = ( From 30ec43de37b2aa21f3c2c710193135405026711c Mon Sep 17 00:00:00 2001 From: Paul van Mulbregt Date: Thu, 12 Apr 2018 20:30:36 -0400 Subject: [PATCH 3/8] BUG: Under Py27 use the UTF-8 for ZERO WIDTH SPACE --- numpydoc/docscrape.py | 8 +++++++- numpydoc/tests/test_docscrape.py | 7 ++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/numpydoc/docscrape.py b/numpydoc/docscrape.py index e518571c..cc151731 100644 --- a/numpydoc/docscrape.py +++ b/numpydoc/docscrape.py @@ -258,6 +258,11 @@ def _parse_param_list(self, content): r"`(?P(?:~\w+\.)?[a-zA-Z0-9_.-]+)`|" r" (?P[a-zA-Z0-9_.-]+))\s*", re.X) + if sys.version_info[0] >= 3: + zerowidthspace = '\u200B' + else: + zerowidthspace = '\xE2\x80\x8B' + def _parse_see_also(self, content): """ func_name : Descriptive text @@ -492,7 +497,8 @@ def _str_see_also(self, func_role): last_had_desc = True else: last_had_desc = False - out += self._str_indent(['\u200B']) + out += self._str_indent([self.zerowidthspace]) + if last_had_desc: out += [''] out += [''] diff --git a/numpydoc/tests/test_docscrape.py b/numpydoc/tests/test_docscrape.py index 14cffec6..54a91263 100644 --- a/numpydoc/tests/test_docscrape.py +++ b/numpydoc/tests/test_docscrape.py @@ -331,7 +331,12 @@ def _strip_blank_lines(s): def line_by_line_compare(a, b): - a = textwrap.dedent(a).replace(u'\u200B', '') + if sys.version_info.major >= 3: + zerowidthspace = '\u200B' + else: + zerowidthspace = '\xE2\x80\x8B' + + a = textwrap.dedent(a).replace(zerowidthspace, '') b = textwrap.dedent(b) a = [l.rstrip() for l in _strip_blank_lines(a).split('\n')] b = [l.rstrip() for l in _strip_blank_lines(b).split('\n')] From 1670bbbae2b9c3ea5745e5d991fa9bd868e8d527 Mon Sep 17 00:00:00 2001 From: Paul van Mulbregt Date: Thu, 12 Apr 2018 22:08:39 -0400 Subject: [PATCH 4/8] BUG: Replace Unicode zero-width-space with ".." --- numpydoc/docscrape.py | 7 ++----- numpydoc/tests/test_docscrape.py | 13 +++++-------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/numpydoc/docscrape.py b/numpydoc/docscrape.py index cc151731..668f3d26 100644 --- a/numpydoc/docscrape.py +++ b/numpydoc/docscrape.py @@ -258,10 +258,7 @@ def _parse_param_list(self, content): r"`(?P(?:~\w+\.)?[a-zA-Z0-9_.-]+)`|" r" (?P[a-zA-Z0-9_.-]+))\s*", re.X) - if sys.version_info[0] >= 3: - zerowidthspace = '\u200B' - else: - zerowidthspace = '\xE2\x80\x8B' + empty_description = '..' def _parse_see_also(self, content): """ @@ -497,7 +494,7 @@ def _str_see_also(self, func_role): last_had_desc = True else: last_had_desc = False - out += self._str_indent([self.zerowidthspace]) + out += self._str_indent([self.empty_description]) if last_had_desc: out += [''] diff --git a/numpydoc/tests/test_docscrape.py b/numpydoc/tests/test_docscrape.py index 54a91263..21881c64 100644 --- a/numpydoc/tests/test_docscrape.py +++ b/numpydoc/tests/test_docscrape.py @@ -331,18 +331,15 @@ def _strip_blank_lines(s): def line_by_line_compare(a, b): - if sys.version_info.major >= 3: - zerowidthspace = '\u200B' - else: - zerowidthspace = '\xE2\x80\x8B' + empty_description = '..' + rgx = re.compile(r"^\s+" + re.escape(empty_description) + "$") - a = textwrap.dedent(a).replace(zerowidthspace, '') + a = textwrap.dedent(a) b = textwrap.dedent(b) - a = [l.rstrip() for l in _strip_blank_lines(a).split('\n')] - b = [l.rstrip() for l in _strip_blank_lines(b).split('\n')] + a = [rgx.sub('', l.rstrip()) for l in _strip_blank_lines(a).split('\n')] + b = [rgx.sub('', l.rstrip()) for l in _strip_blank_lines(b).split('\n')] assert all(x == y for x, y in zip(a, b)) - def test_str(): # doc_txt has the order of Notes and See Also sections flipped. # This should be handled automatically, and so, one thing this test does From a6911fe2525f2694d45f4fbff1ad4ba887d10677 Mon Sep 17 00:00:00 2001 From: Paul van Mulbregt Date: Tue, 2 Apr 2019 22:26:53 -0400 Subject: [PATCH 5/8] STY: Keep line length < 80 for some regexes. --- numpydoc/docscrape.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/numpydoc/docscrape.py b/numpydoc/docscrape.py index 668f3d26..5395e5b7 100644 --- a/numpydoc/docscrape.py +++ b/numpydoc/docscrape.py @@ -243,16 +243,16 @@ def _parse_param_list(self, content): _funcnamenext = _funcname.replace('role', 'rolenext').replace('name', 'namenext') _description = r"(?P\s*:(\s+(?P\S+.*))?)?\s*$" _func_rgx = re.compile(r"^\s*" + _funcname + r"\s*", re.X) - # _funcs_rgx = re.compile(r"^\s*" + _funcname + r"(?P([,\s]\s*" + _funcnamenext + r")*)" + r"\s*", re.X) - _line_rgx = re.compile(r"^\s*" - + r"(?P" # group for all function names - + _funcname - + r"(?P([,]\s+" - + _funcnamenext + r")*)" - + r")" # end of "allfuncs" - + r"(\s*,)?" # Some function lists have a trailing comma - + _description, - re.X) + _line_rgx = re.compile( + r"^\s*" + + r"(?P" # group for all function names + + _funcname + + r"(?P([,]\s+" + + _funcnamenext + r")*)" + + r")" # end of "allfuncs" + + r"(\s*,)?" # Some function lists have a trailing comma + + _description, + re.X) _name_rgx = re.compile(r"^\s*(:(?P\w+):" r"`(?P(?:~\w+\.)?[a-zA-Z0-9_.-]+)`|" From af7eadad0646e9a62dac07b5effbd57ef386fb76 Mon Sep 17 00:00:00 2001 From: Paul van Mulbregt Date: Sun, 7 Apr 2019 11:22:38 -0400 Subject: [PATCH 6/8] MAINT: Updated code-style, doc after reviewer's comments. Added the spec used by NumpyDocString._parse_see_also. Added a warning whenever an unexpected trailing comma appears in a See Also function list. Removed unnecessary re.X qualifiers from some regular expressions. Renamed some variables with more descriptive names. Removed some unused/commented-out code. Removed some unnneeded parentheses. Shortened some lines to keep under 80 characters. --- numpydoc/docscrape.py | 95 +++++++++++++++----------------- numpydoc/tests/test_docscrape.py | 18 +++--- 2 files changed, 55 insertions(+), 58 deletions(-) diff --git a/numpydoc/docscrape.py b/numpydoc/docscrape.py index 5395e5b7..39a8d015 100644 --- a/numpydoc/docscrape.py +++ b/numpydoc/docscrape.py @@ -236,28 +236,38 @@ def _parse_param_list(self, content): return params + # See also supports the following formats. + # + # + # SPACE* COLON SPACE+ SPACE* + # ( COMMA SPACE+ )* SPACE* + # ( COMMA SPACE+ )* SPACE* COLON SPACE+ SPACE* + + # is: + # A legal function name, optionally enclosed in backticks. + # It may have an optional COLON COLON in front where + # is any nonempty sequence of word characters. + # Examples: func_f1 :meth:`func_h1` :obj:`~baz.obj_r` :class:`class_j` + # is a string describing the function. + _role = r":(?P\w+):" _funcbacktick = r"`(?P(?:~\w+\.)?[a-zA-Z0-9_.-]+)`" _funcplain = r"(?P[a-zA-Z0-9_.-]+)" _funcname = r"(" + _role + _funcbacktick + r"|" + _funcplain + r")" - _funcnamenext = _funcname.replace('role', 'rolenext').replace('name', 'namenext') + _funcnamenext = _funcname.replace('role', 'rolenext') + _funcnamenext = _funcnamenext.replace('name', 'namenext') _description = r"(?P\s*:(\s+(?P\S+.*))?)?\s*$" - _func_rgx = re.compile(r"^\s*" + _funcname + r"\s*", re.X) + _func_rgx = re.compile(r"^\s*" + _funcname + r"\s*") _line_rgx = re.compile( - r"^\s*" - + r"(?P" # group for all function names - + _funcname - + r"(?P([,]\s+" - + _funcnamenext + r")*)" - + r")" # end of "allfuncs" - + r"(\s*,)?" # Some function lists have a trailing comma - + _description, - re.X) - - _name_rgx = re.compile(r"^\s*(:(?P\w+):" - r"`(?P(?:~\w+\.)?[a-zA-Z0-9_.-]+)`|" - r" (?P[a-zA-Z0-9_.-]+))\s*", re.X) - + r"^\s*" + + r"(?P" + # group for all function names + _funcname + + r"(?P([,]\s+" + _funcnamenext + r")*)" + + r")" + # end of "allfuncs" + r"(?P\s*,)?" + # Some function lists have a trailing comma + _description) + + # Empty elements are replaced with '..' empty_description = '..' def _parse_see_also(self, content): @@ -268,63 +278,46 @@ def _parse_see_also(self, content): func_name1, func_name2, :meth:`func_name`, func_name3 """ + items = [] def parse_item_name(text): - """Match ':role:`name`' or 'name'""" + """Match ':role:`name`' or 'name'.""" m = self._func_rgx.match(text) if not m: raise ParseError("%s is not a item name" % text) - role = m.groupdict().get('role') - if role: - name = m.group('name') - else: - name = m.group('name2') - return name, role, m - - def push_item(name, rest): - if not name: - return - name, role, m2 = parse_item_name(name) - items.append((name, list(rest), role)) - del rest[:] + role = m.group('role') + name = (m.group('name') if role else m.group('name2')) + return name, role, m.end() rest = [] - for line in content: if not line.strip(): continue - ml = self._line_rgx.match(line) + line_match = self._line_rgx.match(line) description = None - if ml: - if 'description' in ml.groupdict(): - description = ml.groupdict().get('desc') + if line_match: + description = line_match.group('desc') + if line_match.group('trailing'): + self._error_location( + 'Unexpected comma after function list at index %d of ' + 'line "%s"' % (line_match.end('trailing'), line), + error=False) if not description and line.startswith(' '): rest.append(line.strip()) - elif ml: + elif line_match: funcs = [] - text = ml.group('allfuncs') + text = line_match.group('allfuncs') while True: if not text.strip(): break - name, role, m2 = parse_item_name(text) - # m2 = self._func_rgx.match(text) - # if not m2: - # raise ParseError("%s is not a item name" % line) - # role = m2.groupdict().get('role') - # if role: - # name = m2.group('name') - # else: - # name = m2.group('name2') + name, role, match_end = parse_item_name(text) funcs.append((name, role)) - text = text[m2.end():].strip() + text = text[match_end:].strip() if text and text[0] == ',': text = text[1:].strip() - if description: - rest = [description] - else: - rest = [] + rest = list(filter(None, [description])) items.append((funcs, rest)) else: raise ParseError("%s is not a item name" % line) diff --git a/numpydoc/tests/test_docscrape.py b/numpydoc/tests/test_docscrape.py index 21881c64..264f633f 100644 --- a/numpydoc/tests/test_docscrape.py +++ b/numpydoc/tests/test_docscrape.py @@ -331,15 +331,19 @@ def _strip_blank_lines(s): def line_by_line_compare(a, b): - empty_description = '..' - rgx = re.compile(r"^\s+" + re.escape(empty_description) + "$") + empty_description = NumpyDocString.empty_description # '..' + empty_description_rgx = re.compile( + r"^\s+" + re.escape(empty_description) + "$") a = textwrap.dedent(a) b = textwrap.dedent(b) - a = [rgx.sub('', l.rstrip()) for l in _strip_blank_lines(a).split('\n')] - b = [rgx.sub('', l.rstrip()) for l in _strip_blank_lines(b).split('\n')] + a = [l.rstrip() for l in _strip_blank_lines(a).split('\n')] + b = [l.rstrip() for l in _strip_blank_lines(b).split('\n')] + a = [empty_description_rgx.sub('', l) for l in a] + b = [empty_description_rgx.sub('', l) for l in b] assert all(x == y for x, y in zip(a, b)) + def test_str(): # doc_txt has the order of Notes and See Also sections flipped. # This should be handled automatically, and so, one thing this test does @@ -726,11 +730,11 @@ def test_see_also(): 'func_g', 'func_h', 'func_j', 'func_k', 'baz.obj_q', 'func_f1', 'func_g1', 'func_h1', 'func_j1', '~baz.obj_r'): - assert (not desc), str([func, desc]) + assert not desc, str([func, desc]) elif func in ('func_f2', 'func_g2', 'func_h2', 'func_j2'): - assert (desc), str([func, desc]) + assert desc, str([func, desc]) else: - assert(desc), str([func, desc]) + assert desc, str([func, desc]) if func == 'func_h': assert role == 'meth' From a6a1b20619054c7d36e57b7d9cbefc653b19378f Mon Sep 17 00:00:00 2001 From: Paul van Mulbregt Date: Mon, 8 Apr 2019 22:06:15 -0400 Subject: [PATCH 7/8] DOC: Adjusted documentation and some parentheses. --- numpydoc/docscrape.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/numpydoc/docscrape.py b/numpydoc/docscrape.py index 39a8d015..fc24a395 100644 --- a/numpydoc/docscrape.py +++ b/numpydoc/docscrape.py @@ -243,9 +243,11 @@ def _parse_param_list(self, content): # ( COMMA SPACE+ )* SPACE* # ( COMMA SPACE+ )* SPACE* COLON SPACE+ SPACE* - # is: - # A legal function name, optionally enclosed in backticks. - # It may have an optional COLON COLON in front where + # is one of + # + # COLON COLON BACKTICK BACKTICK + # where + # is a legal function name, and # is any nonempty sequence of word characters. # Examples: func_f1 :meth:`func_h1` :obj:`~baz.obj_r` :class:`class_j` # is a string describing the function. @@ -287,7 +289,7 @@ def parse_item_name(text): if not m: raise ParseError("%s is not a item name" % text) role = m.group('role') - name = (m.group('name') if role else m.group('name2')) + name = m.group('name') if role else m.group('name2') return name, role, m.end() rest = [] @@ -470,7 +472,7 @@ def _str_see_also(self, func_role): out += [''] last_had_desc = True for funcs, desc in self['See Also']: - assert isinstance(funcs, (list, tuple)) + assert isinstance(funcs, list) links = [] for func, role in funcs: if role: From d65f91797b95ce16a34defa6e6375de3b246b85f Mon Sep 17 00:00:00 2001 From: Paul van Mulbregt Date: Mon, 8 Apr 2019 22:17:32 -0400 Subject: [PATCH 8/8] TST: Don't remove '..' lines from text comparing against str(doc) output. --- numpydoc/tests/test_docscrape.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/numpydoc/tests/test_docscrape.py b/numpydoc/tests/test_docscrape.py index 264f633f..7dd286c8 100644 --- a/numpydoc/tests/test_docscrape.py +++ b/numpydoc/tests/test_docscrape.py @@ -331,17 +331,11 @@ def _strip_blank_lines(s): def line_by_line_compare(a, b): - empty_description = NumpyDocString.empty_description # '..' - empty_description_rgx = re.compile( - r"^\s+" + re.escape(empty_description) + "$") - a = textwrap.dedent(a) b = textwrap.dedent(b) a = [l.rstrip() for l in _strip_blank_lines(a).split('\n')] b = [l.rstrip() for l in _strip_blank_lines(b).split('\n')] - a = [empty_description_rgx.sub('', l) for l in a] - b = [empty_description_rgx.sub('', l) for l in b] - assert all(x == y for x, y in zip(a, b)) + assert all(x == y for x, y in zip(a, b)), str([[x, y] for x, y in zip(a, b) if x != y]) def test_str(): @@ -409,7 +403,7 @@ def test_str(): -------- `some`_, `other`_, `funcs`_ - + .. `otherfunc`_ relationship @@ -559,7 +553,7 @@ def test_sphinx_str(): .. seealso:: :obj:`some`, :obj:`other`, :obj:`funcs` - + .. :obj:`otherfunc` relationship