format-docstring 0.2.2__py3-none-any.whl → 0.2.4__py3-none-any.whl
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.
- format_docstring/base_fixer.py +1 -1
- format_docstring/config.py +5 -4
- format_docstring/docstring_rewriter.py +37 -26
- format_docstring/line_wrap_google.py +8 -7
- format_docstring/line_wrap_numpy.py +66 -36
- format_docstring/line_wrap_utils.py +21 -19
- format_docstring/main_jupyter.py +6 -6
- format_docstring/main_py.py +5 -3
- {format_docstring-0.2.2.dist-info → format_docstring-0.2.4.dist-info}/METADATA +11 -11
- format_docstring-0.2.4.dist-info/RECORD +15 -0
- format_docstring-0.2.2.dist-info/RECORD +0 -15
- {format_docstring-0.2.2.dist-info → format_docstring-0.2.4.dist-info}/WHEEL +0 -0
- {format_docstring-0.2.2.dist-info → format_docstring-0.2.4.dist-info}/entry_points.txt +0 -0
- {format_docstring-0.2.2.dist-info → format_docstring-0.2.4.dist-info}/licenses/LICENSE +0 -0
- {format_docstring-0.2.2.dist-info → format_docstring-0.2.4.dist-info}/top_level.txt +0 -0
format_docstring/base_fixer.py
CHANGED
|
@@ -42,7 +42,7 @@ class BaseFixer:
|
|
|
42
42
|
if not should_exclude_file(f, self.exclude_pattern)
|
|
43
43
|
]
|
|
44
44
|
|
|
45
|
-
def
|
|
45
|
+
def print_diff(self, filename: str, before: str, after: str) -> None:
|
|
46
46
|
"""Print a unified diff when verbose mode is enabled."""
|
|
47
47
|
if self.verbose != 'diff':
|
|
48
48
|
return
|
format_docstring/config.py
CHANGED
|
@@ -4,9 +4,10 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import sys
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import Any
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
import click
|
|
10
11
|
|
|
11
12
|
if sys.version_info >= (3, 11):
|
|
12
13
|
import tomllib
|
|
@@ -132,7 +133,7 @@ def load_config_from_file(config_file: Path) -> dict[str, Any]:
|
|
|
132
133
|
return {}
|
|
133
134
|
|
|
134
135
|
try:
|
|
135
|
-
with open(
|
|
136
|
+
with Path(config_file).open('rb') as fp:
|
|
136
137
|
raw_config = tomllib.load(fp)
|
|
137
138
|
|
|
138
139
|
# Extract [tool.format_docstring] section
|
|
@@ -144,7 +145,7 @@ def load_config_from_file(config_file: Path) -> dict[str, Any]:
|
|
|
144
145
|
return {
|
|
145
146
|
k.replace('-', '_'): v for k, v in format_docstring_section.items()
|
|
146
147
|
}
|
|
147
|
-
except Exception:
|
|
148
|
+
except Exception: # noqa: BLE001
|
|
148
149
|
# If there's any error reading/parsing the file, return empty config
|
|
149
150
|
return {}
|
|
150
151
|
|
|
@@ -2,14 +2,18 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import ast
|
|
4
4
|
import io
|
|
5
|
+
import operator
|
|
5
6
|
import tokenize
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
6
8
|
|
|
7
9
|
from format_docstring.line_wrap_google import wrap_docstring_google
|
|
8
10
|
from format_docstring.line_wrap_numpy import (
|
|
9
11
|
handle_single_line_docstring,
|
|
10
12
|
wrap_docstring_numpy,
|
|
11
13
|
)
|
|
12
|
-
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from format_docstring.line_wrap_utils import ParameterMetadata
|
|
13
17
|
|
|
14
18
|
ModuleClassOrFunc = (
|
|
15
19
|
ast.Module | ast.ClassDef | ast.FunctionDef | ast.AsyncFunctionDef
|
|
@@ -91,11 +95,13 @@ def _normalize_signature_segment(segment: str | None) -> str | None:
|
|
|
91
95
|
# iterator order mirrors the unparse traversal so we can reapply them.
|
|
92
96
|
original_strings: list[str] = []
|
|
93
97
|
try:
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
98
|
+
original_strings.extend(
|
|
99
|
+
tok.string
|
|
100
|
+
for tok in tokenize.generate_tokens(
|
|
101
|
+
io.StringIO(normalized).readline
|
|
102
|
+
)
|
|
103
|
+
if tok.type == tokenize.STRING
|
|
104
|
+
)
|
|
99
105
|
except tokenize.TokenError:
|
|
100
106
|
original_strings = []
|
|
101
107
|
|
|
@@ -105,6 +111,7 @@ def _normalize_signature_segment(segment: str | None) -> str | None:
|
|
|
105
111
|
for tok in tokenize.generate_tokens(
|
|
106
112
|
io.StringIO(canonical).readline
|
|
107
113
|
):
|
|
114
|
+
current_tok: tokenize.TokenInfo = tok
|
|
108
115
|
if tok.type == tokenize.STRING:
|
|
109
116
|
replacement = next(string_iter, None)
|
|
110
117
|
if replacement is not None:
|
|
@@ -116,12 +123,12 @@ def _normalize_signature_segment(segment: str | None) -> str | None:
|
|
|
116
123
|
if ast.literal_eval(
|
|
117
124
|
replacement
|
|
118
125
|
) == ast.literal_eval(tok.string):
|
|
119
|
-
|
|
120
|
-
except Exception:
|
|
126
|
+
current_tok = tok._replace(string=replacement)
|
|
127
|
+
except Exception: # noqa: BLE001
|
|
121
128
|
pass
|
|
122
129
|
|
|
123
|
-
rebuilt_tokens.append(
|
|
124
|
-
if
|
|
130
|
+
rebuilt_tokens.append(current_tok)
|
|
131
|
+
if current_tok.type == tokenize.ENDMARKER:
|
|
125
132
|
break
|
|
126
133
|
|
|
127
134
|
# Untokenize the rebuilt stream while trimming the leading/trailing
|
|
@@ -305,16 +312,17 @@ def fix_src(
|
|
|
305
312
|
tree: ast.Module = ast.parse(source_code, type_comments=True)
|
|
306
313
|
line_starts: list[int] = calc_line_starts(source_code)
|
|
307
314
|
|
|
315
|
+
# Store (start, end, replacement text) tuples
|
|
308
316
|
replacements: list[tuple[int, int, str]] = []
|
|
309
317
|
|
|
310
318
|
# Module-level docstring
|
|
311
319
|
replacement = build_replacement_docstring(
|
|
312
320
|
tree,
|
|
313
|
-
source_code,
|
|
314
|
-
line_starts,
|
|
315
|
-
line_length,
|
|
316
|
-
docstring_style,
|
|
317
|
-
fix_rst_backticks,
|
|
321
|
+
source_code=source_code,
|
|
322
|
+
line_starts=line_starts,
|
|
323
|
+
line_length=line_length,
|
|
324
|
+
docstring_style=docstring_style,
|
|
325
|
+
fix_rst_backticks=fix_rst_backticks,
|
|
318
326
|
)
|
|
319
327
|
if replacement is not None:
|
|
320
328
|
replacements.append(replacement)
|
|
@@ -326,11 +334,11 @@ def fix_src(
|
|
|
326
334
|
):
|
|
327
335
|
replacement = build_replacement_docstring(
|
|
328
336
|
node,
|
|
329
|
-
source_code,
|
|
330
|
-
line_starts,
|
|
331
|
-
line_length,
|
|
332
|
-
docstring_style,
|
|
333
|
-
fix_rst_backticks,
|
|
337
|
+
source_code=source_code,
|
|
338
|
+
line_starts=line_starts,
|
|
339
|
+
line_length=line_length,
|
|
340
|
+
docstring_style=docstring_style,
|
|
341
|
+
fix_rst_backticks=fix_rst_backticks,
|
|
334
342
|
)
|
|
335
343
|
if replacement is not None:
|
|
336
344
|
replacements.append(replacement)
|
|
@@ -339,7 +347,8 @@ def fix_src(
|
|
|
339
347
|
if not replacements:
|
|
340
348
|
return source_code
|
|
341
349
|
|
|
342
|
-
|
|
350
|
+
# Sort by starting index descending
|
|
351
|
+
replacements.sort(key=operator.itemgetter(0), reverse=True)
|
|
343
352
|
new_src = source_code
|
|
344
353
|
for start, end, text in replacements:
|
|
345
354
|
new_src = new_src[:start] + text + new_src[end:]
|
|
@@ -371,6 +380,7 @@ def calc_line_starts(source_code: str) -> list[int]:
|
|
|
371
380
|
|
|
372
381
|
def build_replacement_docstring(
|
|
373
382
|
node: ModuleClassOrFunc,
|
|
383
|
+
*,
|
|
374
384
|
source_code: str,
|
|
375
385
|
line_starts: list[int],
|
|
376
386
|
line_length: int,
|
|
@@ -411,7 +421,7 @@ def build_replacement_docstring(
|
|
|
411
421
|
return None
|
|
412
422
|
|
|
413
423
|
start: int = calc_abs_pos(line_starts, val.lineno, val.col_offset)
|
|
414
|
-
end: int = calc_abs_pos(line_starts, val.end_lineno, val.end_col_offset) # type: ignore[arg-type]
|
|
424
|
+
end: int = calc_abs_pos(line_starts, val.end_lineno, val.end_col_offset) # type: ignore[arg-type]
|
|
415
425
|
original_literal = source_code[start:end]
|
|
416
426
|
|
|
417
427
|
if _has_inline_no_format_comment(source_code, end):
|
|
@@ -554,10 +564,10 @@ def rebuild_literal(original_literal: str, content: str) -> str | None:
|
|
|
554
564
|
prefix = original_literal[:i]
|
|
555
565
|
|
|
556
566
|
delim = ''
|
|
557
|
-
if original_literal[i : i + 3] in
|
|
567
|
+
if original_literal[i : i + 3] in {'"""', "'''"}:
|
|
558
568
|
delim = original_literal[i : i + 3]
|
|
559
569
|
i += 3
|
|
560
|
-
elif i < n and original_literal[i] in
|
|
570
|
+
elif i < n and original_literal[i] in {'"', "'"}:
|
|
561
571
|
delim = original_literal[i]
|
|
562
572
|
i += 1
|
|
563
573
|
else:
|
|
@@ -576,6 +586,7 @@ def wrap_docstring(
|
|
|
576
586
|
line_length: int = 79,
|
|
577
587
|
docstring_style: str = 'numpy',
|
|
578
588
|
leading_indent: int = 0,
|
|
589
|
+
*,
|
|
579
590
|
fix_rst_backticks: bool = True,
|
|
580
591
|
function_param_metadata: ParameterMetadata | None = None,
|
|
581
592
|
function_return_annotation: str | None = None,
|
|
@@ -622,7 +633,7 @@ def wrap_docstring(
|
|
|
622
633
|
if style == 'google':
|
|
623
634
|
return wrap_docstring_google(
|
|
624
635
|
docstring,
|
|
625
|
-
line_length,
|
|
636
|
+
line_length=line_length,
|
|
626
637
|
leading_indent=leading_indent,
|
|
627
638
|
fix_rst_backticks=fix_rst_backticks,
|
|
628
639
|
parameter_metadata=function_param_metadata,
|
|
@@ -632,7 +643,7 @@ def wrap_docstring(
|
|
|
632
643
|
# Default to NumPy-style for unknown/unspecified styles to be permissive.
|
|
633
644
|
return wrap_docstring_numpy(
|
|
634
645
|
docstring,
|
|
635
|
-
line_length,
|
|
646
|
+
line_length=line_length,
|
|
636
647
|
leading_indent=leading_indent,
|
|
637
648
|
fix_rst_backticks=fix_rst_backticks,
|
|
638
649
|
parameter_metadata=function_param_metadata,
|
|
@@ -2,13 +2,14 @@ from format_docstring.line_wrap_utils import ParameterMetadata
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
def wrap_docstring_google(
|
|
5
|
-
docstring: str,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
docstring: str, # noqa: ARG001
|
|
6
|
+
*,
|
|
7
|
+
line_length: int, # noqa: ARG001
|
|
8
|
+
leading_indent: int | None = None, # noqa: ARG001
|
|
9
|
+
fix_rst_backticks: bool = True, # noqa: ARG001
|
|
10
|
+
parameter_metadata: ParameterMetadata | None = None, # noqa: ARG001
|
|
11
|
+
return_annotation: str | None = None, # noqa: ARG001
|
|
12
|
+
attribute_metadata: ParameterMetadata | None = None, # noqa: ARG001
|
|
12
13
|
) -> str:
|
|
13
14
|
"""A placeholder for now.""" # noqa: D401
|
|
14
15
|
return ''
|
|
@@ -13,8 +13,9 @@ from format_docstring.line_wrap_utils import (
|
|
|
13
13
|
)
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
def wrap_docstring_numpy(
|
|
16
|
+
def wrap_docstring_numpy( # noqa: C901, PLR0915, TODO: https://github.com/jsh9/format-docstring/issues/17
|
|
17
17
|
docstring: str,
|
|
18
|
+
*,
|
|
18
19
|
line_length: int,
|
|
19
20
|
leading_indent: int | None = None,
|
|
20
21
|
fix_rst_backticks: bool = False,
|
|
@@ -53,8 +54,7 @@ def wrap_docstring_numpy(
|
|
|
53
54
|
if not lines:
|
|
54
55
|
return docstring_
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
SECTION_PARAMS = {
|
|
57
|
+
section_params = {
|
|
58
58
|
'parameters',
|
|
59
59
|
'parameters:',
|
|
60
60
|
'parameter', # tolerate typo
|
|
@@ -68,14 +68,13 @@ def wrap_docstring_numpy(
|
|
|
68
68
|
'other parameter', # tolerate typo
|
|
69
69
|
'other parameter:',
|
|
70
70
|
}
|
|
71
|
-
|
|
71
|
+
section_attributes = {
|
|
72
72
|
'attributes',
|
|
73
73
|
'attributes:',
|
|
74
74
|
'attribute', # tolerate typo
|
|
75
75
|
'attribute:',
|
|
76
76
|
}
|
|
77
|
-
|
|
78
|
-
SECTION_RETURNS_YIELDS = {
|
|
77
|
+
section_returns_yields = {
|
|
79
78
|
'returns',
|
|
80
79
|
'returns:',
|
|
81
80
|
'return', # tolerate typo
|
|
@@ -85,13 +84,13 @@ def wrap_docstring_numpy(
|
|
|
85
84
|
'yield', # tolerate typo
|
|
86
85
|
'yield:',
|
|
87
86
|
}
|
|
88
|
-
|
|
87
|
+
section_raises = {
|
|
89
88
|
'raises',
|
|
90
89
|
'raises:',
|
|
91
90
|
'raise', # tolerate typo
|
|
92
91
|
'raise:',
|
|
93
92
|
}
|
|
94
|
-
|
|
93
|
+
section_examples = {
|
|
95
94
|
'examples',
|
|
96
95
|
'examples:',
|
|
97
96
|
'example', # tolerate typo
|
|
@@ -138,9 +137,8 @@ def wrap_docstring_numpy(
|
|
|
138
137
|
heading: str | None = _get_section_heading_title(lines, i)
|
|
139
138
|
if heading:
|
|
140
139
|
current_section = heading
|
|
141
|
-
in_examples = heading in
|
|
142
|
-
temp_out.
|
|
143
|
-
temp_out.append(lines[i + 1])
|
|
140
|
+
in_examples = heading in section_examples
|
|
141
|
+
temp_out.extend((line, lines[i + 1]))
|
|
144
142
|
i += 2
|
|
145
143
|
continue
|
|
146
144
|
|
|
@@ -151,18 +149,16 @@ def wrap_docstring_numpy(
|
|
|
151
149
|
continue
|
|
152
150
|
|
|
153
151
|
# In Examples, skip wrapping and backtick fixing for REPL lines
|
|
154
|
-
if in_examples and (
|
|
155
|
-
stripped.startswith('>>> ') or stripped.startswith('... ')
|
|
156
|
-
):
|
|
152
|
+
if in_examples and stripped.startswith(('>>> ', '... ')):
|
|
157
153
|
temp_out.append(line)
|
|
158
154
|
i += 1
|
|
159
155
|
continue
|
|
160
156
|
|
|
161
157
|
# Parameters-like sections
|
|
162
158
|
section_lower_case: str = current_section.lower()
|
|
163
|
-
if section_lower_case in
|
|
159
|
+
if section_lower_case in section_params | section_attributes:
|
|
164
160
|
metadata_for_section = parameter_metadata
|
|
165
|
-
if section_lower_case in
|
|
161
|
+
if section_lower_case in section_attributes:
|
|
166
162
|
metadata_for_section = attribute_metadata or parameter_metadata
|
|
167
163
|
|
|
168
164
|
if line.strip() == '':
|
|
@@ -193,7 +189,7 @@ def wrap_docstring_numpy(
|
|
|
193
189
|
continue
|
|
194
190
|
|
|
195
191
|
# Returns/Yields sections
|
|
196
|
-
if section_lower_case in
|
|
192
|
+
if section_lower_case in section_returns_yields:
|
|
197
193
|
if line.strip() == '':
|
|
198
194
|
temp_out.append(line)
|
|
199
195
|
i += 1
|
|
@@ -245,7 +241,7 @@ def wrap_docstring_numpy(
|
|
|
245
241
|
continue
|
|
246
242
|
|
|
247
243
|
# Raises section
|
|
248
|
-
if section_lower_case in
|
|
244
|
+
if section_lower_case in section_raises:
|
|
249
245
|
if line.strip() == '':
|
|
250
246
|
temp_out.append(line)
|
|
251
247
|
i += 1
|
|
@@ -294,8 +290,9 @@ def _is_hyphen_underline(s: str) -> bool:
|
|
|
294
290
|
>>> _is_hyphen_underline(' - - ')
|
|
295
291
|
False
|
|
296
292
|
"""
|
|
297
|
-
t = s.strip()
|
|
298
|
-
|
|
293
|
+
t: str = s.strip()
|
|
294
|
+
min_hyphens_in_section_header: int = 2
|
|
295
|
+
return len(t) >= min_hyphens_in_section_header and set(t) <= {'-'}
|
|
299
296
|
|
|
300
297
|
|
|
301
298
|
def _get_section_heading_title(lines: list[str], idx: int) -> str | None:
|
|
@@ -467,19 +464,39 @@ def _standardize_default_value(line: str) -> str:
|
|
|
467
464
|
>>> _standardize_default_value('arg : bool, default: True')
|
|
468
465
|
'arg : bool, default=True'
|
|
469
466
|
"""
|
|
467
|
+
colon_idx = line.find(':')
|
|
468
|
+
if colon_idx == -1:
|
|
469
|
+
return line
|
|
470
|
+
|
|
471
|
+
# `prefix` is everything before the 1st colon (param identifier portion).
|
|
472
|
+
# We leave `prefix` untouched so arg names like `default` aren't rewritten.
|
|
473
|
+
prefix = line[: colon_idx + 1]
|
|
474
|
+
after_colon = line[colon_idx + 1 :]
|
|
475
|
+
|
|
470
476
|
# Check colon format first to avoid matching colons in space-based pattern
|
|
471
|
-
match = _DEFAULT_COLON_RE.match(
|
|
477
|
+
match = _DEFAULT_COLON_RE.match(after_colon)
|
|
472
478
|
if match:
|
|
473
|
-
before = match.group(1)
|
|
479
|
+
before = match.group(1)
|
|
480
|
+
if before.strip() == '':
|
|
481
|
+
return line
|
|
482
|
+
|
|
474
483
|
default_value = match.group(2).strip()
|
|
475
|
-
|
|
484
|
+
rebuilt_suffix = f'{before.rstrip()}, default={default_value}'
|
|
485
|
+
return f'{prefix}{rebuilt_suffix}'
|
|
476
486
|
|
|
477
487
|
# Try space-separated format with optional "is"
|
|
478
|
-
match = _DEFAULT_SPACE_RE.match(
|
|
488
|
+
match = _DEFAULT_SPACE_RE.match(after_colon)
|
|
479
489
|
if match:
|
|
480
|
-
before = match.group(1)
|
|
490
|
+
before = match.group(1)
|
|
491
|
+
if before.strip() == '':
|
|
492
|
+
return line
|
|
493
|
+
|
|
494
|
+
# ``before`` still contains any annotation text; tightening the spacing
|
|
495
|
+
# here standardizes the ``", default=..."`` suffix while preserving
|
|
496
|
+
# whatever appeared to the left.
|
|
481
497
|
default_value = match.group(2).strip()
|
|
482
|
-
|
|
498
|
+
rebuilt_suffix = f'{before.rstrip()}, default={default_value}'
|
|
499
|
+
return f'{prefix}{rebuilt_suffix}'
|
|
483
500
|
|
|
484
501
|
return line
|
|
485
502
|
|
|
@@ -560,11 +577,10 @@ def _rewrite_parameter_signature(
|
|
|
560
577
|
core, tail = _extract_signature_tail(line[colon_idx + 1 :])
|
|
561
578
|
|
|
562
579
|
existing_annotation_text = core.strip()
|
|
563
|
-
if existing_annotation_text:
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
)[0].rstrip(', ')
|
|
580
|
+
if existing_annotation_text and ', default=' in existing_annotation_text:
|
|
581
|
+
existing_annotation_text = existing_annotation_text.split(
|
|
582
|
+
', default=', 1
|
|
583
|
+
)[0].rstrip(', ')
|
|
568
584
|
|
|
569
585
|
existing_annotation_text = existing_annotation_text.strip()
|
|
570
586
|
|
|
@@ -624,7 +640,8 @@ def _split_tuple_annotation(annotation: str | None) -> list[str] | None:
|
|
|
624
640
|
if not isinstance(slice_node, ast.Tuple):
|
|
625
641
|
return None
|
|
626
642
|
|
|
627
|
-
|
|
643
|
+
min_elements_for_an_actual_tuple: int = 2
|
|
644
|
+
if len(slice_node.elts) < min_elements_for_an_actual_tuple:
|
|
628
645
|
return None
|
|
629
646
|
|
|
630
647
|
parts: list[str] = []
|
|
@@ -733,12 +750,20 @@ def handle_single_line_docstring(
|
|
|
733
750
|
# or certain punctuation (like > and . for `>>> ` and `... ` literals)
|
|
734
751
|
# Note: We match [^`]+ (anything except backticks) and then check in the
|
|
735
752
|
# replacement function whether it's an external link (contains < followed by >)
|
|
736
|
-
# The opening backtick must not be immediately followed by _ or __ (to avoid
|
|
737
|
-
# matching the trailing backtick of cross-references like `text`_ or `text`__)
|
|
738
753
|
_RST_BACKTICK_PATTERN = re.compile(
|
|
739
754
|
r'(?:^|(?<=\s)|(?<=\()|(?<=[>.]))(?::[\w-]+:)?`(?!_)([^`]+)`(?!`)(?!__)(?!_)'
|
|
740
755
|
)
|
|
741
756
|
|
|
757
|
+
# 2nd-stage fixer for ``__dunder__`` names that slipped past the main pattern
|
|
758
|
+
# because the literal starts with an underscore. Negative lookbehinds/aheads
|
|
759
|
+
# ensure we only touch isolated single-backtick literals and leave
|
|
760
|
+
# cross-references (`name`_ / `name`__) alone.
|
|
761
|
+
_DUNDER_LITERAL_PATTERN = re.compile(
|
|
762
|
+
r'(?<!`)`(__[A-Za-z0-9_]+__)`(?!`)(?!_)(?!__)'
|
|
763
|
+
)
|
|
764
|
+
# Replacement wraps the captured dunder name (group 1) with double backticks.
|
|
765
|
+
_DUNDER_LITERAL_REPLACEMENT = r'``\1``'
|
|
766
|
+
|
|
742
767
|
|
|
743
768
|
def _fix_rst_backticks(docstring: str) -> str:
|
|
744
769
|
"""
|
|
@@ -824,7 +849,7 @@ def _fix_rst_backticks(docstring: str) -> str:
|
|
|
824
849
|
|
|
825
850
|
# Check if this is an external link (contains <...> pattern)
|
|
826
851
|
# External links look like: `text <url>`_
|
|
827
|
-
if '<' in content and '>' in content:
|
|
852
|
+
if '<' in content and '>' in content: # noqa: SIM102
|
|
828
853
|
# Check if < comes before > (basic validation)
|
|
829
854
|
if content.index('<') < content.rindex('>'):
|
|
830
855
|
return full_match # Keep original (it's an external link)
|
|
@@ -846,7 +871,7 @@ def _fix_rst_backticks(docstring: str) -> str:
|
|
|
846
871
|
for i, line in enumerate(lines):
|
|
847
872
|
stripped = line.lstrip()
|
|
848
873
|
# Protect REPL lines (>>> or ...) - don't fix backticks in these
|
|
849
|
-
if stripped.startswith('>>> '
|
|
874
|
+
if stripped.startswith(('>>> ', '... ')):
|
|
850
875
|
repl_lines[i] = line
|
|
851
876
|
# Use a placeholder that won't be matched by the regex
|
|
852
877
|
protected_lines.append(
|
|
@@ -860,6 +885,11 @@ def _fix_rst_backticks(docstring: str) -> str:
|
|
|
860
885
|
# Process the entire docstring (with REPL lines protected)
|
|
861
886
|
protected_docstring = ''.join(protected_lines)
|
|
862
887
|
processed = _RST_BACKTICK_PATTERN.sub(replace_func, protected_docstring)
|
|
888
|
+
# Upgrade remaining single-backtick ``__dunder__`` literals to double
|
|
889
|
+
# backticks; they are safe literals (not targets or refs) after the guards.
|
|
890
|
+
processed = _DUNDER_LITERAL_PATTERN.sub(
|
|
891
|
+
_DUNDER_LITERAL_REPLACEMENT, processed
|
|
892
|
+
)
|
|
863
893
|
|
|
864
894
|
# Restore REPL lines
|
|
865
895
|
result_lines = processed.splitlines(keepends=True)
|
|
@@ -43,7 +43,7 @@ def finalize_lines(out_lines: list[str], leading_indent: int | None) -> str:
|
|
|
43
43
|
if leading_indent is not None:
|
|
44
44
|
suffix = '\n' + (' ' * leading_indent)
|
|
45
45
|
if not result.endswith(suffix):
|
|
46
|
-
result
|
|
46
|
+
result += suffix
|
|
47
47
|
|
|
48
48
|
return result
|
|
49
49
|
|
|
@@ -139,13 +139,18 @@ def process_temp_output(
|
|
|
139
139
|
and element.index('') < len(element) - 1
|
|
140
140
|
):
|
|
141
141
|
insertion_idx = min(element.index(''), len(wrapped))
|
|
142
|
-
wrapped =
|
|
143
|
-
wrapped[:insertion_idx]
|
|
144
|
-
|
|
142
|
+
wrapped = [
|
|
143
|
+
*wrapped[:insertion_idx],
|
|
144
|
+
'',
|
|
145
|
+
*wrapped[insertion_idx:],
|
|
146
|
+
]
|
|
145
147
|
|
|
146
148
|
out.extend(wrapped)
|
|
147
149
|
else:
|
|
148
|
-
raise
|
|
150
|
+
raise TypeError(
|
|
151
|
+
f'`element` has unexpected type: {type(element)}.'
|
|
152
|
+
' Please contact the author.'
|
|
153
|
+
)
|
|
149
154
|
|
|
150
155
|
return fix_typos_in_section_headings(out)
|
|
151
156
|
|
|
@@ -256,7 +261,7 @@ def _wrap_text_segment(lines: list[str], width: int) -> list[str]:
|
|
|
256
261
|
if result and result[-1] == '':
|
|
257
262
|
result.pop()
|
|
258
263
|
|
|
259
|
-
result_: list[str] = result
|
|
264
|
+
result_: list[str] = result or lines
|
|
260
265
|
|
|
261
266
|
return _add_back_leading_or_trailing_newline(
|
|
262
267
|
original_lines=lines,
|
|
@@ -273,12 +278,12 @@ def _add_back_leading_or_trailing_newline(
|
|
|
273
278
|
|
|
274
279
|
new_result: list[str] = []
|
|
275
280
|
if original_lines[0] == '':
|
|
276
|
-
new_result = [''
|
|
281
|
+
new_result = ['', *wrapped_lines]
|
|
277
282
|
else:
|
|
278
283
|
new_result = wrapped_lines
|
|
279
284
|
|
|
280
285
|
if original_lines[-1] == '':
|
|
281
|
-
return new_result
|
|
286
|
+
return [*new_result, '']
|
|
282
287
|
|
|
283
288
|
return new_result
|
|
284
289
|
|
|
@@ -331,7 +336,8 @@ def merge_lines_and_strip(text: str) -> str:
|
|
|
331
336
|
|
|
332
337
|
def fix_typos_in_section_headings(lines: list[str]) -> list[str]:
|
|
333
338
|
"""Fix typos such as 'Return' in section headings."""
|
|
334
|
-
|
|
339
|
+
min_num_lines_to_form_a_section_header: int = 2
|
|
340
|
+
if len(lines) < min_num_lines_to_form_a_section_header:
|
|
335
341
|
return lines
|
|
336
342
|
|
|
337
343
|
# Define typo corrections (case-insensitive keys, proper case values)
|
|
@@ -379,7 +385,10 @@ def fix_typos_in_section_headings(lines: list[str]) -> list[str]:
|
|
|
379
385
|
|
|
380
386
|
# Check if next line is dashes (at least 2 dashes, only dashes and
|
|
381
387
|
# whitespace)
|
|
382
|
-
|
|
388
|
+
min_hyphens_in_section_header: int = 2
|
|
389
|
+
if len(next_line) >= min_hyphens_in_section_header and all(
|
|
390
|
+
c == '-' for c in next_line
|
|
391
|
+
):
|
|
383
392
|
# Current line is a section heading, check for typos
|
|
384
393
|
# (which are case-insensitive)
|
|
385
394
|
current_line_lower = current_line.lower()
|
|
@@ -502,7 +511,7 @@ def segment_lines_by_wrappability(
|
|
|
502
511
|
return segments
|
|
503
512
|
|
|
504
513
|
|
|
505
|
-
def is_rST_table(lines: list[str], start_idx: int = 0) -> tuple[bool, int]:
|
|
514
|
+
def is_rST_table(lines: list[str], start_idx: int = 0) -> tuple[bool, int]: # noqa: N802
|
|
506
515
|
"""
|
|
507
516
|
Check if lines starting at start_idx form a reStructuredText table.
|
|
508
517
|
|
|
@@ -765,11 +774,7 @@ def _is_list_item(line: str) -> bool:
|
|
|
765
774
|
def _is_unordered_list_item(line: str) -> bool:
|
|
766
775
|
"""Check if a line is an unordered list item (starts with -, *, or +)."""
|
|
767
776
|
stripped = line.lstrip()
|
|
768
|
-
return (
|
|
769
|
-
stripped.startswith('- ')
|
|
770
|
-
or stripped.startswith('* ')
|
|
771
|
-
or stripped.startswith('+ ')
|
|
772
|
-
)
|
|
777
|
+
return stripped.startswith(('- ', '* ', '+ '))
|
|
773
778
|
|
|
774
779
|
|
|
775
780
|
def _is_ordered_list_item(line: str) -> bool:
|
|
@@ -788,7 +793,6 @@ def _is_ordered_list_item(line: str) -> bool:
|
|
|
788
793
|
# Look for patterns:
|
|
789
794
|
# - digits followed by . or ) followed by space
|
|
790
795
|
# - ( followed by digits followed by ) followed by space
|
|
791
|
-
import re
|
|
792
796
|
|
|
793
797
|
pattern = r'^(\d+[.)] |\(\d+\) )'
|
|
794
798
|
return bool(re.match(pattern, stripped))
|
|
@@ -813,8 +817,6 @@ def _get_list_format(line: str) -> str | None:
|
|
|
813
817
|
if not stripped:
|
|
814
818
|
return None
|
|
815
819
|
|
|
816
|
-
import re
|
|
817
|
-
|
|
818
820
|
dot_match = re.match(r'^\d+\. ', stripped)
|
|
819
821
|
paren_match = re.match(r'^\d+\) ', stripped)
|
|
820
822
|
full_paren_match = re.match(r'^\(\d+\) ', stripped)
|
format_docstring/main_jupyter.py
CHANGED
|
@@ -70,6 +70,7 @@ from format_docstring.config import inject_config_from_file
|
|
|
70
70
|
def main(
|
|
71
71
|
paths: tuple[str, ...],
|
|
72
72
|
config: str | None, # noqa: ARG001 (used by Click callback)
|
|
73
|
+
*,
|
|
73
74
|
exclude: str,
|
|
74
75
|
line_length: int,
|
|
75
76
|
docstring_style: str,
|
|
@@ -101,6 +102,7 @@ class JupyterNotebookFixer(BaseFixer):
|
|
|
101
102
|
path: str,
|
|
102
103
|
exclude_pattern: str = r'\.git|\.tox|\.pytest_cache',
|
|
103
104
|
line_length: int = 79,
|
|
105
|
+
*,
|
|
104
106
|
fix_rst_backticks: bool = True,
|
|
105
107
|
verbose: str = 'default',
|
|
106
108
|
) -> None:
|
|
@@ -115,8 +117,6 @@ class JupyterNotebookFixer(BaseFixer):
|
|
|
115
117
|
|
|
116
118
|
def fix_one_directory_or_one_file(self) -> int:
|
|
117
119
|
"""Fix formatting in a file or all .ipynb files in a directory."""
|
|
118
|
-
from pathlib import Path
|
|
119
|
-
|
|
120
120
|
path_obj = Path(self.path)
|
|
121
121
|
|
|
122
122
|
if path_obj.is_file():
|
|
@@ -151,8 +151,8 @@ class JupyterNotebookFixer(BaseFixer):
|
|
|
151
151
|
code_cell_sources: list[SourceCodeContainer] = (
|
|
152
152
|
parsed.get_code_cell_sources()
|
|
153
153
|
)
|
|
154
|
-
except
|
|
155
|
-
print(f'Error reading {filename}: {
|
|
154
|
+
except OSError as exc:
|
|
155
|
+
print(f'Error reading {filename}: {exc!s}', file=sys.stderr)
|
|
156
156
|
return 1
|
|
157
157
|
else:
|
|
158
158
|
ret_val = 0
|
|
@@ -182,8 +182,8 @@ class JupyterNotebookFixer(BaseFixer):
|
|
|
182
182
|
if ret_val == 1:
|
|
183
183
|
new_text = json.dumps(parsed.notebook_content, indent=1) + '\n'
|
|
184
184
|
print(f'Rewriting {filename}', file=sys.stderr)
|
|
185
|
-
self.
|
|
186
|
-
with open(
|
|
185
|
+
self.print_diff(filename, original_text, new_text)
|
|
186
|
+
with Path(filename).open('w', encoding='utf-8') as fp:
|
|
187
187
|
fp.write(new_text)
|
|
188
188
|
|
|
189
189
|
return ret_val
|
format_docstring/main_py.py
CHANGED
|
@@ -62,6 +62,7 @@ from format_docstring.config import inject_config_from_file
|
|
|
62
62
|
def main(
|
|
63
63
|
paths: tuple[str, ...],
|
|
64
64
|
config: str | None, # noqa: ARG001 (used by Click callback)
|
|
65
|
+
*,
|
|
65
66
|
exclude: str,
|
|
66
67
|
line_length: int,
|
|
67
68
|
docstring_style: str,
|
|
@@ -97,6 +98,7 @@ class PythonFileFixer(BaseFixer):
|
|
|
97
98
|
path: str,
|
|
98
99
|
exclude_pattern: str = r'\.git|\.tox|\.pytest_cache',
|
|
99
100
|
line_length: int = 79,
|
|
101
|
+
*,
|
|
100
102
|
fix_rst_backticks: bool = True,
|
|
101
103
|
verbose: str = 'default',
|
|
102
104
|
) -> None:
|
|
@@ -120,7 +122,7 @@ class PythonFileFixer(BaseFixer):
|
|
|
120
122
|
print(msg, file=sys.stderr)
|
|
121
123
|
return 0
|
|
122
124
|
|
|
123
|
-
with open(
|
|
125
|
+
with Path(filename).open('rb') as fb:
|
|
124
126
|
source_bytes = fb.read()
|
|
125
127
|
|
|
126
128
|
try:
|
|
@@ -142,8 +144,8 @@ class PythonFileFixer(BaseFixer):
|
|
|
142
144
|
print(source_text, end='')
|
|
143
145
|
elif source_text != source_text_orig:
|
|
144
146
|
print(f'Rewriting {filename}', file=sys.stderr)
|
|
145
|
-
self.
|
|
146
|
-
with open(
|
|
147
|
+
self.print_diff(filename, source_text_orig, source_text)
|
|
148
|
+
with Path(filename).open('wb') as f:
|
|
147
149
|
f.write(source_text.encode())
|
|
148
150
|
|
|
149
151
|
return int(source_text != source_text_orig)
|
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: format-docstring
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: A Python formatter to wrap/adjust docstring lines
|
|
5
5
|
Author-email: jsh9 <25124332+jsh9@users.noreply.github.com>
|
|
6
6
|
Maintainer-email: jsh9 <25124332+jsh9@users.noreply.github.com>
|
|
7
7
|
License: MIT
|
|
8
8
|
Project-URL: Homepage, https://github.com/your/repo
|
|
9
|
-
Project-URL: Repository, https://github.com/your/repo.git
|
|
10
9
|
Project-URL: Issues, https://github.com/your/repo/issues
|
|
11
|
-
|
|
10
|
+
Project-URL: Repository, https://github.com/your/repo.git
|
|
11
|
+
Keywords: code-style,docstring,formatter,python
|
|
12
12
|
Classifier: Development Status :: 3 - Alpha
|
|
13
13
|
Classifier: Environment :: Console
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
15
15
|
Classifier: License :: OSI Approved :: MIT License
|
|
16
16
|
Classifier: Operating System :: OS Independent
|
|
17
|
-
Classifier: Programming Language :: Python
|
|
18
|
-
Classifier: Programming Language :: Python :: 3
|
|
19
17
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
19
|
Classifier: Programming Language :: Python :: 3.10
|
|
21
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
22
21
|
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python
|
|
23
23
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
24
|
Classifier: Topic :: Software Development :: Quality Assurance
|
|
25
25
|
Requires-Python: >=3.10
|
|
26
26
|
Description-Content-Type: text/markdown
|
|
27
27
|
License-File: LICENSE
|
|
28
|
-
Requires-Dist: jupyter-notebook-parser>=0.1.4
|
|
29
28
|
Requires-Dist: click>=8.0
|
|
29
|
+
Requires-Dist: jupyter-notebook-parser>=0.1.4
|
|
30
30
|
Requires-Dist: tomli>=1.1.0; python_version < "3.11"
|
|
31
31
|
Dynamic: license-file
|
|
32
32
|
|
|
@@ -34,11 +34,11 @@ Dynamic: license-file
|
|
|
34
34
|
|
|
35
35
|
A Python formatter to automatically format numpy-style docstrings.
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
<!--TOC-->
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
______________________________________________________________________
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
**Table of Contents**
|
|
42
42
|
|
|
43
43
|
- [1. Overview](#1-overview)
|
|
44
44
|
- [2. Before vs After Examples](#2-before-vs-after-examples)
|
|
@@ -59,10 +59,10 @@ ______________________________________________________________________
|
|
|
59
59
|
- [5.3. `pyproject.toml` Configuration](#53-pyprojecttoml-configuration)
|
|
60
60
|
- [6. Caveat](#6-caveat)
|
|
61
61
|
|
|
62
|
-
<!--TOC-->
|
|
63
|
-
|
|
64
62
|
______________________________________________________________________
|
|
65
63
|
|
|
64
|
+
<!--TOC-->
|
|
65
|
+
|
|
66
66
|
## 1. Overview
|
|
67
67
|
|
|
68
68
|
`format-docstring` is a tool that automatically formats and wraps docstring
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
format_docstring/__init__.py,sha256=3bPK0B7mVsgqXVtcJYGKBO8JAM5gYWmdQPaRrI2tpsI,146
|
|
2
|
+
format_docstring/base_fixer.py,sha256=8SVkyn2ZSwQr3-7GzxxmHVSYMHymdj6AchBxd53IA1Q,3117
|
|
3
|
+
format_docstring/config.py,sha256=OwVasV5uX2y4gMajzY-2CIcmaSKuVUm3GxWDUdbWDcc,5586
|
|
4
|
+
format_docstring/docstring_rewriter.py,sha256=LFLPd1pMyPBQCkkNTlcMcgHWyuOLOiyJNYjz1HOeVyY,21315
|
|
5
|
+
format_docstring/line_wrap_google.py,sha256=iJdpMwCB97H3CUatmkGAexJ_J8FeWv1kMEv1UzzUvjM,587
|
|
6
|
+
format_docstring/line_wrap_numpy.py,sha256=0Vof7ct5KAA-f_pYv94xP_xRQfKGg7YR9X8_o9gImUM,30333
|
|
7
|
+
format_docstring/line_wrap_utils.py,sha256=q59n26gzbnNgi9sWAIyX-M4MTbi5tITXVrKjJDK-Vao,28178
|
|
8
|
+
format_docstring/main_jupyter.py,sha256=3mHBeT-BVlF5R1m8xpF3OxOzQTReR6NnPjbN80eo18E,6300
|
|
9
|
+
format_docstring/main_py.py,sha256=S4zZCvJMZn8ZoDx2zYWPikKmH5-of10UTc5sad5W5v8,4597
|
|
10
|
+
format_docstring-0.2.4.dist-info/licenses/LICENSE,sha256=UNm_-hqU-1dAB3bLytP6wvGtUeitoJde2xzb5wgVYn0,1061
|
|
11
|
+
format_docstring-0.2.4.dist-info/METADATA,sha256=4Ei4SrGYksCOoMFJc_BNVtfD3LAh800jdpYPyFgy6dU,15453
|
|
12
|
+
format_docstring-0.2.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
13
|
+
format_docstring-0.2.4.dist-info/entry_points.txt,sha256=Re2iHTzfgKJgeXDga5Z4bGNPtWGmxupOdzTolwn52sk,129
|
|
14
|
+
format_docstring-0.2.4.dist-info/top_level.txt,sha256=NXPwfHm_1YwwGuetwtK3pJv0jXwXelqPTNCFpm5LQyE,17
|
|
15
|
+
format_docstring-0.2.4.dist-info/RECORD,,
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
format_docstring/__init__.py,sha256=3bPK0B7mVsgqXVtcJYGKBO8JAM5gYWmdQPaRrI2tpsI,146
|
|
2
|
-
format_docstring/base_fixer.py,sha256=PtTn-bMqlaQAJsNCzMbAxBC5_mjTl1pmC16iLZf_v2w,3118
|
|
3
|
-
format_docstring/config.py,sha256=2VvOL1AaYM-ODCZf-U3R3ptePR_DMZXnOVItbpp6yns,5528
|
|
4
|
-
format_docstring/docstring_rewriter.py,sha256=17tjHWqgptWeJZlFrJN7jqV3326IUgjEB4F3RKHmb-E,20848
|
|
5
|
-
format_docstring/line_wrap_google.py,sha256=PiWeEIJnbyEoxt3nxpzvOyxOMmBaQvcDKEPLbuyB4FM,464
|
|
6
|
-
format_docstring/line_wrap_numpy.py,sha256=8SBSSU47FR1enKxLSBX2tyX09f_cdDfg7FABKR49JeA,28932
|
|
7
|
-
format_docstring/line_wrap_utils.py,sha256=cXHTFMLefUFVZgUzC-UU6FrNCzh4kAOH7E_AMwm-bz0,27988
|
|
8
|
-
format_docstring/main_jupyter.py,sha256=rkNjk0XwCukxCrWsr8PNDCywxYH8Jpj5XCxql_3GPOI,6309
|
|
9
|
-
format_docstring/main_py.py,sha256=aYVOEtH15UpXugqkBdiYFzyR9QCnP7FXZ6PFt7fwvdk,4562
|
|
10
|
-
format_docstring-0.2.2.dist-info/licenses/LICENSE,sha256=UNm_-hqU-1dAB3bLytP6wvGtUeitoJde2xzb5wgVYn0,1061
|
|
11
|
-
format_docstring-0.2.2.dist-info/METADATA,sha256=6ff2qlTJdvJnMRWddv9fJimTyjKljCFqqoay-tT7bNI,15454
|
|
12
|
-
format_docstring-0.2.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
13
|
-
format_docstring-0.2.2.dist-info/entry_points.txt,sha256=Re2iHTzfgKJgeXDga5Z4bGNPtWGmxupOdzTolwn52sk,129
|
|
14
|
-
format_docstring-0.2.2.dist-info/top_level.txt,sha256=NXPwfHm_1YwwGuetwtK3pJv0jXwXelqPTNCFpm5LQyE,17
|
|
15
|
-
format_docstring-0.2.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|