docstring-generator-ext 2.0.2__tar.gz → 2.0.3__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 (27) hide show
  1. {docstring_generator_ext-2.0.2 → docstring_generator_ext-2.0.3}/PKG-INFO +84 -3
  2. {docstring_generator_ext-2.0.2 → docstring_generator_ext-2.0.3}/README.md +83 -2
  3. {docstring_generator_ext-2.0.2 → docstring_generator_ext-2.0.3}/docstring_generator_ext.egg-info/PKG-INFO +84 -3
  4. {docstring_generator_ext-2.0.2 → docstring_generator_ext-2.0.3}/pyproject.toml +1 -1
  5. {docstring_generator_ext-2.0.2 → docstring_generator_ext-2.0.3}/src/FunctionFormat.cpp +64 -0
  6. {docstring_generator_ext-2.0.2 → docstring_generator_ext-2.0.3}/src/FunctionFormat.hpp +13 -0
  7. {docstring_generator_ext-2.0.2 → docstring_generator_ext-2.0.3}/src/GoogleDocstring.cpp +20 -14
  8. {docstring_generator_ext-2.0.2 → docstring_generator_ext-2.0.3}/src/GoogleDocstring.hpp +1 -0
  9. {docstring_generator_ext-2.0.2 → docstring_generator_ext-2.0.3}/src/IDocstringFormat.cpp +0 -1
  10. {docstring_generator_ext-2.0.2 → docstring_generator_ext-2.0.3}/src/docstringFormat.cpp +66 -29
  11. {docstring_generator_ext-2.0.2 → docstring_generator_ext-2.0.3}/src/docstringFormat.hpp +11 -2
  12. {docstring_generator_ext-2.0.2 → docstring_generator_ext-2.0.3}/src/parser.cpp +22 -1
  13. {docstring_generator_ext-2.0.2 → docstring_generator_ext-2.0.3}/src/parser.hpp +1 -0
  14. {docstring_generator_ext-2.0.2 → docstring_generator_ext-2.0.3}/MANIFEST.in +0 -0
  15. {docstring_generator_ext-2.0.2 → docstring_generator_ext-2.0.3}/docstring_generator_ext.egg-info/SOURCES.txt +0 -0
  16. {docstring_generator_ext-2.0.2 → docstring_generator_ext-2.0.3}/docstring_generator_ext.egg-info/dependency_links.txt +0 -0
  17. {docstring_generator_ext-2.0.2 → docstring_generator_ext-2.0.3}/docstring_generator_ext.egg-info/requires.txt +0 -0
  18. {docstring_generator_ext-2.0.2 → docstring_generator_ext-2.0.3}/docstring_generator_ext.egg-info/top_level.txt +0 -0
  19. {docstring_generator_ext-2.0.2 → docstring_generator_ext-2.0.3}/setup.cfg +0 -0
  20. {docstring_generator_ext-2.0.2 → docstring_generator_ext-2.0.3}/setup.py +0 -0
  21. {docstring_generator_ext-2.0.2 → docstring_generator_ext-2.0.3}/src/IDocstringFormat.hpp +0 -0
  22. {docstring_generator_ext-2.0.2 → docstring_generator_ext-2.0.3}/src/NumpyDocstring.cpp +0 -0
  23. {docstring_generator_ext-2.0.2 → docstring_generator_ext-2.0.3}/src/NumpyDocstring.hpp +0 -0
  24. {docstring_generator_ext-2.0.2 → docstring_generator_ext-2.0.3}/src/ReStructuredDocstring.cpp +0 -0
  25. {docstring_generator_ext-2.0.2 → docstring_generator_ext-2.0.3}/src/ReStructuredDocstring.hpp +0 -0
  26. {docstring_generator_ext-2.0.2 → docstring_generator_ext-2.0.3}/src/docComparator.cpp +0 -0
  27. {docstring_generator_ext-2.0.2 → docstring_generator_ext-2.0.3}/src/docComparator.hpp +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: docstring_generator_ext
3
- Version: 2.0.2
3
+ Version: 2.0.3
4
4
  Summary: Generate Docstrings with type-hint information.
5
5
  Author-email: FelixTheC <fberndt87@gmail.com>
6
6
  Classifier: Environment :: Console
@@ -25,13 +25,20 @@ Requires-Dist: pybind11>=3.0.4
25
25
  ## Features
26
26
 
27
27
  - **Automatic Docstring Injection**: Parses Python files and inserts docstrings for functions and methods.
28
+ - **Async-function support**: Handles both `def` and `async def` functions transparently.
28
29
  - **Type-Hint Awareness**: Extracts type information from annotations and default values.
29
30
  - **Multiple Styles**: Supports popular docstring formats:
30
31
  - **reST** (reStructuredText)
31
32
  - **Google** style
32
33
  - **NumPy** style
