docstrfmt 1.5.1.dev0__tar.gz → 1.6.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/CHANGES.rst +32 -3
  2. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/PKG-INFO +34 -14
  3. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/README.rst +5 -5
  4. docstrfmt-1.6.0/docstrfmt/const.py +2 -0
  5. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/docstrfmt/debug.py +2 -2
  6. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/docstrfmt/docstrfmt.py +162 -57
  7. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/docstrfmt/main.py +96 -30
  8. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/docstrfmt/rst_extras.py +15 -4
  9. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/docstrfmt/util.py +10 -4
  10. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/docstrfmt.egg-info/PKG-INFO +34 -14
  11. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/docstrfmt.egg-info/SOURCES.txt +7 -0
  12. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/docstrfmt.egg-info/entry_points.txt +0 -1
  13. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/docstrfmt.egg-info/requires.txt +9 -6
  14. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/pyproject.toml +1 -1
  15. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/setup.py +10 -9
  16. docstrfmt-1.6.0/tests/test_docstrfmt.py +13 -0
  17. docstrfmt-1.6.0/tests/test_files/error_files/py_file_error_duplicate_returns.py +12 -0
  18. docstrfmt-1.6.0/tests/test_files/error_files/py_file_error_duplicate_types.py +11 -0
  19. docstrfmt-1.6.0/tests/test_files/error_files/py_file_error_multiline_types.py +12 -0
  20. docstrfmt-1.6.0/tests/test_files/error_files/py_file_type_field_removal.py +16 -0
  21. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/tests/test_files/py_file.py +18 -1
  22. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/tests/test_files/test_file.rst +28 -0
  23. docstrfmt-1.6.0/tests/test_main.py +521 -0
  24. docstrfmt-1.6.0/tests/test_server.py +51 -0
  25. docstrfmt-1.5.1.dev0/docstrfmt/const.py +0 -2
  26. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/AUTHORS.rst +0 -0
  27. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/LICENSE.txt +0 -0
  28. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/MANIFEST.in +0 -0
  29. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/docstrfmt/__init__.py +0 -0
  30. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/docstrfmt/exceptions.py +0 -0
  31. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/docstrfmt/server.py +0 -0
  32. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/docstrfmt.egg-info/dependency_links.txt +0 -0
  33. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/docstrfmt.egg-info/top_level.txt +0 -0
  34. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/sample.rst +0 -0
  35. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/setup.cfg +0 -0
  36. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/tests/test_files/error_files/bad_pyproject.toml +0 -0
  37. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/tests/test_files/error_files/py_file_error_bad_codeblock.py +0 -0
  38. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/tests/test_files/error_files/py_file_error_empty_returns.py +0 -0
  39. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/tests/test_files/error_files/py_file_error_invalid_rst.py +0 -0
  40. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/tests/test_files/error_files/test_invalid_rst_error.rst +0 -0
  41. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/tests/test_files/error_files/test_invalid_rst_severe.rst +0 -0
  42. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/tests/test_files/error_files/test_invalid_rst_warning.rst +0 -0
  43. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/tests/test_files/error_files/test_invalid_syntax.rst +0 -0
  44. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/tests/test_files/error_files/test_invalid_table.rst +0 -0
  45. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/tests/test_files/images/biohazard.png +0 -0
  46. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/tests/test_files/pyproject.toml +0 -0
  47. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/tests/test_files/test_encoding.rst +0 -0
  48. {docstrfmt-1.5.1.dev0 → docstrfmt-1.6.0}/tests/test_files/test_file.txt +0 -0
@@ -1,10 +1,39 @@
1
1
  Change Log
2
2
  ==========
3
3
 
4
- Unreleased
5
- ----------
4
+ 1.6.0 (2023/12/10)
5
+ ------------------
6
+
7
+ **Added**
8
+
9
+ - Added more missing roles.
10
+ - Added support for Python 3.11.
11
+ - Added support for Python 3.12.
12
+
13
+ **Changed**
14
+
15
+ - Improved field sorting and formatting.
16
+ - Improved handling of ``:param:`` and ``:type:`` fields.
17
+ - Bumped ``black``, ``docutils``, ``libcst``, ``platformdirs``, and ``sphinx`` to latest
18
+ versions.
19
+
20
+ **Fixed**
21
+
22
+ - Fix ``:raises:`` field not supporting types.
23
+
24
+ **Removed**
25
+
26
+ - Removed support for Python 3.6.
27
+ - Removed support for Python 3.7.
28
+
29
+ 1.5.1 (2022/09/01)
30
+ ------------------
31
+
32
+ **Fixed**
33
+
34
+ - Fix ``ImportError`` when importing from black. Pinned black to 22.8.*.
6
35
 
