omdev 0.0.0.dev313__py3-none-any.whl → 0.0.0.dev315__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.dev315.dist-info}/METADATA +2 -2
- {omdev-0.0.0.dev313.dist-info → omdev-0.0.0.dev315.dist-info}/RECORD +20 -11
- {omdev-0.0.0.dev313.dist-info → omdev-0.0.0.dev315.dist-info}/WHEEL +1 -1
- {omdev-0.0.0.dev313.dist-info → omdev-0.0.0.dev315.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev313.dist-info → omdev-0.0.0.dev315.dist-info}/licenses/LICENSE +0 -0
- {omdev-0.0.0.dev313.dist-info → omdev-0.0.0.dev315.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,329 @@
|
|
1
|
+
"""Google-style docstring parsing."""
|
2
|
+
import collections
|
3
|
+
import enum
|
4
|
+
import inspect
|
5
|
+
import re
|
6
|
+
import typing as ta
|
7
|
+
|
8
|
+
from .common import EXAMPLES_KEYWORDS
|
9
|
+
from .common import PARAM_KEYWORDS
|
10
|
+
from .common import RAISES_KEYWORDS
|
11
|
+
from .common import RETURNS_KEYWORDS
|
12
|
+
from .common import YIELDS_KEYWORDS
|
13
|
+
from .common import Docstring
|
14
|
+
from .common import DocstringExample
|
15
|
+
from .common import DocstringMeta
|
16
|
+
from .common import DocstringParam
|
17
|
+
from .common import DocstringRaises
|
18
|
+
from .common import DocstringReturns
|
19
|
+
from .common import DocstringStyle
|
20
|
+
from .common import ParseError
|
21
|
+
|
22
|
+
|
23
|
+
##
|
24
|
+
|
25
|
+
|
26
|
+
class SectionType(enum.IntEnum):
|
27
|
+
"""Types of sections."""
|
28
|
+
|
29
|
+
SINGULAR = 0
|
30
|
+
"""For sections like examples."""
|
31
|
+
|
32
|
+
MULTIPLE = 1
|
33
|
+
"""For sections like params."""
|
34
|
+
|
35
|
+
SINGULAR_OR_MULTIPLE = 2
|
36
|
+
"""For sections like returns or yields."""
|
37
|
+
|
38
|
+
|
39
|
+
class Section(ta.NamedTuple):
|
40
|
+
"""A docstring section."""
|
41
|
+
|
42
|
+
title: str
|
43
|
+
key: str
|
44
|
+
type: SectionType
|
45
|
+
|
46
|
+
|
47
|
+
_GOOGLE_TYPED_ARG_PAT = re.compile(r'\s*(.+?)\s*\(\s*(.*[^\s]+)\s*\)')
|
48
|
+
_GOOGLE_ARG_DESC_PAT = re.compile(r'.*\. Defaults to (.+)\.')
|
49
|
+
_MULTIPLE_PATTERN = re.compile(r'(\s*[^:\s]+:)|([^:]*\]:.*)')
|
50
|
+
|
51
|
+
DEFAULT_SECTIONS = [
|
52
|
+
Section('Arguments', 'param', SectionType.MULTIPLE),
|
53
|
+
Section('Args', 'param', SectionType.MULTIPLE),
|
54
|
+
Section('Parameters', 'param', SectionType.MULTIPLE),
|
55
|
+
Section('Params', 'param', SectionType.MULTIPLE),
|
56
|
+
Section('Raises', 'raises', SectionType.MULTIPLE),
|
57
|
+
Section('Exceptions', 'raises', SectionType.MULTIPLE),
|
58
|
+
Section('Except', 'raises', SectionType.MULTIPLE),
|
59
|
+
Section('Attributes', 'attribute', SectionType.MULTIPLE),
|
60
|
+
Section('Example', 'examples', SectionType.SINGULAR),
|
61
|
+
Section('Examples', 'examples', SectionType.SINGULAR),
|
62
|
+
Section('Returns', 'returns', SectionType.SINGULAR_OR_MULTIPLE),
|
63
|
+
Section('Yields', 'yields', SectionType.SINGULAR_OR_MULTIPLE),
|
64
|
+
]
|
65
|
+
|
66
|
+
|
67
|
+
class GoogleParser:
|
68
|
+
"""Parser for Google-style docstrings."""
|
69
|
+
|
70
|
+
def __init__(
|
71
|
+
self,
|
72
|
+
sections: list[Section] | None = None,
|
73
|
+
title_colon: bool = True,
|
74
|
+
) -> None:
|
75
|
+
"""
|
76
|
+
Setup sections.
|
77
|
+
|
78
|
+
:param sections: Recognized sections or None to defaults.
|
79
|
+
:param title_colon: require colon after section title.
|
80
|
+
"""
|
81
|
+
|
82
|
+
super().__init__()
|
83
|
+
|
84
|
+
if not sections:
|
85
|
+
sections = DEFAULT_SECTIONS
|
86
|
+
self.sections = {s.title: s for s in sections}
|
87
|
+
self.title_colon = title_colon
|
88
|
+
self._setup()
|
89
|
+
|
90
|
+
def _setup(self) -> None:
|
91
|
+
if self.title_colon:
|
92
|
+
colon = ':'
|
93
|
+
else:
|
94
|
+
colon = ''
|
95
|
+
|
96
|
+
self.titles_re = re.compile(
|
97
|
+
'^('
|
98
|
+
+ '|'.join(f'({t})' for t in self.sections)
|
99
|
+
+ ')'
|
100
|
+
+ colon
|
101
|
+
+ '[ \t\r\f\v]*$',
|
102
|
+
flags=re.MULTILINE,
|
103
|
+
)
|
104
|
+
|
105
|
+
def _build_meta(self, text: str, title: str) -> DocstringMeta:
|
106
|
+
"""
|
107
|
+
Build docstring element.
|
108
|
+
|
109
|
+
:param text: docstring element text
|
110
|
+
:param title: title of section containing element
|
111
|
+
:return:
|
112
|
+
"""
|
113
|
+
|
114
|
+
section = self.sections[title]
|
115
|
+
|
116
|
+
if (
|
117
|
+
(
|
118
|
+
section.type == SectionType.SINGULAR_OR_MULTIPLE
|
119
|
+
and not _MULTIPLE_PATTERN.match(text)
|
120
|
+
) or
|
121
|
+
section.type == SectionType.SINGULAR
|
122
|
+
):
|
123
|
+
return self._build_single_meta(section, text)
|
124
|
+
|
125
|
+
if ':' not in text:
|
126
|
+
raise ParseError(f'Expected a colon in {text!r}.')
|
127
|
+
|
128
|
+
# Split spec and description
|
129
|
+
before, desc = text.split(':', 1)
|
130
|
+
if desc:
|
131
|
+
desc = desc[1:] if desc[0] == ' ' else desc
|
132
|
+
if '\n' in desc:
|
133
|
+
first_line, rest = desc.split('\n', 1)
|
134
|
+
desc = first_line + '\n' + inspect.cleandoc(rest)
|
135
|
+
desc = desc.strip('\n')
|
136
|
+
|
137
|
+
return self._build_multi_meta(section, before, desc)
|
138
|
+
|
139
|
+
@staticmethod
|
140
|
+
def _build_single_meta(section: Section, desc: str) -> DocstringMeta:
|
141
|
+
if section.key in RETURNS_KEYWORDS | YIELDS_KEYWORDS:
|
142
|
+
return DocstringReturns(
|
143
|
+
args=[section.key],
|
144
|
+
description=desc,
|
145
|
+
type_name=None,
|
146
|
+
is_generator=section.key in YIELDS_KEYWORDS,
|
147
|
+
)
|
148
|
+
|
149
|
+
if section.key in RAISES_KEYWORDS:
|
150
|
+
return DocstringRaises(
|
151
|
+
args=[section.key],
|
152
|
+
description=desc,
|
153
|
+
type_name=None,
|
154
|
+
)
|
155
|
+
|
156
|
+
if section.key in EXAMPLES_KEYWORDS:
|
157
|
+
return DocstringExample(
|
158
|
+
args=[section.key],
|
159
|
+
snippet=None,
|
160
|
+
description=desc,
|
161
|
+
)
|
162
|
+
|
163
|
+
if section.key in PARAM_KEYWORDS:
|
164
|
+
raise ParseError('Expected paramenter name.')
|
165
|
+
|
166
|
+
return DocstringMeta(args=[section.key], description=desc)
|
167
|
+
|
168
|
+
@staticmethod
|
169
|
+
def _build_multi_meta(
|
170
|
+
section: Section,
|
171
|
+
before: str,
|
172
|
+
desc: str,
|
173
|
+
) -> DocstringMeta:
|
174
|
+
if section.key in PARAM_KEYWORDS:
|
175
|
+
match = _GOOGLE_TYPED_ARG_PAT.match(before)
|
176
|
+
if match:
|
177
|
+
arg_name, type_name = match.group(1, 2)
|
178
|
+
if type_name.endswith(', optional'):
|
179
|
+
is_optional = True
|
180
|
+
type_name = type_name[:-10]
|
181
|
+
elif type_name.endswith('?'):
|
182
|
+
is_optional = True
|
183
|
+
type_name = type_name[:-1]
|
184
|
+
else:
|
185
|
+
is_optional = False
|
186
|
+
else:
|
187
|
+
arg_name, type_name = before, None
|
188
|
+
is_optional = None
|
189
|
+
|
190
|
+
match = _GOOGLE_ARG_DESC_PAT.match(desc)
|
191
|
+
default = match.group(1) if match else None
|
192
|
+
|
193
|
+
return DocstringParam(
|
194
|
+
args=[section.key, before],
|
195
|
+
description=desc,
|
196
|
+
arg_name=arg_name,
|
197
|
+
type_name=type_name,
|
198
|
+
is_optional=is_optional,
|
199
|
+
default=default,
|
200
|
+
)
|
201
|
+
if section.key in RETURNS_KEYWORDS | YIELDS_KEYWORDS:
|
202
|
+
return DocstringReturns(
|
203
|
+
args=[section.key, before],
|
204
|
+
description=desc,
|
205
|
+
type_name=before,
|
206
|
+
is_generator=section.key in YIELDS_KEYWORDS,
|
207
|
+
)
|
208
|
+
if section.key in RAISES_KEYWORDS:
|
209
|
+
return DocstringRaises(
|
210
|
+
args=[section.key, before], description=desc, type_name=before,
|
211
|
+
)
|
212
|
+
return DocstringMeta(args=[section.key, before], description=desc)
|
213
|
+
|
214
|
+
def add_section(self, section: Section) -> None:
|
215
|
+
"""
|
216
|
+
Add or replace a section.
|
217
|
+
|
218
|
+
:param section: The new section.
|
219
|
+
"""
|
220
|
+
|
221
|
+
self.sections[section.title] = section
|
222
|
+
self._setup()
|
223
|
+
|
224
|
+
def parse(self, text: str) -> Docstring:
|
225
|
+
"""
|
226
|
+
Parse the Google-style docstring into its components.
|
227
|
+
|
228
|
+
:returns: parsed docstring
|
229
|
+
"""
|
230
|
+
|
231
|
+
ret = Docstring(style=DocstringStyle.GOOGLE)
|
232
|
+
if not text:
|
233
|
+
return ret
|
234
|
+
|
235
|
+
# Clean according to PEP-0257
|
236
|
+
text = inspect.cleandoc(text)
|
237
|
+
|
238
|
+
# Find first title and split on its position
|
239
|
+
match = self.titles_re.search(text)
|
240
|
+
if match:
|
241
|
+
desc_chunk = text[: match.start()]
|
242
|
+
meta_chunk = text[match.start():]
|
243
|
+
else:
|
244
|
+
desc_chunk = text
|
245
|
+
meta_chunk = ''
|
246
|
+
|
247
|
+
# Break description into short and long parts
|
248
|
+
parts = desc_chunk.split('\n', 1)
|
249
|
+
ret.short_description = parts[0] or None
|
250
|
+
if len(parts) > 1:
|
251
|
+
long_desc_chunk = parts[1] or ''
|
252
|
+
ret.blank_after_short_description = long_desc_chunk.startswith('\n')
|
253
|
+
ret.blank_after_long_description = long_desc_chunk.endswith('\n\n')
|
254
|
+
ret.long_description = long_desc_chunk.strip() or None
|
255
|
+
|
256
|
+
# Split by sections determined by titles
|
257
|
+
matches = list(self.titles_re.finditer(meta_chunk))
|
258
|
+
if not matches:
|
259
|
+
return ret
|
260
|
+
|
261
|
+
splits = []
|
262
|
+
for j in range(len(matches) - 1):
|
263
|
+
splits.append((matches[j].end(), matches[j + 1].start()))
|
264
|
+
|
265
|
+
splits.append((matches[-1].end(), len(meta_chunk)))
|
266
|
+
|
267
|
+
chunks = collections.OrderedDict() # type: ta.MutableMapping[str,str]
|
268
|
+
for j, (start, end) in enumerate(splits):
|
269
|
+
title = matches[j].group(1)
|
270
|
+
if title not in self.sections:
|
271
|
+
continue
|
272
|
+
|
273
|
+
# Clear Any Unknown Meta
|
274
|
+
# Ref: https://github.com/rr-/docstring_parser/issues/29
|
275
|
+
meta_details = meta_chunk[start:end]
|
276
|
+
unknown_meta = re.search(r'\n\S', meta_details)
|
277
|
+
if unknown_meta is not None:
|
278
|
+
meta_details = meta_details[: unknown_meta.start()]
|
279
|
+
|
280
|
+
chunks[title] = meta_details.strip('\n')
|
281
|
+
|
282
|
+
if not chunks:
|
283
|
+
return ret
|
284
|
+
|
285
|
+
# Add elements from each chunk
|
286
|
+
for title, chunk in chunks.items():
|
287
|
+
# Determine indent
|
288
|
+
indent_match = re.search(r'^\s*', chunk)
|
289
|
+
if not indent_match:
|
290
|
+
raise ParseError(f'Can\'t infer indent from "{chunk}"')
|
291
|
+
|
292
|
+
indent = indent_match.group()
|
293
|
+
|
294
|
+
# Check for singular elements
|
295
|
+
if self.sections[title].type in [
|
296
|
+
SectionType.SINGULAR,
|
297
|
+
SectionType.SINGULAR_OR_MULTIPLE,
|
298
|
+
]:
|
299
|
+
part = inspect.cleandoc(chunk)
|
300
|
+
ret.meta.append(self._build_meta(part, title))
|
301
|
+
continue
|
302
|
+
|
303
|
+
# Split based on lines which have exactly that indent
|
304
|
+
_re = '^' + indent + r'(?=\S)'
|
305
|
+
|
306
|
+
c_matches = list(re.finditer(_re, chunk, flags=re.MULTILINE))
|
307
|
+
if not c_matches:
|
308
|
+
raise ParseError(f'No specification for "{title}": "{chunk}"')
|
309
|
+
|
310
|
+
c_splits = []
|
311
|
+
for j in range(len(c_matches) - 1):
|
312
|
+
c_splits.append((c_matches[j].end(), c_matches[j + 1].start()))
|
313
|
+
c_splits.append((c_matches[-1].end(), len(chunk)))
|
314
|
+
|
315
|
+
for start, end in c_splits:
|
316
|
+
part = chunk[start:end].strip('\n')
|
317
|
+
ret.meta.append(self._build_meta(part, title))
|
318
|
+
|
319
|
+
return ret
|
320
|
+
|
321
|
+
|
322
|
+
def parse(text: str) -> Docstring:
|
323
|
+
"""
|
324
|
+
Parse the Google-style docstring into its components.
|
325
|
+
|
326
|
+
:returns: parsed docstring
|
327
|
+
"""
|
328
|
+
|
329
|
+
return GoogleParser().parse(text)
|