34
+ - **Format-style detection**: Automatically detects the style of an existing docstring and refuses to silently mix styles unless `allow_overwrite=True` is passed.
35
+ - **Style conversion**: With `allow_overwrite=True`, converts a docstring written in one style to another in a single call.
36
+ - **Exception detection**: Analyses the function body with Python's `ast` module to identify raised exceptions and include them in the docstring.
37
+ - **Docstring coverage auditing**: `check_docstring()` reports how many functions in a file have complete, partial, or missing docstrings without modifying the file.
33
38
  - **High Performance**: Core logic implemented in C++ for fast processing.
34
- - **Preserves Existing Content**: Can update existing docstrings while trying to preserve manually added descriptions (using a special `$` marker convention).
39
+ - **Preserves Existing Content**: Keeps manually written descriptions across re-runs using special in-docstring markers:
40
+ - `$N` binds the text on that line to the *N*-th function parameter.
41
+ - `>>` provides the return-value description.
35
42
 
36
43
  ## Installation
37
44
 
@@ -81,7 +88,23 @@ style = docstring_generator_ext.DocstringFormatStyle.GOOGLE
81
88
  docstring_generator_ext.parse_file(file_path, style)
82
89
  ```
83
90
 
84
- You can also audit an existing file to see how well its functions are documented, without making any changes:
91
+ ### Overwriting an existing docstring format
92
+
93
+ By default, `parse_file` refuses to overwrite a docstring that was already written in a **different** style than the one you requested, and will print a warning instead. Pass `allow_overwrite=True` to let the extension convert the existing docstring to the new style:
94
+
95
+ ```python
96
+ import docstring_generator_ext
97
+
98
+ file_path = "path/to/your_script.py"
99
+ style = docstring_generator_ext.DocstringFormatStyle.NUMPY
100
+
101
+ # Convert any existing docstring style to NUMPY — previous style will be removed
102
+ docstring_generator_ext.parse_file(file_path, style, allow_overwrite=True)
103
+ ```
104
+
105
+ ### Auditing docstring coverage
106
+
107
+ You can audit an existing file to see how well its functions are documented, without making any changes:
85
108
 
86
109
  ```python
87
110
  import docstring_generator_ext
@@ -115,6 +138,64 @@ The extension provides an enum `DocstringFormatStyle` to choose the desired outp
115
138
  - `docstring_generator_ext.DocstringFormatStyle.GOOGLE`
116
139
  - `docstring_generator_ext.DocstringFormatStyle.NUMPY`
117
140
 
141
+ ### Preserving descriptions with special markers
142
+
143
+ When the extension processes a file that already contains docstrings, it tries to keep manually written descriptions in place. Two marker conventions are supported:
144
+
145
+ #### `$N` — argument description markers
146
+
147
+ Place a `$` followed by the **1-based index** of the parameter inside the docstring to bind a free-form description to that argument. The marker and the text on its line are extracted and attached to the corresponding parameter; the `$N` line is then removed from the generated docstring.
148
+
149
+ ```python
150
+ def add(a: int, b: int) -> int:
151
+ """Add two numbers together.
152
+
153
+ $1 The first operand.
154
+ $2 The second operand.
155
+ """
156
+ return a + b
157
+ ```
158
+
159
+ After the next `parse_file` run the descriptions will be wired to `a` and `b` automatically.
160
+
161
+ ```python
162
+ def add(a: int, b: int) -> int:
163
+ """Add two numbers together.
164
+
165
+ Args:
166
+ a (int): The first operand.
167
+ b (int): The second operand.
168
+ Returns:
169
+ int
170
+ """
171
+ return a + b
172
+ ```
173
+
174
+ #### `>>` — return description marker
175
+
176
+ Place `>>` on its own line inside the docstring to provide the description for the return value. The text after `>>` on that line is extracted as the return description, and the marker line is removed.
177
+
178
+ ```python
179
+ def square(x: int) -> int:
180
+ """Square a number.
181
+
182
+ >> The squared value of x.
183
+ """
184
+ return x * x
185
+ ```
186
+ After the next `parse_file` run the descriptions will be wired to `Returns` description automatically.
187
+ ```python
188
+ def square(x: int) -> int:
189
+ """Square a number.
190
+
191
+ Args:
192
+ x (int):
193
+ Returns:
194
+ int: The squared value of x.
195
+ """
196
+ return x * x
197
+ ```
198
+
118
199
  ## C++20
119
200
 
120
201
  The core of this extension is written in **C++20** to take full advantage of the modern standard's best algorithms and features:
@@ -11,13 +11,20 @@
11
11
  ## Features
12
12
 
13
13
  - **Automatic Docstring Injection**: Parses Python files and inserts docstrings for functions and methods.
14
+ - **Async-function support**: Handles both `def` and `async def` functions transparently.
14
15
  - **Type-Hint Awareness**: Extracts type information from annotations and default values.
15
16
  - **Multiple Styles**: Supports popular docstring formats:
16
17
  - **reST** (reStructuredText)
17
18
  - **Google** style
18
19
  - **NumPy** style
20
+ - **Format-style detection**: Automatically detects the style of an existing docstring and refuses to silently mix styles unless `allow_overwrite=True` is passed.
21
+ - **Style conversion**: With `allow_overwrite=True`, converts a docstring written in one style to another in a single call.
22
+ - **Exception detection**: Analyses the function body with Python's `ast` module to identify raised exceptions and include them in the docstring.
23
+ - **Docstring coverage auditing**: `check_docstring()` reports how many functions in a file have complete, partial, or missing docstrings without modifying the file.
19
24
  - **High Performance**: Core logic implemented in C++ for fast processing.
20
- - **Preserves Existing Content**: Can update existing docstrings while trying to preserve manually added descriptions (using a special `$` marker convention).
25
+ - **Preserves Existing Content**: Keeps manually written descriptions across re-runs using special in-docstring markers:
26
+ - `$N` binds the text on that line to the *N*-th function parameter.
27
+ - `>>` provides the return-value description.
21
28
 
22
29
  ## Installation
23
30
 
@@ -67,7 +74,23 @@ style = docstring_generator_ext.DocstringFormatStyle.GOOGLE
67
74
  docstring_generator_ext.parse_file(file_path, style)
68
75
  ```
69
76
 
70
- You can also audit an existing file to see how well its functions are documented, without making any changes:
77
+ ### Overwriting an existing docstring format
78
+
79
+ By default, `parse_file` refuses to overwrite a docstring that was already written in a **different** style than the one you requested, and will print a warning instead. Pass `allow_overwrite=True` to let the extension convert the existing docstring to the new style:
80
+
81
+ ```python
82
+ import docstring_generator_ext
83
+
84
+ file_path = "path/to/your_script.py"
85
+ style = docstring_generator_ext.DocstringFormatStyle.NUMPY
86
+
87
+ # Convert any existing docstring style to NUMPY — previous style will be removed
88
+ docstring_generator_ext.parse_file(file_path, style, allow_overwrite=True)
89
+ ```
90
+
91
+ ### Auditing docstring coverage
92
+
93
+ You can audit an existing file to see how well its functions are documented, without making any changes:
71
94
 
72
95
  ```python
73
96
  import docstring_generator_ext
@@ -101,6 +124,64 @@ The extension provides an enum `DocstringFormatStyle` to choose the desired outp
101
124
  - `docstring_generator_ext.DocstringFormatStyle.GOOGLE`
102
125
  - `docstring_generator_ext.DocstringFormatStyle.NUMPY`
103
126
 
127
+ ### Preserving descriptions with special markers
128
+
129
+ When the extension processes a file that already contains docstrings, it tries to keep manually written descriptions in place. Two marker conventions are supported:
130
+
131
+ #### `$N` — argument description markers
132
+
133
+ Place a `$` followed by the **1-based index** of the parameter inside the docstring to bind a free-form description to that argument. The marker and the text on its line are extracted and attached to the corresponding parameter; the `$N` line is then removed from the generated docstring.
134
+
135
+ ```python
136
+ def add(a: int, b: int) -> int:
137
+ """Add two numbers together.
138
+
139
+ $1 The first operand.
140
+ $2 The second operand.
141
+ """
142
+ return a + b
143
+ ```
144
+
145
+ After the next `parse_file` run the descriptions will be wired to `a` and `b` automatically.
146
+
147
+ ```python
148
+ def add(a: int, b: int) -> int:
149
+ """Add two numbers together.
150
+
151
+ Args:
152
+ a (int): The first operand.
153
+ b (int): The second operand.
154
+ Returns:
155
+ int
156
+ """
157
+ return a + b
158
+ ```
159
+
160
+ #### `>>` — return description marker
161
+
162
+ Place `>>` on its own line inside the docstring to provide the description for the return value. The text after `>>` on that line is extracted as the return description, and the marker line is removed.
163
+
164
+ ```python
165
+ def square(x: int) -> int:
166
+ """Square a number.
167
+
168
+ >> The squared value of x.
169
+ """
170
+ return x * x
171
+ ```
172
+ After the next `parse_file` run the descriptions will be wired to `Returns` description automatically.
173
+ ```python
174
+ def square(x: int) -> int:
175
+ """Square a number.
176
+
177
+ Args:
178
+ x (int):
179
+ Returns:
180
+ int: The squared value of x.
181
+ """
182
+ return x * x
183
+ ```
184
+
104
185
  ## C++20
105
186
 
106
187
  The core of this extension is written in **C++20** to take full advantage of the modern standard's best algorithms and features:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: docstring_generator_ext
3
- Version: 2.0.2
3
+ Version: 2.0.3
4
4
  Summary: Generate Docstrings with type-hint information.
5
5
  Author-email: FelixTheC <fberndt87@gmail.com>