7
- 1.5.0 (2022/05/31)
36
+ 1.5.0 (2022/07/19)
8
37
  ------------------
9
38
 
10
39
  **Added**
@@ -1,34 +1,56 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: docstrfmt
3
- Version: 1.5.1.dev0
3
+ Version: 1.6.0
4
4
  Summary: A formatter for Sphinx flavored reStructuredText.
5
5
  Home-page: https://github.com/LilSpazJoekp/docstrfmt
6
6
  Author: Joel Payne
7
7
  Author-email: lilspazjoekp@gmail.com
8
8
  License: MIT
9
- Platform: UNKNOWN
10
9
  Classifier: Development Status :: 4 - Beta
11
10
  Classifier: Environment :: Console
12
11
  Classifier: Intended Audience :: Developers
13
12
  Classifier: License :: OSI Approved :: MIT License
14
13
  Classifier: Programming Language :: Python :: 3
15
- Classifier: Programming Language :: Python :: 3.6
16
- Classifier: Programming Language :: Python :: 3.7
17
14
  Classifier: Programming Language :: Python :: 3.8
18
15
  Classifier: Programming Language :: Python :: 3.9
19
16
  Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
20
19
  Classifier: Programming Language :: Python
21
20
  Classifier: Topic :: Documentation
22
21
  Classifier: Topic :: Documentation :: Sphinx
23
22
  Classifier: Topic :: Software Development :: Documentation
24
23
  Classifier: Topic :: Utilities
25
- Requires-Python: ~=3.6
24
+ Requires-Python: ~=3.8
25
+ License-File: LICENSE.txt
26
+ License-File: AUTHORS.rst
27
+ Requires-Dist: black==23.*
28
+ Requires-Dist: click==8.*
29
+ Requires-Dist: docutils==0.20.*
30
+ Requires-Dist: libcst==1.*
31
+ Requires-Dist: platformdirs==4.*
32
+ Requires-Dist: sphinx==7.*
33
+ Requires-Dist: tabulate==0.9.*
34
+ Requires-Dist: toml==0.10.*
35
+ Provides-Extra: ci
36
+ Requires-Dist: coveralls; extra == "ci"
26
37
  Provides-Extra: d
38
+ Requires-Dist: aiohttp==3.*; extra == "d"
27
39
  Provides-Extra: dev
40
+ Requires-Dist: packaging; extra == "dev"
41
+ Requires-Dist: pre-commit; extra == "dev"
42
+ Requires-Dist: pytest; extra == "dev"
43
+ Requires-Dist: pytest-aiohttp; extra == "dev"
44
+ Requires-Dist: flake8; extra == "dev"
45
+ Requires-Dist: flynt; extra == "dev"
46
+ Requires-Dist: isort; extra == "dev"
28
47
  Provides-Extra: test
48
+ Requires-Dist: pytest; extra == "test"
49
+ Requires-Dist: pytest-aiohttp; extra == "test"
29
50
  Provides-Extra: lint
30
- License-File: LICENSE.txt
31
- License-File: AUTHORS.rst
51
+ Requires-Dist: flake8; extra == "lint"
52
+ Requires-Dist: flynt; extra == "lint"
53
+ Requires-Dist: isort; extra == "lint"
32
54
 
33
55
  docstrfmt: a formatter for Sphinx flavored reStructuredText
34
56
  ===========================================================
@@ -167,7 +189,7 @@ Instructions derived from `black documentation
167
189
 
168
190
  Note that if you are using a virtual environment detected by PyCharm, this is an
169
191
  unneeded step. In this case the path to `docstrfmt` is
170
- `$PyInterpreterDirectory$/docstrfmt`.
192
+ ``$PyInterpreterDirectory$/docstrfmt``.
171
193
 
172
194
  3. Open External tools in PyCharm.
173
195
 
@@ -184,7 +206,7 @@ Instructions derived from `black documentation
184
206
  - Name: docstrfmt
185
207
  - Description:
186
208
  - Program: <install_location_from_step_2>
187
- - Arguments: `"$FilePath$"`
209
+ - Arguments: ``"$FilePath$"``
188
210
 
189
211
  5. Format the currently opened file by selecting `Tools -> External Tools -> docstrfmt`.
190
212
 
