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.
Files changed (45) hide show
  1. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/PKG-INFO +48 -30
  2. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/README.md +47 -29
  3. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/pyproject.toml +2 -2
  4. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/src/docstring_tailor/constants.py +4 -0
  5. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/src/docstring_tailor/main.py +55 -28
  6. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/src/docstring_tailor/multi_line_docstring_formatter.py +3 -8
  7. docstring_tailor-0.2.1/src/docstring_tailor/utils/utils_cli.py +55 -0
  8. docstring_tailor-0.2.1/src/docstring_tailor/utils/utils_file_system.py +117 -0
  9. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/src/docstring_tailor/utils/utils_formatting.py +0 -85
  10. docstring_tailor-0.2.1/src/docstring_tailor/utils/utils_list_detection.py +88 -0
  11. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/test_formatting.py +1 -1
  12. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/uv.lock +1 -1
  13. docstring_tailor-0.2.0/src/docstring_tailor/utils/utils_file_system.py +0 -70
  14. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/.gitignore +0 -0
  15. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/LICENSE +0 -0
  16. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/makefile +0 -0
  17. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/src/docstring_tailor/__init__.py +0 -0
  18. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/src/docstring_tailor/cli_config.py +0 -0
  19. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/src/docstring_tailor/docstring_visitor.py +0 -0
  20. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/src/docstring_tailor/utils/__init__.py +0 -0
  21. /docstring_tailor-0.2.0/tests/utils_test.py → /docstring_tailor-0.2.1/src/docstring_tailor/utils/utils_testing.py +0 -0
  22. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/cases/__init__.py +0 -0
  23. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/cases/config_model.py +0 -0
  24. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/cases/formatting_cases.py +0 -0
  25. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/all_docstrings/all_docstrings_100.py +0 -0
  26. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/all_docstrings/all_docstrings_60.py +0 -0
  27. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/all_docstrings/all_docstrings_80.py +0 -0
  28. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/all_docstrings/all_docstrings_too_long.py +0 -0
  29. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/all_docstrings/all_docstrings_too_short.py +0 -0
  30. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/function_docstring_complex/function_docstring_complex_100.py +0 -0
  31. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/function_docstring_complex/function_docstring_complex_60.py +0 -0
  32. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/function_docstring_complex/function_docstring_complex_80.py +0 -0
  33. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/module_docstring_blank_lines/module_docstring_blank_lines.py +0 -0
  34. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/module_docstring_blank_lines/module_docstring_blank_lines_100.py +0 -0
  35. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/module_docstring_empty/module_docstring_empty.py +0 -0
  36. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/module_docstring_empty/module_docstring_empty_blank_lines.py +0 -0
  37. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/module_docstring_example_backticks/module_docstring_example_backticks.py +0 -0
  38. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/module_docstring_example_backticks/module_docstring_example_backticks_60.py +0 -0
  39. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/module_docstring_example_tildes/module_docstring_example_tildes.py +0 -0
  40. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/module_docstring_example_tildes/module_docstring_example_tildes_60.py +0 -0
  41. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/module_docstring_ordered_list/module_docstring_ordered_list_100.py +0 -0
  42. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/module_docstring_ordered_list/module_docstring_ordered_list_60.py +0 -0
  43. {docstring_tailor-0.2.0 → docstring_tailor-0.2.1}/tests/fixtures/module_docstring_ordered_list/module_docstring_ordered_list_80.py +0 -0
  44. {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
  45. {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.0
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 chacacters per line.
36
+ Automatic formatting of Python docstrings according to PEP 257 and a predefined maximum number of characters per line.
37
37
 
38
38
  [![PyPI Version](https://img.shields.io/pypi/v/docstring-tailor?color=lightblue)](https://pypi.org/project/docstring-tailor/)
39
- [![License](https://img.shields.io/pypi/l/docstring-tailor)](https://pypi.org/project/docstring-tailor/)
39
+ [![License](https://img.shields.io/pypi/l/docstring-tailor?color=lightblue)](https://pypi.org/project/docstring-tailor/)
40
40
  [![Wheel](https://img.shields.io/pypi/wheel/docstring-tailor?color=lightblue)](https://pypi.org/project/docstring-tailor/)
41
+ [![Downloads](https://img.shields.io/pypi/dm/docstring-tailor?color=lightblue&cache_bust=1)](https://pypi.org/project/docstring-tailor/)
42
+
41
43
 
42
44
  ## Table of Contents
43
- 1. [Installation](#Installation)
44
- 2. [Quick start](#quick_start)
45
- 3. [API Overview](#api-overview)
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
- 4. [Example docstrings](#example-docstrings)
50
- 5. [Release Notes](#release_notes)
51
- 6. [Roadmap](#roadmap)
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` | `int` | 100 | Maximum number of characters allowed per line after formatting. |
135
- | `--style` | `str` | google | Docstring style to enforce. Currently only the Google docstring style is supported. |
136
- | `--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. |
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
- ```pythonUnordered and numbered lists
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 docstring, 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> |
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 chacacters per line.
3
+ Automatic formatting of Python docstrings according to PEP 257 and a predefined maximum number of characters per line.
4
4
 
5
5
  [![PyPI Version](https://img.shields.io/pypi/v/docstring-tailor?color=lightblue)](https://pypi.org/project/docstring-tailor/)
6
- [![License](https://img.shields.io/pypi/l/docstring-tailor)](https://pypi.org/project/docstring-tailor/)
6
+ [![License](https://img.shields.io/pypi/l/docstring-tailor?color=lightblue)](https://pypi.org/project/docstring-tailor/)
7
7
  [![Wheel](https://img.shields.io/pypi/wheel/docstring-tailor?color=lightblue)](https://pypi.org/project/docstring-tailor/)
8
+ [![Downloads](https://img.shields.io/pypi/dm/docstring-tailor?color=lightblue&cache_bust=1)](https://pypi.org/project/docstring-tailor/)
9
+
8
10
 
9
11
  ## Table of Contents
10
- 1. [Installation](#Installation)
11
- 2. [Quick start](#quick_start)
12
- 3. [API Overview](#api-overview)
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
- 4. [Example docstrings](#example-docstrings)
17
- 5. [Release Notes](#release_notes)
18
- 6. [Roadmap](#roadmap)
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` | `int` | 100 | Maximum number of characters allowed per line after formatting. |
102
- | `--style` | `str` | google | Docstring style to enforce. Currently only the Google docstring style is supported. |
103
- | `--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. |
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
- ```pythonUnordered and numbered lists
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 docstring, 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> |
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.0"
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(paths=resolved_paths)
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 __name__ == "__main__":
104
- app()
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
- - Currently, this code has been written specifically for the 'Google' docstring format. Fine for
112
- now, but the end state goal is to have the functionality that the user can specify the style in the
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
- format_list,
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)
@@ -119,7 +119,7 @@ toml = [
119
119
 
120
120
  [[package]]
121
121
  name = "docstring-tailor"
122
- version = "0.2.0"
122
+ version = "0.2.1"
123
123
  source = { editable = "." }
124
124
  dependencies = [
125
125
  { name = "libcst" },
@@ -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)