6
6
  Classifier: Environment :: Console
@@ -25,13 +25,20 @@ Requires-Dist: pybind11>=3.0.4
25
25
  ## Features
26
26
 
27
27
  - **Automatic Docstring Injection**: Parses Python files and inserts docstrings for functions and methods.
28
+ - **Async-function support**: Handles both `def` and `async def` functions transparently.
28
29
  - **Type-Hint Awareness**: Extracts type information from annotations and default values.
29
30
  - **Multiple Styles**: Supports popular docstring formats:
30
31
  - **reST** (reStructuredText)
31
32
  - **Google** style
32
33
  - **NumPy** style
34
+ - **Format-style detection**: Automatically detects the style of an existing docstring and refuses to silently mix styles unless `allow_overwrite=True` is passed.
35
+ - **Style conversion**: With `allow_overwrite=True`, converts a docstring written in one style to another in a single call.
36
+ - **Exception detection**: Analyses the function body with Python's `ast` module to identify raised exceptions and include them in the docstring.
37
+ - **Docstring coverage auditing**: `check_docstring()` reports how many functions in a file have complete, partial, or missing docstrings without modifying the file.
33
38
  - **High Performance**: Core logic implemented in C++ for fast processing.
34
- - **Preserves Existing Content**: Can update existing docstrings while trying to preserve manually added descriptions (using a special `$` marker convention).
39
+ - **Preserves Existing Content**: Keeps manually written descriptions across re-runs using special in-docstring markers:
40
+ - `$N` binds the text on that line to the *N*-th function parameter.
41
+ - `>>` provides the return-value description.
35
42
 
36
43
  ## Installation
37
44
 
@@ -81,7 +88,23 @@ style = docstring_generator_ext.DocstringFormatStyle.GOOGLE
81
88
  docstring_generator_ext.parse_file(file_path, style)
82
89
  ```
83
90
 
84
- You can also audit an existing file to see how well its functions are documented, without making any changes:
91
+ ### Overwriting an existing docstring format
92
+
93
+ By default, `parse_file` refuses to overwrite a docstring that was already written in a **different** style than the one you requested, and will print a warning instead. Pass `allow_overwrite=True` to let the extension convert the existing docstring to the new style:
94
+
95
+ ```python
96
+ import docstring_generator_ext
97
+
98
+ file_path = "path/to/your_script.py"
99
+ style = docstring_generator_ext.DocstringFormatStyle.NUMPY
100
+
101
+ # Convert any existing docstring style to NUMPY — previous style will be removed
102
+ docstring_generator_ext.parse_file(file_path, style, allow_overwrite=True)
103
+ ```
104
+
105
+ ### Auditing docstring coverage
106
+
107
+ You can audit an existing file to see how well its functions are documented, without making any changes:
85
108
 
86
109
  ```python
87
110
  import docstring_generator_ext
@@ -115,6 +138,64 @@ The extension provides an enum `DocstringFormatStyle` to choose the desired outp
115
138
  - `docstring_generator_ext.DocstringFormatStyle.GOOGLE`
116
139
  - `docstring_generator_ext.DocstringFormatStyle.NUMPY`
117
140
 
141
+ ### Preserving descriptions with special markers
142
+
143
+ When the extension processes a file that already contains docstrings, it tries to keep manually written descriptions in place. Two marker conventions are supported:
144
+
145
+ #### `$N` — argument description markers
146
+
147
+ Place a `$` followed by the **1-based index** of the parameter inside the docstring to bind a free-form description to that argument. The marker and the text on its line are extracted and attached to the corresponding parameter; the `$N` line is then removed from the generated docstring.
148
+
149
+ ```python
150
+ def add(a: int, b: int) -> int:
151
+ """Add two numbers together.
152
+
153
+ $1 The first operand.
154
+ $2 The second operand.
155
+ """
156
+ return a + b
157
+ ```
158
+
159
+ After the next `parse_file` run the descriptions will be wired to `a` and `b` automatically.
160
+
161
+ ```python
162
+ def add(a: int, b: int) -> int:
163
+ """Add two numbers together.
164
+
165
+ Args:
166
+ a (int): The first operand.
167
+ b (int): The second operand.
168
+ Returns:
169
+ int
170
+ """
171
+ return a + b
172
+ ```
173
+
174
+ #### `>>` — return description marker
175
+
176
+ Place `>>` on its own line inside the docstring to provide the description for the return value. The text after `>>` on that line is extracted as the return description, and the marker line is removed.
177
+
178
+ ```python
179
+ def square(x: int) -> int:
180
+ """Square a number.
181
+
182
+ >> The squared value of x.
183
+ """
184
+ return x * x
185
+ ```
186
+ After the next `parse_file` run the descriptions will be wired to `Returns` description automatically.
187
+ ```python
188
+ def square(x: int) -> int:
189
+ """Square a number.
190
+
191
+ Args:
192
+ x (int):
193
+ Returns:
194
+ int: The squared value of x.
195
+ """
196
+ return x * x
197
+ ```
198
+
118
199
  ## C++20