@@ -202,9 +224,9 @@ Instructions derived from `black documentation
202
224
  - File type: Python
203
225
  - Scope: Project Files
204
226
  - Program: <install_location_from_step_2>
205
- - Arguments: `$FilePath$`
206
- - Output paths to refresh: `$FilePath$`
207
- - Working directory: `$ProjectFileDir$`
227
+ - Arguments: ``$FilePath$``
228
+ - Output paths to refresh: ``$FilePath$``
229
+ - Working directory: ``$ProjectFileDir$``
208
230
 
209
231
  3. Uncheck "Auto-save edited files to trigger the watcher" in Advanced Options
210
232
 
@@ -236,5 +258,3 @@ With pre-commit
236
258
  .. _rstfmt: https://github.com/dzhu/rstfmt
237
259
 
238
260
  .. _rustfmt: https://github.com/rust-lang/rustfmt
239
-
240
-
@@ -135,7 +135,7 @@ Instructions derived from `black documentation
135
135
 
136
136
  Note that if you are using a virtual environment detected by PyCharm, this is an
137
137
  unneeded step. In this case the path to `docstrfmt` is
138
- `$PyInterpreterDirectory$/docstrfmt`.
138
+ ``$PyInterpreterDirectory$/docstrfmt``.
139
139
 
140
140
  3. Open External tools in PyCharm.
141
141
 
@@ -152,7 +152,7 @@ Instructions derived from `black documentation
152
152
  - Name: docstrfmt
153
153
  - Description:
154
154
  - Program: <install_location_from_step_2>
155
- - Arguments: `"$FilePath$"`
155
+ - Arguments: ``"$FilePath$"``
156
156
 
157
157
  5. Format the currently opened file by selecting `Tools -> External Tools -> docstrfmt`.
158
158
 
@@ -170,9 +170,9 @@ Instructions derived from `black documentation
170
170
  - File type: Python
171
171
  - Scope: Project Files
172
172
  - Program: <install_location_from_step_2>
173
- - Arguments: `$FilePath$`
174
- - Output paths to refresh: `$FilePath$`
175
- - Working directory: `$ProjectFileDir$`
173
+ - Arguments: ``$FilePath$``
174
+ - Output paths to refresh: ``$FilePath$``
175
+ - Working directory: ``$ProjectFileDir$``
176
176
 
177
177
  3. Uncheck "Auto-save edited files to trigger the watcher" in Advanced Options
178
178
 
@@ -0,0 +1,2 @@
1
+ # pragma: no cover
2
+ __version__ = "1.6.0"
@@ -16,5 +16,5 @@ def _dump_lines(node: docutils.nodes.Node) -> Iterator[Tuple[int, str]]:
16
16
  body = str({k: v for k, v in node.attributes.items() if v})
17
17
  yield 0, f"{head} {body}"
18
18
  for c in node.children:
19
- for n, l in _dump_lines(c):
20
- yield n + 1, l
19
+ for n, line in _dump_lines(c):
20
+ yield n + 1, line
@@ -126,14 +126,12 @@ class CodeFormatters:
126
126
  compile(code, context.current_file, mode="exec")
127
127
  except SyntaxError as syntax_error:
128
128
  context.manager.error_count += 1
129
- with open(context.current_file, encoding="utf-8") as f:
130
- source = f.read()
131
- current_line = get_code_line(source, code)
129
+ current_line = get_code_line(context.current_file, code, strict=True)
132
130
  if context.manager.reporter:
133
131
  context.manager.reporter.error(
134
132
  f"SyntaxError: {syntax_error.msg}:\n\nFile"
135
133
  f' "{context.current_file}", line'
136
- f' {current_line}:\n{syntax_error.text}{" " * (syntax_error.offset-1)}^'
134
+ f' {current_line}:\n{syntax_error.text}{" " * (syntax_error.offset - 1)}^'
137
135
  )
138
136
  return code
139
137
 
@@ -422,8 +420,8 @@ class Formatters:
422
420
  sub_doc = self.manager.parse_string(
423
421
  context.current_file, "\n".join(directive.content)
424
422
  )
425
- if sub_doc.children: # pragma: no cover
426
- yield "" # no idea how to cover this
423
+ if sub_doc.children:
424
+ yield ""
427
425
  yield from self._with_spaces(
428
426
  4, self.manager.perform_format(sub_doc, context.indent(4))
429
427
  )
@@ -456,20 +454,19 @@ class Formatters:
456
454
  try:
457
455
  first_line = next(children)
458
456
  except StopIteration:
