format-docstring 0.1.0__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/__init__.py +5 -0
- format_docstring/base_fixer.py +70 -0
- format_docstring/config.py +211 -0
- format_docstring/docstring_rewriter.py +314 -0
- format_docstring/line_wrap_google.py +7 -0
- format_docstring/line_wrap_numpy.py +387 -0
- format_docstring/line_wrap_utils.py +781 -0
- format_docstring/main_jupyter.py +165 -0
- format_docstring/main_py.py +125 -0
- format_docstring-0.1.0.dist-info/METADATA +311 -0
- format_docstring-0.1.0.dist-info/RECORD +15 -0
- format_docstring-0.1.0.dist-info/WHEEL +5 -0
- format_docstring-0.1.0.dist-info/entry_points.txt +3 -0
- format_docstring-0.1.0.dist-info/licenses/LICENSE +21 -0
- format_docstring-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
from jupyter_notebook_parser import (
|
|
10
|
+
JupyterNotebookParser,
|
|
11
|
+
JupyterNotebookRewriter,
|
|
12
|
+
SourceCodeContainer,
|
|
13
|
+
reconstruct_source,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
import format_docstring.docstring_rewriter as doc_rewriter
|
|
17
|
+
from format_docstring import __version__
|
|
18
|
+
from format_docstring.base_fixer import BaseFixer
|
|
19
|
+
from format_docstring.config import inject_config_from_file
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@click.command()
|
|
23
|
+
@click.version_option(version=__version__)
|
|
24
|
+
@click.argument('paths', nargs=-1, type=click.Path())
|
|
25
|
+
@click.option(
|
|
26
|
+
'--config',
|
|
27
|
+
type=click.Path(exists=False, file_okay=True, dir_okay=False),
|
|
28
|
+
is_eager=True,
|
|
29
|
+
callback=inject_config_from_file,
|
|
30
|
+
default='pyproject.toml',
|
|
31
|
+
help=(
|
|
32
|
+
'Path to a pyproject.toml config file. '
|
|
33
|
+
'If not specified, searches for pyproject.toml in parent directories. '
|
|
34
|
+
'Command-line options take precedence over config file settings.'
|
|
35
|
+
),
|
|
36
|
+
)
|
|
37
|
+
@click.option(
|
|
38
|
+
'--exclude',
|
|
39
|
+
type=str,
|
|
40
|
+
default=r'\.git|\.tox|\.pytest_cache',
|
|
41
|
+
help='Regex pattern to exclude files/directories',
|
|
42
|
+
)
|
|
43
|
+
@click.option(
|
|
44
|
+
'--line-length',
|
|
45
|
+
type=int,
|
|
46
|
+
default=79,
|
|
47
|
+
show_default=True,
|
|
48
|
+
help='Maximum line length for wrapping docstrings',
|
|
49
|
+
)
|
|
50
|
+
@click.option(
|
|
51
|
+
'--docstring-style',
|
|
52
|
+
type=click.Choice(['numpy', 'google'], case_sensitive=False),
|
|
53
|
+
default='numpy',
|
|
54
|
+
show_default=True,
|
|
55
|
+
help='Docstring style to target',
|
|
56
|
+
)
|
|
57
|
+
def main(
|
|
58
|
+
paths: tuple[str, ...],
|
|
59
|
+
config: str | None, # noqa: ARG001 (used by Click callback)
|
|
60
|
+
exclude: str,
|
|
61
|
+
line_length: int,
|
|
62
|
+
docstring_style: str,
|
|
63
|
+
) -> None:
|
|
64
|
+
"""Format .ipynb files."""
|
|
65
|
+
ret = 0
|
|
66
|
+
for path in paths:
|
|
67
|
+
fixer = JupyterNotebookFixer(
|
|
68
|
+
path=path, exclude_pattern=exclude, line_length=line_length
|
|
69
|
+
)
|
|
70
|
+
fixer.docstring_style = docstring_style
|
|
71
|
+
ret |= fixer.fix_one_directory_or_one_file()
|
|
72
|
+
|
|
73
|
+
if ret != 0:
|
|
74
|
+
raise SystemExit(ret)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class JupyterNotebookFixer(BaseFixer):
|
|
78
|
+
"""Fixer for Jupyter notebook files."""
|
|
79
|
+
|
|
80
|
+
def __init__(
|
|
81
|
+
self,
|
|
82
|
+
path: str,
|
|
83
|
+
exclude_pattern: str = r'\.git|\.tox|\.pytest_cache',
|
|
84
|
+
line_length: int = 79,
|
|
85
|
+
) -> None:
|
|
86
|
+
super().__init__(path=path, exclude_pattern=exclude_pattern)
|
|
87
|
+
self.line_length = line_length
|
|
88
|
+
self.docstring_style: str = 'numpy'
|
|
89
|
+
|
|
90
|
+
def fix_one_directory_or_one_file(self) -> int:
|
|
91
|
+
"""Fix formatting in a file or all .ipynb files in a directory."""
|
|
92
|
+
from pathlib import Path
|
|
93
|
+
|
|
94
|
+
path_obj = Path(self.path)
|
|
95
|
+
|
|
96
|
+
if path_obj.is_file():
|
|
97
|
+
return self.fix_one_file(path_obj.as_posix())
|
|
98
|
+
|
|
99
|
+
# Process .ipynb files instead of .py files for Jupyter notebooks
|
|
100
|
+
filenames = self._get_files_to_process(path_obj, '*.ipynb')
|
|
101
|
+
all_status = set()
|
|
102
|
+
for filename in filenames:
|
|
103
|
+
status = self.fix_one_file(str(filename))
|
|
104
|
+
all_status.add(status)
|
|
105
|
+
|
|
106
|
+
return 0 if not all_status or all_status == {0} else 1
|
|
107
|
+
|
|
108
|
+
def fix_one_file(self, filename: str) -> int:
|
|
109
|
+
"""Fix formatting in a single Jupyter notebook file."""
|
|
110
|
+
file_path = Path(filename)
|
|
111
|
+
if not file_path.is_file():
|
|
112
|
+
msg = f'{filename} is not a file (skipping)'
|
|
113
|
+
print(msg, file=sys.stderr)
|
|
114
|
+
return 0
|
|
115
|
+
|
|
116
|
+
try:
|
|
117
|
+
parsed: JupyterNotebookParser = JupyterNotebookParser(filename)
|
|
118
|
+
rewriter: JupyterNotebookRewriter = JupyterNotebookRewriter(
|
|
119
|
+
parsed_notebook=parsed
|
|
120
|
+
)
|
|
121
|
+
code_cells: list[dict[str, Any]] = parsed.get_code_cells()
|
|
122
|
+
code_cell_indices: list[int] = parsed.get_code_cell_indices()
|
|
123
|
+
code_cell_sources: list[SourceCodeContainer] = (
|
|
124
|
+
parsed.get_code_cell_sources()
|
|
125
|
+
)
|
|
126
|
+
except Exception as exc:
|
|
127
|
+
print(f'Error reading {filename}: {str(exc)}', file=sys.stderr)
|
|
128
|
+
return 1
|
|
129
|
+
else:
|
|
130
|
+
ret_val = 0
|
|
131
|
+
assert len(code_cells) == len(code_cell_indices)
|
|
132
|
+
assert len(code_cells) == len(code_cell_sources)
|
|
133
|
+
|
|
134
|
+
for i in range(len(code_cells)):
|
|
135
|
+
index: int = code_cell_indices[i]
|
|
136
|
+
source: SourceCodeContainer = code_cell_sources[i]
|
|
137
|
+
source_without_magic: str = source.source_without_magic
|
|
138
|
+
magics: dict[str, str] = source.magics
|
|
139
|
+
fixed: str = doc_rewriter.fix_src(
|
|
140
|
+
source_code=source_without_magic,
|
|
141
|
+
line_length=self.line_length,
|
|
142
|
+
docstring_style=self.docstring_style,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
if fixed != source_without_magic:
|
|
146
|
+
ret_val = 1
|
|
147
|
+
fixed_with_magics = reconstruct_source(fixed, magics)
|
|
148
|
+
rewriter.replace_source_in_code_cell(
|
|
149
|
+
index=index,
|
|
150
|
+
new_source=fixed_with_magics,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
if ret_val == 1:
|
|
154
|
+
print(f'Rewriting {filename}', file=sys.stderr)
|
|
155
|
+
with open(filename, 'w') as fp:
|
|
156
|
+
json.dump(parsed.notebook_content, fp, indent=1)
|
|
157
|
+
# Jupyter notebooks (.ipynb) always ends with a new line
|
|
158
|
+
# but json.dump does not.
|
|
159
|
+
fp.write('\n')
|
|
160
|
+
|
|
161
|
+
return ret_val
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
if __name__ == '__main__':
|
|
165
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import click
|
|
7
|
+
|
|
8
|
+
import format_docstring.docstring_rewriter as rewriter
|
|
9
|
+
from format_docstring import __version__
|
|
10
|
+
from format_docstring.base_fixer import BaseFixer
|
|
11
|
+
from format_docstring.config import inject_config_from_file
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@click.command()
|
|
15
|
+
@click.version_option(version=__version__)
|
|
16
|
+
@click.argument('paths', nargs=-1, type=click.Path())
|
|
17
|
+
@click.option(
|
|
18
|
+
'--config',
|
|
19
|
+
type=click.Path(exists=False, file_okay=True, dir_okay=False),
|
|
20
|
+
is_eager=True,
|
|
21
|
+
callback=inject_config_from_file,
|
|
22
|
+
default='pyproject.toml',
|
|
23
|
+
help=(
|
|
24
|
+
'Path to a pyproject.toml config file. '
|
|
25
|
+
'If not specified, searches for pyproject.toml in parent directories. '
|
|
26
|
+
'Command-line options take precedence over config file settings.'
|
|
27
|
+
),
|
|
28
|
+
)
|
|
29
|
+
@click.option(
|
|
30
|
+
'--exclude',
|
|
31
|
+
type=str,
|
|
32
|
+
default=r'\.git|\.tox|\.pytest_cache',
|
|
33
|
+
help='Regex pattern to exclude files/directories',
|
|
34
|
+
)
|
|
35
|
+
@click.option(
|
|
36
|
+
'--line-length',
|
|
37
|
+
type=int,
|
|
38
|
+
default=79,
|
|
39
|
+
show_default=True,
|
|
40
|
+
help='Maximum line length for wrapping docstrings',
|
|
41
|
+
)
|
|
42
|
+
@click.option(
|
|
43
|
+
'--docstring-style',
|
|
44
|
+
type=click.Choice(['numpy', 'google'], case_sensitive=False),
|
|
45
|
+
default='numpy',
|
|
46
|
+
show_default=True,
|
|
47
|
+
help='Docstring style to target',
|
|
48
|
+
)
|
|
49
|
+
def main(
|
|
50
|
+
paths: tuple[str, ...],
|
|
51
|
+
config: str | None, # noqa: ARG001 (used by Click callback)
|
|
52
|
+
exclude: str,
|
|
53
|
+
line_length: int,
|
|
54
|
+
docstring_style: str,
|
|
55
|
+
) -> None:
|
|
56
|
+
"""Format .py files."""
|
|
57
|
+
ret = 0
|
|
58
|
+
|
|
59
|
+
if docstring_style.lower() != 'numpy':
|
|
60
|
+
raise ValueError('Only "numpy" style is supported for now.')
|
|
61
|
+
|
|
62
|
+
for path in paths:
|
|
63
|
+
fixer = PythonFileFixer(
|
|
64
|
+
path=path, exclude_pattern=exclude, line_length=line_length
|
|
65
|
+
)
|
|
66
|
+
fixer.docstring_style = docstring_style
|
|
67
|
+
ret |= fixer.fix_one_directory_or_one_file()
|
|
68
|
+
|
|
69
|
+
if ret != 0:
|
|
70
|
+
raise SystemExit(ret)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class PythonFileFixer(BaseFixer):
|
|
74
|
+
"""Fixer for Python source files."""
|
|
75
|
+
|
|
76
|
+
def __init__(
|
|
77
|
+
self,
|
|
78
|
+
path: str,
|
|
79
|
+
exclude_pattern: str = r'\.git|\.tox|\.pytest_cache',
|
|
80
|
+
line_length: int = 79,
|
|
81
|
+
) -> None:
|
|
82
|
+
super().__init__(path=path, exclude_pattern=exclude_pattern)
|
|
83
|
+
self.line_length = line_length
|
|
84
|
+
self.docstring_style: str = 'numpy'
|
|
85
|
+
|
|
86
|
+
def fix_one_file(self, filename: str) -> int:
|
|
87
|
+
"""Fix formatting in a single Python file."""
|
|
88
|
+
if filename == '-':
|
|
89
|
+
source_bytes: bytes = sys.stdin.buffer.read()
|
|
90
|
+
else:
|
|
91
|
+
file_path: Path = Path(filename)
|
|
92
|
+
if not file_path.is_file():
|
|
93
|
+
msg: str = f'{filename} is not a file (skipping)'
|
|
94
|
+
print(msg, file=sys.stderr)
|
|
95
|
+
return 0
|
|
96
|
+
|
|
97
|
+
with open(filename, 'rb') as fb:
|
|
98
|
+
source_bytes = fb.read()
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
source_text: str = source_bytes.decode()
|
|
102
|
+
source_text_orig: str = source_text
|
|
103
|
+
except UnicodeDecodeError:
|
|
104
|
+
error_msg: str = f'{filename} is non-utf-8 (not supported)'
|
|
105
|
+
print(error_msg, file=sys.stderr)
|
|
106
|
+
return 1
|
|
107
|
+
|
|
108
|
+
source_text = rewriter.fix_src(
|
|
109
|
+
source_text,
|
|
110
|
+
line_length=self.line_length,
|
|
111
|
+
docstring_style=self.docstring_style,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
if filename == '-':
|
|
115
|
+
print(source_text, end='')
|
|
116
|
+
elif source_text != source_text_orig:
|
|
117
|
+
print(f'Rewriting {filename}', file=sys.stderr)
|
|
118
|
+
with open(filename, 'wb') as f:
|
|
119
|
+
f.write(source_text.encode())
|
|
120
|
+
|
|
121
|
+
return int(source_text != source_text_orig)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
if __name__ == '__main__':
|
|
125
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: format-docstring
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python formatter to wrap/adjust docstring lines
|
|
5
|
+
Author-email: jsh9 <25124332+jsh9@users.noreply.github.com>
|
|
6
|
+
Maintainer-email: jsh9 <25124332+jsh9@users.noreply.github.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Project-URL: Homepage, https://github.com/your/repo
|
|
9
|
+
Project-URL: Repository, https://github.com/your/repo.git
|
|
10
|
+
Project-URL: Issues, https://github.com/your/repo/issues
|
|
11
|
+
Keywords: formatter,code-style,docstring,python
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Requires-Dist: jupyter-notebook-parser>=0.1.4
|
|
29
|
+
Requires-Dist: click>=8.0
|
|
30
|
+
Requires-Dist: docstring_parser_fork>=0.0.14
|
|
31
|
+
Requires-Dist: tomli>=1.1.0; python_version < "3.11"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# format-docstring
|
|
35
|
+
|
|
36
|
+
A Python formatter to automatically format numpy-style docstrings.
|
|
37
|
+
|
|
38
|
+
**Table of Contents:**
|
|
39
|
+
|
|
40
|
+
<!--TOC-->
|
|
41
|
+
|
|
42
|
+
- [1. Overview](#1-overview)
|
|
43
|
+
- [2. Before vs After Examples](#2-before-vs-after-examples)
|
|
44
|
+
- [2.1. Long lines are wrapped to fit line length limit](#21-long-lines-are-wrapped-to-fit-line-length-limit)
|
|
45
|
+
- [2.2. One-line summaries are formatted to fit line length limit](#22-one-line-summaries-are-formatted-to-fit-line-length-limit)
|
|
46
|
+
- [2.3. Minor typos can be automatically fixed](#23-minor-typos-can-be-automatically-fixed)
|
|
47
|
+
- [2.4. Default value declarations are standardized](#24-default-value-declarations-are-standardized)
|
|
48
|
+
- [3. Installation](#3-installation)
|
|
49
|
+
- [4. Usage](#4-usage)
|
|
50
|
+
- [4.1. Command Line Interface](#41-command-line-interface)
|
|
51
|
+
- [4.2. Pre-commit Hook](#42-pre-commit-hook)
|
|
52
|
+
- [5. Configuration](#5-configuration)
|
|
53
|
+
- [5.1. Command-Line Options](#51-command-line-options)
|
|
54
|
+
- [5.2. Usage Examples](#52-usage-examples)
|
|
55
|
+
- [5.3. `pyproject.toml` Configuration](#53-pyprojecttoml-configuration)
|
|
56
|
+
|
|
57
|
+
<!--TOC-->
|
|
58
|
+
|
|
59
|
+
## 1. Overview
|
|
60
|
+
|
|
61
|
+
`format-docstring` is a tool that automatically formats and wraps docstring
|
|
62
|
+
content in Python files and Jupyter notebooks.
|
|
63
|
+
|
|
64
|
+
Compared with [`docformatter`](https://github.com/PyCQA/docformatter) and
|
|
65
|
+
[`pydocstringformatter`](https://github.com/DanielNoord/pydocstringformatter),
|
|
66
|
+
this tool (`format-docstring`) goes further by intelligently wrapping docstring
|
|
67
|
+
contents, fixing common typos, etc.
|
|
68
|
+
|
|
69
|
+
The formatting that would be done by
|
|
70
|
+
[docformatter](https://github.com/PyCQA/docformatter) and
|
|
71
|
+
[pydocstringformatter](https://github.com/DanielNoord/pydocstringformatter) can
|
|
72
|
+
be readily handled by [Ruff](https://github.com/astral-sh/ruff) or
|
|
73
|
+
[Black](https://github.com/psf/black).
|
|
74
|
+
|
|
75
|
+
## 2. Before vs After Examples
|
|
76
|
+
|
|
77
|
+
### 2.1. Long lines are wrapped to fit line length limit
|
|
78
|
+
|
|
79
|
+
```diff
|
|
80
|
+
def example_function(param1, param2, option='default'):
|
|
81
|
+
- """This summary line is intentionally very long and exceeds the line length limit to demonstrate that format-docstring will automatically wrap it across multiple lines while preserving code structure.
|
|
82
|
+
+ """
|
|
83
|
+
+ This summary line is intentionally very long and exceeds the line length
|
|
84
|
+
+ limit to demonstrate that format-docstring will automatically wrap it
|
|
85
|
+
+ across multiple lines while preserving code structure.
|
|
86
|
+
|
|
87
|
+
Parameters
|
|
88
|
+
----------
|
|
89
|
+
- param1 : str
|
|
90
|
+
- This parameter description is also intentionally long to show how parameter descriptions get wrapped when they exceed the configured line length limit
|
|
91
|
+
- param2 : int
|
|
92
|
+
- Another long parameter description that demonstrates the wrapping behavior for parameter documentation in NumPy-style docstrings
|
|
93
|
+
+ param1 : str
|
|
94
|
+
+ This parameter description is also intentionally long to show how
|
|
95
|
+
+ parameter descriptions get wrapped when they exceed the configured
|
|
96
|
+
+ line length limit
|
|
97
|
+
+ param2 : int
|
|
98
|
+
+ Another long parameter description that demonstrates the wrapping
|
|
99
|
+
+ behavior for parameter documentation in NumPy-style docstrings
|
|
100
|
+
option : str, optional
|
|
101
|
+
Short description (not wrapped)
|
|
102
|
+
|
|
103
|
+
Returns
|
|
104
|
+
-------
|
|
105
|
+
dict
|
|
106
|
+
- The return value wrapped, because it is a very long line that exceeds line length limit by a lot.
|
|
107
|
+
+ The return value wrapped, because it is a very long line that exceeds
|
|
108
|
+
+ line length limit by a lot.
|
|
109
|
+
|
|
110
|
+
Examples
|
|
111
|
+
--------
|
|
112
|
+
Code examples with >>> prompts are preserved without wrapping:
|
|
113
|
+
|
|
114
|
+
>>> result = example_function('test', 42, option='custom_value_with_a_very_long_name_that_exceeds_line_length')
|
|
115
|
+
>>> print(result)
|
|
116
|
+
{'status': 'success'}
|
|
117
|
+
|
|
118
|
+
rST tables are preserved without wrapping:
|
|
119
|
+
|
|
120
|
+
=========== ================== ===============================
|
|
121
|
+
Format Wrapped Preserved
|
|
122
|
+
=========== ================== ===============================
|
|
123
|
+
Text Yes No (in tables, code, lists)
|
|
124
|
+
Params Yes Signature lines preserved
|
|
125
|
+
=========== ================== ===============================
|
|
126
|
+
|
|
127
|
+
Content following double colons (::) is preserved::
|
|
128
|
+
|
|
129
|
+
- This indented block after :: is not wrapped
|
|
130
|
+
- Even if lines are very long they stay intact
|
|
131
|
+
- Useful for code blocks or literal content
|
|
132
|
+
|
|
133
|
+
Regular bullet lists are also preserved:
|
|
134
|
+
|
|
135
|
+
- First bullet point that is intentionally long but not wrapped
|
|
136
|
+
- Second point also stays on one line regardless of length
|
|
137
|
+
"""
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### 2.2. One-line summaries are formatted to fit line length limit
|
|
141
|
+
|
|
142
|
+
```diff
|
|
143
|
+
def my_function():
|
|
144
|
+
- """Contents are short, but with quotation marks this exceeds length limit."""
|
|
145
|
+
+ """
|
|
146
|
+
+ Contents are short, but with quotation marks this exceeds length limit.
|
|
147
|
+
+ """
|
|
148
|
+
pass
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### 2.3. Minor typos can be automatically fixed
|
|
152
|
+
|
|
153
|
+
```diff
|
|
154
|
+
def mu_function():
|
|
155
|
+
"""
|
|
156
|
+
Minor typos in section titles or "signatures" can be fixed.
|
|
157
|
+
|
|
158
|
+
- Parameter
|
|
159
|
+
- ----
|
|
160
|
+
+ Parameters
|
|
161
|
+
+ ----------
|
|
162
|
+
arg1 : str
|
|
163
|
+
Arg 1
|
|
164
|
+
arg2 : bool
|
|
165
|
+
Arg 2
|
|
166
|
+
- arg3: int
|
|
167
|
+
+ arg3 : int
|
|
168
|
+
Arg 3
|
|
169
|
+
- arg4 : int
|
|
170
|
+
+ arg4 : int
|
|
171
|
+
Arg 4
|
|
172
|
+
|
|
173
|
+
- ReTurn
|
|
174
|
+
- ----------
|
|
175
|
+
+ Returns
|
|
176
|
+
+ -------
|
|
177
|
+
int
|
|
178
|
+
The return value
|
|
179
|
+
"""
|
|
180
|
+
pass
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### 2.4. Default value declarations are standardized
|
|
184
|
+
|
|
185
|
+
```diff
|
|
186
|
+
def example_function(arg1, arg2, arg3, arg4):
|
|
187
|
+
"""
|
|
188
|
+
Parameters
|
|
189
|
+
----------
|
|
190
|
+
- arg1 : int default 10
|
|
191
|
+
+ arg1 : int, default=10
|
|
192
|
+
First argument
|
|
193
|
+
- arg2 : str, default "hello"
|
|
194
|
+
+ arg2 : str, default="hello"
|
|
195
|
+
Second argument
|
|
196
|
+
- arg3 : bool, default is True
|
|
197
|
+
+ arg3 : bool, default=True
|
|
198
|
+
Third argument
|
|
199
|
+
- arg4 : float default: 3.14
|
|
200
|
+
+ arg4 : float, default=3.14
|
|
201
|
+
Fourth argument
|
|
202
|
+
"""
|
|
203
|
+
pass
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## 3. Installation
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
pip install format-docstring
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## 4. Usage
|
|
213
|
+
|
|
214
|
+
### 4.1. Command Line Interface
|
|
215
|
+
|
|
216
|
+
**For Python files:**
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
format-docstring path/to/file.py
|
|
220
|
+
format-docstring path/to/directory/
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
**For Jupyter notebooks:**
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
format-docstring-jupyter path/to/notebook.ipynb
|
|
227
|
+
format-docstring-jupyter path/to/directory/
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### 4.2. Pre-commit Hook
|
|
231
|
+
|
|
232
|
+
To use `format-docstring` as a pre-commit hook, add this to your
|
|
233
|
+
`.pre-commit-config.yaml`:
|
|
234
|
+
|
|
235
|
+
```yaml
|
|
236
|
+
repos:
|
|
237
|
+
- repo: https://github.com/jsh9/format-docstring
|
|
238
|
+
rev: <LATEST_VERSION>
|
|
239
|
+
hooks:
|
|
240
|
+
- id: format-docstring
|
|
241
|
+
name: Format docstrings in .py files
|
|
242
|
+
args: [--line-length=79]
|
|
243
|
+
- id: format-docstring-jupyter
|
|
244
|
+
name: Format docstrings in .ipynb files
|
|
245
|
+
args: [--line-length=79]
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Then install the pre-commit hook:
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
pre-commit install
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## 5. Configuration
|
|
255
|
+
|
|
256
|
+
### 5.1. Command-Line Options
|
|
257
|
+
|
|
258
|
+
- `--line-length INTEGER`: Maximum line length for wrapping docstrings
|
|
259
|
+
(default: 79)
|
|
260
|
+
- `--docstring-style CHOICE`: Docstring style to target (`numpy` or `google`,
|
|
261
|
+
default: `numpy`). Note: Currently only `numpy` style is fully supported.
|
|
262
|
+
- `--exclude TEXT`: Regex pattern to exclude files/directories (default:
|
|
263
|
+
`\.git|\.tox|\.pytest_cache`)
|
|
264
|
+
- `--config PATH`: Path to a `pyproject.toml` config file. If not specified,
|
|
265
|
+
the tool automatically searches for `pyproject.toml` in parent directories.
|
|
266
|
+
Command-line options take precedence over config file settings.
|
|
267
|
+
- `--version`: Show version information
|
|
268
|
+
- `--help`: Show help message
|
|
269
|
+
|
|
270
|
+
### 5.2. Usage Examples
|
|
271
|
+
|
|
272
|
+
```bash
|
|
273
|
+
# Format a single file with default settings
|
|
274
|
+
format-docstring my_module.py
|
|
275
|
+
|
|
276
|
+
# Format all Python files in a directory with custom line length
|
|
277
|
+
format-docstring --line-length 72 src/
|
|
278
|
+
|
|
279
|
+
# Format Jupyter notebooks excluding certain directories
|
|
280
|
+
format-docstring-jupyter --exclude "\.git|\.venv|__pycache__" notebooks/
|
|
281
|
+
|
|
282
|
+
# Use a specific config file
|
|
283
|
+
format-docstring --config path/to/pyproject.toml src/
|
|
284
|
+
|
|
285
|
+
# CLI options override config file settings
|
|
286
|
+
format-docstring --config pyproject.toml --line-length 100 src/
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### 5.3. `pyproject.toml` Configuration
|
|
290
|
+
|
|
291
|
+
You can configure default values in your `pyproject.toml`. CLI arguments will
|
|
292
|
+
override these settings:
|
|
293
|
+
|
|
294
|
+
```toml
|
|
295
|
+
[tool.format_docstring]
|
|
296
|
+
line_length = 79
|
|
297
|
+
docstring_style = "numpy"
|
|
298
|
+
exclude = "\\.git|\\.venv|__pycache__"
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
**Available options:**
|
|
302
|
+
|
|
303
|
+
- `line_length` (int): Maximum line length for wrapping docstrings (default:
|
|
304
|
+
79\)
|
|
305
|
+
- `docstring_style` (str): Docstring style, either `"numpy"` or `"google"`
|
|
306
|
+
(default: `"numpy"`)
|
|
307
|
+
- `exclude` (str): Regex pattern to exclude files/directories (default:
|
|
308
|
+
`"\\.git|\\.tox|\\.pytest_cache"`)
|
|
309
|
+
|
|
310
|
+
The tool searches for `pyproject.toml` starting from the target file/directory
|
|
311
|
+
and walking up the parent directories until one is found.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
format_docstring/__init__.py,sha256=3bPK0B7mVsgqXVtcJYGKBO8JAM5gYWmdQPaRrI2tpsI,146
|
|
2
|
+
format_docstring/base_fixer.py,sha256=bXbIa9v6LUQXcHYLU5byuB1xo614hGoRKdnR9T3q4oc,2121
|
|
3
|
+
format_docstring/config.py,sha256=2VvOL1AaYM-ODCZf-U3R3ptePR_DMZXnOVItbpp6yns,5528
|
|
4
|
+
format_docstring/docstring_rewriter.py,sha256=bhLWep_-5Qe5XgWI8cBdpZSZPbVurrUGx1IzNob-5zE,9068
|
|
5
|
+
format_docstring/line_wrap_google.py,sha256=arwHkcDybu2K8TCq7UtIiZF8gq4nlcWy0KzDf3bEcZc,191
|
|
6
|
+
format_docstring/line_wrap_numpy.py,sha256=e0QvPeyf4pVl_1KKbVoKuuxGWGyLxJkMsqUu2q814FY,12924
|
|
7
|
+
format_docstring/line_wrap_utils.py,sha256=NCx8JA6GZdVvvudh5u_YbLrDte7R5lMXQ681N22043w,24389
|
|
8
|
+
format_docstring/main_jupyter.py,sha256=7WrbQhuoONHn3W4hOECPSXyXWr8BnJBK96KlNBORrLI,5458
|
|
9
|
+
format_docstring/main_py.py,sha256=l3Rzd1h02erBqpTMtaqDfr9HFtXjxiUATEfsK8-3804,3690
|
|
10
|
+
format_docstring-0.1.0.dist-info/licenses/LICENSE,sha256=UNm_-hqU-1dAB3bLytP6wvGtUeitoJde2xzb5wgVYn0,1061
|
|
11
|
+
format_docstring-0.1.0.dist-info/METADATA,sha256=iZofojbHoJgmTcrXz2CV2fhFFii-EA-U4HdHksVwUf4,9970
|
|
12
|
+
format_docstring-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
13
|
+
format_docstring-0.1.0.dist-info/entry_points.txt,sha256=Re2iHTzfgKJgeXDga5Z4bGNPtWGmxupOdzTolwn52sk,129
|
|
14
|
+
format_docstring-0.1.0.dist-info/top_level.txt,sha256=NXPwfHm_1YwwGuetwtK3pJv0jXwXelqPTNCFpm5LQyE,17
|
|
15
|
+
format_docstring-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 jsh9
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
format_docstring
|