119
200
 
120
201
  The core of this extension is written in **C++20** to take full advantage of the modern standard's best algorithms and features:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "docstring_generator_ext"
3
- version = "2.0.2"
3
+ version = "2.0.3"
4
4
  description = "Generate Docstrings with type-hint information."
5
5
  authors = [
6
6
  { name = "FelixTheC", email = "fberndt87@gmail.com" },
@@ -152,6 +152,70 @@ void FunctionInfo::update_descriptions(const DocstringFormatStyle &formatStyle)
152
152
  }
153
153
  }
154
154
 
155
+ void FunctionDocstring::detect_format_style() noexcept {
156
+ bool could_be_numpy = false;
157
+ bool could_be_google = false;
158
+ bool could_be_rest = false;
159
+
160
+ could_be_google = std::ranges::any_of(TypicalFormatStyle[1].second, [this](const auto &part) {
161
+ return docstring.find(part) != std::string::npos;
162
+ });
163
+
164
+ // Numpy uses `Returns` as the keyword for the return value description and Google uses `Returns:` as the keyword for the return value description
165
+ // so if it's not google, it could be numpy
166
+ if (!could_be_google)
167
+ could_be_numpy = std::ranges::any_of(TypicalFormatStyle[0].second, [this](const auto &part) {
168
+ return docstring.find(part) != std::string::npos;
169
+ });
170
+
171
+ could_be_rest = std::ranges::any_of(TypicalFormatStyle[2].second, [this](const auto &part) {
172
+ return docstring.find(part) != std::string::npos;
173
+ });
174
+
175
+ const int total = could_be_numpy + could_be_google + could_be_rest;
176
+
177
+ // this means we have multiple format styles in the docstring which should be avoided
178
+ // so we should not detect the format style
179
+ if (total > 1) {
180
+ detected_format_style = std::nullopt;
181
+ return;
182
+ }
183
+
184
+ if (could_be_numpy) {
185
+ detected_format_style = DocstringFormatStyle::NUMPY;
186
+ } else if (could_be_google) {
187
+ detected_format_style = DocstringFormatStyle::GOOGLE;
188
+ } else if (could_be_rest) {
189
+ detected_format_style = DocstringFormatStyle::reST;
190
+ }
191
+ }
192
+
193
+ [[nodiscard]] int FunctionDocstring::remove_old_format_style() noexcept {
194
+ if (!detected_format_style.has_value())
195
+ return -1;
196
+
197
+ const auto get_end_pos = [](std::string& _docstring, StyleSections sections) {
198
+ std::vector<size_t> res;
199
+ std::ranges::for_each(sections, [&](const auto& line) {
200
+ res.emplace_back(_docstring.find(line));
201
+ });
202
+ std::ranges::sort(res, std::less<>());
203
+ return res[0];
204
+ };
205
+
206
+ switch (*detected_format_style)
207
+ {
208
+ case DocstringFormatStyle::NUMPY:
209
+ return get_end_pos(docstring, TypicalFormatStyle[0].second);
210
+ case DocstringFormatStyle::GOOGLE:
211
+ return get_end_pos(docstring, TypicalFormatStyle[1].second);
212
+ case DocstringFormatStyle::reST:
213
+ return get_end_pos(docstring, TypicalFormatStyle[2].second);
214
+ }
215
+
216
+ return -1;
217
+ }
218
+
155
219
  FunctionDocstring get_docstring(const py::object &obj, const py::module &ast_module) noexcept {
156
220
  FunctionDocstring functionDocstring{};
157
221
 
@@ -1,5 +1,7 @@
1
1
  #ifndef DOCSTRING_GENERATOR_EXT_FUNCTIONFORMAT_HPP
2
2
  #define DOCSTRING_GENERATOR_EXT_FUNCTIONFORMAT_HPP
3
+
4
+ #include <map>
3
5
  #include <set>
4
6
  #include <string>
5
7
  #include <vector>
@@ -21,6 +23,13 @@ enum class DocstringFormatStyle {
21
23
  NUMPY
22
24
  };
23
25
 
26
+ using StyleSections = std::array<std::string_view, 3>;
27
+ constexpr std::array<std::pair<std::string_view, StyleSections>, 3> TypicalFormatStyle = {{
28
+ {"numpy", {"Parameters\n", "Returns\n", "Raises\n"}},
29
+ {"google", {"Args:", "Returns:", "Raises:"}},
30
+ {"rest", {":param", ":returns:", ":raises"}}
31
+ }};
32
+
24
33
  ParameterKind from_str(const std::string &kind);
25
34
  std::ostream &operator<<(std::ostream &out, ParameterKind const &obj) noexcept;
26
35
 
@@ -47,6 +56,10 @@ struct FunctionDocstring {
47
56
  uint32_t start_line{};
48
57
  size_t end_line{};
49
58
  std::string docstring;
59
+ std::optional<DocstringFormatStyle> detected_format_style {std::nullopt};
60
+
61
+ void detect_format_style() noexcept;
62
+ [[nodiscard]] int remove_old_format_style() noexcept;
50
63
  };
51
64
 
52
65
  FunctionDocstring get_docstring(const py::object &obj, const py::module &ast_module) noexcept;
@@ -17,6 +17,21 @@ void GoogleDocstring::check_current_docstring() noexcept {
17
17
  }
18
18
  }
