docstring-tailor 0.2.0__tar.gz → 0.2.1__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}/PKG-INFO +48 -30
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/README.md +47 -29
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/pyproject.toml +2 -2
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/src/docstring_tailor/constants.py +4 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/src/docstring_tailor/main.py +55 -28
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/src/docstring_tailor/multi_line_docstring_formatter.py +3 -8
- docstring_tailor-0.2.1/src/docstring_tailor/utils/utils_cli.py +55 -0
- docstring_tailor-0.2.1/src/docstring_tailor/utils/utils_file_system.py +117 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/src/docstring_tailor/utils/utils_formatting.py +0 -85
- docstring_tailor-0.2.1/src/docstring_tailor/utils/utils_list_detection.py +88 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/test_formatting.py +1 -1
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/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}/.gitignore +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/LICENSE +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/makefile +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/src/docstring_tailor/__init__.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/src/docstring_tailor/cli_config.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/src/docstring_tailor/docstring_visitor.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/src/docstring_tailor/utils/__init__.py +0 -0
- /docstring_tailor-0.2.0/tests/utils_test.py → /docstring_tailor-0.2.1/src/docstring_tailor/utils/utils_testing.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/cases/__init__.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/cases/config_model.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/cases/formatting_cases.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/all_docstrings/all_docstrings_100.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/all_docstrings/all_docstrings_60.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/all_docstrings/all_docstrings_80.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/all_docstrings/all_docstrings_too_long.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/all_docstrings/all_docstrings_too_short.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/function_docstring_complex/function_docstring_complex_100.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/function_docstring_complex/function_docstring_complex_60.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/function_docstring_complex/function_docstring_complex_80.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/module_docstring_blank_lines/module_docstring_blank_lines.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/module_docstring_blank_lines/module_docstring_blank_lines_100.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/module_docstring_empty/module_docstring_empty.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/module_docstring_empty/module_docstring_empty_blank_lines.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/module_docstring_example_backticks/module_docstring_example_backticks.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/module_docstring_example_backticks/module_docstring_example_backticks_60.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/module_docstring_example_tildes/module_docstring_example_tildes.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/module_docstring_example_tildes/module_docstring_example_tildes_60.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/module_docstring_ordered_list/module_docstring_ordered_list_100.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/module_docstring_ordered_list/module_docstring_ordered_list_60.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/module_docstring_ordered_list/module_docstring_ordered_list_80.py +0 -0
- {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/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}/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
|
|
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,43 @@ 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 example 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
|
+
<img src="https://github.com/user-attachments/assets/983ae257-472d-465e-9924-9d90bca10f0d" alt="Demo" />
|
|
69
|
+
|
|
70
|
+
If the image does not show, click [here](https://private-user-images.githubusercontent.com/32795119/606010424-983ae257-472d-465e-9924-9d90bca10f0d.gif?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3ODExNzU3ODQsIm5iZiI6MTc4MTE3NTQ4NCwicGF0aCI6Ii8zMjc5NTExOS82MDYwMTA0MjQtOTgzYWUyNTctNDcyZC00NjVlLTk5MjQtOWQ5MGJjYTEwZjBkLmdpZj9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjA2MTElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwNjExVDEwNTgwNFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWMzYjdjYjYyZGJkNDgwZmM3NDFjNjlmMDdiOGI1ZWE0ODA1YTczOTg3NzYwZjkwY2VjYWYwNGJiZDc1NjhkZTMmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JnJlc3BvbnNlLWNvbnRlbnQtdHlwZT1pbWFnZSUyRmdpZiJ9.CgQI0L986Mb1A6rcpn22KzG3mnJBTX5kVtlLGEoVN-w).
|
|
71
|
+
|
|
72
|
+
</details>
|
|
52
73
|
|
|
53
74
|
## Installation
|
|
54
75
|
|
|
@@ -112,55 +133,56 @@ style = "google"
|
|
|
112
133
|
```bash
|
|
113
134
|
uv run docstring_tailor [PATHS ...] [OPTIONS]
|
|
114
135
|
```
|
|
115
|
-
|
|
116
136
|
`PATHS` may contain one or more files and/or directories.
|
|
117
|
-
|
|
118
137
|
Examples:
|
|
119
|
-
|
|
120
138
|
```bash
|
|
121
139
|
uv run docstring_tailor my_file.py
|
|
122
140
|
uv run docstring_tailor src/
|
|
123
141
|
uv run docstring_tailor src/ tests/test_file.py
|
|
124
142
|
```
|
|
125
|
-
|
|
126
143
|
If no paths are provided, `docstring_tailor` will attempt to locate and format files inside the `src` directory.
|
|
127
144
|
|
|
128
|
-
---
|
|
129
|
-
|
|
130
145
|
### Options
|
|
131
146
|
|
|
132
147
|
| <div style="width:140px">Option</div> | <div style="width:50px">Type</div> | <div style="width:80px">Default</div> | Description |
|
|
133
148
|
|---|---|---|---|
|
|
134
|
-
| `--line-length`
|
|
135
|
-
| `--style`
|
|
136
|
-
| `--detect-lists`
|
|
149
|
+
| `--line-length` | `int` | 100 | Maximum number of characters allowed per line after formatting. |
|
|
150
|
+
| `--style` | `str` | google | Docstring style to enforce. Currently only the Google docstring style is supported. |
|
|
151
|
+
| `--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. |
|
|
152
|
+
| `--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. |
|
|
153
|
+
| `--diff` | flag | — | Print a unified diff of changes to stdout instead of modifying files. No files are written when this flag is set. |
|
|
154
|
+
| `--version`, `-V` | flag | — | Print the installed version and exit. |
|
|
155
|
+
| `--help` | flag | — | Show the help message and exit. |
|
|
137
156
|
|
|
138
157
|
### Examples
|
|
139
158
|
|
|
140
159
|
`CLI`
|
|
141
|
-
|
|
142
160
|
```bash
|
|
143
161
|
uv run docstring_tailor src/ --line-length 88
|
|
144
162
|
uv run docstring_tailor my_file.py --style google
|
|
145
163
|
uv run docstring_tailor --detect-lists
|
|
146
164
|
uv run docstring_tailor --no-detect-lists
|
|
147
|
-
|
|
165
|
+
uv run docstring_tailor src/ --exclude tests --exclude "src/generated/*.py"
|
|
166
|
+
uv run docstring_tailor src/ --diff
|
|
167
|
+
uv run docstring_tailor --version
|
|
168
|
+
uv run docstring_tailor --help
|
|
148
169
|
|
|
170
|
+
```
|
|
149
171
|
`pyproject.toml`
|
|
150
|
-
|
|
151
172
|
```toml
|
|
152
173
|
[tool.docstring_tailor]
|
|
153
174
|
line-length = 88
|
|
154
175
|
style = "google"
|
|
155
176
|
detect-lists = true
|
|
177
|
+
exclude = ["tests", "src/generated/*.py"]
|
|
156
178
|
```
|
|
157
179
|
|
|
158
180
|
`docstring_tailor.toml`
|
|
159
|
-
|
|
160
181
|
```toml
|
|
161
182
|
line-length = 88
|
|
162
183
|
style = "google"
|
|
163
184
|
detect-lists = true
|
|
185
|
+
exclude = ["tests", "src/generated/*.py"]
|
|
164
186
|
```
|
|
165
187
|
|
|
166
188
|
## Example docstrings
|
|
@@ -315,8 +337,7 @@ def example_generator(n):
|
|
|
315
337
|
```
|
|
316
338
|
- Similar to `Returns`, `Yields` is also supported.
|
|
317
339
|
|
|
318
|
-
```
|
|
319
|
-
|
|
340
|
+
```python
|
|
320
341
|
"""Demonstrates a Google-style module docstring containing a Note
|
|
321
342
|
section.
|
|
322
343
|
|
|
@@ -378,24 +399,21 @@ Steps:
|
|
|
378
399
|
|
|
379
400
|
- 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
401
|
|
|
381
|
-
|
|
382
402
|
## Release Notes
|
|
383
403
|
|
|
384
404
|
| <div style="width:70px">Version</div> | <div style="width:100px">Release date</div> | <div style="width:130px">Type</div> | Details |
|
|
385
405
|
|---|---|---|---|
|
|
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
|
|
406
|
+
| `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
407
|
| `0.1.1` | 2026-05-31 | Documentation update | Updated the `README.md` file with the 'Installation' and 'Quick Start' section. |
|
|
388
408
|
| `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> |
|
|
409
|
+
| `0.2.1` | 2026-06-11 | 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
410
|
|
|
390
411
|
## Roadmap
|
|
391
412
|
|
|
392
413
|
### Must have
|
|
393
414
|
|
|
394
415
|
- 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
416
|
- 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
417
|
|
|
400
418
|
### Nice to have
|
|
401
419
|
|
|
@@ -404,4 +422,4 @@ Steps:
|
|
|
404
422
|
|
|
405
423
|
### Maybe later
|
|
406
424
|
|
|
407
|
-
- Parameter that allows the user to format module, class and function docstrings independently.
|
|
425
|
+
- Parameter that allows the user to format module, class and function docstrings independently.
|
|
@@ -1,21 +1,42 @@
|
|
|
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 example 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
|
+
<img src="https://github.com/user-attachments/assets/983ae257-472d-465e-9924-9d90bca10f0d" alt="Demo" />
|
|
36
|
+
|
|
37
|
+
If the image does not show, click [here](https://private-user-images.githubusercontent.com/32795119/606010424-983ae257-472d-465e-9924-9d90bca10f0d.gif?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3ODExNzU3ODQsIm5iZiI6MTc4MTE3NTQ4NCwicGF0aCI6Ii8zMjc5NTExOS82MDYwMTA0MjQtOTgzYWUyNTctNDcyZC00NjVlLTk5MjQtOWQ5MGJjYTEwZjBkLmdpZj9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjA2MTElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwNjExVDEwNTgwNFomWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPWMzYjdjYjYyZGJkNDgwZmM3NDFjNjlmMDdiOGI1ZWE0ODA1YTczOTg3NzYwZjkwY2VjYWYwNGJiZDc1NjhkZTMmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JnJlc3BvbnNlLWNvbnRlbnQtdHlwZT1pbWFnZSUyRmdpZiJ9.CgQI0L986Mb1A6rcpn22KzG3mnJBTX5kVtlLGEoVN-w).
|
|
38
|
+
|
|
39
|
+
</details>
|
|
19
40
|
|
|
20
41
|
## Installation
|
|
21
42
|
|
|
@@ -79,55 +100,56 @@ style = "google"
|
|
|
79
100
|
```bash
|
|
80
101
|
uv run docstring_tailor [PATHS ...] [OPTIONS]
|
|
81
102
|
```
|
|
82
|
-
|
|
83
103
|
`PATHS` may contain one or more files and/or directories.
|
|
84
|
-
|
|
85
104
|
Examples:
|
|
86
|
-
|
|
87
105
|
```bash
|
|
88
106
|
uv run docstring_tailor my_file.py
|
|
89
107
|
uv run docstring_tailor src/
|
|
90
108
|
uv run docstring_tailor src/ tests/test_file.py
|
|
91
109
|
```
|
|
92
|
-
|
|
93
110
|
If no paths are provided, `docstring_tailor` will attempt to locate and format files inside the `src` directory.
|
|
94
111
|
|
|
95
|
-
---
|
|
96
|
-
|
|
97
112
|
### Options
|
|
98
113
|
|
|
99
114
|
| <div style="width:140px">Option</div> | <div style="width:50px">Type</div> | <div style="width:80px">Default</div> | Description |
|
|
100
115
|
|---|---|---|---|
|
|
101
|
-
| `--line-length`
|
|
102
|
-
| `--style`
|
|
103
|
-
| `--detect-lists`
|
|
116
|
+
| `--line-length` | `int` | 100 | Maximum number of characters allowed per line after formatting. |
|
|
117
|
+
| `--style` | `str` | google | Docstring style to enforce. Currently only the Google docstring style is supported. |
|
|
118
|
+
| `--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. |
|
|
119
|
+
| `--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. |
|
|
120
|
+
| `--diff` | flag | — | Print a unified diff of changes to stdout instead of modifying files. No files are written when this flag is set. |
|
|
121
|
+
| `--version`, `-V` | flag | — | Print the installed version and exit. |
|
|
122
|
+
| `--help` | flag | — | Show the help message and exit. |
|
|
104
123
|
|
|
105
124
|
### Examples
|
|
106
125
|
|
|
107
126
|
`CLI`
|
|
108
|
-
|
|
109
127
|
```bash
|
|
110
128
|
uv run docstring_tailor src/ --line-length 88
|
|
111
129
|
uv run docstring_tailor my_file.py --style google
|
|
112
130
|
uv run docstring_tailor --detect-lists
|
|
113
131
|
uv run docstring_tailor --no-detect-lists
|
|
114
|
-
|
|
132
|
+
uv run docstring_tailor src/ --exclude tests --exclude "src/generated/*.py"
|
|
133
|
+
uv run docstring_tailor src/ --diff
|
|
134
|
+
uv run docstring_tailor --version
|
|
135
|
+
uv run docstring_tailor --help
|
|
115
136
|
|
|
137
|
+
```
|
|
116
138
|
`pyproject.toml`
|
|
117
|
-
|
|
118
139
|
```toml
|
|
119
140
|
[tool.docstring_tailor]
|
|
120
141
|
line-length = 88
|
|
121
142
|
style = "google"
|
|
122
143
|
detect-lists = true
|
|
144
|
+
exclude = ["tests", "src/generated/*.py"]
|
|
123
145
|
```
|
|
124
146
|
|
|
125
147
|
`docstring_tailor.toml`
|
|
126
|
-
|
|
127
148
|
```toml
|
|
128
149
|
line-length = 88
|
|
129
150
|
style = "google"
|
|
130
151
|
detect-lists = true
|
|
152
|
+
exclude = ["tests", "src/generated/*.py"]
|
|
131
153
|
```
|
|
132
154
|
|
|
133
155
|
## Example docstrings
|
|
@@ -282,8 +304,7 @@ def example_generator(n):
|
|
|
282
304
|
```
|
|
283
305
|
- Similar to `Returns`, `Yields` is also supported.
|
|
284
306
|
|
|
285
|
-
```
|
|
286
|
-
|
|
307
|
+
```python
|
|
287
308
|
"""Demonstrates a Google-style module docstring containing a Note
|
|
288
309
|
section.
|
|
289
310
|
|
|
@@ -345,24 +366,21 @@ Steps:
|
|
|
345
366
|
|
|
346
367
|
- 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
368
|
|
|
348
|
-
|
|
349
369
|
## Release Notes
|
|
350
370
|
|
|
351
371
|
| <div style="width:70px">Version</div> | <div style="width:100px">Release date</div> | <div style="width:130px">Type</div> | Details |
|
|
352
372
|
|---|---|---|---|
|
|
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
|
|
373
|
+
| `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
374
|
| `0.1.1` | 2026-05-31 | Documentation update | Updated the `README.md` file with the 'Installation' and 'Quick Start' section. |
|
|
355
375
|
| `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> |
|
|
376
|
+
| `0.2.1` | 2026-06-11 | 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
377
|
|
|
357
378
|
## Roadmap
|
|
358
379
|
|
|
359
380
|
### Must have
|
|
360
381
|
|
|
361
382
|
- 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
383
|
- 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
384
|
|
|
367
385
|
### Nice to have
|
|
368
386
|
|
|
@@ -371,4 +389,4 @@ Steps:
|
|
|
371
389
|
|
|
372
390
|
### Maybe later
|
|
373
391
|
|
|
374
|
-
- Parameter that allows the user to format module, class and function docstrings independently.
|
|
392
|
+
- Parameter that allows the user to format module, class and function docstrings independently.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "docstring-tailor"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.1"
|
|
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" }
|
|
@@ -43,4 +43,4 @@ build-backend = "hatchling.build"
|
|
|
43
43
|
packages = ["src/docstring_tailor"]
|
|
44
44
|
|
|
45
45
|
[project.scripts]
|
|
46
|
-
docstring_tailor = "docstring_tailor.main:app"
|
|
46
|
+
docstring_tailor = "docstring_tailor.main:app"
|
|
@@ -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)
|
{docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/src/docstring_tailor/utils/utils_formatting.py
RENAMED
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
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}/tests/fixtures/all_docstrings/all_docstrings_60.py
RENAMED
|
File without changes
|
{docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/all_docstrings/all_docstrings_80.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
|
{docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/readme_examples/readme_examples.py
RENAMED
|
File without changes
|