459
- with open(context.current_file, encoding="utf-8") as f:
460
- source = f.read()
461
- line = get_code_line(source, f":{node.astext().strip()}:")
462
457
  raise InvalidRstError(
463
458
  context.current_file,
464
459
  "ERROR",
465
- line,
460
+ get_code_line(
461
+ context.current_file, f":{node.astext().strip()}:", strict=True
462
+ ),
466
463
  f"Empty `:{node.astext().strip()}:` field. Please add a field body or"
467
- " omit completely",
464
+ " omit completely.",
468
465
  )
469
466
  children = list(children)
470
467
  children_processed = []
471
468
  for i, child in enumerate(children):
472
- if child.startswith(("..",)):
469
+ if child.startswith(".."):
473
470
  blocks_in_child = [child]
474
471
  for block in children[i + 1 :]:
475
472
  if block.startswith(" "):
@@ -485,11 +482,6 @@ class Formatters:
485
482
  else:
486
483
  children_processed.append(child)
487
484
  children = children_processed
488
- if field_name in [":raises:", ":returns:"]:
489
- if node.parent.index(node):
490
- previous = node.parent.children[node.parent.children.index(node) - 1]
491
- if previous.tagname == "field":
492
- yield ""
493
485
  yield f"{field_name} {first_line}"
494
486
  yield from self._with_spaces(4, children)
495
487
 
@@ -505,16 +497,119 @@ class Formatters:
505
497
  )
506
498
 
507
499
  def field_list(self, node: docutils.nodes.field_list, context) -> line_iterator:
508
- yield from chain(self._format_children(node, context))
500
+ param_types = {}
501
+ param_fields = []
502
+ returns_fields = []
503
+ rtype_fields = []
504
+ raises_fields = []
505
+ other_fields = []
506
+ field_types_mapping = {
507
+ "param": param_fields,
508
+ "arg": param_fields,
509
+ "argument": param_fields,
510
+ "return": returns_fields,
511
+ "returns": returns_fields,
512
+ "rtype": rtype_fields,
513
+ "raise": raises_fields,
514
+ "raises": raises_fields,
515
+ }
516
+ already_typed = []
517
+ children = node.children
518
+ for child in children[:]:
519
+ field_body = child.children[0].children[0]
520
+ field_typing = None
521
+ try:
522
+ field_kind, *field_typing, field_name = field_body.split(" ")
523
+ except ValueError:
524
+ field_name = None
525
+ field_kind = field_body
526
+ child.children[0].setdefault("name", field_name)
527
+ if field_kind == "type":
528
+ field_type = child.children[1].children[0].astext()
529
+ if "\n" in field_type:
530
+ raise InvalidRstError(
531
+ context.current_file,
532
+ "ERROR",
533
+ get_code_line(context.current_file, field_type),
534
+ "Multi-line type hints are not supported.",
535
+ )
536
+ param_types[field_name] = field_type
537
+ node.remove(child)
538
+ continue
539
+ if field_typing:
540
+ already_typed.append(field_name)
541
+ if field_kind in field_types_mapping:
542
+ if field_kind.startswith("return") and returns_fields:
543
+ raise InvalidRstError(
544
+ context.current_file,
545
+ "ERROR",
546
+ get_code_line(context.current_file, child.astext()),
547
+ "Multiple `:return:` fields are not allowed. Please"
548
+ " combine them into one.",
549
+ )
550
+ field_types_mapping[field_kind].append(child)
551
+ else:
552
+ other_fields.append(child)
553
+ for field in param_fields:
554
+ field_name = field.children[0].get("name")
555
+ if field_name in already_typed and field_name in param_types:
556
+ raise InvalidRstError(
557
+ context.current_file,
558
+ "ERROR",
559
+ get_code_line(context.current_file, field.astext()),
560
+ "Type hint is specified both in the field body and in the"
561
+ " `:type:` field. Please remove one of them.",
562
+ )
563
+ else:
564
+ field_typing = param_types.get(field_name, None)
565
+ if field_typing:
566
+ field.children[0].replace_self(
567
+ docutils.nodes.field_name(
568
+ "", f"param {field_typing} {field_name}"
569
+ )
570
+ )
571
+ yield from chain(
572
+ self.manager.perform_format(child, context) for child in param_fields
573
+ )
574
+ yield from self._prepend_if_any(
575
+ "",
576
+ chain(
577
+ self.manager.perform_format(child, context)
578
+ for child in returns_fields + rtype_fields
579
+ ),
580
+ )
581
+ yield from self._prepend_if_any(
582
+ "",
583
+ chain(
584
+ self.manager.perform_format(child, context) for child in raises_fields
585
+ ),
586
+ )
587
+ if (
588
+ other_fields
589
+ and param_fields + returns_fields + rtype_fields + raises_fields
590
+ ):
591
+ yield ""
592
+ yield from chain(
593
+ self.manager.perform_format(child, context) for child in other_fields
594
+ )
509
595
 