19
19
 
20
+ std::string GoogleDocstring::cleanArgsDescription(const std::string &arg, const std::string &description) noexcept {
21
+ std::string res = std::string {description};
22
+
23
+ if (const auto startPos = description.find("Args:\n"); startPos < std::string::npos) {
24
+ res = res.substr(startPos, res.size() - startPos);
25
+ }
26
+
27
+ if (const auto startPos = res.find(arg); startPos < std::string::npos) {
28
+ const auto pos = startPos + arg.size() + 1;
29
+ res = res.substr(pos, res.size() - pos);
30
+ }
31
+
32
+ return res;
33
+ }
34
+
20
35
  std::string GoogleDocstring::docstringArgs() noexcept {
21
36
  auto current_py_tab = get_tabs();
22
37
 
@@ -56,7 +71,7 @@ std::string GoogleDocstring::docstringArgs() noexcept {
56
71
  }
57
72
  if (!val.description.empty()) {
58
73
  result += ": ";
59
- result += val.description;
74
+ result += cleanArgsDescription(val.name, val.description);
60
75
  }
61
76
  if (!val.default_value.empty()) {
62
77
  result += " (default is ";
@@ -79,15 +94,10 @@ std::string GoogleDocstring::docstringReturn() noexcept {
79
94
 
80
95
  if (!functionInfo.returns.description.empty() || !functionInfo.returns.type.empty()) {
81
96
  result += "\n";
82
-
83
- if (PY_TAB != current_py_tab) {
84
- result += PY_TAB;
85
- } else {
86
- result += PY_TAB;
87
- current_py_tab = PY_TAB + PY_TAB;
88
- }
97
+ result += current_py_tab;
89
98
 
90
99
  result += "Returns:\n";
100
+ current_py_tab += PY_TAB;
91
101
  result += current_py_tab;
92
102
 
93
103
  if (!functionInfo.returns.type.empty()) {
@@ -112,13 +122,9 @@ std::string GoogleDocstring::docstringExceptions() noexcept {
112
122
  auto current_py_tab = get_tabs();
113
123
 
114
124
  result += "\n";
125
+ result += current_py_tab;
115
126
 
116
- if (PY_TAB != current_py_tab) {
117
- result += PY_TAB;
118
- } else {
119
- result += PY_TAB;
120
- current_py_tab = PY_TAB + PY_TAB;
121
- }
127
+ current_py_tab += PY_TAB;
122
128
 
123
129
  result += "Raises:\n";
124
130
  result += current_py_tab;
@@ -8,6 +8,7 @@ struct GoogleDocstring : DocstringFormat {
8
8
  std::string docstringArgs() noexcept override;
9
9
  std::string docstringReturn() noexcept override;
10
10
  std::string docstringExceptions() noexcept override;
11
+ std::string cleanArgsDescription(const std::string &arg, const std::string &description) noexcept;
11
12
  };
12
13
 
13
14
 
@@ -1,6 +1,5 @@
1
1
  #include "IDocstringFormat.hpp"
2
2
  #include "parser.hpp"
3
- #include <sstream>
4
3
 
5
4
  [[ nodiscard ]] std::string DocstringFormat::docstring() noexcept {
6
5
  std::string result;
@@ -1,6 +1,5 @@
1
1
  #include "docstringFormat.hpp"
2
2
  #include <algorithm>
3
- #include <format>
4
3
  #include <fstream>
5
4
  #include <iostream>
6
5
  #include <sstream>
@@ -17,13 +16,14 @@
17
16
 
18
17
  namespace py = pybind11;
19
18
 
20
- void write_to_file_position(
19
+ [[nodiscard]] WriteResult write_to_file_position(
21
20
  std::vector<FunctionInfo> &&infos,
22
21
  const std::string &file_path,
23
- const DocstringFormatStyle &formatStyle
22
+ const DocstringFormatStyle &formatStyle,
23
+ const bool &allow_overwrite
24
24
  ) noexcept;
25
25
 
26
- std::string read_file(const std::string &file_path) {
26
+ std::optional<std::string> read_file(const std::string &file_path) {
27
27
  std::ifstream file(file_path, std::ios::binary);
28
28
  std::string file_content{};
29
29
 
@@ -32,19 +32,18 @@ std::string read_file(const std::string &file_path) {
32
32
  outsstream << file.rdbuf();
33
33
  file_content = outsstream.str();
34
34
  } else {
35
- throw py::value_error(file_path + " is not a valid path.");
35
+ return std::nullopt;
36
36
  }
37
37
 
38
38
  return file_content;
39
39
  }
40
40
 
41
- void parse_file(std::string &file_path, DocstringFormatStyle &formatStyle) {
41
+ std::vector<FunctionInfo> createFunctionInfos(const std::string &text, const DocstringFormatStyle &formatStyle) {
42
+ std::vector<FunctionInfo> infos{};
42
43
  py::module ast_module = py::module_::import("ast");
43
- py::object generator_result = ast_module.attr("walk")(ast_module.attr("parse")(read_file(file_path)));
44
+ const py::object generator_result = ast_module.attr("walk")(ast_module.attr("parse")(text));
44
45
  py::iterator iter = py::iter(generator_result);
45
46
 
46
- std::vector<FunctionInfo> infos{};
47
-
48
47
  for (auto &obj: iter) {
49
48
  if (py::isinstance(obj, ast_module.attr("FunctionDef")) || py::isinstance(
50
49
  obj,
@@ -53,9 +52,20 @@ void parse_file(std::string &file_path, DocstringFormatStyle &formatStyle) {
53
52
  FunctionInfo function_info = analyze_function(obj, ast_module);
54
53
  function_info.update_descriptions(formatStyle);
55
54
  infos.emplace_back(function_info);
56
- }
55
+ }
56
+ }
57
+
58
+ return infos;
59
+ }
60
+
61
+ void parse_file(const std::string &file_path, const DocstringFormatStyle &formatStyle, const bool &allow_overwrite) {
62
+ const auto result = read_file(file_path);
63
+ if (!result.has_value()) {
64
+ throw py::value_error(file_path + " is not a valid path.");
57
65
  }
58
66
 
67
+ std::vector<FunctionInfo> infos = createFunctionInfos(*result, formatStyle);
68
+
59
69
  std::ranges::sort(
60
70
  infos,
61
71
  [](const FunctionInfo &left, const FunctionInfo &right) {
@@ -64,7 +74,21 @@ void parse_file(std::string &file_path, DocstringFormatStyle &formatStyle) {
64
74
  );
65
75
 
66
76
  #ifndef DRY_RUN
67
- write_to_file_position(std::move(infos), file_path, formatStyle);
77
+ const auto res = write_to_file_position(std::move(infos), file_path, formatStyle, allow_overwrite);
78
+ switch (res) {
79
+
80
+ case SUCCESS:
81
+ break;
82
+ case FAILURE:
83
+ py::print("Could not write to file.");
84
+ break;
85
+ case FILE_NOT_FOUND:
86
+ py::print("Could not write to file.");
87
+ break;
88
+ case INVALID_FORMAT:
89
+ py::print("Aborting changes. A different docstring format was detected from what is specified.");
90
+ break;
91
+ }
68
92
  #endif
69
93
  }
70
94
 
@@ -111,10 +135,11 @@ std::map<std::string, int> check_docstrings(std::string &file_path) {
111
135
  };
112
136
  }
113
137
 
114
- void write_to_file_position(
138
+ [[nodiscard]] WriteResult write_to_file_position(
115
139
  std::vector<FunctionInfo> &&infos,
116
140
  const std::string &file_path,
117
- const DocstringFormatStyle &formatStyle
141
+ const DocstringFormatStyle &formatStyle,
142
+ const bool &allow_overwrite
118
143
  ) noexcept {
119
144
  std::fstream file;
120
145
 
@@ -139,6 +164,17 @@ void write_to_file_position(
139
164
  end_pos = val.docstring.end_line;
140
165
  }
141
166
 
167
+ if (val.docstring.detected_format_style.has_value()) {
168
+ if (!allow_overwrite && *val.docstring.detected_format_style != formatStyle) {
169
+ return INVALID_FORMAT;
170
+ }
171
+ if (allow_overwrite && *val.docstring.detected_format_style != formatStyle) {
172
+ const auto pos = val.docstring.remove_old_format_style();
173
+ if (pos >= 0)
174
+ start_pos = pos;
175
+ }
176
+ }
177
+
142
178
  if (start_pos < 0 || end_pos - 1 < 0) {
143
179
  break;
144
180
  }
@@ -165,25 +201,25 @@ void write_to_file_position(
165
201
  lines.insert(lines.begin() + start_pos, docstring_format->docstring());
166
202
  }
167
203
 
168
- {
169
- std::ofstream out_file(file_path);
170
- if (!out_file.is_open()) {
171
- py::print("Error: Unable to open file for writing.");
172
- std::cerr << "Error: Unable to open file for writing.\n";
173
- return;
174
- }
204
+ std::ofstream out_file(file_path);
205
+ if (!out_file.is_open()) {
206
+ py::print("Error: Unable to open file for writing.");
207
+ std::cerr << "Error: Unable to open file for writing.\n";
208
+ return FILE_NOT_FOUND;
209
+ }
175
210
 
176
- std::string output;
177
- output.reserve(lines.size() * 80); // rough estimate
178
- for (const auto &line : lines) {
179
- output += line;
180
- if (line.empty() || line[line.size() - 1] != '\n') {
181
- output += '\n';
182
- }
211
+ std::string output;
212
+ output.reserve(lines.size() * 80); // rough estimate
213
+ for (const auto &line : lines) {
214
+ output += line;
215
+ if (line.empty() || line[line.size() - 1] != '\n') {
216
+ output += '\n';
183
217
  }
184
- out_file.write(output.data(), static_cast<std::streamsize>(output.size()));
185
- out_file.close();
186
218
  }
219
+ out_file.write(output.data(), static_cast<std::streamsize>(output.size()));
220
+ out_file.close();
221
+
222
+ return SUCCESS;
187
223
  }
188
224
 
189
225
 
@@ -195,6 +231,7 @@ PYBIND11_MODULE(docstring_generator_ext, m) {
195
231
  "A function that parses a file",
196
232
  py::arg("file_path"),
197
233
  py::arg("formatStyle"),
234
+ py::arg("allow_overwrite") = false,
198
235
  "The file_path where automatically docstrings should be added.",
199
236
  "In which style should the Docstring be written."
200
237
  );
@@ -5,11 +5,20 @@
5
5
  #include <string>
6
6
  #include "FunctionFormat.hpp"
7
7
 
8
- std::string read_file(const std::string &file_path);
8
+ enum WriteResult {
9
+ SUCCESS,
10
+ FAILURE,
11
+ FILE_NOT_FOUND,
12
+ INVALID_FORMAT
13
+ };
14
+
15
+ [[nodiscard]] std::optional<std::string> read_file(const std::string &file_path);
9
16
 
10
17
  void get_docstring_arg_descr(FunctionInfo &functionInfo) noexcept;
11
18
 
12
- void parse_file(std::string &file_path, DocstringFormatStyle &formatStyle);
19
+ [[nodiscard]] std::vector<FunctionInfo> createFunctionInfos(const std::string &text, const DocstringFormatStyle &formatStyle);
20
+
21
+ void parse_file(const std::string &file_path, const DocstringFormatStyle &formatStyle, const bool &allow_overwrite = false);
13
22
 
14
23
  /*
15
24
  * {
@@ -349,6 +349,26 @@ void get_docstring_arg_descr(FunctionInfo &functionInfo) noexcept {
349
349
  }
350
350
  }
351
351
 
352
+ void get_docstring_return_descr(FunctionInfo &functionInfo) noexcept {
353
+ if (functionInfo.docstring.docstring.empty()) {
354
+ return;
355
+ }
356
+
357
+ if (functionInfo.returns.description.empty()) {
358
+ const auto iter = std::ranges::search(functionInfo.docstring.docstring, std::string_view(">>"));
359
+ if (iter.empty()) {
360
+ return;
361
+ }
362
+
363
+ const auto start = std::ranges::distance(functionInfo.docstring.docstring.begin(), iter.end());
364
+ const auto end = functionInfo.docstring.docstring.find('\n', start);
365
+ if (end != std::string::npos && start > 0) {
366
+ functionInfo.returns.description = functionInfo.docstring.docstring.substr(start, end - start);
367
+ functionInfo.docstring.docstring.erase(start - 2, functionInfo.returns.description.size() + 3); // 3 = '>>' + '\n'
368
+ }
369
+ }
370
+ }
371
+
352
372
  void get_exception(
353
373
  const pybind11::object &function_body,
354
374
  py::module &ast_module,
@@ -448,6 +468,7 @@ FunctionInfo analyze_function(
448
468
  auto offset = py::cast<uint32_t>(py::getattr(obj, "col_offset"));
449
469
 
450
470
  FunctionDocstring doc_str = get_docstring(py::reinterpret_borrow<py::object>(obj), ast_module);
471
+ doc_str.detect_format_style();
451
472
 
452
473
  auto returns = py::getattr(obj, "returns");
453
474
 
@@ -481,7 +502,7 @@ FunctionInfo analyze_function(
481
502
  }
482
503
 
483
504
  get_docstring_arg_descr(functionInfo);
484
-
505
+ get_docstring_return_descr(functionInfo);
485
506
 
486
507
  return functionInfo;
487
508
  }
@@ -39,6 +39,7 @@ std::vector<FunctionParameter> generate_function_parameters(
39
39
 
40
40
  void get_docstring_arg_descr(FunctionInfo &functionInfo) noexcept;
41
41
 
42
+ void get_docstring_return_descr(FunctionInfo &functionInfo) noexcept;
42
43
 
43
44
  void get_exception(
44
45
  const pybind11::object &function_body,