docstring-tailor 0.2.0__tar.gz → 0.2.1.dev0__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.
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/PKG-INFO +45 -29
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/README.md +44 -28
- docstring_tailor-0.2.1.dev0/assets/gif_images/docstring_slider.gif +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/pyproject.toml +1 -1
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/src/docstring_tailor/constants.py +4 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/src/docstring_tailor/main.py +55 -28
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/src/docstring_tailor/multi_line_docstring_formatter.py +3 -8
- docstring_tailor-0.2.1.dev0/src/docstring_tailor/utils/utils_cli.py +55 -0
- docstring_tailor-0.2.1.dev0/src/docstring_tailor/utils/utils_file_system.py +117 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/src/docstring_tailor/utils/utils_formatting.py +0 -85
- docstring_tailor-0.2.1.dev0/src/docstring_tailor/utils/utils_list_detection.py +88 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/tests/test_formatting.py +1 -1
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/uv.lock +1 -1
- docstring_tailor-0.2.0/src/docstring_tailor/utils/utils_file_system.py +0 -70
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/.gitignore +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/LICENSE +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/makefile +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/src/docstring_tailor/__init__.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/src/docstring_tailor/cli_config.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/src/docstring_tailor/docstring_visitor.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/src/docstring_tailor/utils/__init__.py +0 -0
- /docstring_tailor-0.2.0/tests/utils_test.py → /docstring_tailor-0.2.1.dev0/src/docstring_tailor/utils/utils_testing.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/tests/cases/__init__.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/tests/cases/config_model.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/tests/cases/formatting_cases.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/tests/fixtures/all_docstrings/all_docstrings_100.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/tests/fixtures/all_docstrings/all_docstrings_60.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/tests/fixtures/all_docstrings/all_docstrings_80.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/tests/fixtures/all_docstrings/all_docstrings_too_long.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/tests/fixtures/all_docstrings/all_docstrings_too_short.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/tests/fixtures/function_docstring_complex/function_docstring_complex_100.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/tests/fixtures/function_docstring_complex/function_docstring_complex_60.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/tests/fixtures/function_docstring_complex/function_docstring_complex_80.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/tests/fixtures/module_docstring_blank_lines/module_docstring_blank_lines.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/tests/fixtures/module_docstring_blank_lines/module_docstring_blank_lines_100.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/tests/fixtures/module_docstring_empty/module_docstring_empty.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/tests/fixtures/module_docstring_empty/module_docstring_empty_blank_lines.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/tests/fixtures/module_docstring_example_backticks/module_docstring_example_backticks.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/tests/fixtures/module_docstring_example_backticks/module_docstring_example_backticks_60.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/tests/fixtures/module_docstring_example_tildes/module_docstring_example_tildes.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/tests/fixtures/module_docstring_example_tildes/module_docstring_example_tildes_60.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/tests/fixtures/module_docstring_ordered_list/module_docstring_ordered_list_100.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/tests/fixtures/module_docstring_ordered_list/module_docstring_ordered_list_60.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/tests/fixtures/module_docstring_ordered_list/module_docstring_ordered_list_80.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/tests/fixtures/module_docstring_ordered_list/module_docstring_ordered_list_too_long.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/tests/fixtures/readme_examples/readme_examples.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: docstring-tailor
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1.dev0
|
|
4
4
|
Summary: Automatic formatting of Python docstrings according to PEP 257
|
|
5
5
|
Author-email: Auke Bruinsma <afbruinsma@gmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -33,22 +33,41 @@ Description-Content-Type: text/markdown
|
|
|
33
33
|
|
|
34
34
|
# Docstring Tailor 🪡
|
|
35
35
|
|
|
36
|
-
Automatic formatting of Python docstrings according to PEP 257 and a predefined maximum number of
|
|
36
|
+
Automatic formatting of Python docstrings according to PEP 257 and a predefined maximum number of characters per line.
|
|
37
37
|
|
|
38
38
|
[](https://pypi.org/project/docstring-tailor/)
|
|
39
|
-
[](https://pypi.org/project/docstring-tailor/)
|
|
39
|
+
[](https://pypi.org/project/docstring-tailor/)
|
|
40
40
|
[](https://pypi.org/project/docstring-tailor/)
|
|
41
|
+
[](https://pypi.org/project/docstring-tailor/)
|
|
42
|
+
|
|
41
43
|
|
|
42
44
|
## Table of Contents
|
|
43
|
-
1. [
|
|
44
|
-
2. [
|
|
45
|
-
3. [
|
|
45
|
+
1. [Demo](#demo)
|
|
46
|
+
2. [Installation](#Installation)
|
|
47
|
+
3. [Quick start](#quick_start)
|
|
48
|
+
4. [API Overview](#api-overview)
|
|
46
49
|
- [Command](#command)
|
|
47
50
|
- [Options](#options)
|
|
48
51
|
- [Examples](#examples)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
5. [Example docstrings](#example-docstrings)
|
|
53
|
+
6. [Release Notes](#release_notes)
|
|
54
|
+
7. [Roadmap](#roadmap)
|
|
55
|
+
|
|
56
|
+
## Demo
|
|
57
|
+
|
|
58
|
+
<details>
|
|
59
|
+
<summary><b>Show demo</b></summary>
|
|
60
|
+
|
|
61
|
+
<br>
|
|
62
|
+
|
|
63
|
+
- `docstring-tailor` formats docstrings to fit a given line length, while preserving its structure throughout — For exapmle blank lines, argument indentation, continuation line alignment, and code blocks in the Examples section all remain intact.
|
|
64
|
+
- **Note**: The slider in the [Marimo notebook](https://marimo.io/) is not part of the package. It was created solely to illustrate how the output changes continuously as the line length varies.
|
|
65
|
+
|
|
66
|
+
<br>
|
|
67
|
+
|
|
68
|
+

|
|
69
|
+
|
|
70
|
+
</details>
|
|
52
71
|
|
|
53
72
|
## Installation
|
|
54
73
|
|
|
@@ -112,55 +131,56 @@ style = "google"
|
|
|
112
131
|
```bash
|
|
113
132
|
uv run docstring_tailor [PATHS ...] [OPTIONS]
|
|
114
133
|
```
|
|
115
|
-
|
|
116
134
|
`PATHS` may contain one or more files and/or directories.
|
|
117
|
-
|
|
118
135
|
Examples:
|
|
119
|
-
|
|
120
136
|
```bash
|
|
121
137
|
uv run docstring_tailor my_file.py
|
|
122
138
|
uv run docstring_tailor src/
|
|
123
139
|
uv run docstring_tailor src/ tests/test_file.py
|
|
124
140
|
```
|
|
125
|
-
|
|
126
141
|
If no paths are provided, `docstring_tailor` will attempt to locate and format files inside the `src` directory.
|
|
127
142
|
|
|
128
|
-
---
|
|
129
|
-
|
|
130
143
|
### Options
|
|
131
144
|
|
|
132
145
|
| <div style="width:140px">Option</div> | <div style="width:50px">Type</div> | <div style="width:80px">Default</div> | Description |
|
|
133
146
|
|---|---|---|---|
|
|
134
|
-
| `--line-length`
|
|
135
|
-
| `--style`
|
|
136
|
-
| `--detect-lists`
|
|
147
|
+
| `--line-length` | `int` | 100 | Maximum number of characters allowed per line after formatting. |
|
|
148
|
+
| `--style` | `str` | google | Docstring style to enforce. Currently only the Google docstring style is supported. |
|
|
149
|
+
| `--detect-lists` | `bool` | true | Detect unordered and ordered/numbered lists anywhere in a docstring and preserve each list element on its own line during formatting. |
|
|
150
|
+
| `--exclude` | `str` | — | A glob pattern for paths to exclude. Can be passed multiple times. Single-path patterns (e.g. `tests`, `*.pyi`) match by name anywhere in the tree. Relative patterns (e.g. `src/generated/*.py`) match against the path relative to the project root. |
|
|
151
|
+
| `--diff` | flag | — | Print a unified diff of changes to stdout instead of modifying files. No files are written when this flag is set. |
|
|
152
|
+
| `--version`, `-V` | flag | — | Print the installed version and exit. |
|
|
153
|
+
| `--help` | flag | — | Show the help message and exit. |
|
|
137
154
|
|
|
138
155
|
### Examples
|
|
139
156
|
|
|
140
157
|
`CLI`
|
|
141
|
-
|
|
142
158
|
```bash
|
|
143
159
|
uv run docstring_tailor src/ --line-length 88
|
|
144
160
|
uv run docstring_tailor my_file.py --style google
|
|
145
161
|
uv run docstring_tailor --detect-lists
|
|
146
162
|
uv run docstring_tailor --no-detect-lists
|
|
147
|
-
|
|
163
|
+
uv run docstring_tailor src/ --exclude tests --exclude "src/generated/*.py"
|
|
164
|
+
uv run docstring_tailor src/ --diff
|
|
165
|
+
uv run docstring_tailor --version
|
|
166
|
+
uv run docstring_tailor --help
|
|
148
167
|
|
|
168
|
+
```
|
|
149
169
|
`pyproject.toml`
|
|
150
|
-
|
|
151
170
|
```toml
|
|
152
171
|
[tool.docstring_tailor]
|
|
153
172
|
line-length = 88
|
|
154
173
|
style = "google"
|
|
155
174
|
detect-lists = true
|
|
175
|
+
exclude = ["tests", "src/generated/*.py"]
|
|
156
176
|
```
|
|
157
177
|
|
|
158
178
|
`docstring_tailor.toml`
|
|
159
|
-
|
|
160
179
|
```toml
|
|
161
180
|
line-length = 88
|
|
162
181
|
style = "google"
|
|
163
182
|
detect-lists = true
|
|
183
|
+
exclude = ["tests", "src/generated/*.py"]
|
|
164
184
|
```
|
|
165
185
|
|
|
166
186
|
## Example docstrings
|
|
@@ -315,8 +335,7 @@ def example_generator(n):
|
|
|
315
335
|
```
|
|
316
336
|
- Similar to `Returns`, `Yields` is also supported.
|
|
317
337
|
|
|
318
|
-
```
|
|
319
|
-
|
|
338
|
+
```python
|
|
320
339
|
"""Demonstrates a Google-style module docstring containing a Note
|
|
321
340
|
section.
|
|
322
341
|
|
|
@@ -378,24 +397,21 @@ Steps:
|
|
|
378
397
|
|
|
379
398
|
- Personally, I like to use unordered and numbered lists sometimes in a docstring. Similar to what has been described before, **indentation** is used to detect new list elements.
|
|
380
399
|
|
|
381
|
-
|
|
382
400
|
## Release Notes
|
|
383
401
|
|
|
384
402
|
| <div style="width:70px">Version</div> | <div style="width:100px">Release date</div> | <div style="width:130px">Type</div> | Details |
|
|
385
403
|
|---|---|---|---|
|
|
386
|
-
| `0.1.0` | 2026-05-31 | Initial release | First public release of `docstring-tailor`. Includes <ul><li>Automatic docstring wrapping for module, class and function
|
|
404
|
+
| `0.1.0` | 2026-05-31 | Initial release | First public release of `docstring-tailor`. Includes <ul><li>Automatic docstring wrapping for module, class and function docstrings, for both one line and multi line docstrings, with a configurable `line-length` parameter.</li><li>Paragraph-aware formatting, differentiating between 'Args', 'Examples' or normal text sections.</li> <li> Docstring support for the Google `style` (Numpy, Sphinx, Epydoc not yet supported). </li><li>TOML-based configuration support.</li><li> Test coverage: 52% </ul> |
|
|
387
405
|
| `0.1.1` | 2026-05-31 | Documentation update | Updated the `README.md` file with the 'Installation' and 'Quick Start' section. |
|
|
388
406
|
| `0.2.0` | 2026-06-07 | Feature update | <ul><li>Implemented the `detect-lists` parameter, adding support for unordered and ordered (numbered) lists in docstrings. When enabled, list structures are detected automatically and each list item is formatted onto its own line.</li><li>Introduced a declarative golden-file test framework for formatter validation. Test cases are now generated from parametrized templates using Cartesian-product expansion, significantly reducing boilerplate and improving scalability for configuration coverage.</li><li>Expanded this `README.md` with the 'API Overview', 'Release Notes', 'Example docstrings' and 'Roadmap' sections.</li><li>Test coverage: 75%</li></ul> |
|
|
407
|
+
| `0.2.1` | 2026-06-10 | Feature update | <ul><li>Added the `-V`/`--version` command to the CLI.</li><li>Added the `--exclude` command to the CLI.</li><li>Added the `--diff` command to the CLI.</li><li>Added the 'Demo' part to to the `README.md`.</ul> |
|
|
389
408
|
|
|
390
409
|
## Roadmap
|
|
391
410
|
|
|
392
411
|
### Must have
|
|
393
412
|
|
|
394
413
|
- Support for all major docstrings styles (Google, Numpy, Sphinx, Epydoc).
|
|
395
|
-
- Add `diff` functionality that will show you the formatting changes before actually changing the file(s).
|
|
396
414
|
- Make sure the package can be used as a pre-commit hook.
|
|
397
|
-
- Add `exclude` parameters that allows the user to ignore specific files.
|
|
398
|
-
- Add `v`/`version` parameter that shows the version of the package.
|
|
399
415
|
|
|
400
416
|
### Nice to have
|
|
401
417
|
|
|
@@ -1,21 +1,40 @@
|
|
|
1
1
|
# Docstring Tailor 🪡
|
|
2
2
|
|
|
3
|
-
Automatic formatting of Python docstrings according to PEP 257 and a predefined maximum number of
|
|
3
|
+
Automatic formatting of Python docstrings according to PEP 257 and a predefined maximum number of characters per line.
|
|
4
4
|
|
|
5
5
|
[](https://pypi.org/project/docstring-tailor/)
|
|
6
|
-
[](https://pypi.org/project/docstring-tailor/)
|
|
6
|
+
[](https://pypi.org/project/docstring-tailor/)
|
|
7
7
|
[](https://pypi.org/project/docstring-tailor/)
|
|
8
|
+
[](https://pypi.org/project/docstring-tailor/)
|
|
9
|
+
|
|
8
10
|
|
|
9
11
|
## Table of Contents
|
|
10
|
-
1. [
|
|
11
|
-
2. [
|
|
12
|
-
3. [
|
|
12
|
+
1. [Demo](#demo)
|
|
13
|
+
2. [Installation](#Installation)
|
|
14
|
+
3. [Quick start](#quick_start)
|
|
15
|
+
4. [API Overview](#api-overview)
|
|
13
16
|
- [Command](#command)
|
|
14
17
|
- [Options](#options)
|
|
15
18
|
- [Examples](#examples)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
5. [Example docstrings](#example-docstrings)
|
|
20
|
+
6. [Release Notes](#release_notes)
|
|
21
|
+
7. [Roadmap](#roadmap)
|
|
22
|
+
|
|
23
|
+
## Demo
|
|
24
|
+
|
|
25
|
+
<details>
|
|
26
|
+
<summary><b>Show demo</b></summary>
|
|
27
|
+
|
|
28
|
+
<br>
|
|
29
|
+
|
|
30
|
+
- `docstring-tailor` formats docstrings to fit a given line length, while preserving its structure throughout — For exapmle blank lines, argument indentation, continuation line alignment, and code blocks in the Examples section all remain intact.
|
|
31
|
+
- **Note**: The slider in the [Marimo notebook](https://marimo.io/) is not part of the package. It was created solely to illustrate how the output changes continuously as the line length varies.
|
|
32
|
+
|
|
33
|
+
<br>
|
|
34
|
+
|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
</details>
|
|
19
38
|
|
|
20
39
|
## Installation
|
|
21
40
|
|
|
@@ -79,55 +98,56 @@ style = "google"
|
|
|
79
98
|
```bash
|
|
80
99
|
uv run docstring_tailor [PATHS ...] [OPTIONS]
|
|
81
100
|
```
|
|
82
|
-
|
|
83
101
|
`PATHS` may contain one or more files and/or directories.
|
|
84
|
-
|
|
85
102
|
Examples:
|
|
86
|
-
|
|
87
103
|
```bash
|
|
88
104
|
uv run docstring_tailor my_file.py
|
|
89
105
|
uv run docstring_tailor src/
|
|
90
106
|
uv run docstring_tailor src/ tests/test_file.py
|
|
91
107
|
```
|
|
92
|
-
|
|
93
108
|
If no paths are provided, `docstring_tailor` will attempt to locate and format files inside the `src` directory.
|
|
94
109
|
|
|
95
|
-
---
|
|
96
|
-
|
|
97
110
|
### Options
|
|
98
111
|
|
|
99
112
|
| <div style="width:140px">Option</div> | <div style="width:50px">Type</div> | <div style="width:80px">Default</div> | Description |
|
|
100
113
|
|---|---|---|---|
|
|
101
|
-
| `--line-length`
|
|
102
|
-
| `--style`
|
|
103
|
-
| `--detect-lists`
|
|
114
|
+
| `--line-length` | `int` | 100 | Maximum number of characters allowed per line after formatting. |
|
|
115
|
+
| `--style` | `str` | google | Docstring style to enforce. Currently only the Google docstring style is supported. |
|
|
116
|
+
| `--detect-lists` | `bool` | true | Detect unordered and ordered/numbered lists anywhere in a docstring and preserve each list element on its own line during formatting. |
|
|
117
|
+
| `--exclude` | `str` | — | A glob pattern for paths to exclude. Can be passed multiple times. Single-path patterns (e.g. `tests`, `*.pyi`) match by name anywhere in the tree. Relative patterns (e.g. `src/generated/*.py`) match against the path relative to the project root. |
|
|
118
|
+
| `--diff` | flag | — | Print a unified diff of changes to stdout instead of modifying files. No files are written when this flag is set. |
|
|
119
|
+
| `--version`, `-V` | flag | — | Print the installed version and exit. |
|
|
120
|
+
| `--help` | flag | — | Show the help message and exit. |
|
|
104
121
|
|
|
105
122
|
### Examples
|
|
106
123
|
|
|
107
124
|
`CLI`
|
|
108
|
-
|
|
109
125
|
```bash
|
|
110
126
|
uv run docstring_tailor src/ --line-length 88
|
|
111
127
|
uv run docstring_tailor my_file.py --style google
|
|
112
128
|
uv run docstring_tailor --detect-lists
|
|
113
129
|
uv run docstring_tailor --no-detect-lists
|
|
114
|
-
|
|
130
|
+
uv run docstring_tailor src/ --exclude tests --exclude "src/generated/*.py"
|
|
131
|
+
uv run docstring_tailor src/ --diff
|
|
132
|
+
uv run docstring_tailor --version
|
|
133
|
+
uv run docstring_tailor --help
|
|
115
134
|
|
|
135
|
+
```
|
|
116
136
|
`pyproject.toml`
|
|
117
|
-
|
|
118
137
|
```toml
|
|
119
138
|
[tool.docstring_tailor]
|
|
120
139
|
line-length = 88
|
|
121
140
|
style = "google"
|
|
122
141
|
detect-lists = true
|
|
142
|
+
exclude = ["tests", "src/generated/*.py"]
|
|
123
143
|
```
|
|
124
144
|
|
|
125
145
|
`docstring_tailor.toml`
|
|
126
|
-
|
|
127
146
|
```toml
|
|
128
147
|
line-length = 88
|
|
129
148
|
style = "google"
|
|
130
149
|
detect-lists = true
|
|
150
|
+
exclude = ["tests", "src/generated/*.py"]
|
|
131
151
|
```
|
|
132
152
|
|
|
133
153
|
## Example docstrings
|
|
@@ -282,8 +302,7 @@ def example_generator(n):
|
|
|
282
302
|
```
|
|
283
303
|
- Similar to `Returns`, `Yields` is also supported.
|
|
284
304
|
|
|
285
|
-
```
|
|
286
|
-
|
|
305
|
+
```python
|
|
287
306
|
"""Demonstrates a Google-style module docstring containing a Note
|
|
288
307
|
section.
|
|
289
308
|
|
|
@@ -345,24 +364,21 @@ Steps:
|
|
|
345
364
|
|
|
346
365
|
- Personally, I like to use unordered and numbered lists sometimes in a docstring. Similar to what has been described before, **indentation** is used to detect new list elements.
|
|
347
366
|
|
|
348
|
-
|
|
349
367
|
## Release Notes
|
|
350
368
|
|
|
351
369
|
| <div style="width:70px">Version</div> | <div style="width:100px">Release date</div> | <div style="width:130px">Type</div> | Details |
|
|
352
370
|
|---|---|---|---|
|
|
353
|
-
| `0.1.0` | 2026-05-31 | Initial release | First public release of `docstring-tailor`. Includes <ul><li>Automatic docstring wrapping for module, class and function
|
|
371
|
+
| `0.1.0` | 2026-05-31 | Initial release | First public release of `docstring-tailor`. Includes <ul><li>Automatic docstring wrapping for module, class and function docstrings, for both one line and multi line docstrings, with a configurable `line-length` parameter.</li><li>Paragraph-aware formatting, differentiating between 'Args', 'Examples' or normal text sections.</li> <li> Docstring support for the Google `style` (Numpy, Sphinx, Epydoc not yet supported). </li><li>TOML-based configuration support.</li><li> Test coverage: 52% </ul> |
|
|
354
372
|
| `0.1.1` | 2026-05-31 | Documentation update | Updated the `README.md` file with the 'Installation' and 'Quick Start' section. |
|
|
355
373
|
| `0.2.0` | 2026-06-07 | Feature update | <ul><li>Implemented the `detect-lists` parameter, adding support for unordered and ordered (numbered) lists in docstrings. When enabled, list structures are detected automatically and each list item is formatted onto its own line.</li><li>Introduced a declarative golden-file test framework for formatter validation. Test cases are now generated from parametrized templates using Cartesian-product expansion, significantly reducing boilerplate and improving scalability for configuration coverage.</li><li>Expanded this `README.md` with the 'API Overview', 'Release Notes', 'Example docstrings' and 'Roadmap' sections.</li><li>Test coverage: 75%</li></ul> |
|
|
374
|
+
| `0.2.1` | 2026-06-10 | Feature update | <ul><li>Added the `-V`/`--version` command to the CLI.</li><li>Added the `--exclude` command to the CLI.</li><li>Added the `--diff` command to the CLI.</li><li>Added the 'Demo' part to to the `README.md`.</ul> |
|
|
356
375
|
|
|
357
376
|
## Roadmap
|
|
358
377
|
|
|
359
378
|
### Must have
|
|
360
379
|
|
|
361
380
|
- Support for all major docstrings styles (Google, Numpy, Sphinx, Epydoc).
|
|
362
|
-
- Add `diff` functionality that will show you the formatting changes before actually changing the file(s).
|
|
363
381
|
- Make sure the package can be used as a pre-commit hook.
|
|
364
|
-
- Add `exclude` parameters that allows the user to ignore specific files.
|
|
365
|
-
- Add `v`/`version` parameter that shows the version of the package.
|
|
366
382
|
|
|
367
383
|
### Nice to have
|
|
368
384
|
|
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "docstring-tailor"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.1.dev0"
|
|
4
4
|
description = "Automatic formatting of Python docstrings according to PEP 257"
|
|
5
5
|
authors = [{ name = "Auke Bruinsma", email = "afbruinsma@gmail.com" }]
|
|
6
6
|
license = { file = "LICENSE" }
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Module for storing project constants."""
|
|
2
2
|
|
|
3
|
+
from collections import namedtuple
|
|
3
4
|
from pathlib import Path
|
|
4
5
|
|
|
5
6
|
# Repository relative file paths.
|
|
@@ -58,3 +59,6 @@ CODE_BLOCK_PREFIXES = (
|
|
|
58
59
|
FENCED_CODE_BLOCK_BACKTICKS,
|
|
59
60
|
FENCED_CODE_BLOCK_TILDES,
|
|
60
61
|
)
|
|
62
|
+
|
|
63
|
+
# Named tuples
|
|
64
|
+
Section = namedtuple("Section", ["name", "body"])
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Main module"""
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import Annotated
|
|
4
|
+
from typing import Annotated, Optional
|
|
5
5
|
|
|
6
6
|
import libcst as cst
|
|
7
7
|
import typer
|
|
@@ -18,6 +18,7 @@ from docstring_tailor.cli_config import (
|
|
|
18
18
|
)
|
|
19
19
|
from docstring_tailor.constants import ENCODING
|
|
20
20
|
from docstring_tailor.docstring_visitor import DocstringVisitor
|
|
21
|
+
from docstring_tailor.utils.utils_cli import show_diff, version_callback
|
|
21
22
|
from docstring_tailor.utils.utils_file_system import (
|
|
22
23
|
collect_python_files,
|
|
23
24
|
load_config,
|
|
@@ -33,10 +34,6 @@ def main(
|
|
|
33
34
|
list[Path] | None,
|
|
34
35
|
typer.Argument(help="Files or directories to process. Defaults to 'src/'."),
|
|
35
36
|
] = None,
|
|
36
|
-
style: Annotated[
|
|
37
|
-
DocstringStyle | None,
|
|
38
|
-
typer.Option("--style", help="Docstring style to format to."),
|
|
39
|
-
] = None,
|
|
40
37
|
line_length: Annotated[
|
|
41
38
|
int | None,
|
|
42
39
|
typer.Option(
|
|
@@ -46,6 +43,10 @@ def main(
|
|
|
46
43
|
max=LINE_LENGTH_MAX,
|
|
47
44
|
),
|
|
48
45
|
] = None,
|
|
46
|
+
style: Annotated[
|
|
47
|
+
DocstringStyle | None,
|
|
48
|
+
typer.Option("--style", help="Docstring style to format to."),
|
|
49
|
+
] = None,
|
|
49
50
|
detect_lists: Annotated[
|
|
50
51
|
bool | None,
|
|
51
52
|
typer.Option(
|
|
@@ -53,6 +54,35 @@ def main(
|
|
|
53
54
|
help="Detect and preserve list formatting.",
|
|
54
55
|
),
|
|
55
56
|
] = None,
|
|
57
|
+
exclude: Annotated[
|
|
58
|
+
list[str] | None,
|
|
59
|
+
typer.Option(
|
|
60
|
+
"--exclude",
|
|
61
|
+
help=(
|
|
62
|
+
"A glob pattern for paths to exclude. Can be passed multiple times. "
|
|
63
|
+
"Single-path patterns (e.g. 'tests', '*.pyi') match by name anywhere "
|
|
64
|
+
"in the tree. Relative patterns (e.g. 'src/generated/*.py') match "
|
|
65
|
+
"against the path relative to the project root."
|
|
66
|
+
),
|
|
67
|
+
),
|
|
68
|
+
] = None,
|
|
69
|
+
diff: Annotated[
|
|
70
|
+
bool,
|
|
71
|
+
typer.Option(
|
|
72
|
+
"--diff",
|
|
73
|
+
help="Show a diff of changes without modifying any files.",
|
|
74
|
+
),
|
|
75
|
+
] = False,
|
|
76
|
+
version: Annotated[
|
|
77
|
+
Optional[bool],
|
|
78
|
+
typer.Option(
|
|
79
|
+
"-V",
|
|
80
|
+
"--version",
|
|
81
|
+
callback=version_callback,
|
|
82
|
+
is_eager=True,
|
|
83
|
+
help="Show the version and exit.",
|
|
84
|
+
),
|
|
85
|
+
] = None,
|
|
56
86
|
) -> None:
|
|
57
87
|
"""Formats Python docstrings in the given files or directories to the specified style.
|
|
58
88
|
|
|
@@ -61,23 +91,28 @@ def main(
|
|
|
61
91
|
|
|
62
92
|
Args:
|
|
63
93
|
paths (list[Path] | None): Files or directories to process. Defaults to 'src/'.
|
|
64
|
-
style (DocstringStyle | None): The docstring style to format to.
|
|
65
94
|
line_length (int | None): The maximum line length to wrap docstrings to.
|
|
95
|
+
style (DocstringStyle | None): The docstring style to format to.
|
|
96
|
+
detect_lists (bool | None): Whether to detect and preserve list formatting.
|
|
97
|
+
diff (bool): If True, print a unified diff to stdout instead of writing files.
|
|
98
|
+
exclude (list[str] | None): Glob patterns for paths to exclude.
|
|
99
|
+
version (bool | None): If passed, print the version and exit.
|
|
66
100
|
"""
|
|
67
101
|
# Resolve configuration with priority: CLI argument > config file > built-in default.
|
|
68
102
|
file_config = load_config()
|
|
69
103
|
resolved_paths = paths or [Path(p) for p in DEFAULT_PATHS]
|
|
70
|
-
resolved_style = style or DocstringStyle(
|
|
71
|
-
file_config.get("style", DEFAULT_STYLE.value)
|
|
72
|
-
)
|
|
73
104
|
resolved_line_length = line_length or file_config.get(
|
|
74
105
|
"line-length", LINE_LENGTH_DEFAULT
|
|
75
106
|
)
|
|
107
|
+
resolved_style = style or DocstringStyle(
|
|
108
|
+
file_config.get("style", DEFAULT_STYLE.value)
|
|
109
|
+
)
|
|
76
110
|
resolved_detect_lists = (
|
|
77
111
|
detect_lists
|
|
78
112
|
if detect_lists is not None
|
|
79
113
|
else file_config.get("detect-lists", DETECT_LISTS_DEFAULT)
|
|
80
114
|
)
|
|
115
|
+
resolved_exclude = exclude or file_config.get("exclude", [])
|
|
81
116
|
|
|
82
117
|
if resolved_style not in SUPPORTED_STYLES:
|
|
83
118
|
typer.echo(
|
|
@@ -87,7 +122,10 @@ def main(
|
|
|
87
122
|
raise typer.Exit(code=1)
|
|
88
123
|
|
|
89
124
|
validate_paths(paths=resolved_paths)
|
|
90
|
-
python_files = collect_python_files(
|
|
125
|
+
python_files = collect_python_files(
|
|
126
|
+
paths=resolved_paths,
|
|
127
|
+
exclude_patterns=resolved_exclude,
|
|
128
|
+
)
|
|
91
129
|
|
|
92
130
|
for file_path in python_files:
|
|
93
131
|
input_data = file_path.read_text(encoding=ENCODING)
|
|
@@ -97,25 +135,14 @@ def main(
|
|
|
97
135
|
line_length=resolved_line_length, detect_lists=resolved_detect_lists
|
|
98
136
|
)
|
|
99
137
|
)
|
|
100
|
-
file_path.write_text(modified_tree.code, encoding=ENCODING)
|
|
101
138
|
|
|
139
|
+
modified_code = modified_tree.code
|
|
102
140
|
|
|
103
|
-
if
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
"""TODO:
|
|
141
|
+
if diff:
|
|
142
|
+
show_diff(original=input_data, modified=modified_code, path=file_path)
|
|
143
|
+
else:
|
|
144
|
+
file_path.write_text(modified_code, encoding=ENCODING)
|
|
108
145
|
|
|
109
|
-
- Fix issue with (un)ordered lists if list element span multiple lines.
|
|
110
146
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
pyproject.toml and that everything formats correctly to that style. The reading from pyproject.toml
|
|
114
|
-
is already there, the biggest effort is in reformatting docstring_section_formatter a bit to make it
|
|
115
|
-
work for all styles.
|
|
116
|
-
|
|
117
|
-
- Check all the parameters in the docstringformatter package to see which ones I also want.
|
|
118
|
-
|
|
119
|
-
- Implement feature that you can display the diff in terminal, instead of immediately formatting and
|
|
120
|
-
overwriting the .py files.
|
|
121
|
-
"""
|
|
147
|
+
if __name__ == "__main__":
|
|
148
|
+
app()
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
import textwrap
|
|
5
|
-
from collections import namedtuple
|
|
6
5
|
|
|
7
6
|
from docstring_tailor.constants import (
|
|
8
7
|
CODE_BLOCK_PREFIXES,
|
|
@@ -11,14 +10,10 @@ from docstring_tailor.constants import (
|
|
|
11
10
|
GOOGLE_ITEM_SECTIONS,
|
|
12
11
|
GOOGLE_PLAIN_SECTIONS,
|
|
13
12
|
GOOGLE_SECTION_HEADERS,
|
|
13
|
+
Section,
|
|
14
14
|
)
|
|
15
|
-
from docstring_tailor.utils.utils_formatting import
|
|
16
|
-
|
|
17
|
-
format_paragraph,
|
|
18
|
-
is_list,
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
Section = namedtuple("Section", ["name", "body"])
|
|
15
|
+
from docstring_tailor.utils.utils_formatting import format_list, format_paragraph
|
|
16
|
+
from docstring_tailor.utils.utils_list_detection import is_list
|
|
22
17
|
|
|
23
18
|
|
|
24
19
|
class MultiLineDocstringFormatter:
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Utility module containing helper functions for the CLI."""
|
|
2
|
+
|
|
3
|
+
import difflib
|
|
4
|
+
from importlib import metadata
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def version_callback(value: bool) -> None:
|
|
11
|
+
"""Print the package version and exit.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
value (bool): Whether the flag was passed.
|
|
15
|
+
|
|
16
|
+
Raises:
|
|
17
|
+
typer.Exit: Always raised after printing, to halt execution.
|
|
18
|
+
"""
|
|
19
|
+
if value:
|
|
20
|
+
version = metadata.version("docstring-tailor")
|
|
21
|
+
typer.echo(version)
|
|
22
|
+
raise typer.Exit()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def show_diff(original: str, modified: str, path: Path) -> None:
|
|
26
|
+
"""Prints a unified diff between the original and modified source to stdout.
|
|
27
|
+
|
|
28
|
+
Skips output entirely if the two sources are identical. Each line of the diff is coloured:
|
|
29
|
+
additions in green, removals in red, and header lines in bold, falling back to plain output on
|
|
30
|
+
terminals that don't support ANSI codes.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
original (str): The source text before formatting.
|
|
34
|
+
modified (str): The source text after formatting.
|
|
35
|
+
path (Path): The file path, used as the diff header label.
|
|
36
|
+
"""
|
|
37
|
+
if original == modified:
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
diff_lines = difflib.unified_diff(
|
|
41
|
+
original.splitlines(keepends=True),
|
|
42
|
+
modified.splitlines(keepends=True),
|
|
43
|
+
fromfile=f"{path} (original)",
|
|
44
|
+
tofile=f"{path} (formatted)",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
for line in diff_lines:
|
|
48
|
+
if line.startswith("+++") or line.startswith("---"):
|
|
49
|
+
typer.echo(typer.style(line, bold=True), nl=False)
|
|
50
|
+
elif line.startswith("+"):
|
|
51
|
+
typer.echo(typer.style(line, fg=typer.colors.GREEN), nl=False)
|
|
52
|
+
elif line.startswith("-"):
|
|
53
|
+
typer.echo(typer.style(line, fg=typer.colors.RED), nl=False)
|
|
54
|
+
else:
|
|
55
|
+
typer.echo(line, nl=False)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Module containing various utility functions related to interacting with local file system."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def load_config() -> dict:
|
|
9
|
+
"""Loads configuration from docstring_tailor.toml or pyproject.toml.
|
|
10
|
+
|
|
11
|
+
Walks up from the current directory. docstring_tailor.toml takes priority over pyproject.toml if
|
|
12
|
+
both exist at the same level. Stops at the first file found containing docstring_tailor
|
|
13
|
+
configuration.
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
config (dict): Configuration settings, or an empty dict if none found.
|
|
17
|
+
"""
|
|
18
|
+
import tomllib
|
|
19
|
+
|
|
20
|
+
for directory in [Path.cwd(), *Path.cwd().parents]:
|
|
21
|
+
tailor_config = directory / "docstring_tailor.toml"
|
|
22
|
+
if tailor_config.exists():
|
|
23
|
+
with open(tailor_config, "rb") as file:
|
|
24
|
+
return tomllib.load(file)
|
|
25
|
+
|
|
26
|
+
pyproject = directory / "pyproject.toml"
|
|
27
|
+
if pyproject.exists():
|
|
28
|
+
with open(pyproject, "rb") as file:
|
|
29
|
+
data = tomllib.load(file)
|
|
30
|
+
tool_config = data.get("tool", {}).get("docstring_tailor", {})
|
|
31
|
+
if tool_config:
|
|
32
|
+
return tool_config
|
|
33
|
+
|
|
34
|
+
return {}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _is_excluded(path: Path, exclude_patterns: list[str], project_root: Path) -> bool:
|
|
38
|
+
"""Checks whether a path matches any of the provided exclusion patterns.
|
|
39
|
+
|
|
40
|
+
Supports two pattern types, mirroring Ruff's exclude behaviour:
|
|
41
|
+
- Single-path patterns (e.g. '.mypy_cache', 'foo.py', 'foo_*.py') are matched against every
|
|
42
|
+
component of the path, so they exclude by name anywhere in the tree.
|
|
43
|
+
- Relative patterns containing a separator (e.g. 'directory/foo.py', 'directory/*.py') are
|
|
44
|
+
matched against the path relative to the project root, so they only exclude at that specific
|
|
45
|
+
location.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
path (Path): The absolute path to test.
|
|
49
|
+
exclude_patterns (list[str]): Glob patterns provided via --exclude.
|
|
50
|
+
project_root (Path): The directory against which relative patterns are resolved (typically
|
|
51
|
+
cwd or the directory containing pyproject.toml).
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
excluded (bool): True if the path matches at least one pattern.
|
|
55
|
+
"""
|
|
56
|
+
for pattern in exclude_patterns:
|
|
57
|
+
is_relative_pattern = "/" in pattern or "\\" in pattern
|
|
58
|
+
if is_relative_pattern:
|
|
59
|
+
try:
|
|
60
|
+
relative_path = path.relative_to(project_root)
|
|
61
|
+
if relative_path.match(pattern):
|
|
62
|
+
return True
|
|
63
|
+
except ValueError:
|
|
64
|
+
pass
|
|
65
|
+
else:
|
|
66
|
+
for part in path.parts:
|
|
67
|
+
if Path(part).match(pattern):
|
|
68
|
+
return True
|
|
69
|
+
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def collect_python_files(
|
|
74
|
+
paths: list[Path],
|
|
75
|
+
exclude_patterns: list[str] | None = None,
|
|
76
|
+
) -> list[Path]:
|
|
77
|
+
"""Collects all Python files from a list of file and/or directory paths.
|
|
78
|
+
|
|
79
|
+
Directories are searched recursively. Files are included directly. Any path matching one of the
|
|
80
|
+
provided exclusion patterns is silently skipped.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
paths (list[Path]): A list of file and/or directory paths to search.
|
|
84
|
+
exclude_patterns (list[str] | None): Glob patterns for paths to exclude.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
python_files (list[Path]): A flat list of all collected .py file paths.
|
|
88
|
+
"""
|
|
89
|
+
resolved_patterns = exclude_patterns or []
|
|
90
|
+
project_root = Path.cwd()
|
|
91
|
+
python_files: list[Path] = []
|
|
92
|
+
|
|
93
|
+
for path in paths:
|
|
94
|
+
if path.is_dir():
|
|
95
|
+
for candidate in path.rglob("*.py"):
|
|
96
|
+
if not _is_excluded(candidate, resolved_patterns, project_root):
|
|
97
|
+
python_files.append(candidate)
|
|
98
|
+
else:
|
|
99
|
+
if not _is_excluded(path, resolved_patterns, project_root):
|
|
100
|
+
python_files.append(path)
|
|
101
|
+
|
|
102
|
+
return python_files
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def validate_paths(paths: list[Path]) -> None:
|
|
106
|
+
"""Validates that all provided paths exist on the filesystem.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
paths (list[Path]): A list of file and/or directory paths to validate.
|
|
110
|
+
|
|
111
|
+
Raises:
|
|
112
|
+
typer.Exit: If any path does not exist.
|
|
113
|
+
"""
|
|
114
|
+
for path in paths:
|
|
115
|
+
if not path.exists():
|
|
116
|
+
typer.echo(f"Error: path '{path}' does not exist.")
|
|
117
|
+
raise typer.Exit(code=1)
|
|
@@ -33,91 +33,6 @@ def format_paragraph(
|
|
|
33
33
|
return formatted
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
def _is_unordered_list(lines: list[str]) -> bool:
|
|
37
|
-
"""Returns True if at least two consecutive list items starting with an unordered marker are
|
|
38
|
-
found, ignoring continuation lines from wrapped items.
|
|
39
|
-
|
|
40
|
-
Args:
|
|
41
|
-
lines (list[str]): The lines to check.
|
|
42
|
-
|
|
43
|
-
Returns:
|
|
44
|
-
result (bool): True if an unordered list is detected, False otherwise.
|
|
45
|
-
"""
|
|
46
|
-
non_empty_lines = [line for line in lines if line.strip()]
|
|
47
|
-
if not non_empty_lines:
|
|
48
|
-
return False
|
|
49
|
-
|
|
50
|
-
base_indent = min(len(line) - len(line.lstrip()) for line in non_empty_lines)
|
|
51
|
-
pattern = re.compile(r"^\s*[-*+]\s+")
|
|
52
|
-
count = 0
|
|
53
|
-
|
|
54
|
-
for line in lines:
|
|
55
|
-
if not line.strip():
|
|
56
|
-
continue
|
|
57
|
-
|
|
58
|
-
if len(line) - len(line.lstrip()) == base_indent:
|
|
59
|
-
if pattern.match(line):
|
|
60
|
-
count += 1
|
|
61
|
-
if count >= 2:
|
|
62
|
-
return True
|
|
63
|
-
else:
|
|
64
|
-
count = 0
|
|
65
|
-
|
|
66
|
-
return False
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def _is_ordered_list(lines: list[str]) -> bool:
|
|
70
|
-
"""Returns True if at least two consecutive sequentially numbered list items are found, ignoring
|
|
71
|
-
continuation lines from wrapped items.
|
|
72
|
-
|
|
73
|
-
Args:
|
|
74
|
-
lines (list[str]): The lines to check.
|
|
75
|
-
|
|
76
|
-
Returns:
|
|
77
|
-
result (bool): True if an ordered list is detected, False otherwise.
|
|
78
|
-
"""
|
|
79
|
-
non_empty_lines = [line for line in lines if line.strip()]
|
|
80
|
-
if not non_empty_lines:
|
|
81
|
-
return False
|
|
82
|
-
|
|
83
|
-
base_indent = min(len(line) - len(line.lstrip()) for line in non_empty_lines)
|
|
84
|
-
pattern = re.compile(r"^\s*(\d+)[.)]\s+")
|
|
85
|
-
expected = 1
|
|
86
|
-
count = 0
|
|
87
|
-
|
|
88
|
-
for line in lines:
|
|
89
|
-
if not line.strip():
|
|
90
|
-
continue
|
|
91
|
-
|
|
92
|
-
if len(line) - len(line.lstrip()) == base_indent:
|
|
93
|
-
match = pattern.match(line)
|
|
94
|
-
if match and int(match.group(1)) == expected:
|
|
95
|
-
count += 1
|
|
96
|
-
expected += 1
|
|
97
|
-
if count >= 2:
|
|
98
|
-
return True
|
|
99
|
-
else:
|
|
100
|
-
expected = 1
|
|
101
|
-
count = 0
|
|
102
|
-
|
|
103
|
-
return False
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def is_list(text: str) -> bool:
|
|
107
|
-
"""Returns True if the text block contains an unordered or ordered list.
|
|
108
|
-
|
|
109
|
-
Args:
|
|
110
|
-
text (str): The text block to check.
|
|
111
|
-
|
|
112
|
-
Returns:
|
|
113
|
-
result (bool): True if a list is detected, False otherwise.
|
|
114
|
-
"""
|
|
115
|
-
lines = text.split("\n")
|
|
116
|
-
result = _is_unordered_list(lines=lines) or _is_ordered_list(lines=lines)
|
|
117
|
-
|
|
118
|
-
return result
|
|
119
|
-
|
|
120
|
-
|
|
121
36
|
def format_list(text: str, wrap_width: int, line_separator: str) -> str:
|
|
122
37
|
"""Formats a list block, preserving each item on its own line.
|
|
123
38
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Utility module with functions for list detection in docstrings."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _is_unordered_list(lines: list[str]) -> bool:
|
|
7
|
+
"""Returns True if at least two consecutive list items starting with an unordered marker are
|
|
8
|
+
found, ignoring continuation lines from wrapped items.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
lines (list[str]): The lines to check.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
result (bool): True if an unordered list is detected, False otherwise.
|
|
15
|
+
"""
|
|
16
|
+
non_empty_lines = [line for line in lines if line.strip()]
|
|
17
|
+
if not non_empty_lines:
|
|
18
|
+
return False
|
|
19
|
+
|
|
20
|
+
base_indent = min(len(line) - len(line.lstrip()) for line in non_empty_lines)
|
|
21
|
+
pattern = re.compile(r"^\s*[-*+]\s+")
|
|
22
|
+
count = 0
|
|
23
|
+
|
|
24
|
+
for line in lines:
|
|
25
|
+
if not line.strip():
|
|
26
|
+
continue
|
|
27
|
+
|
|
28
|
+
if len(line) - len(line.lstrip()) == base_indent:
|
|
29
|
+
if pattern.match(line):
|
|
30
|
+
count += 1
|
|
31
|
+
if count >= 2:
|
|
32
|
+
return True
|
|
33
|
+
else:
|
|
34
|
+
count = 0
|
|
35
|
+
|
|
36
|
+
return False
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _is_ordered_list(lines: list[str]) -> bool:
|
|
40
|
+
"""Returns True if at least two consecutive sequentially numbered list items are found, ignoring
|
|
41
|
+
continuation lines from wrapped items.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
lines (list[str]): The lines to check.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
result (bool): True if an ordered list is detected, False otherwise.
|
|
48
|
+
"""
|
|
49
|
+
non_empty_lines = [line for line in lines if line.strip()]
|
|
50
|
+
if not non_empty_lines:
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
base_indent = min(len(line) - len(line.lstrip()) for line in non_empty_lines)
|
|
54
|
+
pattern = re.compile(r"^\s*(\d+)[.)]\s+")
|
|
55
|
+
expected = 1
|
|
56
|
+
count = 0
|
|
57
|
+
|
|
58
|
+
for line in lines:
|
|
59
|
+
if not line.strip():
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
if len(line) - len(line.lstrip()) == base_indent:
|
|
63
|
+
match = pattern.match(line)
|
|
64
|
+
if match and int(match.group(1)) == expected:
|
|
65
|
+
count += 1
|
|
66
|
+
expected += 1
|
|
67
|
+
if count >= 2:
|
|
68
|
+
return True
|
|
69
|
+
else:
|
|
70
|
+
expected = 1
|
|
71
|
+
count = 0
|
|
72
|
+
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def is_list(text: str) -> bool:
|
|
77
|
+
"""Returns True if the text block contains an unordered or ordered list.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
text (str): The text block to check.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
result (bool): True if a list is detected, False otherwise.
|
|
84
|
+
"""
|
|
85
|
+
lines = text.split("\n")
|
|
86
|
+
result = _is_unordered_list(lines=lines) or _is_ordered_list(lines=lines)
|
|
87
|
+
|
|
88
|
+
return result
|
|
@@ -4,9 +4,9 @@ import libcst as cst
|
|
|
4
4
|
import pytest
|
|
5
5
|
|
|
6
6
|
from docstring_tailor.docstring_visitor import DocstringVisitor
|
|
7
|
+
from docstring_tailor.utils.utils_testing import generate_case_ids, read_fixture
|
|
7
8
|
from tests.cases.config_model import Case
|
|
8
9
|
from tests.cases.formatting_cases import CASES
|
|
9
|
-
from tests.utils_test import generate_case_ids, read_fixture
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
@pytest.mark.parametrize("case", CASES, ids=generate_case_ids)
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
"""Module containing various utility functions."""
|
|
2
|
-
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
|
|
5
|
-
import typer
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def load_config() -> dict:
|
|
9
|
-
"""Loads configuration from docstring_tailor.toml or pyproject.toml.
|
|
10
|
-
|
|
11
|
-
Walks up from the current directory. docstring_tailor.toml takes priority over pyproject.toml if
|
|
12
|
-
both exist at the same level. Stops at the first file found containing docstring_tailor
|
|
13
|
-
configuration.
|
|
14
|
-
|
|
15
|
-
Returns:
|
|
16
|
-
config (dict): Configuration settings, or an empty dict if none found.
|
|
17
|
-
"""
|
|
18
|
-
import tomllib
|
|
19
|
-
|
|
20
|
-
for directory in [Path.cwd(), *Path.cwd().parents]:
|
|
21
|
-
tailor_config = directory / "docstring_tailor.toml"
|
|
22
|
-
if tailor_config.exists():
|
|
23
|
-
with open(tailor_config, "rb") as file:
|
|
24
|
-
return tomllib.load(file)
|
|
25
|
-
|
|
26
|
-
pyproject = directory / "pyproject.toml"
|
|
27
|
-
if pyproject.exists():
|
|
28
|
-
with open(pyproject, "rb") as file:
|
|
29
|
-
data = tomllib.load(file)
|
|
30
|
-
tool_config = data.get("tool", {}).get("docstring_tailor", {})
|
|
31
|
-
if tool_config:
|
|
32
|
-
return tool_config
|
|
33
|
-
|
|
34
|
-
return {}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def collect_python_files(paths: list[Path]) -> list[Path]:
|
|
38
|
-
"""Collects all Python files from a list of file and/or directory paths.
|
|
39
|
-
|
|
40
|
-
Directories are searched recursively. Files are included directly.
|
|
41
|
-
|
|
42
|
-
Args:
|
|
43
|
-
paths (list[Path]): A list of file and/or directory paths to search.
|
|
44
|
-
|
|
45
|
-
Returns:
|
|
46
|
-
python_files (list[Path]): A flat list of all collected .py file paths.
|
|
47
|
-
"""
|
|
48
|
-
python_files: list[Path] = []
|
|
49
|
-
for path in paths:
|
|
50
|
-
if path.is_dir():
|
|
51
|
-
python_files.extend(path.rglob("*.py"))
|
|
52
|
-
else:
|
|
53
|
-
python_files.append(path)
|
|
54
|
-
|
|
55
|
-
return python_files
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def validate_paths(paths: list[Path]) -> None:
|
|
59
|
-
"""Validates that all provided paths exist on the filesystem.
|
|
60
|
-
|
|
61
|
-
Args:
|
|
62
|
-
paths (list[Path]): A list of file and/or directory paths to validate.
|
|
63
|
-
|
|
64
|
-
Raises:
|
|
65
|
-
typer.Exit: If any path does not exist.
|
|
66
|
-
"""
|
|
67
|
-
for path in paths:
|
|
68
|
-
if not path.exists():
|
|
69
|
-
typer.echo(f"Error: path '{path}' does not exist.")
|
|
70
|
-
raise typer.Exit(code=1)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/src/docstring_tailor/docstring_visitor.py
RENAMED
|
File without changes
|
{docstring_tailor-0.2.0 → docstring_tailor-0.2.1.dev0}/src/docstring_tailor/utils/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|