510
596
  def field_name(self, node: docutils.nodes.field_name, context) -> line_iterator:
511
597
  text = " ".join(chain(self._format_children(node, context)))
512
- if text.startswith(("raise", "raises")):
513
- yield ":raises:"
514
- elif text.startswith(("return", "returns")):
515
- yield ":returns:"
516
- else:
517
- yield f":{text}:"
598
+ body = ":"
599
+ field_kinds = [
600
+ ("param", "arg"),
601
+ "raise",
602
+ "return",
603
+ ]
604
+ for field_kind in field_kinds:
605
+ if text.startswith(field_kind):
606
+ field_kind, *_ = text.split(" ", maxsplit=1)
607
+ body += field_kind
608
+ text = text[len(field_kind) :]
609
+ break
610
+ body += text
611
+ body += ":"
612
+ yield body
518
613
 
519
614
  def footnote(self, node: docutils.nodes.footnote_reference, context):
520
615
  prefix = ".."
@@ -545,7 +640,7 @@ class Formatters:
545
640
 
546
641
  indent = 4 * context.line_block_depth
547
642
  context = context.indent(indent)
548
- prefix1 = "|" + " " * (indent - 1)
643
+ prefix1 = f"|{' ' * (indent - 1)}"
549
644
  prefix2 = " " * indent
