docstrfmt 2.0.2__tar.gz → 2.1.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.
- {docstrfmt-2.0.2 → docstrfmt-2.1.0}/PKG-INFO +3 -3
- {docstrfmt-2.0.2 → docstrfmt-2.1.0}/docstrfmt/__init__.py +1 -1
- {docstrfmt-2.0.2 → docstrfmt-2.1.0}/docstrfmt/docstrfmt.py +54 -9
- {docstrfmt-2.0.2 → docstrfmt-2.1.0}/docstrfmt/main.py +89 -36
- {docstrfmt-2.0.2 → docstrfmt-2.1.0}/docstrfmt/util.py +33 -12
- {docstrfmt-2.0.2 → docstrfmt-2.1.0}/pyproject.toml +3 -3
- {docstrfmt-2.0.2 → docstrfmt-2.1.0}/LICENSE.txt +0 -0
- {docstrfmt-2.0.2 → docstrfmt-2.1.0}/README.rst +0 -0
- {docstrfmt-2.0.2 → docstrfmt-2.1.0}/docstrfmt/__main__.py +0 -0
- {docstrfmt-2.0.2 → docstrfmt-2.1.0}/docstrfmt/const.py +0 -0
- {docstrfmt-2.0.2 → docstrfmt-2.1.0}/docstrfmt/debug.py +0 -0
- {docstrfmt-2.0.2 → docstrfmt-2.1.0}/docstrfmt/exceptions.py +0 -0
- {docstrfmt-2.0.2 → docstrfmt-2.1.0}/docstrfmt/rst_extras.py +0 -0
- {docstrfmt-2.0.2 → docstrfmt-2.1.0}/docstrfmt/server.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: docstrfmt
|
|
3
|
-
Version: 2.0
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: docstrfmt: A formatter for Sphinx flavored reStructuredText.
|
|
5
5
|
Keywords: black,docutils,autoformatter,formatter,lint,restructuredtext,rst,sphinx
|
|
6
6
|
Author-email: Joel Payne <lilspazjoekp@gmail.com>
|
|
@@ -32,9 +32,9 @@ Requires-Dist: libcst>=1
|
|
|
32
32
|
Requires-Dist: platformdirs>=4
|
|
33
33
|
Requires-Dist: roman
|
|
34
34
|
Requires-Dist: sphinx>=7
|
|
35
|
-
Requires-Dist: tabulate>=0.
|
|
35
|
+
Requires-Dist: tabulate>=0.10.0
|
|
36
36
|
Requires-Dist: tomli>=0.10;python_version<'3.11'
|
|
37
|
-
Requires-Dist: types-docutils==0.22.3.
|
|
37
|
+
Requires-Dist: types-docutils==0.22.3.20260518
|
|
38
38
|
Requires-Dist: aiohttp ; extra == "d"
|
|
39
39
|
Project-URL: Issue Tracker, https://github.com/LilSpazJoekp/docstrfmt/issues
|
|
40
40
|
Project-URL: Source Code, https://github.com/LilSpazJoekp/docstrfmt
|
|
@@ -25,6 +25,7 @@ from docutils import nodes, utils
|
|
|
25
25
|
from docutils.frontend import OptionParser
|
|
26
26
|
from docutils.parsers import rst
|
|
27
27
|
from docutils.parsers.rst import Directive, roles
|
|
28
|
+
from docutils.statemachine import StringList
|
|
28
29
|
from docutils.transforms import Transform
|
|
29
30
|
from docutils.utils import new_document, unescape
|
|
30
31
|
|
|
@@ -167,7 +168,7 @@ class FormatContext:
|
|
|
167
168
|
self.manager = manager
|
|
168
169
|
self.black_config = black_config
|
|
169
170
|
self.starting_width = width
|
|
170
|
-
self.bullet: str = ""
|
|
171
|
+
self.bullet: str = "-"
|
|
171
172
|
self.column_widths = []
|
|
172
173
|
self.current_ordinal = 0
|
|
173
174
|
self.first_line_len: int = 0
|
|
@@ -338,6 +339,7 @@ class CodeFormatters:
|
|
|
338
339
|
|
|
339
340
|
"""
|
|
340
341
|
manager = self.context.manager
|
|
342
|
+
manager.original_text = self.code
|
|
341
343
|
try:
|
|
342
344
|
document = manager.parse_string(
|
|
343
345
|
self.code, line_offset=manager.get_code_line(self.code) - 1
|
|
@@ -364,6 +366,8 @@ class Manager:
|
|
|
364
366
|
*,
|
|
365
367
|
current_file: Path | str,
|
|
366
368
|
black_config: Mode | None = None,
|
|
369
|
+
center_section_titles: bool = True,
|
|
370
|
+
bullet_list_marker: str = "-",
|
|
367
371
|
docstring_trailing_line: bool = True,
|
|
368
372
|
format_python_code_blocks: bool = True,
|
|
369
373
|
reporter: Reporter | utils.Reporter | logging.Logger,
|
|
@@ -374,6 +378,9 @@ class Manager:
|
|
|
374
378
|
:param current_file: The current file being processed.
|
|
375
379
|
:param reporter: utils.Reporter instance for logging.
|
|
376
380
|
:param black_config: Black formatting configuration.
|
|
381
|
+
:param center_section_titles: Whether to center section titles with overlines
|
|
382
|
+
by adding a leading space.
|
|
383
|
+
:param bullet_list_marker: Bullet character to use for unordered lists.
|
|
377
384
|
:param docstring_trailing_line: Whether to add trailing line to docstrings.
|
|
378
385
|
:param format_python_code_blocks: Whether to format Python code blocks.
|
|
379
386
|
:param section_adornments: Section adornment configuration.
|
|
@@ -382,6 +389,8 @@ class Manager:
|
|
|
382
389
|
rst_extras.register()
|
|
383
390
|
self.current_file = current_file
|
|
384
391
|
self.black_config = black_config
|
|
392
|
+
self.center_section_titles = center_section_titles
|
|
393
|
+
self.bullet_list_marker = bullet_list_marker
|
|
385
394
|
self.current_offset = 0
|
|
386
395
|
self.error_count = 0
|
|
387
396
|
self.reporter = reporter
|
|
@@ -901,7 +910,9 @@ class Formatters:
|
|
|
901
910
|
- Third item
|
|
902
911
|
|
|
903
912
|
"""
|
|
904
|
-
yield from self._list(
|
|
913
|
+
yield from self._list(
|
|
914
|
+
node, context.with_bullet(context.manager.bullet_list_marker)
|
|
915
|
+
)
|
|
905
916
|
|
|
906
917
|
def comment(
|
|
907
918
|
self,
|
|
@@ -1008,6 +1019,18 @@ class Formatters:
|
|
|
1008
1019
|
directive = attributes["directive"]
|
|
1009
1020
|
is_code_block = directive.name in ["code", "code-block", "sourcecode"]
|
|
1010
1021
|
in_substitution = isinstance(node.parent, nodes.substitution_definition)
|
|
1022
|
+
if (
|
|
1023
|
+
directive.name
|
|
1024
|
+
in ["deprecated", "versionadded", "versionchanged", "versionremoved"]
|
|
1025
|
+
and len(directive.arguments) > 1
|
|
1026
|
+
):
|
|
1027
|
+
# These directives have a required argument that we want to preserve, but
|
|
1028
|
+
# the content is just a normal paragraph that we can format like usual.
|
|
1029
|
+
# If there is more than 1 argument, then those need to be moved to the
|
|
1030
|
+
# content attribute.
|
|
1031
|
+
directive.content = StringList(directive.arguments[1:])
|
|
1032
|
+
directive.arguments = directive.arguments[:1]
|
|
1033
|
+
|
|
1011
1034
|
parts = [
|
|
1012
1035
|
f".. {'code-block' if is_code_block else directive.name}::",
|
|
1013
1036
|
*directive.arguments,
|
|
@@ -1574,9 +1597,9 @@ class Formatters:
|
|
|
1574
1597
|
|
|
1575
1598
|
"""
|
|
1576
1599
|
if not node.children: # pragma: no cover
|
|
1577
|
-
yield
|
|
1600
|
+
yield context.bullet # no idea why this isn't covered anymore
|
|
1578
1601
|
return
|
|
1579
|
-
if context.current_ordinal
|
|
1602
|
+
if context.current_ordinal:
|
|
1580
1603
|
context.bullet = make_enumerator(
|
|
1581
1604
|
context.current_ordinal, context.ordinal_format, ("", ".")
|
|
1582
1605
|
)
|
|
@@ -1596,7 +1619,7 @@ class Formatters:
|
|
|
1596
1619
|
node: nodes.literal,
|
|
1597
1620
|
context: FormatContext,
|
|
1598
1621
|
) -> inline_iterator:
|
|
1599
|
-
"""Format a literal node.
|
|
1622
|
+
r"""Format a literal node.
|
|
1600
1623
|
|
|
1601
1624
|
Example:
|
|
1602
1625
|
|
|
@@ -1604,7 +1627,23 @@ class Formatters:
|
|
|
1604
1627
|
|
|
1605
1628
|
This is ``literal`` text.
|
|
1606
1629
|
|
|
1630
|
+
If the literal needs to end with a space:
|
|
1631
|
+
|
|
1632
|
+
.. code-block:: rst
|
|
1633
|
+
|
|
1634
|
+
This is a :literal:`literal with a trailing space \ ` that is not surrounded
|
|
1635
|
+
with (``).
|
|
1636
|
+
|
|
1607
1637
|
"""
|
|
1638
|
+
if node.rawsource.startswith(":literal:") and node.rawsource.endswith(
|
|
1639
|
+
(r"\ `", "\\\n`")
|
|
1640
|
+
):
|
|
1641
|
+
# When the node ends with a backslash followed by a space or new line, don't
|
|
1642
|
+
# remove the :literal: role and just return the node untouched. This is a
|
|
1643
|
+
# workaround for specific edge cases in docutils.
|
|
1644
|
+
yield inline_markup(node.rawsource)
|
|
1645
|
+
return
|
|
1646
|
+
|
|
1608
1647
|
yield inline_markup(
|
|
1609
1648
|
f"``{''.join(chain(self._format_children(node, context)))}``"
|
|
1610
1649
|
)
|
|
@@ -2203,10 +2242,16 @@ class Formatters:
|
|
|
2203
2242
|
raise
|
|
2204
2243
|
|
|
2205
2244
|
if overline:
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2245
|
+
if context.manager.center_section_titles:
|
|
2246
|
+
# section headings with overline are centered
|
|
2247
|
+
yield char * (2 + len(text))
|
|
2248
|
+
yield " " + text
|
|
2249
|
+
yield char * (2 + len(text))
|
|
2250
|
+
else:
|
|
2251
|
+
# section headings with overline are not centered
|
|
2252
|
+
yield char * len(text)
|
|
2253
|
+
yield text
|
|
2254
|
+
yield char * len(text)
|
|
2210
2255
|
else:
|
|
2211
2256
|
# sections headings without overline are justified
|
|
2212
2257
|
yield text
|
|
@@ -66,6 +66,8 @@ def _format_file(
|
|
|
66
66
|
section_adornments: list[tuple[str, bool]] | None,
|
|
67
67
|
raw_output: bool,
|
|
68
68
|
lock: Lock | None,
|
|
69
|
+
bullet_list_marker: str = "-",
|
|
70
|
+
center_section_titles: bool = True,
|
|
69
71
|
):
|
|
70
72
|
"""Format a single file with the given parameters.
|
|
71
73
|
|
|
@@ -80,6 +82,8 @@ def _format_file(
|
|
|
80
82
|
:param section_adornments: Section adornment configuration.
|
|
81
83
|
:param raw_output: Whether to output raw formatted text.
|
|
82
84
|
:param lock: Lock for thread safety.
|
|
85
|
+
:param bullet_list_marker: Bullet character to use for unordered lists.
|
|
86
|
+
:param center_section_titles: Whether to center section titles with overlines.
|
|
83
87
|
|
|
84
88
|
:returns: A tuple containing a boolean indicating if the file was misformatted and
|
|
85
89
|
the number of errors.
|
|
@@ -89,6 +93,8 @@ def _format_file(
|
|
|
89
93
|
manager = Manager(
|
|
90
94
|
current_file=file.name,
|
|
91
95
|
black_config=mode,
|
|
96
|
+
bullet_list_marker=bullet_list_marker,
|
|
97
|
+
center_section_titles=center_section_titles,
|
|
92
98
|
docstring_trailing_line=docstring_trailing_line,
|
|
93
99
|
format_python_code_blocks=format_python_code_blocks,
|
|
94
100
|
reporter=reporter,
|
|
@@ -181,14 +187,14 @@ def _parse_pyproject_config(
|
|
|
181
187
|
config_value = config.get(key)
|
|
182
188
|
if config_value is not None and not isinstance(config_value, list):
|
|
183
189
|
raise click.BadOptionUsage(key, f"Config key {key} must be a list")
|
|
184
|
-
|
|
190
|
+
# Expose config values through the default map only; writing them
|
|
191
|
+
# directly into context.params conflicts with how click >=8.4
|
|
192
|
+
# arbitrates which parameter owns a value slot.
|
|
193
|
+
default_map = {}
|
|
185
194
|
if context.default_map is not None: # pragma: no cover
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
params.update(config)
|
|
190
|
-
context.params = params
|
|
191
|
-
context.default_map = params
|
|
195
|
+
default_map.update(context.default_map)
|
|
196
|
+
default_map.update(config)
|
|
197
|
+
context.default_map = default_map
|
|
192
198
|
|
|
193
199
|
black_config = parse_pyproject_toml(value)
|
|
194
200
|
black_config.pop("exclude", None)
|
|
@@ -215,11 +221,21 @@ def _parse_sources(context: click.Context, _: click.Parameter, value: list[str]
|
|
|
215
221
|
:returns: List of resolved file paths to format.
|
|
216
222
|
|
|
217
223
|
"""
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
224
|
+
default_map = context.default_map or {}
|
|
225
|
+
|
|
226
|
+
def lookup(name: str, fallback: Any) -> Any:
|
|
227
|
+
# Sibling parameters may not be processed yet when this callback runs,
|
|
228
|
+
# so fall back to the default map, which _parse_pyproject_config fills
|
|
229
|
+
# with pyproject.toml values.
|
|
230
|
+
if name in context.params:
|
|
231
|
+
return context.params[name]
|
|
232
|
+
return default_map.get(name, fallback)
|
|
233
|
+
|
|
234
|
+
sources = value or lookup("files", [])
|
|
235
|
+
exclude = list(lookup("exclude", DEFAULT_EXCLUDE))
|
|
236
|
+
extend_exclude = list(lookup("extend_exclude", []))
|
|
221
237
|
exclude.extend(extend_exclude)
|
|
222
|
-
include_txt =
|
|
238
|
+
include_txt = lookup("include_txt", False)
|
|
223
239
|
files_to_format = set()
|
|
224
240
|
extensions = [".py", ".rst"] + ([".txt"] if include_txt else [])
|
|
225
241
|
for source in sources:
|
|
@@ -243,10 +259,7 @@ def _parse_sources(context: click.Context, _: click.Parameter, value: list[str]
|
|
|
243
259
|
if file.parent.match(exclusion) or file.match(exclusion):
|
|
244
260
|
files_to_format.discard(abspath(file))
|
|
245
261
|
break
|
|
246
|
-
|
|
247
|
-
if context.params.get("files", []):
|
|
248
|
-
context.params["files"] = sorted_files
|
|
249
|
-
return sorted_files
|
|
262
|
+
return sorted(files_to_format)
|
|
250
263
|
|
|
251
264
|
|
|
252
265
|
def _process_python(
|
|
@@ -359,20 +372,6 @@ def _process_rst(
|
|
|
359
372
|
return misformatted, error_count
|
|
360
373
|
|
|
361
374
|
|
|
362
|
-
def _resolve_length(context: click.Context, _: click.Parameter, value: int | None):
|
|
363
|
-
"""Resolve line length from command line or pyproject.toml.
|
|
364
|
-
|
|
365
|
-
:param context: Click context containing command parameters.
|
|
366
|
-
:param _: Unused parameter.
|
|
367
|
-
:param value: Line length from command line.
|
|
368
|
-
|
|
369
|
-
:returns: Resolved line length value.
|
|
370
|
-
|
|
371
|
-
"""
|
|
372
|
-
pyproject_line_length = context.params.pop("line_length", None)
|
|
373
|
-
return value or pyproject_line_length
|
|
374
|
-
|
|
375
|
-
|
|
376
375
|
def _validate_adornments(
|
|
377
376
|
context: click.Context, _: click.Parameter, value: str | None
|
|
378
377
|
) -> list[tuple[str, bool]] | None:
|
|
@@ -380,14 +379,14 @@ def _validate_adornments(
|
|
|
380
379
|
|
|
381
380
|
:param context: Click context containing command parameters.
|
|
382
381
|
:param _: Unused parameter.
|
|
383
|
-
:param value: Section adornments string from command line.
|
|
382
|
+
:param value: Section adornments string from the command line or pyproject.toml.
|
|
384
383
|
|
|
385
384
|
:returns: List of tuples containing (character, has_overline) for each adornment.
|
|
386
385
|
|
|
387
386
|
:raises click.BadParameter: If adornments are not unique.
|
|
388
387
|
|
|
389
388
|
"""
|
|
390
|
-
actual_value =
|
|
389
|
+
actual_value = SECTION_CHARS if value is None else value
|
|
391
390
|
|
|
392
391
|
if len(actual_value) != len(set(actual_value)):
|
|
393
392
|
msg = "Section adornments must be unique"
|
|
@@ -418,6 +417,8 @@ async def _run_formatter(
|
|
|
418
417
|
cache: FileCache,
|
|
419
418
|
loop: asyncio.AbstractEventLoop,
|
|
420
419
|
executor: ProcessPoolExecutor | ThreadPoolExecutor,
|
|
420
|
+
bullet_list_marker: str = "-",
|
|
421
|
+
center_section_titles: bool = True,
|
|
421
422
|
):
|
|
422
423
|
"""Run the formatter on multiple files asynchronously.
|
|
423
424
|
|
|
@@ -434,6 +435,8 @@ async def _run_formatter(
|
|
|
434
435
|
:param cache: File cache for tracking changes.
|
|
435
436
|
:param loop: Event loop for async operations.
|
|
436
437
|
:param executor: Process or thread pool executor.
|
|
438
|
+
:param bullet_list_marker: Bullet character to use for unordered lists.
|
|
439
|
+
:param center_section_titles: Whether to center section titles with overlines.
|
|
437
440
|
|
|
438
441
|
:returns: Tuple of (misformatted_files, total_error_count).
|
|
439
442
|
|
|
@@ -461,6 +464,8 @@ async def _run_formatter(
|
|
|
461
464
|
section_adornments,
|
|
462
465
|
raw_output,
|
|
463
466
|
lock,
|
|
467
|
+
bullet_list_marker,
|
|
468
|
+
center_section_titles,
|
|
464
469
|
)
|
|
465
470
|
): file
|
|
466
471
|
for file in sorted(todo)
|
|
@@ -489,8 +494,13 @@ async def _run_formatter(
|
|
|
489
494
|
if misformatted:
|
|
490
495
|
misformatted_files.add(file)
|
|
491
496
|
if (
|
|
492
|
-
|
|
493
|
-
|
|
497
|
+
file.name != "-" # stdin cannot be cached
|
|
498
|
+
and (
|
|
499
|
+
not (misformatted and raw_output)
|
|
500
|
+
or (check and not misformatted)
|
|
501
|
+
)
|
|
502
|
+
and errors == 0
|
|
503
|
+
):
|
|
494
504
|
files_to_cache.append(file)
|
|
495
505
|
if cancelled: # pragma: no cover
|
|
496
506
|
await asyncio.gather(*cancelled, return_exceptions=True)
|
|
@@ -839,6 +849,23 @@ class Visitor(CSTTransformer):
|
|
|
839
849
|
|
|
840
850
|
# noinspection PyUnusedLocal
|
|
841
851
|
@click.command(context_settings={"help_option_names": ["-h", "--help"]})
|
|
852
|
+
@click.option(
|
|
853
|
+
"-b",
|
|
854
|
+
"--bullet-list-marker",
|
|
855
|
+
default="-",
|
|
856
|
+
help="Bullet character to use for unordered lists.",
|
|
857
|
+
show_default=True,
|
|
858
|
+
type=click.Choice(["-", "*", "+"], case_sensitive=False),
|
|
859
|
+
)
|
|
860
|
+
@click.option(
|
|
861
|
+
"--center-section-titles/--no-center-section-titles",
|
|
862
|
+
default=True,
|
|
863
|
+
help=(
|
|
864
|
+
"Whether to center section titles with overlines by adding a leading space."
|
|
865
|
+
" When disabled, section titles with overlines will not have a leading space"
|
|
866
|
+
" and the adornment will match the exact length of the title text."
|
|
867
|
+
),
|
|
868
|
+
)
|
|
842
869
|
@click.option(
|
|
843
870
|
"-c",
|
|
844
871
|
"--check",
|
|
@@ -909,7 +936,6 @@ class Visitor(CSTTransformer):
|
|
|
909
936
|
" 'line-length' set in pyproject.toml if set. Defaults to the length provided"
|
|
910
937
|
" to black if not set."
|
|
911
938
|
),
|
|
912
|
-
callback=_resolve_length,
|
|
913
939
|
)
|
|
914
940
|
@click.option(
|
|
915
941
|
"-pA",
|
|
@@ -987,6 +1013,8 @@ class Visitor(CSTTransformer):
|
|
|
987
1013
|
@click.pass_context
|
|
988
1014
|
def main(
|
|
989
1015
|
context: Context,
|
|
1016
|
+
bullet_list_marker: str,
|
|
1017
|
+
center_section_titles: bool,
|
|
990
1018
|
check: bool,
|
|
991
1019
|
docstring_trailing_line: bool,
|
|
992
1020
|
exclude: list[str],
|
|
@@ -1008,6 +1036,8 @@ def main(
|
|
|
1008
1036
|
"""Format reStructuredText and Python files.
|
|
1009
1037
|
|
|
1010
1038
|
:param context: Click context containing command parameters.
|
|
1039
|
+
:param bullet_list_marker: Bullet character to use for unordered lists.
|
|
1040
|
+
:param center_section_titles: Whether to center section titles with overlines.
|
|
1011
1041
|
:param check: Whether to check formatting without modifying files.
|
|
1012
1042
|
:param docstring_trailing_line: Whether to add trailing line to docstrings.
|
|
1013
1043
|
:param exclude: List of paths to exclude from formatting.
|
|
@@ -1050,6 +1080,8 @@ def main(
|
|
|
1050
1080
|
manager = Manager(
|
|
1051
1081
|
current_file=file,
|
|
1052
1082
|
black_config=mode,
|
|
1083
|
+
bullet_list_marker=bullet_list_marker,
|
|
1084
|
+
center_section_titles=center_section_titles,
|
|
1053
1085
|
docstring_trailing_line=docstring_trailing_line,
|
|
1054
1086
|
format_python_code_blocks=format_python_code_blocks,
|
|
1055
1087
|
reporter=reporter,
|
|
@@ -1077,10 +1109,18 @@ def main(
|
|
|
1077
1109
|
|
|
1078
1110
|
cache = FileCache(context, ignore_cache)
|
|
1079
1111
|
if len(files) < 2:
|
|
1080
|
-
|
|
1112
|
+
if raw_output:
|
|
1113
|
+
# Raw output must always be emitted, even for cached files.
|
|
1114
|
+
todo = {Path(file).resolve() for file in files}
|
|
1115
|
+
else:
|
|
1116
|
+
todo, _already_done = cache.gen_todo_list(files)
|
|
1117
|
+
files_to_cache = []
|
|
1118
|
+
for file in (Path(f) for f in files):
|
|
1119
|
+
if file.resolve() not in todo:
|
|
1120
|
+
continue
|
|
1081
1121
|
misformatted, error_count = _format_file(
|
|
1082
1122
|
check,
|
|
1083
|
-
|
|
1123
|
+
file,
|
|
1084
1124
|
file_type,
|
|
1085
1125
|
include_txt,
|
|
1086
1126
|
line_length,
|
|
@@ -1090,9 +1130,20 @@ def main(
|
|
|
1090
1130
|
section_adornments,
|
|
1091
1131
|
raw_output,
|
|
1092
1132
|
None,
|
|
1133
|
+
bullet_list_marker,
|
|
1134
|
+
center_section_titles,
|
|
1093
1135
|
)
|
|
1094
1136
|
if misformatted:
|
|
1095
1137
|
misformatted_files.add(file)
|
|
1138
|
+
if (
|
|
1139
|
+
file.name != "-" # stdin cannot be cached
|
|
1140
|
+
and not raw_output # raw output does not modify the file
|
|
1141
|
+
and not (check and misformatted) # the file remains misformatted
|
|
1142
|
+
and error_count == 0
|
|
1143
|
+
):
|
|
1144
|
+
files_to_cache.append(file)
|
|
1145
|
+
if files_to_cache:
|
|
1146
|
+
cache.write_cache(files_to_cache)
|
|
1096
1147
|
|
|
1097
1148
|
else:
|
|
1098
1149
|
# This code is heavily based on that of psf/black
|
|
@@ -1126,6 +1177,8 @@ def main(
|
|
|
1126
1177
|
cache,
|
|
1127
1178
|
loop,
|
|
1128
1179
|
executor,
|
|
1180
|
+
bullet_list_marker,
|
|
1181
|
+
center_section_titles,
|
|
1129
1182
|
)
|
|
1130
1183
|
)
|
|
1131
1184
|
finally:
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import hashlib
|
|
5
6
|
import pickle
|
|
6
7
|
import tempfile
|
|
7
8
|
from collections import defaultdict
|
|
@@ -59,6 +60,23 @@ def make_enumerator(ordinal: int, sequence: str, fmt: tuple[str, str]) -> str:
|
|
|
59
60
|
class FileCache:
|
|
60
61
|
"""A class to manage the cache of files."""
|
|
61
62
|
|
|
63
|
+
# Parameters that do not affect how a given file is formatted and therefore
|
|
64
|
+
# must not influence the cache key.
|
|
65
|
+
_NON_FORMATTING_PARAMS = frozenset(
|
|
66
|
+
[
|
|
67
|
+
"check",
|
|
68
|
+
"exclude",
|
|
69
|
+
"extend_exclude",
|
|
70
|
+
"files",
|
|
71
|
+
"ignore_cache",
|
|
72
|
+
"mode",
|
|
73
|
+
"quiet",
|
|
74
|
+
"raw_input",
|
|
75
|
+
"raw_output",
|
|
76
|
+
"verbose",
|
|
77
|
+
]
|
|
78
|
+
)
|
|
79
|
+
|
|
62
80
|
@staticmethod
|
|
63
81
|
def _get_file_info(file: Path) -> tuple[float, int]:
|
|
64
82
|
"""Get the file info.
|
|
@@ -88,20 +106,22 @@ class FileCache:
|
|
|
88
106
|
def _get_cache_filename(self) -> Path:
|
|
89
107
|
"""Get the cache filename.
|
|
90
108
|
|
|
109
|
+
The filename incorporates every parameter that affects formatting output
|
|
110
|
+
(e.g., ``section_adornments``, ``bullet_list_marker``) so that changing any
|
|
111
|
+
of them invalidates previously cached results.
|
|
112
|
+
|
|
91
113
|
:returns: Path to the cache file.
|
|
92
114
|
|
|
93
115
|
"""
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
/ f"cache.{f'{docstring_trailing_line}_{format_python_code_blocks}_{include_txt}_{line_length}_{mode}'}.pickle"
|
|
104
|
-
)
|
|
116
|
+
params = self.context.params
|
|
117
|
+
parts = [
|
|
118
|
+
f"{name}={params[name]!r}"
|
|
119
|
+
for name in sorted(params)
|
|
120
|
+
if name not in self._NON_FORMATTING_PARAMS
|
|
121
|
+
]
|
|
122
|
+
parts.append(f"mode={params['mode'].get_cache_key()}")
|
|
123
|
+
key = hashlib.sha256("|".join(parts).encode("utf-8")).hexdigest()
|
|
124
|
+
return self.cache_dir / f"cache.{key}.pickle"
|
|
105
125
|
|
|
106
126
|
def _read_cache(self) -> dict[str, tuple[float, int]]:
|
|
107
127
|
"""Read the cache file.
|
|
@@ -134,8 +154,9 @@ class FileCache:
|
|
|
134
154
|
todo, done = set(), set()
|
|
135
155
|
for file in (Path(f).resolve() for f in files):
|
|
136
156
|
if (
|
|
137
|
-
|
|
157
|
+
file.name == "-" # stdin cannot be cached
|
|
138
158
|
or self.ignore_cache
|
|
159
|
+
or self.cache.get(str(file)) != self._get_file_info(file)
|
|
139
160
|
):
|
|
140
161
|
todo.add(file)
|
|
141
162
|
else:
|
|
@@ -11,7 +11,7 @@ dev = [
|
|
|
11
11
|
lint = [
|
|
12
12
|
"pre-commit",
|
|
13
13
|
"pyright>=1.1.406",
|
|
14
|
-
"ruff>=0.
|
|
14
|
+
"ruff>=0.15.10"
|
|
15
15
|
]
|
|
16
16
|
test = [
|
|
17
17
|
"aiohttp",
|
|
@@ -52,9 +52,9 @@ dependencies = [
|
|
|
52
52
|
"platformdirs>=4",
|
|
53
53
|
"roman",
|
|
54
54
|
"sphinx>=7",
|
|
55
|
-
"tabulate>=0.
|
|
55
|
+
"tabulate>=0.10.0",
|
|
56
56
|
"tomli>=0.10;python_version<'3.11'",
|
|
57
|
-
"types-docutils==0.22.3.
|
|
57
|
+
"types-docutils==0.22.3.20260518"
|
|
58
58
|
]
|
|
59
59
|
dynamic = ["version", "description"]
|
|
60
60
|
keywords = ["black", "docutils", "autoformatter", "formatter", "lint", "restructuredtext", "rst", "sphinx"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|