omdev 0.0.0.dev313__py3-none-any.whl → 0.0.0.dev314__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- omdev/.manifests.json +1 -1
- omdev/magic/styles.py +9 -2
- omdev/py/docstrings/LICENSE +16 -0
- omdev/py/docstrings/__init__.py +19 -0
- omdev/py/docstrings/attrdoc.py +151 -0
- omdev/py/docstrings/common.py +285 -0
- omdev/py/docstrings/epydoc.py +198 -0
- omdev/py/docstrings/google.py +329 -0
- omdev/py/docstrings/numpydoc.py +403 -0
- omdev/py/docstrings/parser.py +84 -0
- omdev/py/docstrings/rest.py +176 -0
- omdev/scripts/pyproject.py +9 -2
- omdev/tools/antlr/gen.py +1 -0
- omdev/tools/docker.py +11 -6
- {omdev-0.0.0.dev313.dist-info → omdev-0.0.0.dev314.dist-info}/METADATA +2 -2
- {omdev-0.0.0.dev313.dist-info → omdev-0.0.0.dev314.dist-info}/RECORD +20 -11
- {omdev-0.0.0.dev313.dist-info → omdev-0.0.0.dev314.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev313.dist-info → omdev-0.0.0.dev314.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev313.dist-info → omdev-0.0.0.dev314.dist-info}/licenses/LICENSE +0 -0
- {omdev-0.0.0.dev313.dist-info → omdev-0.0.0.dev314.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,403 @@
|
|
1
|
+
"""
|
2
|
+
Numpydoc-style docstring parsing.
|
3
|
+
|
4
|
+
:see: https://numpydoc.readthedocs.io/en/latest/format.html
|
5
|
+
"""
|
6
|
+
import abc
|
7
|
+
import inspect
|
8
|
+
import itertools
|
9
|
+
import re
|
10
|
+
import textwrap
|
11
|
+
import typing as ta
|
12
|
+
|
13
|
+
from .common import Docstring
|
14
|
+
from .common import DocstringDeprecated
|
15
|
+
from .common import DocstringExample
|
16
|
+
from .common import DocstringMeta
|
17
|
+
from .common import DocstringParam
|
18
|
+
from .common import DocstringRaises
|
19
|
+
from .common import DocstringReturns
|
20
|
+
from .common import DocstringStyle
|
21
|
+
from .common import ParseError
|
22
|
+
|
23
|
+
|
24
|
+
##
|
25
|
+
|
26
|
+
|
27
|
+
def _pairwise(iterable: ta.Iterable, end=None) -> ta.Iterable:
|
28
|
+
left, right = itertools.tee(iterable)
|
29
|
+
next(right, None)
|
30
|
+
return itertools.zip_longest(left, right, fillvalue=end)
|
31
|
+
|
32
|
+
|
33
|
+
def _clean_str(string: str) -> str | None:
|
34
|
+
string = string.strip()
|
35
|
+
if len(string) > 0:
|
36
|
+
return string
|
37
|
+
return None
|
38
|
+
|
39
|
+
|
40
|
+
KV_PAT = re.compile(r'^[^\s].*$', flags=re.MULTILINE)
|
41
|
+
PARAM_KEY_PAT = re.compile(r'^(?P<name>.*?)(?:\s*:\s*(?P<type>.*?))?$')
|
42
|
+
PARAM_OPTIONAL_PAT = re.compile(r'(?P<type>.*?)(?:, optional|\(optional\))$')
|
43
|
+
|
44
|
+
# numpydoc format has no formal grammar for this, but we can make some educated guesses...
|
45
|
+
PARAM_DEFAULT_PAT = re.compile(r'(?<!\S)[Dd]efault(?: is | = |: |s to |)\s*(?P<value>[\w\-\.]*\w)')
|
46
|
+
|
47
|
+
RETURN_KEY_PAT = re.compile(r'^(?:(?P<name>.*?)\s*:\s*)?(?P<type>.*?)$')
|
48
|
+
|
49
|
+
|
50
|
+
class Section:
|
51
|
+
"""
|
52
|
+
Numpydoc section parser.
|
53
|
+
|
54
|
+
:param title: section title. For most sections, this is a heading like "Parameters" which appears on its own line,
|
55
|
+
underlined by en-dashes ('-') on the following line.
|
56
|
+
:param key: meta key string. In the parsed ``DocstringMeta`` instance this will be the first element of the ``args``
|
57
|
+
attribute list.
|
58
|
+
"""
|
59
|
+
|
60
|
+
def __init__(self, title: str, key: str) -> None:
|
61
|
+
super().__init__()
|
62
|
+
|
63
|
+
self.title = title
|
64
|
+
self.key = key
|
65
|
+
|
66
|
+
@property
|
67
|
+
def title_pattern(self) -> str:
|
68
|
+
"""
|
69
|
+
Regular expression pattern matching this section's header.
|
70
|
+
|
71
|
+
This pattern will match this instance's ``title`` attribute in an anonymous group.
|
72
|
+
"""
|
73
|
+
|
74
|
+
dashes = '-' * len(self.title)
|
75
|
+
return rf'^({self.title})\s*?\n{dashes}\s*$'
|
76
|
+
|
77
|
+
def parse(self, text: str) -> ta.Iterable[DocstringMeta]:
|
78
|
+
"""
|
79
|
+
Parse ``DocstringMeta`` objects from the body of this section.
|
80
|
+
|
81
|
+
:param text: section body text. Should be cleaned with ``inspect.cleandoc`` before parsing.
|
82
|
+
"""
|
83
|
+
|
84
|
+
yield DocstringMeta([self.key], description=_clean_str(text))
|
85
|
+
|
86
|
+
|
87
|
+
class _KVSection(Section, abc.ABC):
|
88
|
+
"""
|
89
|
+
Base parser for numpydoc sections with key-value syntax.
|
90
|
+
|
91
|
+
E.g. sections that look like this:
|
92
|
+
key
|
93
|
+
value
|
94
|
+
key2 : type
|
95
|
+
values can also span...
|
96
|
+
... multiple lines
|
97
|
+
"""
|
98
|
+
|
99
|
+
@abc.abstractmethod
|
100
|
+
def _parse_item(self, key: str, value: str) -> DocstringMeta:
|
101
|
+
raise NotImplementedError
|
102
|
+
|
103
|
+
def parse(self, text: str) -> ta.Iterable[DocstringMeta]:
|
104
|
+
for match, next_match in _pairwise(KV_PAT.finditer(text)):
|
105
|
+
start = match.end()
|
106
|
+
end = next_match.start() if next_match is not None else None
|
107
|
+
value = text[start:end]
|
108
|
+
yield self._parse_item(
|
109
|
+
key=match.group(),
|
110
|
+
value=inspect.cleandoc(value),
|
111
|
+
)
|
112
|
+
|
113
|
+
|
114
|
+
class _SphinxSection(Section):
|
115
|
+
"""
|
116
|
+
Base parser for numpydoc sections with sphinx-style syntax.
|
117
|
+
|
118
|
+
E.g. sections that look like this:
|
119
|
+
.. title:: something
|
120
|
+
possibly over multiple lines
|
121
|
+
"""
|
122
|
+
|
123
|
+
@property
|
124
|
+
def title_pattern(self) -> str:
|
125
|
+
return rf'^\.\.\s*({self.title})\s*::'
|
126
|
+
|
127
|
+
|
128
|
+
class ParamSection(_KVSection):
|
129
|
+
"""
|
130
|
+
Parser for numpydoc parameter sections.
|
131
|
+
|
132
|
+
E.g. any section that looks like this:
|
133
|
+
arg_name
|
134
|
+
arg_description
|
135
|
+
arg_2 : type, optional
|
136
|
+
descriptions can also span...
|
137
|
+
... multiple lines
|
138
|
+
"""
|
139
|
+
|
140
|
+
def _parse_item(self, key: str, value: str) -> DocstringParam:
|
141
|
+
match = PARAM_KEY_PAT.match(key)
|
142
|
+
arg_name = type_name = is_optional = None
|
143
|
+
if match is not None:
|
144
|
+
arg_name = match.group('name')
|
145
|
+
type_name = match.group('type')
|
146
|
+
if type_name is not None:
|
147
|
+
optional_match = PARAM_OPTIONAL_PAT.match(type_name)
|
148
|
+
if optional_match is not None:
|
149
|
+
type_name = optional_match.group('type')
|
150
|
+
is_optional = True
|
151
|
+
else:
|
152
|
+
is_optional = False
|
153
|
+
|
154
|
+
default = None
|
155
|
+
if len(value) > 0:
|
156
|
+
default_match = PARAM_DEFAULT_PAT.search(value)
|
157
|
+
if default_match is not None:
|
158
|
+
default = default_match.group('value')
|
159
|
+
|
160
|
+
if arg_name is None:
|
161
|
+
raise ParseError
|
162
|
+
|
163
|
+
return DocstringParam(
|
164
|
+
args=[self.key, arg_name],
|
165
|
+
description=_clean_str(value),
|
166
|
+
arg_name=arg_name,
|
167
|
+
type_name=type_name,
|
168
|
+
is_optional=is_optional,
|
169
|
+
default=default,
|
170
|
+
)
|
171
|
+
|
172
|
+
|
173
|
+
class RaisesSection(_KVSection):
|
174
|
+
"""
|
175
|
+
Parser for numpydoc raises sections.
|
176
|
+
|
177
|
+
E.g. any section that looks like this:
|
178
|
+
ValueError
|
179
|
+
A description of what might raise ValueError
|
180
|
+
"""
|
181
|
+
|
182
|
+
def _parse_item(self, key: str, value: str) -> DocstringRaises:
|
183
|
+
return DocstringRaises(
|
184
|
+
args=[self.key, key],
|
185
|
+
description=_clean_str(value),
|
186
|
+
type_name=key if len(key) > 0 else None,
|
187
|
+
)
|
188
|
+
|
189
|
+
|
190
|
+
class ReturnsSection(_KVSection):
|
191
|
+
"""
|
192
|
+
Parser for numpydoc returns sections.
|
193
|
+
|
194
|
+
E.g. any section that looks like this:
|
195
|
+
return_name : type
|
196
|
+
A description of this returned value
|
197
|
+
another_type
|
198
|
+
Return names are optional, types are required
|
199
|
+
"""
|
200
|
+
|
201
|
+
is_generator = False
|
202
|
+
|
203
|
+
def _parse_item(self, key: str, value: str) -> DocstringReturns:
|
204
|
+
match = RETURN_KEY_PAT.match(key)
|
205
|
+
if match is not None:
|
206
|
+
return_name = match.group('name')
|
207
|
+
type_name = match.group('type')
|
208
|
+
else:
|
209
|
+
return_name = None
|
210
|
+
type_name = None
|
211
|
+
|
212
|
+
return DocstringReturns(
|
213
|
+
args=[self.key],
|
214
|
+
description=_clean_str(value),
|
215
|
+
type_name=type_name,
|
216
|
+
is_generator=self.is_generator,
|
217
|
+
return_name=return_name,
|
218
|
+
)
|
219
|
+
|
220
|
+
|
221
|
+
class YieldsSection(ReturnsSection):
|
222
|
+
"""Parser for numpydoc generator "yields" sections."""
|
223
|
+
|
224
|
+
is_generator = True
|
225
|
+
|
226
|
+
|
227
|
+
class DeprecationSection(_SphinxSection):
|
228
|
+
"""Parser for numpydoc "deprecation warning" sections."""
|
229
|
+
|
230
|
+
def parse(self, text: str) -> ta.Iterable[DocstringDeprecated]:
|
231
|
+
version, desc, *_ = [*text.split(sep='\n', maxsplit=1), None, None]
|
232
|
+
|
233
|
+
if desc is not None:
|
234
|
+
desc = _clean_str(inspect.cleandoc(desc)) # type: ignore[unreachable]
|
235
|
+
|
236
|
+
yield DocstringDeprecated(
|
237
|
+
args=[self.key],
|
238
|
+
description=desc,
|
239
|
+
version=_clean_str(version),
|
240
|
+
)
|
241
|
+
|
242
|
+
|
243
|
+
class ExamplesSection(Section):
|
244
|
+
"""
|
245
|
+
Parser for numpydoc examples sections.
|
246
|
+
|
247
|
+
E.g. any section that looks like this:
|
248
|
+
>>> import numpy.matlib
|
249
|
+
>>> np.matlib.empty((2, 2)) # filled with random data
|
250
|
+
matrix([[ 6.76425276e-320, 9.79033856e-307], # random
|
251
|
+
[ 7.39337286e-309, 3.22135945e-309]])
|
252
|
+
>>> np.matlib.empty((2, 2), dtype=int)
|
253
|
+
matrix([[ 6600475, 0], # random
|
254
|
+
[ 6586976, 22740995]])
|
255
|
+
"""
|
256
|
+
|
257
|
+
def parse(self, text: str) -> ta.Iterable[DocstringMeta]:
|
258
|
+
"""
|
259
|
+
Parse ``DocstringExample`` objects from the body of this section.
|
260
|
+
|
261
|
+
:param text: section body text. Should be cleaned with ``inspect.cleandoc`` before parsing.
|
262
|
+
"""
|
263
|
+
|
264
|
+
lines = textwrap.dedent(text).strip().splitlines()
|
265
|
+
while lines:
|
266
|
+
snippet_lines = []
|
267
|
+
description_lines = []
|
268
|
+
while lines:
|
269
|
+
if not lines[0].startswith('>>>'):
|
270
|
+
break
|
271
|
+
snippet_lines.append(lines.pop(0))
|
272
|
+
while lines:
|
273
|
+
if lines[0].startswith('>>>'):
|
274
|
+
break
|
275
|
+
description_lines.append(lines.pop(0))
|
276
|
+
yield DocstringExample(
|
277
|
+
[self.key],
|
278
|
+
snippet='\n'.join(snippet_lines) if snippet_lines else None,
|
279
|
+
description='\n'.join(description_lines),
|
280
|
+
)
|
281
|
+
|
282
|
+
|
283
|
+
DEFAULT_SECTIONS = [
|
284
|
+
ParamSection('Parameters', 'param'),
|
285
|
+
ParamSection('Params', 'param'),
|
286
|
+
ParamSection('Arguments', 'param'),
|
287
|
+
ParamSection('Args', 'param'),
|
288
|
+
ParamSection('Other Parameters', 'other_param'),
|
289
|
+
ParamSection('Other Params', 'other_param'),
|
290
|
+
ParamSection('Other Arguments', 'other_param'),
|
291
|
+
ParamSection('Other Args', 'other_param'),
|
292
|
+
ParamSection('Receives', 'receives'),
|
293
|
+
ParamSection('Receive', 'receives'),
|
294
|
+
RaisesSection('Raises', 'raises'),
|
295
|
+
RaisesSection('Raise', 'raises'),
|
296
|
+
RaisesSection('Warns', 'warns'),
|
297
|
+
RaisesSection('Warn', 'warns'),
|
298
|
+
ParamSection('Attributes', 'attribute'),
|
299
|
+
ParamSection('Attribute', 'attribute'),
|
300
|
+
ReturnsSection('Returns', 'returns'),
|
301
|
+
ReturnsSection('Return', 'returns'),
|
302
|
+
YieldsSection('Yields', 'yields'),
|
303
|
+
YieldsSection('Yield', 'yields'),
|
304
|
+
ExamplesSection('Examples', 'examples'),
|
305
|
+
ExamplesSection('Example', 'examples'),
|
306
|
+
Section('Warnings', 'warnings'),
|
307
|
+
Section('Warning', 'warnings'),
|
308
|
+
Section('See Also', 'see_also'),
|
309
|
+
Section('Related', 'see_also'),
|
310
|
+
Section('Notes', 'notes'),
|
311
|
+
Section('Note', 'notes'),
|
312
|
+
Section('References', 'references'),
|
313
|
+
Section('Reference', 'references'),
|
314
|
+
DeprecationSection('deprecated', 'deprecation'),
|
315
|
+
]
|
316
|
+
|
317
|
+
|
318
|
+
class NumpydocParser:
|
319
|
+
"""Parser for numpydoc-style docstrings."""
|
320
|
+
|
321
|
+
def __init__(self, sections: ta.Iterable[Section] | None = None) -> None:
|
322
|
+
"""
|
323
|
+
Setup sections.
|
324
|
+
|
325
|
+
:param sections: Recognized sections or None to defaults.
|
326
|
+
"""
|
327
|
+
|
328
|
+
super().__init__()
|
329
|
+
|
330
|
+
sections = sections or DEFAULT_SECTIONS
|
331
|
+
self.sections = {s.title: s for s in sections}
|
332
|
+
self._setup()
|
333
|
+
|
334
|
+
def _setup(self):
|
335
|
+
self.titles_re = re.compile(
|
336
|
+
r'|'.join(s.title_pattern for s in self.sections.values()),
|
337
|
+
flags=re.MULTILINE,
|
338
|
+
)
|
339
|
+
|
340
|
+
def add_section(self, section: Section) -> None:
|
341
|
+
"""
|
342
|
+
Add or replace a section.
|
343
|
+
|
344
|
+
:param section: The new section.
|
345
|
+
"""
|
346
|
+
|
347
|
+
self.sections[section.title] = section
|
348
|
+
self._setup()
|
349
|
+
|
350
|
+
def parse(self, text: str) -> Docstring:
|
351
|
+
"""
|
352
|
+
Parse the numpy-style docstring into its components.
|
353
|
+
|
354
|
+
:returns: parsed docstring
|
355
|
+
"""
|
356
|
+
|
357
|
+
ret = Docstring(style=DocstringStyle.NUMPYDOC)
|
358
|
+
if not text:
|
359
|
+
return ret
|
360
|
+
|
361
|
+
# Clean according to PEP-0257
|
362
|
+
text = inspect.cleandoc(text)
|
363
|
+
|
364
|
+
# Find first title and split on its position
|
365
|
+
match = self.titles_re.search(text)
|
366
|
+
if match:
|
367
|
+
desc_chunk = text[: match.start()]
|
368
|
+
meta_chunk = text[match.start():]
|
369
|
+
else:
|
370
|
+
desc_chunk = text
|
371
|
+
meta_chunk = ''
|
372
|
+
|
373
|
+
# Break description into short and long parts
|
374
|
+
parts = desc_chunk.split('\n', 1)
|
375
|
+
ret.short_description = parts[0] or None
|
376
|
+
if len(parts) > 1:
|
377
|
+
long_desc_chunk = parts[1] or ''
|
378
|
+
ret.blank_after_short_description = long_desc_chunk.startswith('\n')
|
379
|
+
ret.blank_after_long_description = long_desc_chunk.endswith('\n\n')
|
380
|
+
ret.long_description = long_desc_chunk.strip() or None
|
381
|
+
|
382
|
+
for match, nextmatch in _pairwise(self.titles_re.finditer(meta_chunk)):
|
383
|
+
if match is None:
|
384
|
+
raise ParseError
|
385
|
+
title = next(g for g in match.groups() if g is not None)
|
386
|
+
factory = self.sections[title]
|
387
|
+
|
388
|
+
# section chunk starts after the header, ends at the start of the next header
|
389
|
+
start = match.end()
|
390
|
+
end = nextmatch.start() if nextmatch is not None else None
|
391
|
+
ret.meta.extend(factory.parse(meta_chunk[start:end]))
|
392
|
+
|
393
|
+
return ret
|
394
|
+
|
395
|
+
|
396
|
+
def parse(text: str) -> Docstring:
|
397
|
+
"""
|
398
|
+
Parse the numpy-style docstring into its components.
|
399
|
+
|
400
|
+
:returns: parsed docstring
|
401
|
+
"""
|
402
|
+
|
403
|
+
return NumpydocParser().parse(text)
|
@@ -0,0 +1,84 @@
|
|
1
|
+
"""The main parsing routine."""
|
2
|
+
import inspect
|
3
|
+
import typing as ta
|
4
|
+
|
5
|
+
from . import epydoc
|
6
|
+
from . import google
|
7
|
+
from . import numpydoc
|
8
|
+
from . import rest
|
9
|
+
from .attrdoc import add_attribute_docstrings
|
10
|
+
from .common import Docstring
|
11
|
+
from .common import DocstringStyle
|
12
|
+
from .common import ParseError
|
13
|
+
|
14
|
+
|
15
|
+
##
|
16
|
+
|
17
|
+
|
18
|
+
_STYLE_MAP = {
|
19
|
+
DocstringStyle.REST: rest,
|
20
|
+
DocstringStyle.GOOGLE: google,
|
21
|
+
DocstringStyle.NUMPYDOC: numpydoc,
|
22
|
+
DocstringStyle.EPYDOC: epydoc,
|
23
|
+
}
|
24
|
+
|
25
|
+
|
26
|
+
def parse(
|
27
|
+
text: str,
|
28
|
+
style: DocstringStyle = DocstringStyle.AUTO,
|
29
|
+
) -> Docstring:
|
30
|
+
"""
|
31
|
+
Parse the docstring into its components.
|
32
|
+
|
33
|
+
:param text: docstring text to parse
|
34
|
+
:param style: docstring style
|
35
|
+
:returns: parsed docstring representation
|
36
|
+
"""
|
37
|
+
|
38
|
+
if style != DocstringStyle.AUTO:
|
39
|
+
return _STYLE_MAP[style].parse(text)
|
40
|
+
|
41
|
+
exc: Exception | type[Exception] = ParseError
|
42
|
+
rets = []
|
43
|
+
for module in _STYLE_MAP.values():
|
44
|
+
try:
|
45
|
+
ret = module.parse(text)
|
46
|
+
except ParseError as ex:
|
47
|
+
exc = ex
|
48
|
+
else:
|
49
|
+
rets.append(ret)
|
50
|
+
|
51
|
+
if not rets:
|
52
|
+
raise exc
|
53
|
+
|
54
|
+
return sorted(rets, key=lambda d: len(d.meta), reverse=True)[0]
|
55
|
+
|
56
|
+
|
57
|
+
def parse_from_object(
|
58
|
+
obj: ta.Any,
|
59
|
+
style: DocstringStyle = DocstringStyle.AUTO,
|
60
|
+
) -> Docstring:
|
61
|
+
"""
|
62
|
+
Parse the object's docstring(s) into its components.
|
63
|
+
|
64
|
+
The object can be anything that has a ``__doc__`` attribute. In contrast to the ``parse`` function,
|
65
|
+
``parse_from_object`` is able to parse attribute docstrings which are defined in the source code instead of
|
66
|
+
``__doc__``.
|
67
|
+
|
68
|
+
Currently only attribute docstrings defined at class and module levels are supported. Attribute docstrings defined
|
69
|
+
in ``__init__`` methods are not supported.
|
70
|
+
|
71
|
+
When given a class, only the attribute docstrings of that class are parsed, not its inherited classes. This is a
|
72
|
+
design decision. Separate calls to this function should be performed to get attribute docstrings of parent classes.
|
73
|
+
|
74
|
+
:param obj: object from which to parse the docstring(s)
|
75
|
+
:param style: docstring style
|
76
|
+
:returns: parsed docstring representation
|
77
|
+
"""
|
78
|
+
|
79
|
+
docstring = parse(obj.__doc__, style=style)
|
80
|
+
|
81
|
+
if inspect.isclass(obj) or inspect.ismodule(obj):
|
82
|
+
add_attribute_docstrings(obj, docstring)
|
83
|
+
|
84
|
+
return docstring
|
@@ -0,0 +1,176 @@
|
|
1
|
+
"""ReST-style docstring parsing."""
|
2
|
+
import inspect
|
3
|
+
import re
|
4
|
+
|
5
|
+
from .common import DEPRECATION_KEYWORDS
|
6
|
+
from .common import PARAM_KEYWORDS
|
7
|
+
from .common import RAISES_KEYWORDS
|
8
|
+
from .common import RETURNS_KEYWORDS
|
9
|
+
from .common import YIELDS_KEYWORDS
|
10
|
+
from .common import Docstring
|
11
|
+
from .common import DocstringDeprecated
|
12
|
+
from .common import DocstringMeta
|
13
|
+
from .common import DocstringParam
|
14
|
+
from .common import DocstringRaises
|
15
|
+
from .common import DocstringReturns
|
16
|
+
from .common import DocstringStyle
|
17
|
+
from .common import ParseError
|
18
|
+
|
19
|
+
|
20
|
+
##
|
21
|
+
|
22
|
+
|
23
|
+
def _build_meta(args: list[str], desc: str) -> DocstringMeta:
|
24
|
+
key = args[0]
|
25
|
+
|
26
|
+
if key in PARAM_KEYWORDS:
|
27
|
+
if len(args) == 3:
|
28
|
+
key, type_name, arg_name = args
|
29
|
+
if type_name.endswith('?'):
|
30
|
+
is_optional = True
|
31
|
+
type_name = type_name[:-1]
|
32
|
+
else:
|
33
|
+
is_optional = False
|
34
|
+
|
35
|
+
elif len(args) == 2:
|
36
|
+
key, arg_name = args
|
37
|
+
type_name = None
|
38
|
+
is_optional = None
|
39
|
+
|
40
|
+
else:
|
41
|
+
raise ParseError(f'Expected one or two arguments for a {key} keyword.')
|
42
|
+
|
43
|
+
match = re.match(r'.*defaults to (.+)', desc, flags=re.DOTALL)
|
44
|
+
default = match.group(1).rstrip('.') if match else None
|
45
|
+
|
46
|
+
return DocstringParam(
|
47
|
+
args=args,
|
48
|
+
description=desc,
|
49
|
+
arg_name=arg_name,
|
50
|
+
type_name=type_name,
|
51
|
+
is_optional=is_optional,
|
52
|
+
default=default,
|
53
|
+
)
|
54
|
+
|
55
|
+
if key in RETURNS_KEYWORDS | YIELDS_KEYWORDS:
|
56
|
+
if len(args) == 2:
|
57
|
+
type_name = args[1]
|
58
|
+
elif len(args) == 1:
|
59
|
+
type_name = None
|
60
|
+
else:
|
61
|
+
raise ParseError(f'Expected one or no arguments for a {key} keyword.')
|
62
|
+
|
63
|
+
return DocstringReturns(
|
64
|
+
args=args,
|
65
|
+
description=desc,
|
66
|
+
type_name=type_name,
|
67
|
+
is_generator=key in YIELDS_KEYWORDS,
|
68
|
+
)
|
69
|
+
|
70
|
+
if key in DEPRECATION_KEYWORDS:
|
71
|
+
match = re.search(
|
72
|
+
r'^(?P<version>v?((?:\d+)(?:\.[0-9a-z\.]+))) (?P<desc>.+)',
|
73
|
+
desc,
|
74
|
+
flags=re.IGNORECASE,
|
75
|
+
)
|
76
|
+
|
77
|
+
return DocstringDeprecated(
|
78
|
+
args=args,
|
79
|
+
version=match.group('version') if match else None,
|
80
|
+
description=match.group('desc') if match else desc,
|
81
|
+
)
|
82
|
+
|
83
|
+
if key in RAISES_KEYWORDS:
|
84
|
+
if len(args) == 2:
|
85
|
+
type_name = args[1]
|
86
|
+
elif len(args) == 1:
|
87
|
+
type_name = None
|
88
|
+
else:
|
89
|
+
raise ParseError(f'Expected one or no arguments for a {key} keyword.')
|
90
|
+
|
91
|
+
return DocstringRaises(
|
92
|
+
args=args,
|
93
|
+
description=desc,
|
94
|
+
type_name=type_name,
|
95
|
+
)
|
96
|
+
|
97
|
+
return DocstringMeta(args=args, description=desc)
|
98
|
+
|
99
|
+
|
100
|
+
def parse(text: str) -> Docstring:
|
101
|
+
"""
|
102
|
+
Parse the ReST-style docstring into its components.
|
103
|
+
|
104
|
+
:returns: parsed docstring
|
105
|
+
"""
|
106
|
+
|
107
|
+
ret = Docstring(style=DocstringStyle.REST)
|
108
|
+
if not text:
|
109
|
+
return ret
|
110
|
+
|
111
|
+
text = inspect.cleandoc(text)
|
112
|
+
match = re.search('^:', text, flags=re.MULTILINE)
|
113
|
+
if match:
|
114
|
+
desc_chunk = text[: match.start()]
|
115
|
+
meta_chunk = text[match.start():]
|
116
|
+
else:
|
117
|
+
desc_chunk = text
|
118
|
+
meta_chunk = ''
|
119
|
+
|
120
|
+
parts = desc_chunk.split('\n', 1)
|
121
|
+
ret.short_description = parts[0] or None
|
122
|
+
if len(parts) > 1:
|
123
|
+
long_desc_chunk = parts[1] or ''
|
124
|
+
ret.blank_after_short_description = long_desc_chunk.startswith('\n')
|
125
|
+
ret.blank_after_long_description = long_desc_chunk.endswith('\n\n')
|
126
|
+
ret.long_description = long_desc_chunk.strip() or None
|
127
|
+
|
128
|
+
types = {}
|
129
|
+
rtypes = {}
|
130
|
+
for match in re.finditer(r'(^:.*?)(?=^:|\Z)', meta_chunk, flags=re.DOTALL | re.MULTILINE):
|
131
|
+
chunk = match.group(0)
|
132
|
+
if not chunk:
|
133
|
+
continue
|
134
|
+
|
135
|
+
try:
|
136
|
+
args_chunk, desc_chunk = chunk.lstrip(':').split(':', 1)
|
137
|
+
except ValueError as ex:
|
138
|
+
raise ParseError(f'Error parsing meta information near "{chunk}".') from ex
|
139
|
+
|
140
|
+
args = args_chunk.split()
|
141
|
+
desc = desc_chunk.strip()
|
142
|
+
|
143
|
+
if '\n' in desc:
|
144
|
+
first_line, rest = desc.split('\n', 1)
|
145
|
+
desc = first_line + '\n' + inspect.cleandoc(rest)
|
146
|
+
|
147
|
+
# Add special handling for :type a: typename
|
148
|
+
if len(args) == 2 and args[0] == 'type':
|
149
|
+
types[args[1]] = desc
|
150
|
+
|
151
|
+
elif len(args) in [1, 2] and args[0] == 'rtype':
|
152
|
+
rtypes[None if len(args) == 1 else args[1]] = desc
|
153
|
+
|
154
|
+
else:
|
155
|
+
ret.meta.append(_build_meta(args, desc))
|
156
|
+
|
157
|
+
for meta in ret.meta:
|
158
|
+
if isinstance(meta, DocstringParam):
|
159
|
+
meta.type_name = meta.type_name or types.get(meta.arg_name)
|
160
|
+
|
161
|
+
elif isinstance(meta, DocstringReturns):
|
162
|
+
meta.type_name = meta.type_name or rtypes.get(meta.return_name)
|
163
|
+
|
164
|
+
if not any(isinstance(m, DocstringReturns) for m in ret.meta) and rtypes:
|
165
|
+
for return_name, type_name in rtypes.items():
|
166
|
+
ret.meta.append(
|
167
|
+
DocstringReturns(
|
168
|
+
args=[],
|
169
|
+
type_name=type_name,
|
170
|
+
description=None,
|
171
|
+
is_generator=False,
|
172
|
+
return_name=return_name,
|
173
|
+
),
|
174
|
+
)
|
175
|
+
|
176
|
+
return ret
|
omdev/scripts/pyproject.py
CHANGED
@@ -205,14 +205,21 @@ class MagicStyle:
|
|
205
205
|
|
206
206
|
PY_MAGIC_STYLE = MagicStyle(
|
207
207
|
name='py',
|
208
|
-
exts=frozenset([
|
208
|
+
exts=frozenset([
|
209
|
+
'py',
|
210
|
+
]),
|
209
211
|
line_prefix='# ',
|
210
212
|
)
|
211
213
|
|
212
214
|
|
213
215
|
C_MAGIC_STYLE = MagicStyle(
|
214
216
|
name='c',
|
215
|
-
exts=frozenset([
|
217
|
+
exts=frozenset([
|
218
|
+
'c',
|
219
|
+
'cc',
|
220
|
+
'cpp',
|
221
|
+
'cu',
|
222
|
+
]),
|
216
223
|
line_prefix='// ',
|
217
224
|
block_prefix_suffix=('/* ', '*/'),
|
218
225
|
)
|