550
645
  for first, line in self._enum_first(
551
646
  self._wrap_text(
@@ -568,7 +663,7 @@ class Formatters:
568
663
  )
569
664
  context.current_ordinal += 1
570
665
  width = len(context.bullet) + 1
571
- bullet = context.bullet + " "
666
+ bullet = f"{context.bullet} "
572
667
  spaces = " " * width
573
668
  context = context.indent(width)
574
669
  context.bullet = ""
@@ -658,40 +753,42 @@ class Formatters:
658
753
  yield inline_markup(title + anonymous_suffix(anonymous))
659
754
  return
660
755
 
661
- # Handle references to external URIs. They can be either standalone hyperlinks, written as
662
- # just the URI, or an explicit "`text <url>`_" or "`text <url>`__".
756
+ # Handle references to external URIs. They can be either standalone hyperlinks,
757
+ # written as just the URI, or an explicit "`text <url>`_" or "`text <url>`__".
663
758
  if "refuri" in attributes:
664
759
  uri = attributes["refuri"]
665
- if uri == title or uri == "mailto:" + title:
760
+ if uri == title or uri == f"mailto:{title}":
666
761
  yield inline_markup(title)
667
762
  else:
668
763
  anonymous = "target" not in attributes
669
764
  yield inline_markup(f"`{title} <{uri}>`{anonymous_suffix(anonymous)}")
670
765
  return
671
766
 
672
- # Simple reference names can consist of "alphanumerics plus isolated (no two adjacent)
673
- # internal hyphens, underscores, periods, colons and plus signs", according to
767
+ # Simple reference names can consist of "alphanumerics plus isolated (no two
768
+ # adjacent) internal hyphens, underscores, periods, colons and plus signs",
769
+ # according to
674
770
  # https://docutils.sourceforge.io/docs/ref/rst/restructuredtext.html#reference-names.
675
771
  is_single_word = re.match("^[-_.:+a-zA-Z0-9]+$", title) and not re.search(
676
772
  "[-_.:+][-_.:+]", title
677
773
  )
678
774
 
679
- # "x__" is one of the few cases to trigger an explicit "anonymous" attribute (the other
680
- # being the similar "|x|__", which is already handled above).
775
+ # "x__" is one of the few cases to trigger an explicit "anonymous" attribute
776
+ # (the other being the similar "|x|__", which is already handled above).
681
777
  if "anonymous" in attributes:
682
778
  if not is_single_word:
683
- title = "`" + title + "`"
779
+ title = f"`{title}`"
684
780
  yield inline_markup(title + anonymous_suffix(True))
685
781
  return
686
782
 
687
783
  anonymous = "target" not in attributes
688
784
  ref = attributes["refname"]
689
- # Check whether the reference name matches the text and can be made implicit. (Reference
690
- # names are case-insensitive.)
785
+ # Check whether the reference name matches the text and can be made implicit.
786
+ # (Reference names are case-insensitive.)
691
787
  if anonymous and ref.lower() == title.lower():
692
788
  if not is_single_word:
693
- title = "`" + title + "`"
694
- # "x_" is equivalent to "`x <x_>`__"; it's anonymous despite having a single underscore.
789
+ title = f"`{title}`"
790
+ # "x_" is equivalent to "`x <x_>`__"; it's anonymous despite having a single
791
+ # underscore.
695
792
  yield inline_markup(title + anonymous_suffix(False))
696
793
  else:
697
794
  yield inline_markup(f"`{title} <{ref}_>`{anonymous_suffix(anonymous)}")
@@ -748,9 +845,9 @@ class Formatters:
748
845
 
749
846
  def table(self, node: docutils.nodes.table, context) -> line_iterator:
750
847
  rows = []
751
- for row in node.traverse(docutils.nodes.row):
848
+ for row in node.findall(docutils.nodes.row):
752
849
  current_row = []
753
- for column in row.traverse(docutils.nodes.entry):
850
+ for column in row.findall(docutils.nodes.entry):
754
851
  if column.attributes.get("morerows", False) or column.attributes.get(
755
852
  "morecols", False
756
853
  ):
@@ -779,7 +876,7 @@ class Formatters:
779
876
  final_widths = [
780
877
  max(
781
878
  self._generate_table_matrix(context, rows, None, max_col_len),
782
- key=lambda l: l[i],
879
+ key=lambda lengths: lengths[i],
783
880
  )[i]
784
881
  for i in range(column_count)
785
882
  ]
@@ -811,7 +908,7 @@ class Formatters:
811
908
  final_widths = [
812
909
  max(
813
910
  self._generate_table_matrix(context, rows, None, column_lengths),
814
- key=lambda l: l[i],
911
+ key=lambda lengths: lengths[i],
815
912
  )[i]
816
913
  for i in range(column_count)
817
914
  ]
@@ -831,7 +928,11 @@ class Formatters:
831
928
  try:
832
929
  body = f" {node.attributes['refuri']}"
833
930
  except KeyError:
834
- body = ""
931
+ body = (
932
+ f" {node.attributes['refname']}_"
933
+ if "refname" in node.attributes
934
+ else ""
935
+ )
835
936
 
836
937
  name = "_" if node.attributes.get("anonymous") else node.attributes["names"][0]
837
938
  yield f".. _{name}:{body}"
@@ -922,7 +1023,13 @@ class Manager:
922
1023
  self.docstring_trailing_line = docstring_trailing_line
923
1024
 
924
1025
  def _pre_process(self, node: docutils.nodes.Node, source: str) -> None:
925
- """Do some node preprocessing that is generic across node types and is therefore most convenient to do as a simple recursive function rather than as part of the big dispatcher class."""
1026
+ """Preprocess nodes.
1027
+
1028
+ This does some preprocessing to all nodes that is generic across node types and
1029
+ is therefore most convenient to do as a simple recursive function rather than as
1030
+ part of the big dispatcher class.
1031
+
1032
+ """
926
1033
  # Strip all system_message nodes. (Just formatting them with no markup isn't enough, since that
927
1034
  # could lead to extra spaces or empty lines between other elements.)
928
1035
  errors = [
@@ -975,21 +1082,19 @@ class Manager:
975
1082
  self._pre_process(child, source)
976
1083
 
977
1084
  def format_node(self, width, node: docutils.nodes.Node, is_docstring=False) -> str:
978
- return (
979
- "\n".join(
980
- self.perform_format(
981
- node,
982
- FormatContext(
983
- width,
984
- current_file=self.current_file,
985
- manager=self,
986
- black_config=self.black_config,
987
- is_docstring=is_docstring,
988
- ),
989
- )
1085
+ formatted_node = "\n".join(
1086
+ self.perform_format(
1087
+ node,
1088
+ FormatContext(
1089
+ width,
1090
+ current_file=self.current_file,
1091
+ manager=self,
1092
+ black_config=self.black_config,
1093
+ is_docstring=is_docstring,
1094
+ ),
990
1095
  )
991
- + "\n"
992
1096
  )
1097
+ return f"{formatted_node}\n"
993
1098
 
994
1099
  def parse_string(self, file_name: str, text: str) -> docutils.nodes.document:
995
1100
  self.current_file = file_name